Compare commits
1 Commits
open-relea
...
inf-554-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
034638f093 |
5
.env
5
.env
@@ -20,6 +20,5 @@ SEGMENT_KEY=''
|
|||||||
SITE_NAME=''
|
SITE_NAME=''
|
||||||
USER_INFO_COOKIE_NAME=''
|
USER_INFO_COOKIE_NAME=''
|
||||||
SUPPORT_URL=''
|
SUPPORT_URL=''
|
||||||
TA_FEEDBACK_FORM= ''
|
TA_FEEDBACK_FORM: ''
|
||||||
STAFF_FEEDBACK_FORM= ''
|
STAFF_FEEDBACK_FORM: ''
|
||||||
DISPLAY_FEEDBACK_BANNER='false'
|
|
||||||
@@ -21,6 +21,5 @@ SEGMENT_KEY=''
|
|||||||
SITE_NAME=localhost
|
SITE_NAME=localhost
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
SUPPORT_URL='https://support.edx.org'
|
SUPPORT_URL='https://support.edx.org'
|
||||||
TA_FEEDBACK_FORM='https://learner-form.test'
|
TA_FEEDBACK_FORM: 'https://learner-form.test'
|
||||||
STAFF_FEEDBACK_FORM='https://staff-form.test'
|
STAFF_FEEDBACK_FORM: 'https://staff-form.test'
|
||||||
DISPLAY_FEEDBACK_BANNER='false'
|
|
||||||
@@ -19,6 +19,5 @@ SEGMENT_KEY=''
|
|||||||
SITE_NAME='localhost'
|
SITE_NAME='localhost'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
SUPPORT_URL='https://support.edx.org'
|
SUPPORT_URL='https://support.edx.org'
|
||||||
TA_FEEDBACK_FORM='https://learner-form.test'
|
TA_FEEDBACK_FORM: 'https://learner-form.test'
|
||||||
STAFF_FEEDBACK_FORM='https://staff-form.test'
|
STAFF_FEEDBACK_FORM: 'https://staff-form.test'
|
||||||
DISPLAY_FEEDBACK_BANNER='false'
|
|
||||||
2
Makefile
2
Makefile
@@ -59,7 +59,7 @@ push_translations:
|
|||||||
|
|
||||||
# Pulls translations from Transifex.
|
# Pulls translations from Transifex.
|
||||||
pull_translations:
|
pull_translations:
|
||||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
tx pull -f --mode reviewed --languages=$(transifex_langs)
|
||||||
|
|
||||||
# This target is used by Travis.
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ openedx-release:
|
|||||||
# The openedx-release key is described in OEP-10:
|
# The openedx-release key is described in OEP-10:
|
||||||
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html
|
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html
|
||||||
# The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ
|
# The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ
|
||||||
|
maybe: true # Delete this "maybe" line when you have decided about Open edX inclusion.
|
||||||
ref: master
|
ref: master
|
||||||
|
|||||||
1262
package-lock.json
generated
1262
package-lock.json
generated
@@ -10,10 +10,10 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "12.0.0",
|
"@edx/frontend-component-footer": "11.2.0",
|
||||||
"@edx/frontend-component-header": "4.0.0",
|
"@edx/frontend-component-header": "3.2.0",
|
||||||
"@edx/frontend-platform": "4.2.0",
|
"@edx/frontend-platform": "2.6.1",
|
||||||
"@edx/paragon": "20.15.0",
|
"@edx/paragon": "19.10.1",
|
||||||
"@reduxjs/toolkit": "1.8.0",
|
"@reduxjs/toolkit": "1.8.0",
|
||||||
"@tinymce/tinymce-react": "3.13.1",
|
"@tinymce/tinymce-react": "3.13.1",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
@@ -3395,42 +3395,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-component-footer": {
|
"node_modules/@edx/frontend-component-footer": {
|
||||||
"version": "12.0.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-11.2.0.tgz",
|
||||||
"integrity": "sha512-m8Rx6ZPWzIN5XLrz6Ft3aTuFo0rty0jECd79CBYWdm0D9KD1WxoYEG+fElluyOQp/t42T5jLImHTSWjFURx5kw==",
|
"integrity": "sha512-prN6SeoWenbNq6jCqlpmYx57OhNbVGw1wjQFVH+aA5JMjjLUf6PmjPG87fuD9udQyFteb2lvbCyongPEZOozrg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0"
|
"@fortawesome/react-fontawesome": "0.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edx/frontend-platform": "^4.0.0",
|
"@edx/frontend-platform": "^2.3.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react": "^16.9.0 || ^17.0.0",
|
"react": "^16.9.0",
|
||||||
"react-dom": "^16.9.0 || ^17.0.0"
|
"react-dom": "^16.9.0"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/fontawesome-svg-core": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/react-fontawesome": {
|
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/react-fontawesome": {
|
||||||
@@ -3446,110 +3425,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-component-header": {
|
"node_modules/@edx/frontend-component-header": {
|
||||||
"version": "4.0.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-3.2.0.tgz",
|
||||||
"integrity": "sha512-r/L3p2ZSI1DitjxVKAor18GmgJllafYslrdpzGI0vcX/gTemH13jf2Xr9iQqrT921DP2nzZ5GOwGJNptTSjiaA==",
|
"integrity": "sha512-ep9yOwe9CG03lSf/QBsBOZO3S/Ksrttgi4MkjpUe/6Sm/KdAnMCOhx2Gbj9jBcFrHqCkpeef26zGjjhpHGDwkQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/paragon": "20.30.1",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.3.0",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.3.0",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.3.0",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
"react-responsive": "8.2.0",
|
"react-responsive": "8.2.0",
|
||||||
"react-transition-group": "4.4.5"
|
"react-transition-group": "4.4.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edx/frontend-platform": "^4.0.0",
|
"@edx/frontend-platform": "^2.0.0",
|
||||||
|
"@edx/paragon": ">= 7.0.0 < 21.0.0",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react": "^16.9.0 || ^17.0.0",
|
"react": "^16.9.0",
|
||||||
"react-dom": "^16.9.0 || ^17.0.0"
|
"react-dom": "^16.9.0"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@edx/paragon": {
|
|
||||||
"version": "20.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.30.1.tgz",
|
|
||||||
"integrity": "sha512-v3Ek8deZWqVKi3IWP08Mj4egrvbmbqQEyRA6+qazHZdgHJA4qOP1SST42UKd9XxPeRbLWUgaJWd0iBAOAna/gw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
|
||||||
"@popperjs/core": "^2.11.4",
|
|
||||||
"bootstrap": "^4.6.2",
|
|
||||||
"classnames": "^2.3.1",
|
|
||||||
"email-prop-type": "^3.0.0",
|
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"glob": "^8.0.3",
|
|
||||||
"lodash.uniqby": "^4.7.0",
|
|
||||||
"mailto-link": "^2.0.0",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"react-bootstrap": "^1.6.5",
|
|
||||||
"react-colorful": "^5.6.1",
|
|
||||||
"react-dropzone": "^14.2.1",
|
|
||||||
"react-focus-on": "^3.5.4",
|
|
||||||
"react-loading-skeleton": "^3.1.0",
|
|
||||||
"react-popper": "^2.2.5",
|
|
||||||
"react-proptype-conditional-require": "^1.0.4",
|
|
||||||
"react-responsive": "^8.2.0",
|
|
||||||
"react-table": "^7.7.0",
|
|
||||||
"react-transition-group": "^4.4.2",
|
|
||||||
"tabbable": "^5.3.3",
|
|
||||||
"uncontrollable": "^7.2.1",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8.6 || ^17.0.0",
|
|
||||||
"react-dom": "^16.8.6 || ^17.0.0",
|
|
||||||
"react-intl": "^5.25.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@edx/paragon/node_modules/@fortawesome/react-fontawesome": {
|
|
||||||
"version": "0.1.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
|
|
||||||
"integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
|
||||||
"react": ">=16.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-brands-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-regular-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-solid-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/react-fontawesome": {
|
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/react-fontawesome": {
|
||||||
@@ -3564,63 +3458,18 @@
|
|||||||
"react": ">=16.3"
|
"react": ">=16.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/glob": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^5.0.1",
|
|
||||||
"once": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/minimatch": {
|
|
||||||
"version": "5.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
|
||||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-component-header/node_modules/uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-platform": {
|
"node_modules/@edx/frontend-platform": {
|
||||||
"version": "4.2.0",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.6.1.tgz",
|
||||||
"integrity": "sha512-iDoFeccENQKBjqUgdjl5KSwBrjNEj8YW6Ual+6twcHHJUBg3yRoBEphwHIoRREcMgQjhdKVAdWj8eleh4JsEKA==",
|
"integrity": "sha512-5ZcHBvwmJRYPPKEqv+H4DbsfW9atr5V2+uY/zX58F/Tqh0T5X8MmgppVjplWFbmon5QV4aNw5C1ViQnirNqUiA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cospired/i18n-iso-languages": "2.2.0",
|
"@cospired/i18n-iso-languages": "2.2.0",
|
||||||
"@formatjs/intl-pluralrules": "4.3.3",
|
"@formatjs/intl-pluralrules": "4.3.3",
|
||||||
"@formatjs/intl-relativetimeformat": "10.0.1",
|
"@formatjs/intl-relativetimeformat": "10.0.1",
|
||||||
"axios": "0.27.2",
|
"axios": "0.26.1",
|
||||||
"axios-cache-interceptor": "0.10.7",
|
"axios-cache-adapter": "2.7.3",
|
||||||
"form-urlencoded": "4.1.4",
|
"form-urlencoded": "4.1.4",
|
||||||
"glob": "7.2.3",
|
"glob": "7.2.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"i18n-iso-countries": "4.3.1",
|
"i18n-iso-countries": "4.3.1",
|
||||||
"jwt-decode": "3.1.2",
|
"jwt-decode": "3.1.2",
|
||||||
@@ -3635,58 +3484,24 @@
|
|||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "4.0.4"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"intl-imports.js": "i18n/scripts/intl-imports.js",
|
|
||||||
"transifex-utils.js": "i18n/scripts/transifex-utils.js"
|
"transifex-utils.js": "i18n/scripts/transifex-utils.js"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edx/paragon": ">= 10.0.0 < 21.0.0",
|
"@edx/paragon": ">= 10.0.0 < 21.0.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.9.0 || ^17.0.0",
|
"react": "^16.9.0",
|
||||||
"react-dom": "^16.9.0 || ^17.0.0",
|
"react-dom": "^16.9.0",
|
||||||
"react-redux": "^7.1.1",
|
"react-redux": "^7.1.1",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"redux": "^4.0.4"
|
"redux": "^4.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-platform/node_modules/axios": {
|
"node_modules/@edx/frontend-platform/node_modules/axios": {
|
||||||
"version": "0.27.2",
|
"version": "0.26.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.14.9",
|
"follow-redirects": "^1.14.8"
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-platform/node_modules/form-data": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/frontend-platform/node_modules/glob": {
|
|
||||||
"version": "7.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^3.1.1",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/new-relic-source-map-webpack-plugin": {
|
"node_modules/@edx/new-relic-source-map-webpack-plugin": {
|
||||||
@@ -3699,75 +3514,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/paragon": {
|
"node_modules/@edx/paragon": {
|
||||||
"version": "20.15.0",
|
"version": "19.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-19.10.1.tgz",
|
||||||
"integrity": "sha512-Sq5/je3Ub3UpXrrneaCShz+kB6wuMNmeF1Y/XfVt6X2i+ZCQGlQrBa3KtcwGwDOHIQ65eC6dqJpL9LISHg+xtg==",
|
"integrity": "sha512-kVIx/yJWYeaTIb+j2NmB/iz1XGR4WY8aYEQbt1FtGFWcKzeiThtiqr4TeRxQeA8UrooyQl5HyzYISq7xC5AH2w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@popperjs/core": "^2.11.4",
|
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||||
"bootstrap": "^4.6.2",
|
"@popperjs/core": "^2.11.2",
|
||||||
|
"airbnb-prop-types": "^2.12.0",
|
||||||
|
"bootstrap": "4.6.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"email-prop-type": "^3.0.0",
|
"email-prop-type": "^3.0.0",
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"glob": "^8.0.3",
|
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"mailto-link": "^2.0.0",
|
"mailto-link": "^1.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-bootstrap": "^1.6.5",
|
"react-bootstrap": "^1.6.4",
|
||||||
"react-dropzone": "^14.2.1",
|
|
||||||
"react-focus-on": "^3.5.4",
|
"react-focus-on": "^3.5.4",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
|
||||||
"react-popper": "^2.2.5",
|
"react-popper": "^2.2.5",
|
||||||
"react-proptype-conditional-require": "^1.0.4",
|
"react-proptype-conditional-require": "^1.0.4",
|
||||||
"react-responsive": "^8.2.0",
|
"react-responsive": "^8.2.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-transition-group": "^4.4.2",
|
"react-transition-group": "^4.4.2",
|
||||||
"tabbable": "^5.3.3",
|
"tabbable": "^4.0.0",
|
||||||
"uncontrollable": "^7.2.1"
|
"uncontrollable": "7.2.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.6 || ^17.0.0",
|
"prop-types": "^15.7.2",
|
||||||
"react-dom": "^16.8.6 || ^17.0.0",
|
"react": "^16.8.6",
|
||||||
"react-intl": "^5.25.1"
|
"react-dom": "^16.8.6"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/paragon/node_modules/brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/paragon/node_modules/glob": {
|
|
||||||
"version": "8.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
|
||||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^5.0.1",
|
|
||||||
"once": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@edx/paragon/node_modules/minimatch": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/reactifex": {
|
"node_modules/@edx/reactifex": {
|
||||||
@@ -4056,89 +3832,62 @@
|
|||||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
"version": "6.3.0",
|
"version": "0.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
||||||
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==",
|
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||||
"version": "6.3.0",
|
"version": "1.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
|
||||||
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
|
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/free-brands-svg-icons": {
|
"node_modules/@fortawesome/free-brands-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==",
|
"integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/free-brands-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fortawesome/free-regular-svg-icons": {
|
"node_modules/@fortawesome/free-regular-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==",
|
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==",
|
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fortawesome/react-fontawesome": {
|
"node_modules/@fortawesome/react-fontawesome": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz",
|
||||||
@@ -7325,6 +7074,28 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/airbnb-prop-types": {
|
||||||
|
"version": "2.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz",
|
||||||
|
"integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==",
|
||||||
|
"dependencies": {
|
||||||
|
"array.prototype.find": "^2.1.1",
|
||||||
|
"function.prototype.name": "^1.1.2",
|
||||||
|
"is-regex": "^1.1.0",
|
||||||
|
"object-is": "^1.1.2",
|
||||||
|
"object.assign": "^4.1.0",
|
||||||
|
"object.entries": "^1.1.2",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"prop-types-exact": "^1.2.0",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14 || ^15.0.0 || ^16.0.0-alpha"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@@ -7612,6 +7383,20 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/array.prototype.find": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-sn40qmUiLYAcRb/1HsIQjTTZ1kCy8II8VtZJpMn2Aoen9twULhbWXisfh3HimGqMlHGUul0/TfKCnXg42LuPpQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.19.4",
|
||||||
|
"es-shim-unscopables": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/array.prototype.flat": {
|
"node_modules/array.prototype.flat": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
|
||||||
@@ -7677,7 +7462,8 @@
|
|||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/at-least-node": {
|
"node_modules/at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -7700,14 +7486,6 @@
|
|||||||
"node": ">= 4.5.0"
|
"node": ">= 4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/attr-accept": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.2.6",
|
"version": "10.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.6.tgz",
|
||||||
@@ -7739,22 +7517,20 @@
|
|||||||
"version": "0.21.4",
|
"version": "0.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.14.0"
|
"follow-redirects": "^1.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios-cache-interceptor": {
|
"node_modules/axios-cache-adapter": {
|
||||||
"version": "0.10.7",
|
"version": "2.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz",
|
||||||
"integrity": "sha512-UjpxChG5DpF6Kf1IPGMLOzRDNL8ZNS6TOn1jTaVvCE7cWFU904jJwi0T1s+IbijpnLEjK2iq5uLIuR8Sj+RsFQ==",
|
"integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cache-parser": "^1.2.4",
|
"cache-control-esm": "1.0.0",
|
||||||
"fast-defer": "^1.1.7",
|
"md5": "^2.2.1"
|
||||||
"object-code": "^1.2.4"
|
|
||||||
},
|
},
|
||||||
"funding": {
|
"peerDependencies": {
|
||||||
"url": "https://github.com/ArthurFiorette/axios-cache-interceptor?sponsor=1"
|
"axios": "~0.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios-mock-adapter": {
|
"node_modules/axios-mock-adapter": {
|
||||||
@@ -8728,19 +8504,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/bootstrap": {
|
"node_modules/bootstrap": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
|
||||||
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
|
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
|
||||||
"funding": [
|
"funding": {
|
||||||
{
|
"type": "opencollective",
|
||||||
"type": "github",
|
"url": "https://opencollective.com/bootstrap"
|
||||||
"url": "https://github.com/sponsors/twbs"
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/bootstrap"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"jquery": "1.9.1 - 3",
|
"jquery": "1.9.1 - 3",
|
||||||
"popper.js": "^1.16.1"
|
"popper.js": "^1.16.1"
|
||||||
@@ -8910,10 +8680,10 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cache-parser": {
|
"node_modules/cache-control-esm": {
|
||||||
"version": "1.2.4",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/cache-control-esm/-/cache-control-esm-1.0.0.tgz",
|
||||||
"integrity": "sha512-O0KwuHuJnbHUrghHi2kGp0SxnWSIBXTYt7M8WVhW0kbPRUNUKoE/Of6e1rRD6AAxmfxFunKnt90yEK09D+sc5g=="
|
"integrity": "sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g=="
|
||||||
},
|
},
|
||||||
"node_modules/cacheable-request": {
|
"node_modules/cacheable-request": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
@@ -9008,7 +8778,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
@@ -9146,6 +8915,14 @@
|
|||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/charenc": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/check-types": {
|
"node_modules/check-types": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
||||||
@@ -9588,6 +9365,7 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
},
|
},
|
||||||
@@ -9886,6 +9664,14 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypt": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css": {
|
"node_modules/css": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
||||||
@@ -10247,6 +10033,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
@@ -10634,7 +10421,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
||||||
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-property-descriptors": "^1.0.0",
|
"has-property-descriptors": "^1.0.0",
|
||||||
"object-keys": "^1.1.1"
|
"object-keys": "^1.1.1"
|
||||||
@@ -10681,6 +10467,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@@ -11234,7 +11021,6 @@
|
|||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
|
||||||
"integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
|
"integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
@@ -11294,7 +11080,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
|
||||||
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
|
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
}
|
}
|
||||||
@@ -11303,7 +11088,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-callable": "^1.1.4",
|
"is-callable": "^1.1.4",
|
||||||
"is-date-object": "^1.0.1",
|
"is-date-object": "^1.0.1",
|
||||||
@@ -12523,11 +12307,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
"node_modules/fast-defer": {
|
|
||||||
"version": "1.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.7.tgz",
|
|
||||||
"integrity": "sha512-tJ01ulDWT2WhqxMKS20nXX6wyX2iInBYpbN3GO7yjKwXMY4qvkdBRxak9IFwBLlFDESox+SwSvqMCZDfe1tqeg=="
|
|
||||||
},
|
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.2.11",
|
"version": "3.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||||
@@ -12686,22 +12465,6 @@
|
|||||||
"webpack": "^4.0.0 || ^5.0.0"
|
"webpack": "^4.0.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/file-selector": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/file-selector/node_modules/tslib": {
|
|
||||||
"version": "2.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
|
||||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
|
||||||
},
|
|
||||||
"node_modules/file-type": {
|
"node_modules/file-type": {
|
||||||
"version": "12.4.2",
|
"version": "12.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
|
||||||
@@ -12757,14 +12520,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/filter-obj": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
@@ -13298,14 +13053,12 @@
|
|||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/function.prototype.name": {
|
"node_modules/function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
|
||||||
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
|
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -13329,7 +13082,6 @@
|
|||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -13356,7 +13108,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
||||||
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
|
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@@ -13412,7 +13163,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||||
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"get-intrinsic": "^1.1.1"
|
"get-intrinsic": "^1.1.1"
|
||||||
@@ -13596,7 +13346,6 @@
|
|||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"inflight": "^1.0.4",
|
"inflight": "^1.0.4",
|
||||||
@@ -13768,7 +13517,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -13780,7 +13528,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
||||||
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
|
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -13798,7 +13545,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.1.1"
|
"get-intrinsic": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -13820,7 +13566,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -13845,7 +13590,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.2"
|
"has-symbols": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -15010,7 +14754,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
||||||
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.1.0",
|
"get-intrinsic": "^1.1.0",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@@ -15146,7 +14889,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
||||||
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-bigints": "^1.0.1"
|
"has-bigints": "^1.0.1"
|
||||||
},
|
},
|
||||||
@@ -15182,7 +14924,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||||
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
@@ -15221,7 +14962,6 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
||||||
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==",
|
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -15295,7 +15035,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
||||||
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -15463,7 +15202,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
|
||||||
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
|
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -15484,7 +15222,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
||||||
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
|
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -15580,7 +15317,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
@@ -15621,7 +15357,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
|
||||||
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
|
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2"
|
"call-bind": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -15642,7 +15377,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
||||||
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -15673,7 +15407,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
||||||
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
|
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.2"
|
"has-symbols": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -15706,7 +15439,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
|
||||||
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
|
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2"
|
"call-bind": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -19810,17 +19542,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailto-link": {
|
"node_modules/mailto-link": {
|
||||||
"version": "2.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-1.0.0.tgz",
|
||||||
"integrity": "sha512-b5FErkZ4t6mpH1IFZSw7Mm2IQHXQ2R0/5Q4xd7Rv8dVkWvE54mFG/UW7HjfFazXFjXTNsM+dSX2tTeIDrV9K9A==",
|
"integrity": "sha512-DVRvtkoeXLHAbH+S+9m3ILIdnvQsSc9IvJwfEclQVD8e8FhzwA5Mtw4Q0XXYr/sAziw/HsMc/gpGAI+5w6ohIw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"assert-ok": "~1.0.0",
|
"assert-ok": "~1.0.0",
|
||||||
"cast-array": "~1.0.1",
|
"cast-array": "~1.0.0",
|
||||||
"object-filter": "~1.0.2",
|
"object-filter": "~1.0.2",
|
||||||
"query-string": "~7.0.0"
|
"query-string": "~2.4.1"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
@@ -19883,6 +19612,21 @@
|
|||||||
"css-mediaquery": "^0.1.2"
|
"css-mediaquery": "^0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/md5": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"charenc": "0.0.2",
|
||||||
|
"crypt": "0.0.2",
|
||||||
|
"is-buffer": "~1.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/md5/node_modules/is-buffer": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||||
|
},
|
||||||
"node_modules/mdn-data": {
|
"node_modules/mdn-data": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
@@ -20460,11 +20204,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/object-code": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-code/-/object-code-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-uGq4ETUuWe+GA586NXEriiaozNuff+YNFXlpD8cVrM1GoiuTZpCABP+bZCWDrvQDoCiSTyiWAFHD/HF/iwhb2w=="
|
|
||||||
},
|
|
||||||
"node_modules/object-copy": {
|
"node_modules/object-copy": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||||
@@ -20565,7 +20304,6 @@
|
|||||||
"version": "1.12.2",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
|
||||||
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
|
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -20574,7 +20312,6 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
||||||
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3"
|
"define-properties": "^1.1.3"
|
||||||
@@ -20590,7 +20327,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -20611,7 +20347,6 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||||
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -20629,7 +20364,6 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz",
|
||||||
"integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==",
|
"integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -22219,6 +21953,16 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types-exact": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==",
|
||||||
|
"dependencies": {
|
||||||
|
"has": "^1.0.3",
|
||||||
|
"object.assign": "^4.1.0",
|
||||||
|
"reflect.ownkeys": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types-extra": {
|
"node_modules/prop-types-extra": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||||
@@ -22308,28 +22052,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/query-string": {
|
"node_modules/query-string": {
|
||||||
"version": "7.0.1",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-2.4.2.tgz",
|
||||||
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
|
"integrity": "sha512-Y+OMYUuY7HxznI6WBN822fi/FMvnCTiuqd6KNcidPColOmMWPoV1RGYyyzObve1T/dD1i0ZgCCbO8ytu0ZUrkA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"decode-uri-component": "^0.2.0",
|
"strict-uri-encode": "^1.0.0"
|
||||||
"filter-obj": "^1.1.0",
|
|
||||||
"split-on-first": "^1.0.0",
|
|
||||||
"strict-uri-encode": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=0.10.0"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/query-string/node_modules/strict-uri-encode": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
@@ -22455,15 +22185,6 @@
|
|||||||
"react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
"react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-colorful": {
|
|
||||||
"version": "5.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
|
||||||
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8.0",
|
|
||||||
"react-dom": ">=16.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dev-utils": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
|
||||||
@@ -22722,22 +22443,6 @@
|
|||||||
"react": "^16.14.0"
|
"react": "^16.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dropzone": {
|
|
||||||
"version": "14.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.2.tgz",
|
|
||||||
"integrity": "sha512-5oyGN/B5rNhop2ggUnxztXBQ6q6zii+OMEftPzsxAR2hhpVWz0nAV+3Ktxo2h5bZzdcCKrpd8bfWAVsveIBM+w==",
|
|
||||||
"dependencies": {
|
|
||||||
"attr-accept": "^2.2.2",
|
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">= 16.8 || 18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-error-overlay": {
|
"node_modules/react-error-overlay": {
|
||||||
"version": "6.0.11",
|
"version": "6.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
||||||
@@ -22852,14 +22557,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
},
|
},
|
||||||
"node_modules/react-loading-skeleton": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-mathjax-preview": {
|
"node_modules/react-mathjax-preview": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-mathjax-preview/-/react-mathjax-preview-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-mathjax-preview/-/react-mathjax-preview-2.2.6.tgz",
|
||||||
@@ -23330,6 +23027,11 @@
|
|||||||
"redux": "^4"
|
"redux": "^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reflect.ownkeys": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-qOLsBKHCpSOFKK1NUOCGC5VyeufB6lEsFe92AL2bhIJsacZS1qdoOZSbPk3MYKuT2cFlRDnulKXuuElIrMjGUg=="
|
||||||
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
@@ -23385,7 +23087,6 @@
|
|||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
|
||||||
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
|
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -24412,7 +24113,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2",
|
"get-intrinsic": "^1.0.2",
|
||||||
@@ -24906,14 +24606,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/split-on-first": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/split-string": {
|
"node_modules/split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
@@ -25098,8 +24790,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||||
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==",
|
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==",
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -25193,7 +24883,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
"integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
|
"integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.4",
|
"define-properties": "^1.1.4",
|
||||||
@@ -25207,7 +24896,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
|
||||||
"integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
|
"integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.4",
|
"define-properties": "^1.1.4",
|
||||||
@@ -25507,9 +25195,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/tabbable": {
|
"node_modules/tabbable": {
|
||||||
"version": "5.3.3",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
|
||||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
|
"integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
|
||||||
},
|
},
|
||||||
"node_modules/table": {
|
"node_modules/table": {
|
||||||
"version": "5.4.6",
|
"version": "5.4.6",
|
||||||
@@ -26220,7 +25908,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||||
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
|
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-bigints": "^1.0.2",
|
"has-bigints": "^1.0.2",
|
||||||
@@ -27418,7 +27105,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
||||||
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-bigint": "^1.0.1",
|
"is-bigint": "^1.0.1",
|
||||||
"is-boolean-object": "^1.1.0",
|
"is-boolean-object": "^1.1.0",
|
||||||
@@ -30201,30 +29887,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@edx/frontend-component-footer": {
|
"@edx/frontend-component-footer": {
|
||||||
"version": "12.0.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-11.2.0.tgz",
|
||||||
"integrity": "sha512-m8Rx6ZPWzIN5XLrz6Ft3aTuFo0rty0jECd79CBYWdm0D9KD1WxoYEG+fElluyOQp/t42T5jLImHTSWjFURx5kw==",
|
"integrity": "sha512-prN6SeoWenbNq6jCqlpmYx57OhNbVGw1wjQFVH+aA5JMjjLUf6PmjPG87fuD9udQyFteb2lvbCyongPEZOozrg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0"
|
"@fortawesome/react-fontawesome": "0.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ=="
|
|
||||||
},
|
|
||||||
"@fortawesome/fontawesome-svg-core": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==",
|
|
||||||
"requires": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@fortawesome/react-fontawesome": {
|
"@fortawesome/react-fontawesome": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||||
@@ -30236,87 +29909,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@edx/frontend-component-header": {
|
"@edx/frontend-component-header": {
|
||||||
"version": "4.0.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-3.2.0.tgz",
|
||||||
"integrity": "sha512-r/L3p2ZSI1DitjxVKAor18GmgJllafYslrdpzGI0vcX/gTemH13jf2Xr9iQqrT921DP2nzZ5GOwGJNptTSjiaA==",
|
"integrity": "sha512-ep9yOwe9CG03lSf/QBsBOZO3S/Ksrttgi4MkjpUe/6Sm/KdAnMCOhx2Gbj9jBcFrHqCkpeef26zGjjhpHGDwkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@edx/paragon": "20.30.1",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.3.0",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.3.0",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.3.0",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
"react-responsive": "8.2.0",
|
"react-responsive": "8.2.0",
|
||||||
"react-transition-group": "4.4.5"
|
"react-transition-group": "4.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/paragon": {
|
|
||||||
"version": "20.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.30.1.tgz",
|
|
||||||
"integrity": "sha512-v3Ek8deZWqVKi3IWP08Mj4egrvbmbqQEyRA6+qazHZdgHJA4qOP1SST42UKd9XxPeRbLWUgaJWd0iBAOAna/gw==",
|
|
||||||
"requires": {
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
|
||||||
"@popperjs/core": "^2.11.4",
|
|
||||||
"bootstrap": "^4.6.2",
|
|
||||||
"classnames": "^2.3.1",
|
|
||||||
"email-prop-type": "^3.0.0",
|
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"glob": "^8.0.3",
|
|
||||||
"lodash.uniqby": "^4.7.0",
|
|
||||||
"mailto-link": "^2.0.0",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"react-bootstrap": "^1.6.5",
|
|
||||||
"react-colorful": "^5.6.1",
|
|
||||||
"react-dropzone": "^14.2.1",
|
|
||||||
"react-focus-on": "^3.5.4",
|
|
||||||
"react-loading-skeleton": "^3.1.0",
|
|
||||||
"react-popper": "^2.2.5",
|
|
||||||
"react-proptype-conditional-require": "^1.0.4",
|
|
||||||
"react-responsive": "^8.2.0",
|
|
||||||
"react-table": "^7.7.0",
|
|
||||||
"react-transition-group": "^4.4.2",
|
|
||||||
"tabbable": "^5.3.3",
|
|
||||||
"uncontrollable": "^7.2.1",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/react-fontawesome": {
|
|
||||||
"version": "0.1.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
|
|
||||||
"integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
|
|
||||||
"requires": {
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@fortawesome/free-brands-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
|
|
||||||
"requires": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@fortawesome/free-regular-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
|
|
||||||
"requires": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@fortawesome/free-solid-svg-icons": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
|
|
||||||
"requires": {
|
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@fortawesome/react-fontawesome": {
|
"@fortawesome/react-fontawesome": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||||
@@ -30324,54 +29930,21 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"requires": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
|
|
||||||
"requires": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^5.0.1",
|
|
||||||
"once": "^1.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimatch": {
|
|
||||||
"version": "5.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
|
||||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
|
||||||
"requires": {
|
|
||||||
"brace-expansion": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@edx/frontend-platform": {
|
"@edx/frontend-platform": {
|
||||||
"version": "4.2.0",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-2.6.1.tgz",
|
||||||
"integrity": "sha512-iDoFeccENQKBjqUgdjl5KSwBrjNEj8YW6Ual+6twcHHJUBg3yRoBEphwHIoRREcMgQjhdKVAdWj8eleh4JsEKA==",
|
"integrity": "sha512-5ZcHBvwmJRYPPKEqv+H4DbsfW9atr5V2+uY/zX58F/Tqh0T5X8MmgppVjplWFbmon5QV4aNw5C1ViQnirNqUiA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cospired/i18n-iso-languages": "2.2.0",
|
"@cospired/i18n-iso-languages": "2.2.0",
|
||||||
"@formatjs/intl-pluralrules": "4.3.3",
|
"@formatjs/intl-pluralrules": "4.3.3",
|
||||||
"@formatjs/intl-relativetimeformat": "10.0.1",
|
"@formatjs/intl-relativetimeformat": "10.0.1",
|
||||||
"axios": "0.27.2",
|
"axios": "0.26.1",
|
||||||
"axios-cache-interceptor": "0.10.7",
|
"axios-cache-adapter": "2.7.3",
|
||||||
"form-urlencoded": "4.1.4",
|
"form-urlencoded": "4.1.4",
|
||||||
"glob": "7.2.3",
|
"glob": "7.2.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"i18n-iso-countries": "4.3.1",
|
"i18n-iso-countries": "4.3.1",
|
||||||
"jwt-decode": "3.1.2",
|
"jwt-decode": "3.1.2",
|
||||||
@@ -30387,35 +29960,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.27.2",
|
"version": "0.26.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.14.9",
|
"follow-redirects": "^1.14.8"
|
||||||
"form-data": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"form-data": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
|
||||||
"requires": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"version": "7.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
|
||||||
"requires": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^3.1.1",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30430,63 +29979,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@edx/paragon": {
|
"@edx/paragon": {
|
||||||
"version": "20.15.0",
|
"version": "19.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-19.10.1.tgz",
|
||||||
"integrity": "sha512-Sq5/je3Ub3UpXrrneaCShz+kB6wuMNmeF1Y/XfVt6X2i+ZCQGlQrBa3KtcwGwDOHIQ65eC6dqJpL9LISHg+xtg==",
|
"integrity": "sha512-kVIx/yJWYeaTIb+j2NmB/iz1XGR4WY8aYEQbt1FtGFWcKzeiThtiqr4TeRxQeA8UrooyQl5HyzYISq7xC5AH2w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@popperjs/core": "^2.11.4",
|
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||||
"bootstrap": "^4.6.2",
|
"@popperjs/core": "^2.11.2",
|
||||||
|
"airbnb-prop-types": "^2.12.0",
|
||||||
|
"bootstrap": "4.6.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"email-prop-type": "^3.0.0",
|
"email-prop-type": "^3.0.0",
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"glob": "^8.0.3",
|
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"mailto-link": "^2.0.0",
|
"mailto-link": "^1.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-bootstrap": "^1.6.5",
|
"react-bootstrap": "^1.6.4",
|
||||||
"react-dropzone": "^14.2.1",
|
|
||||||
"react-focus-on": "^3.5.4",
|
"react-focus-on": "^3.5.4",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
|
||||||
"react-popper": "^2.2.5",
|
"react-popper": "^2.2.5",
|
||||||
"react-proptype-conditional-require": "^1.0.4",
|
"react-proptype-conditional-require": "^1.0.4",
|
||||||
"react-responsive": "^8.2.0",
|
"react-responsive": "^8.2.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-transition-group": "^4.4.2",
|
"react-transition-group": "^4.4.2",
|
||||||
"tabbable": "^5.3.3",
|
"tabbable": "^4.0.0",
|
||||||
"uncontrollable": "^7.2.1"
|
"uncontrollable": "7.2.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"requires": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"version": "8.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
|
||||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
|
||||||
"requires": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^5.0.1",
|
|
||||||
"once": "^1.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimatch": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
|
||||||
"requires": {
|
|
||||||
"brace-expansion": "^2.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@edx/reactifex": {
|
"@edx/reactifex": {
|
||||||
@@ -30782,61 +30299,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fortawesome/fontawesome-common-types": {
|
"@fortawesome/fontawesome-common-types": {
|
||||||
"version": "6.3.0",
|
"version": "0.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
|
||||||
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
|
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
|
||||||
},
|
},
|
||||||
"@fortawesome/fontawesome-svg-core": {
|
"@fortawesome/fontawesome-svg-core": {
|
||||||
"version": "6.3.0",
|
"version": "1.2.36",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
|
||||||
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
|
"integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fortawesome/free-brands-svg-icons": {
|
"@fortawesome/free-brands-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==",
|
"integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fortawesome/free-regular-svg-icons": {
|
"@fortawesome/free-regular-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==",
|
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fortawesome/free-solid-svg-icons": {
|
"@fortawesome/free-solid-svg-icons": {
|
||||||
"version": "6.4.0",
|
"version": "5.15.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
|
||||||
"integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==",
|
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fortawesome/fontawesome-common-types": "6.4.0"
|
"@fortawesome/fontawesome-common-types": "^0.2.36"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fortawesome/fontawesome-common-types": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fortawesome/react-fontawesome": {
|
"@fortawesome/react-fontawesome": {
|
||||||
@@ -33331,6 +32827,22 @@
|
|||||||
"indent-string": "^4.0.0"
|
"indent-string": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"airbnb-prop-types": {
|
||||||
|
"version": "2.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz",
|
||||||
|
"integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==",
|
||||||
|
"requires": {
|
||||||
|
"array.prototype.find": "^2.1.1",
|
||||||
|
"function.prototype.name": "^1.1.2",
|
||||||
|
"is-regex": "^1.1.0",
|
||||||
|
"object-is": "^1.1.2",
|
||||||
|
"object.assign": "^4.1.0",
|
||||||
|
"object.entries": "^1.1.2",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"prop-types-exact": "^1.2.0",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@@ -33530,6 +33042,17 @@
|
|||||||
"integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==",
|
"integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"array.prototype.find": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-sn40qmUiLYAcRb/1HsIQjTTZ1kCy8II8VtZJpMn2Aoen9twULhbWXisfh3HimGqMlHGUul0/TfKCnXg42LuPpQ==",
|
||||||
|
"requires": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.19.4",
|
||||||
|
"es-shim-unscopables": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"array.prototype.flat": {
|
"array.prototype.flat": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
|
||||||
@@ -33583,7 +33106,8 @@
|
|||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"at-least-node": {
|
"at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -33597,11 +33121,6 @@
|
|||||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"attr-accept": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
|
|
||||||
},
|
|
||||||
"autoprefixer": {
|
"autoprefixer": {
|
||||||
"version": "10.2.6",
|
"version": "10.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.6.tgz",
|
||||||
@@ -33620,19 +33139,17 @@
|
|||||||
"version": "0.21.4",
|
"version": "0.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.14.0"
|
"follow-redirects": "^1.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"axios-cache-interceptor": {
|
"axios-cache-adapter": {
|
||||||
"version": "0.10.7",
|
"version": "2.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-0.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz",
|
||||||
"integrity": "sha512-UjpxChG5DpF6Kf1IPGMLOzRDNL8ZNS6TOn1jTaVvCE7cWFU904jJwi0T1s+IbijpnLEjK2iq5uLIuR8Sj+RsFQ==",
|
"integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cache-parser": "^1.2.4",
|
"cache-control-esm": "1.0.0",
|
||||||
"fast-defer": "^1.1.7",
|
"md5": "^2.2.1"
|
||||||
"object-code": "^1.2.4"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"axios-mock-adapter": {
|
"axios-mock-adapter": {
|
||||||
@@ -34425,9 +33942,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
|
||||||
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
|
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
@@ -34552,10 +34069,10 @@
|
|||||||
"unset-value": "^1.0.0"
|
"unset-value": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cache-parser": {
|
"cache-control-esm": {
|
||||||
"version": "1.2.4",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/cache-control-esm/-/cache-control-esm-1.0.0.tgz",
|
||||||
"integrity": "sha512-O0KwuHuJnbHUrghHi2kGp0SxnWSIBXTYt7M8WVhW0kbPRUNUKoE/Of6e1rRD6AAxmfxFunKnt90yEK09D+sc5g=="
|
"integrity": "sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g=="
|
||||||
},
|
},
|
||||||
"cacheable-request": {
|
"cacheable-request": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
@@ -34634,7 +34151,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
@@ -34740,6 +34256,11 @@
|
|||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"charenc": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
|
||||||
|
},
|
||||||
"check-types": {
|
"check-types": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
||||||
@@ -35098,6 +34619,7 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
@@ -35331,6 +34853,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"crypt": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
|
||||||
|
},
|
||||||
"css": {
|
"css": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
||||||
@@ -35600,7 +35127,8 @@
|
|||||||
"decode-uri-component": {
|
"decode-uri-component": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"decompress": {
|
"decompress": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
@@ -35900,7 +35428,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
||||||
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-property-descriptors": "^1.0.0",
|
"has-property-descriptors": "^1.0.0",
|
||||||
"object-keys": "^1.1.1"
|
"object-keys": "^1.1.1"
|
||||||
@@ -35934,7 +35461,8 @@
|
|||||||
"delayed-stream": {
|
"delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -36381,7 +35909,6 @@
|
|||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
|
||||||
"integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
|
"integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"es-to-primitive": "^1.2.1",
|
"es-to-primitive": "^1.2.1",
|
||||||
@@ -36429,7 +35956,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
|
||||||
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
|
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
}
|
}
|
||||||
@@ -36438,7 +35964,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-callable": "^1.1.4",
|
"is-callable": "^1.1.4",
|
||||||
"is-date-object": "^1.0.1",
|
"is-date-object": "^1.0.1",
|
||||||
@@ -37400,11 +36925,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
"fast-defer": {
|
|
||||||
"version": "1.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.7.tgz",
|
|
||||||
"integrity": "sha512-tJ01ulDWT2WhqxMKS20nXX6wyX2iInBYpbN3GO7yjKwXMY4qvkdBRxak9IFwBLlFDESox+SwSvqMCZDfe1tqeg=="
|
|
||||||
},
|
|
||||||
"fast-glob": {
|
"fast-glob": {
|
||||||
"version": "3.2.11",
|
"version": "3.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||||
@@ -37533,21 +37053,6 @@
|
|||||||
"schema-utils": "^3.0.0"
|
"schema-utils": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"file-selector": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
|
|
||||||
"requires": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": {
|
|
||||||
"version": "2.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
|
||||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"file-type": {
|
"file-type": {
|
||||||
"version": "12.4.2",
|
"version": "12.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
|
||||||
@@ -37588,11 +37093,6 @@
|
|||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter-obj": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
|
|
||||||
},
|
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
@@ -37979,14 +37479,12 @@
|
|||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"function.prototype.name": {
|
"function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
|
||||||
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
|
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -38003,8 +37501,7 @@
|
|||||||
"functions-have-names": {
|
"functions-have-names": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"gensync": {
|
"gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
@@ -38022,7 +37519,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
||||||
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
|
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.1",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@@ -38063,7 +37559,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||||
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"get-intrinsic": "^1.1.1"
|
"get-intrinsic": "^1.1.1"
|
||||||
@@ -38188,7 +37683,6 @@
|
|||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"inflight": "^1.0.4",
|
"inflight": "^1.0.4",
|
||||||
@@ -38328,7 +37822,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
}
|
}
|
||||||
@@ -38336,8 +37829,7 @@
|
|||||||
"has-bigints": {
|
"has-bigints": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
||||||
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
|
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -38349,7 +37841,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"get-intrinsic": "^1.1.1"
|
"get-intrinsic": "^1.1.1"
|
||||||
}
|
}
|
||||||
@@ -38364,8 +37855,7 @@
|
|||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-to-string-tag-x": {
|
"has-to-string-tag-x": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
@@ -38381,7 +37871,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-symbols": "^1.0.2"
|
"has-symbols": "^1.0.2"
|
||||||
}
|
}
|
||||||
@@ -39247,7 +38736,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
|
||||||
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"get-intrinsic": "^1.1.0",
|
"get-intrinsic": "^1.1.0",
|
||||||
"has": "^1.0.3",
|
"has": "^1.0.3",
|
||||||
@@ -39365,7 +38853,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
|
||||||
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-bigints": "^1.0.1"
|
"has-bigints": "^1.0.1"
|
||||||
}
|
}
|
||||||
@@ -39389,7 +38876,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||||
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
@@ -39404,8 +38890,7 @@
|
|||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
|
||||||
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==",
|
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-ci": {
|
"is-ci": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -39465,7 +38950,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
||||||
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -39585,8 +39069,7 @@
|
|||||||
"is-negative-zero": {
|
"is-negative-zero": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
|
||||||
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
|
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
@@ -39598,7 +39081,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
||||||
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
|
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -39667,7 +39149,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
@@ -39696,7 +39177,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
|
||||||
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
|
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2"
|
"call-bind": "^1.0.2"
|
||||||
}
|
}
|
||||||
@@ -39711,7 +39191,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
||||||
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -39730,7 +39209,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
||||||
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
|
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-symbols": "^1.0.2"
|
"has-symbols": "^1.0.2"
|
||||||
}
|
}
|
||||||
@@ -39754,7 +39232,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
|
||||||
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
|
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2"
|
"call-bind": "^1.0.2"
|
||||||
}
|
}
|
||||||
@@ -42918,14 +42395,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mailto-link": {
|
"mailto-link": {
|
||||||
"version": "2.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-1.0.0.tgz",
|
||||||
"integrity": "sha512-b5FErkZ4t6mpH1IFZSw7Mm2IQHXQ2R0/5Q4xd7Rv8dVkWvE54mFG/UW7HjfFazXFjXTNsM+dSX2tTeIDrV9K9A==",
|
"integrity": "sha512-DVRvtkoeXLHAbH+S+9m3ILIdnvQsSc9IvJwfEclQVD8e8FhzwA5Mtw4Q0XXYr/sAziw/HsMc/gpGAI+5w6ohIw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"assert-ok": "~1.0.0",
|
"assert-ok": "~1.0.0",
|
||||||
"cast-array": "~1.0.1",
|
"cast-array": "~1.0.0",
|
||||||
"object-filter": "~1.0.2",
|
"object-filter": "~1.0.2",
|
||||||
"query-string": "~7.0.0"
|
"query-string": "~2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
@@ -42978,6 +42455,23 @@
|
|||||||
"css-mediaquery": "^0.1.2"
|
"css-mediaquery": "^0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"md5": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||||
|
"requires": {
|
||||||
|
"charenc": "0.0.2",
|
||||||
|
"crypt": "0.0.2",
|
||||||
|
"is-buffer": "~1.1.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"is-buffer": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"mdn-data": {
|
"mdn-data": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
@@ -43425,11 +42919,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||||
},
|
},
|
||||||
"object-code": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-code/-/object-code-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-uGq4ETUuWe+GA586NXEriiaozNuff+YNFXlpD8cVrM1GoiuTZpCABP+bZCWDrvQDoCiSTyiWAFHD/HF/iwhb2w=="
|
|
||||||
},
|
|
||||||
"object-copy": {
|
"object-copy": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||||
@@ -43512,14 +43001,12 @@
|
|||||||
"object-inspect": {
|
"object-inspect": {
|
||||||
"version": "1.12.2",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
|
||||||
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
|
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"object-is": {
|
"object-is": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
||||||
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3"
|
"define-properties": "^1.1.3"
|
||||||
@@ -43528,8 +43015,7 @@
|
|||||||
"object-keys": {
|
"object-keys": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"object-visit": {
|
"object-visit": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -43544,7 +43030,6 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||||
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -43556,7 +43041,6 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz",
|
||||||
"integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==",
|
"integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -44668,6 +44152,16 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prop-types-exact": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==",
|
||||||
|
"requires": {
|
||||||
|
"has": "^1.0.3",
|
||||||
|
"object.assign": "^4.1.0",
|
||||||
|
"reflect.ownkeys": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prop-types-extra": {
|
"prop-types-extra": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||||
@@ -44742,21 +44236,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query-string": {
|
"query-string": {
|
||||||
"version": "7.0.1",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-2.4.2.tgz",
|
||||||
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
|
"integrity": "sha512-Y+OMYUuY7HxznI6WBN822fi/FMvnCTiuqd6KNcidPColOmMWPoV1RGYyyzObve1T/dD1i0ZgCCbO8ytu0ZUrkA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"decode-uri-component": "^0.2.0",
|
"strict-uri-encode": "^1.0.0"
|
||||||
"filter-obj": "^1.1.0",
|
|
||||||
"split-on-first": "^1.0.0",
|
|
||||||
"strict-uri-encode": "^2.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"strict-uri-encode": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"queue-microtask": {
|
"queue-microtask": {
|
||||||
@@ -44842,12 +44326,6 @@
|
|||||||
"@babel/runtime": "^7.12.13"
|
"@babel/runtime": "^7.12.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-colorful": {
|
|
||||||
"version": "5.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
|
||||||
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
|
||||||
@@ -45036,16 +44514,6 @@
|
|||||||
"scheduler": "^0.19.1"
|
"scheduler": "^0.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dropzone": {
|
|
||||||
"version": "14.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.2.tgz",
|
|
||||||
"integrity": "sha512-5oyGN/B5rNhop2ggUnxztXBQ6q6zii+OMEftPzsxAR2hhpVWz0nAV+3Ktxo2h5bZzdcCKrpd8bfWAVsveIBM+w==",
|
|
||||||
"requires": {
|
|
||||||
"attr-accept": "^2.2.2",
|
|
||||||
"file-selector": "^0.6.0",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.11",
|
"version": "6.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
||||||
@@ -45134,12 +44602,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
},
|
},
|
||||||
"react-loading-skeleton": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"react-mathjax-preview": {
|
"react-mathjax-preview": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-mathjax-preview/-/react-mathjax-preview-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-mathjax-preview/-/react-mathjax-preview-2.2.6.tgz",
|
||||||
@@ -45495,6 +44957,11 @@
|
|||||||
"integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
|
"integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"reflect.ownkeys": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-qOLsBKHCpSOFKK1NUOCGC5VyeufB6lEsFe92AL2bhIJsacZS1qdoOZSbPk3MYKuT2cFlRDnulKXuuElIrMjGUg=="
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
@@ -45544,7 +45011,6 @@
|
|||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
|
||||||
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
|
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.3",
|
"define-properties": "^1.1.3",
|
||||||
@@ -46351,7 +45817,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.0",
|
"call-bind": "^1.0.0",
|
||||||
"get-intrinsic": "^1.0.2",
|
"get-intrinsic": "^1.0.2",
|
||||||
@@ -46778,11 +46243,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"split-on-first": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
|
|
||||||
},
|
|
||||||
"split-string": {
|
"split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
@@ -46935,9 +46395,7 @@
|
|||||||
"strict-uri-encode": {
|
"strict-uri-encode": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||||
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==",
|
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ=="
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -47017,7 +46475,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
"integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
|
"integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.4",
|
"define-properties": "^1.1.4",
|
||||||
@@ -47028,7 +46485,6 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
|
||||||
"integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
|
"integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"define-properties": "^1.1.4",
|
"define-properties": "^1.1.4",
|
||||||
@@ -47256,9 +46712,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tabbable": {
|
"tabbable": {
|
||||||
"version": "5.3.3",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
|
||||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
|
"integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"version": "5.4.6",
|
"version": "5.4.6",
|
||||||
@@ -47821,7 +47277,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||||
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
|
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.2",
|
"call-bind": "^1.0.2",
|
||||||
"has-bigints": "^1.0.2",
|
"has-bigints": "^1.0.2",
|
||||||
@@ -48702,7 +48157,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
||||||
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-bigint": "^1.0.1",
|
"is-bigint": "^1.0.1",
|
||||||
"is-boolean-object": "^1.1.0",
|
"is-boolean-object": "^1.1.0",
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "12.0.0",
|
"@edx/frontend-component-footer": "11.2.0",
|
||||||
"@edx/frontend-component-header": "4.0.0",
|
"@edx/frontend-component-header": "3.2.0",
|
||||||
"@edx/frontend-platform": "4.2.0",
|
"@edx/frontend-platform": "2.6.1",
|
||||||
"@edx/paragon": "20.15.0",
|
"@edx/paragon": "19.10.1",
|
||||||
"@reduxjs/toolkit": "1.8.0",
|
"@reduxjs/toolkit": "1.8.0",
|
||||||
"@tinymce/tinymce-react": "3.13.1",
|
"@tinymce/tinymce-react": "3.13.1",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
type="image/x-icon"
|
type="image/x-icon"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="vh-100 vw-100 h-100 m-0">
|
||||||
<div id="root" class="small"></div>
|
<div id="root" class="vh-100 vw-100 small"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
/* eslint-disable react/forbid-prop-types */
|
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { capitalize, toString } from 'lodash';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
|
||||||
import {
|
|
||||||
Collapsible, Form, Icon, Spinner,
|
|
||||||
} from '@edx/paragon';
|
|
||||||
import { Tune } from '@edx/paragon/icons';
|
|
||||||
|
|
||||||
import {
|
|
||||||
PostsStatusFilter, RequestStatus,
|
|
||||||
ThreadOrdering, ThreadType,
|
|
||||||
} from '../data/constants';
|
|
||||||
import { selectCourseCohorts } from '../discussions/cohorts/data/selectors';
|
|
||||||
import messages from '../discussions/posts/post-filter-bar/messages';
|
|
||||||
import { ActionItem } from '../discussions/posts/post-filter-bar/PostFilterBar';
|
|
||||||
|
|
||||||
function FilterBar({
|
|
||||||
intl,
|
|
||||||
filters,
|
|
||||||
selectedFilters,
|
|
||||||
onFilterChange,
|
|
||||||
showCohortsFilter,
|
|
||||||
}) {
|
|
||||||
const [isOpen, setOpen] = useState(false);
|
|
||||||
const cohorts = useSelector(selectCourseCohorts);
|
|
||||||
const { status } = useSelector(state => state.cohorts);
|
|
||||||
const selectedCohort = useMemo(() => cohorts.find(cohort => (
|
|
||||||
toString(cohort.id) === selectedFilters.cohort)),
|
|
||||||
[selectedFilters.cohort]);
|
|
||||||
|
|
||||||
const allFilters = [
|
|
||||||
{
|
|
||||||
id: 'type-all',
|
|
||||||
label: intl.formatMessage(messages.allPosts),
|
|
||||||
value: ThreadType.ALL,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'type-discussions',
|
|
||||||
label: intl.formatMessage(messages.filterDiscussions),
|
|
||||||
value: ThreadType.DISCUSSION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'type-questions',
|
|
||||||
label: intl.formatMessage(messages.filterQuestions),
|
|
||||||
value: ThreadType.QUESTION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status-any',
|
|
||||||
label: intl.formatMessage(messages.filterAnyStatus),
|
|
||||||
value: PostsStatusFilter.ALL,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status-unread',
|
|
||||||
label: intl.formatMessage(messages.filterUnread),
|
|
||||||
value: PostsStatusFilter.UNREAD,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status-reported',
|
|
||||||
label: intl.formatMessage(messages.filterReported),
|
|
||||||
value: PostsStatusFilter.REPORTED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status-unanswered',
|
|
||||||
label: intl.formatMessage(messages.filterUnanswered),
|
|
||||||
value: PostsStatusFilter.UNANSWERED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'status-unresponded',
|
|
||||||
label: intl.formatMessage(messages.filterUnresponded),
|
|
||||||
value: PostsStatusFilter.UNRESPONDED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sort-activity',
|
|
||||||
label: intl.formatMessage(messages.lastActivityAt),
|
|
||||||
value: ThreadOrdering.BY_LAST_ACTIVITY,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sort-comments',
|
|
||||||
label: intl.formatMessage(messages.commentCount),
|
|
||||||
value: ThreadOrdering.BY_COMMENT_COUNT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sort-votes',
|
|
||||||
label: intl.formatMessage(messages.voteCount),
|
|
||||||
value: ThreadOrdering.BY_VOTE_COUNT,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapsible.Advanced
|
|
||||||
open={isOpen}
|
|
||||||
onToggle={() => setOpen(!isOpen)}
|
|
||||||
className="filter-bar collapsible-card-lg border-0"
|
|
||||||
>
|
|
||||||
<Collapsible.Trigger className="collapsible-trigger border-0">
|
|
||||||
<span className="text-primary-700 pr-4">
|
|
||||||
{intl.formatMessage(messages.sortFilterStatus, {
|
|
||||||
own: false,
|
|
||||||
type: selectedFilters.postType,
|
|
||||||
sort: selectedFilters.orderBy,
|
|
||||||
status: selectedFilters.status,
|
|
||||||
cohortType: selectedCohort?.name ? 'group' : 'all',
|
|
||||||
cohort: capitalize(selectedCohort?.name),
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<Collapsible.Visible whenClosed>
|
|
||||||
<Icon src={Tune} />
|
|
||||||
</Collapsible.Visible>
|
|
||||||
<Collapsible.Visible whenOpen>
|
|
||||||
<Icon src={Tune} />
|
|
||||||
</Collapsible.Visible>
|
|
||||||
</Collapsible.Trigger>
|
|
||||||
<Collapsible.Body className="collapsible-body px-4 pb-3 pt-0">
|
|
||||||
<Form>
|
|
||||||
<div className="d-flex flex-row py-2 justify-content-between">
|
|
||||||
{filters.map((value) => (
|
|
||||||
<Form.RadioSet
|
|
||||||
name={value.name}
|
|
||||||
className="d-flex flex-column list-group list-group-flush"
|
|
||||||
value={selectedFilters[value.name]}
|
|
||||||
onChange={onFilterChange}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
value.filters.map(filterName => {
|
|
||||||
const element = allFilters.find(obj => obj.id === filterName);
|
|
||||||
if (element) {
|
|
||||||
return (
|
|
||||||
<ActionItem
|
|
||||||
id={element.id}
|
|
||||||
label={element.label}
|
|
||||||
value={element.value}
|
|
||||||
selected={selectedFilters[value.name]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
</Form.RadioSet>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{showCohortsFilter && (
|
|
||||||
<>
|
|
||||||
<div className="border-bottom my-2" />
|
|
||||||
{status === RequestStatus.IN_PROGRESS ? (
|
|
||||||
<div className="d-flex justify-content-center p-4">
|
|
||||||
<Spinner animation="border" variant="primary" size="lg" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="d-flex flex-row pt-2">
|
|
||||||
<Form.RadioSet
|
|
||||||
name="cohort"
|
|
||||||
className="d-flex flex-column list-group list-group-flush w-100"
|
|
||||||
value={selectedFilters.cohort}
|
|
||||||
onChange={onFilterChange}
|
|
||||||
>
|
|
||||||
<ActionItem
|
|
||||||
id="all-groups"
|
|
||||||
label="All groups"
|
|
||||||
value=""
|
|
||||||
selected={selectedFilters.cohort}
|
|
||||||
/>
|
|
||||||
{cohorts.map(cohort => (
|
|
||||||
<ActionItem
|
|
||||||
key={toString(cohort.id)}
|
|
||||||
id={toString(cohort.id)}
|
|
||||||
label={capitalize(cohort.name)}
|
|
||||||
value={toString(cohort.id)}
|
|
||||||
selected={selectedFilters.cohort}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Form.RadioSet>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Collapsible.Body>
|
|
||||||
</Collapsible.Advanced>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterBar.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
filters: PropTypes.array.isRequired,
|
|
||||||
selectedFilters: PropTypes.object.isRequired,
|
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
|
||||||
showCohortsFilter: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
FilterBar.defaultProps = {
|
|
||||||
showCohortsFilter: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(FilterBar);
|
|
||||||
@@ -11,15 +11,12 @@ const baseConfig = {
|
|||||||
['\\\\(', '\\\\)'],
|
['\\\\(', '\\\\)'],
|
||||||
['\\(', '\\)'],
|
['\\(', '\\)'],
|
||||||
['[mathjaxinline]', '[/mathjaxinline]'],
|
['[mathjaxinline]', '[/mathjaxinline]'],
|
||||||
['\\begin{math}', '\\end{math}'],
|
|
||||||
],
|
],
|
||||||
displayMath: [
|
displayMath: [
|
||||||
['[mathjax]', '[/mathjax]'],
|
['[mathjax]', '[/mathjax]'],
|
||||||
['$$', '$$'],
|
['$$', '$$'],
|
||||||
['\\\\[', '\\\\]'],
|
['\\\\[', '\\\\]'],
|
||||||
['\\[', '\\]'],
|
['\\[', '\\]'],
|
||||||
['\\begin{displaymath}', '\\end{displaymath}'],
|
|
||||||
['\\begin{equation}', '\\end{equation}'],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -30,9 +27,6 @@ function HTMLLoader({ htmlNode, componentId, cssClassName }) {
|
|||||||
const isLatex = htmlNode.match(/(\${1,2})((?:\\.|.)*)\1/)
|
const isLatex = htmlNode.match(/(\${1,2})((?:\\.|.)*)\1/)
|
||||||
|| htmlNode.match(/(\[mathjax](.+?)\[\/mathjax])+/)
|
|| htmlNode.match(/(\[mathjax](.+?)\[\/mathjax])+/)
|
||||||
|| htmlNode.match(/(\[mathjaxinline](.+?)\[\/mathjaxinline])+/)
|
|| htmlNode.match(/(\[mathjaxinline](.+?)\[\/mathjaxinline])+/)
|
||||||
|| htmlNode.match(/(\\begin\{math}(.+?)\\end\{math})+/)
|
|
||||||
|| htmlNode.match(/(\\begin\{displaymath}(.+?)\\end\{displaymath})+/)
|
|
||||||
|| htmlNode.match(/(\\begin\{equation}(.+?)\\end\{equation})+/)
|
|
||||||
|| htmlNode.match(/(\\\[(.+?)\\\])+/)
|
|| htmlNode.match(/(\\\[(.+?)\\\])+/)
|
||||||
|| htmlNode.match(/(\\\((.+?)\\\))+/);
|
|| htmlNode.match(/(\\\((.+?)\\\))+/);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { camelCaseObject } from '@edx/frontend-platform';
|
import { camelCaseObject } from '@edx/frontend-platform';
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
import { getApiBaseUrl } from '../../../data/constants';
|
import { API_BASE_URL } from '../../../data/constants';
|
||||||
|
|
||||||
function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
|
function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
|
||||||
const data = camelCaseObject(metadata);
|
const data = camelCaseObject(metadata);
|
||||||
@@ -21,7 +21,7 @@ function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
|
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
|
||||||
const url = `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
|
const url = `${API_BASE_URL}/api/course_home/course_metadata/${courseId}`;
|
||||||
// don't know the context of adding timezone in url. hence omitting it
|
// don't know the context of adding timezone in url. hence omitting it
|
||||||
// url = appendBrowserTimezoneToUrl(url);
|
// url = appendBrowserTimezoneToUrl(url);
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ function PostPreviewPane({
|
|||||||
<>
|
<>
|
||||||
{showPreviewPane && (
|
{showPreviewPane && (
|
||||||
<div
|
<div
|
||||||
className={`w-100 p-2 bg-light-200 rounded box-shadow-down-1 post-preview ${isPost ? 'mt-2 mb-5' : 'my-3'}`}
|
className={`p-2 bg-light-200 rounded box-shadow-down-1 post-preview ${isPost ? 'mt-2 mb-5' : 'my-3'}`}
|
||||||
style={{ minHeight: '200px', wordBreak: 'break-word' }}
|
style={{ maxHeight: '200px', overflow: 'scroll' }}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => setShowPreviewPane(false)}
|
onClick={() => setShowPreviewPane(false)}
|
||||||
|
|||||||
@@ -14,23 +14,18 @@ function SearchInfo({
|
|||||||
text,
|
text,
|
||||||
loadingStatus,
|
loadingStatus,
|
||||||
onClear,
|
onClear,
|
||||||
textSearchRewrite,
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="d-flex flex-row border-bottom border-light-400">
|
<div className="d-flex flex-row border-bottom border-light-400">
|
||||||
<Icon src={Search} className="justify-content-start ml-3.5 mr-2 mb-2 mt-2.5" />
|
<Icon src={Search} className="justify-content-start ml-3.5 mr-2 mb-2 mt-2.5" />
|
||||||
<Button variant="" size="inline" className="text-justify p-2">
|
<Button variant="" size="inline">
|
||||||
{loadingStatus === RequestStatus.SUCCESSFUL && (
|
{
|
||||||
textSearchRewrite ? intl.formatMessage(messages.searchRewriteInfo, {
|
loadingStatus === RequestStatus.SUCCESSFUL
|
||||||
searchString: text,
|
? intl.formatMessage(messages.searchInfo, { count, text })
|
||||||
count,
|
: intl.formatMessage(messages.searchInfoSearching)
|
||||||
textSearchRewrite,
|
}
|
||||||
})
|
|
||||||
: intl.formatMessage(messages.searchInfo, { count, text })
|
|
||||||
)}
|
|
||||||
{loadingStatus !== RequestStatus.SUCCESSFUL && intl.formatMessage(messages.searchInfoSearching)}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" size="inline" className="ml-auto mr-3" onClick={onClear} style={{ minWidth: '26%' }}>
|
<Button variant="link" size="inline" className="ml-auto mr-4" onClick={onClear}>
|
||||||
{intl.formatMessage(messages.clearSearch)}
|
{intl.formatMessage(messages.clearSearch)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,13 +37,11 @@ SearchInfo.propTypes = {
|
|||||||
count: PropTypes.number.isRequired,
|
count: PropTypes.number.isRequired,
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
loadingStatus: PropTypes.string.isRequired,
|
loadingStatus: PropTypes.string.isRequired,
|
||||||
textSearchRewrite: PropTypes.string,
|
|
||||||
onClear: PropTypes.func,
|
onClear: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
SearchInfo.defaultProps = {
|
SearchInfo.defaultProps = {
|
||||||
onClear: () => {},
|
onClear: () => {},
|
||||||
textSearchRewrite: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(SearchInfo);
|
export default injectIntl(SearchInfo);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Editor } from '@tinymce/tinymce-react';
|
import { Editor } from '@tinymce/tinymce-react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
@@ -6,11 +6,7 @@ import { useParams } from 'react-router';
|
|||||||
// eslint-disable-next-line no-unused-vars,import/no-extraneous-dependencies
|
// eslint-disable-next-line no-unused-vars,import/no-extraneous-dependencies
|
||||||
import tinymce from 'tinymce/tinymce';
|
import tinymce from 'tinymce/tinymce';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { ActionRow, AlertModal, Button } from '@edx/paragon';
|
|
||||||
|
|
||||||
import { MAX_UPLOAD_FILE_SIZE } from '../data/constants';
|
import { MAX_UPLOAD_FILE_SIZE } from '../data/constants';
|
||||||
import messages from '../discussions/messages';
|
|
||||||
import { uploadFile } from '../discussions/posts/data/api';
|
import { uploadFile } from '../discussions/posts/data/api';
|
||||||
|
|
||||||
import 'tinymce/plugins/code';
|
import 'tinymce/plugins/code';
|
||||||
@@ -22,7 +18,6 @@ import 'tinymce/icons/default';
|
|||||||
import 'tinymce/skins/ui/oxide/skin.css';
|
import 'tinymce/skins/ui/oxide/skin.css';
|
||||||
// importing the plugin js.
|
// importing the plugin js.
|
||||||
import 'tinymce/plugins/autolink';
|
import 'tinymce/plugins/autolink';
|
||||||
import 'tinymce/plugins/autoresize';
|
|
||||||
import 'tinymce/plugins/autosave';
|
import 'tinymce/plugins/autosave';
|
||||||
import 'tinymce/plugins/codesample';
|
import 'tinymce/plugins/codesample';
|
||||||
import 'tinymce/plugins/image';
|
import 'tinymce/plugins/image';
|
||||||
@@ -62,8 +57,7 @@ export default function TinyMCEEditor(props) {
|
|||||||
// loading process and is instead loaded as a string via content_style
|
// loading process and is instead loaded as a string via content_style
|
||||||
|
|
||||||
const { courseId, postId } = useParams();
|
const { courseId, postId } = useParams();
|
||||||
const [showImageWarning, setShowImageWarning] = useState(false);
|
|
||||||
const intl = useIntl();
|
|
||||||
const uploadHandler = async (blobInfo, success, failure) => {
|
const uploadHandler = async (blobInfo, success, failure) => {
|
||||||
try {
|
try {
|
||||||
const blob = blobInfo.blob();
|
const blob = blobInfo.blob();
|
||||||
@@ -74,11 +68,6 @@ export default function TinyMCEEditor(props) {
|
|||||||
}
|
}
|
||||||
const filename = blobInfo.filename();
|
const filename = blobInfo.filename();
|
||||||
const { location } = await uploadFile(blob, filename, courseId, postId || 'root');
|
const { location } = await uploadFile(blob, filename, courseId, postId || 'root');
|
||||||
const img = new Image();
|
|
||||||
img.onload = function () {
|
|
||||||
if (img.height > 999 || img.width > 999) { setShowImageWarning(true); }
|
|
||||||
};
|
|
||||||
img.src = location;
|
|
||||||
success(location);
|
success(location);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failure(e.toString(), { remove: true });
|
failure(e.toString(), { remove: true });
|
||||||
@@ -94,54 +83,34 @@ export default function TinyMCEEditor(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Editor
|
||||||
<Editor
|
init={{
|
||||||
init={{
|
skin: false,
|
||||||
skin: false,
|
menubar: false,
|
||||||
menubar: false,
|
branding: false,
|
||||||
branding: false,
|
contextmenu: false,
|
||||||
contextmenu: false,
|
browser_spellcheck: true,
|
||||||
browser_spellcheck: true,
|
a11y_advanced_options: true,
|
||||||
a11y_advanced_options: true,
|
autosave_interval: '1s',
|
||||||
autosave_interval: '1s',
|
autosave_restore_when_empty: false,
|
||||||
autosave_restore_when_empty: false,
|
plugins: 'autosave codesample link lists image imagetools code emoticons charmap',
|
||||||
plugins: 'autoresize autosave codesample link lists image imagetools code emoticons charmap',
|
toolbar: 'undo redo'
|
||||||
toolbar: 'undo redo'
|
+ ' | formatselect | bold italic underline'
|
||||||
+ ' | formatselect | bold italic underline'
|
+ ' | link blockquote openedx_code image'
|
||||||
+ ' | link blockquote openedx_code image'
|
+ ' | bullist numlist outdent indent'
|
||||||
+ ' | bullist numlist outdent indent'
|
+ ' | removeformat'
|
||||||
+ ' | removeformat'
|
+ ' | openedx_html'
|
||||||
+ ' | openedx_html'
|
+ ' | emoticons'
|
||||||
+ ' | emoticons'
|
+ ' | charmap',
|
||||||
+ ' | charmap',
|
content_css: false,
|
||||||
content_css: false,
|
content_style: contentStyle,
|
||||||
content_style: contentStyle,
|
body_class: 'm-2 text-editor',
|
||||||
body_class: 'm-2 text-editor',
|
default_link_target: '_blank',
|
||||||
default_link_target: '_blank',
|
target_list: false,
|
||||||
target_list: false,
|
images_upload_handler: uploadHandler,
|
||||||
images_upload_handler: uploadHandler,
|
setup,
|
||||||
setup,
|
}}
|
||||||
}}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
|
||||||
<AlertModal
|
|
||||||
title={intl.formatMessage(messages.imageWarningModalTitle)}
|
|
||||||
isOpen={showImageWarning}
|
|
||||||
onClose={() => setShowImageWarning(false)}
|
|
||||||
isBlocking
|
|
||||||
footerNode={(
|
|
||||||
<ActionRow>
|
|
||||||
<Button variant="danger" onClick={() => setShowImageWarning(false)}>
|
|
||||||
{intl.formatMessage(messages.imageWarningDismissButton)}
|
|
||||||
</Button>
|
|
||||||
</ActionRow>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage(messages.imageWarningMessage)}
|
|
||||||
</p>
|
|
||||||
</AlertModal>
|
|
||||||
</>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/components/icons/ReportGmailerrorred.jsx
Normal file
24
src/components/icons/ReportGmailerrorred.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function ReportGmailerrorred() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#clip0_6935_1296)">
|
||||||
|
<path d="M13.1083 2.5H6.89167L2.5 6.89167V13.1083L6.89167 17.5H13.1083L17.5 13.1083V6.89167L13.1083 2.5ZM15.8333 12.4167L12.4167 15.8333H7.58333L4.16667 12.4167V7.58333L7.58333 4.16667H12.4167L15.8333 7.58333V12.4167Z" fill="#00262B" />
|
||||||
|
<path d="M9.99996 14.1667C10.4602 14.1667 10.8333 13.7936 10.8333 13.3333C10.8333 12.8731 10.4602 12.5 9.99996 12.5C9.53972 12.5 9.16663 12.8731 9.16663 13.3333C9.16663 13.7936 9.53972 14.1667 9.99996 14.1667Z" fill="#00262B" />
|
||||||
|
<path d="M9.16663 5.83331H10.8333V11.6666H9.16663V5.83331Z" fill="#00262B" />
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_6935_1296">
|
||||||
|
<rect width="20" height="20" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ export { default as PushPin } from './PushPin';
|
|||||||
export { default as Question } from './Question';
|
export { default as Question } from './Question';
|
||||||
export { default as QuestionAnswer } from './QuestionAnswer';
|
export { default as QuestionAnswer } from './QuestionAnswer';
|
||||||
export { default as QuestionAnswerOutline } from './QuestionAnswerOutline';
|
export { default as QuestionAnswerOutline } from './QuestionAnswerOutline';
|
||||||
|
export { default as ReportGmailerrorred } from './ReportGmailerrorred';
|
||||||
export { default as StarFilled } from './StarFilled';
|
export { default as StarFilled } from './StarFilled';
|
||||||
export { default as StarOutline } from './StarOutline';
|
export { default as StarOutline } from './StarOutline';
|
||||||
export { default as ThumbUpFilled } from './ThumbUpFilled';
|
export { default as ThumbUpFilled } from './ThumbUpFilled';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
import { getApiBaseUrl } from './constants';
|
import { API_BASE_URL } from './constants';
|
||||||
|
|
||||||
export const getBlocksAPIURL = () => `${getApiBaseUrl()}/api/courses/v1/blocks/`;
|
export const blocksAPIURL = `${API_BASE_URL}/api/courses/v1/blocks/`;
|
||||||
export async function getCourseBlocks(courseId, username) {
|
export async function getCourseBlocks(courseId, username) {
|
||||||
const params = {
|
const params = {
|
||||||
course_id: courseId,
|
course_id: courseId,
|
||||||
@@ -14,6 +14,6 @@ export async function getCourseBlocks(courseId, username) {
|
|||||||
student_view_data: 'discussion',
|
student_view_data: 'discussion',
|
||||||
};
|
};
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.get(getBlocksAPIURL(), { params });
|
.get(blocksAPIURL, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
export const getApiBaseUrl = () => getConfig().LMS_BASE_URL;
|
export const API_BASE_URL = getConfig().LMS_BASE_URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for thread types.
|
* Enum for thread types.
|
||||||
@@ -90,6 +90,16 @@ export const ThreadOrdering = {
|
|||||||
BY_VOTE_COUNT: 'voteCount',
|
BY_VOTE_COUNT: 'voteCount',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for thread view status filtering.
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const ThreadViewStatus = {
|
||||||
|
UNREAD: 'unread',
|
||||||
|
UNANSWERED: 'unanswered',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for filtering posts by status.
|
* Enum for filtering posts by status.
|
||||||
* @readonly
|
* @readonly
|
||||||
@@ -101,7 +111,6 @@ export const PostsStatusFilter = {
|
|||||||
FOLLOWING: 'statusFollowing',
|
FOLLOWING: 'statusFollowing',
|
||||||
REPORTED: 'statusReported',
|
REPORTED: 'statusReported',
|
||||||
UNANSWERED: 'statusUnanswered',
|
UNANSWERED: 'statusUnanswered',
|
||||||
UNRESPONDED: 'statusUnresponded',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +133,6 @@ export const TopicOrdering = {
|
|||||||
export const LearnersOrdering = {
|
export const LearnersOrdering = {
|
||||||
BY_FLAG: 'flagged',
|
BY_FLAG: 'flagged',
|
||||||
BY_LAST_ACTIVITY: 'activity',
|
BY_LAST_ACTIVITY: 'activity',
|
||||||
BY_RECENCY: 'recency',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import { initializeMockApp } from '@edx/frontend-platform/testing';
|
|||||||
import { initializeStore } from '../store';
|
import { initializeStore } from '../store';
|
||||||
import { executeThunk } from '../test-utils';
|
import { executeThunk } from '../test-utils';
|
||||||
import { getBlocksAPIResponse } from './__factories__';
|
import { getBlocksAPIResponse } from './__factories__';
|
||||||
import { getBlocksAPIURL } from './api';
|
import { blocksAPIURL } from './api';
|
||||||
import { RequestStatus } from './constants';
|
import { RequestStatus } from './constants';
|
||||||
import { fetchCourseBlocks } from './thunks';
|
import { fetchCourseBlocks } from './thunks';
|
||||||
|
|
||||||
const blocksAPIURL = getBlocksAPIURL();
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
|
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { selectDiscussionProvider, selectGroupAtSubsection } from '../discussions/data/selectors';
|
import { selectDiscussionProvider } from '../discussions/data/selectors';
|
||||||
import { DiscussionProvider } from './constants';
|
import { DiscussionProvider } from './constants';
|
||||||
|
|
||||||
export const selectTopicContext = (topicId) => (state) => state.blocks.topics[topicId];
|
export const selectTopicContext = (topicId) => (state) => state.blocks.topics[topicId];
|
||||||
@@ -14,18 +14,6 @@ export const selectorForUnitSubsection = createSelector(
|
|||||||
blocks => key => blocks[blocks[key]?.parent],
|
blocks => key => blocks[blocks[key]?.parent],
|
||||||
);
|
);
|
||||||
|
|
||||||
// If subsection grouping is enabled, and the current selection is a unit, then get the current subsection.
|
|
||||||
export const selectCurrentCategoryGrouping = createSelector(
|
|
||||||
selectDiscussionProvider,
|
|
||||||
selectGroupAtSubsection,
|
|
||||||
selectBlocks,
|
|
||||||
(provider, groupAtSubsection, blocks) => blockId => (
|
|
||||||
(provider !== 'openedx' || !groupAtSubsection || blocks[blockId]?.type !== 'vertical')
|
|
||||||
? blockId
|
|
||||||
: blocks[blockId].parent
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectChapters = (state) => state.blocks.chapters;
|
export const selectChapters = (state) => state.blocks.chapters;
|
||||||
export const selectTopicsUnderCategory = createSelector(
|
export const selectTopicsUnderCategory = createSelector(
|
||||||
selectDiscussionProvider,
|
selectDiscussionProvider,
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ ensureConfig([
|
|||||||
'LMS_BASE_URL',
|
'LMS_BASE_URL',
|
||||||
], 'Comments API service');
|
], 'Comments API service');
|
||||||
|
|
||||||
export const getCohortsApiUrl = courseId => `${getConfig().LMS_BASE_URL}/api/cohorts/v1/courses/${courseId}/cohorts/`;
|
const apiBaseUrl = getConfig().LMS_BASE_URL;
|
||||||
|
|
||||||
|
export const getCohortsApiUrl = courseId => `${apiBaseUrl}/api/cohorts/v1/courses/${courseId}/cohorts/`;
|
||||||
|
|
||||||
export async function getCourseCohorts(courseId) {
|
export async function getCourseCohorts(courseId) {
|
||||||
const params = snakeCaseObject({ courseId });
|
const params = snakeCaseObject({ courseId });
|
||||||
|
|||||||
@@ -1,28 +1,18 @@
|
|||||||
import React, { useContext, useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import { Button, Spinner } from '@edx/paragon';
|
||||||
Button, Icon, IconButton, Spinner,
|
|
||||||
} from '@edx/paragon';
|
|
||||||
import { ArrowBack } from '@edx/paragon/icons';
|
|
||||||
|
|
||||||
import {
|
import { EndorsementStatus, ThreadType } from '../../data/constants';
|
||||||
EndorsementStatus, PostsPages, ThreadType,
|
|
||||||
} from '../../data/constants';
|
|
||||||
import { useDispatchWithState } from '../../data/hooks';
|
import { useDispatchWithState } from '../../data/hooks';
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import { useIsOnDesktop } from '../data/hooks';
|
|
||||||
import { EmptyPage } from '../empty-posts';
|
|
||||||
import { Post } from '../posts';
|
import { Post } from '../posts';
|
||||||
import { selectThread } from '../posts/data/selectors';
|
import { selectThread } from '../posts/data/selectors';
|
||||||
import { fetchThread, markThreadAsRead } from '../posts/data/thunks';
|
import { fetchThread, markThreadAsRead } from '../posts/data/thunks';
|
||||||
import { discussionsPath, filterPosts } from '../utils';
|
import { filterPosts } from '../utils';
|
||||||
import { selectThreadComments, selectThreadCurrentPage, selectThreadHasMorePages } from './data/selectors';
|
import { selectThreadComments, selectThreadCurrentPage, selectThreadHasMorePages } from './data/selectors';
|
||||||
import { fetchThreadComments } from './data/thunks';
|
import { fetchThreadComments } from './data/thunks';
|
||||||
import { Comment, ResponseEditor } from './comment';
|
import { Comment, ResponseEditor } from './comment';
|
||||||
@@ -79,31 +69,25 @@ function DiscussionCommentsView({
|
|||||||
} = usePostComments(postId, endorsed);
|
} = usePostComments(postId, endorsed);
|
||||||
const sortedComments = useMemo(() => [...filterPosts(comments, 'endorsed'),
|
const sortedComments = useMemo(() => [...filterPosts(comments, 'endorsed'),
|
||||||
...filterPosts(comments, 'unendorsed')], [comments]);
|
...filterPosts(comments, 'unendorsed')], [comments]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{((hasMorePages && isLoading) || !isLoading)
|
|
||||||
&& (
|
|
||||||
<div className="mx-4 text-primary-700" role="heading" aria-level="2" style={{ lineHeight: '28px' }}>
|
<div className="mx-4 text-primary-700" role="heading" aria-level="2" style={{ lineHeight: '28px' }}>
|
||||||
{endorsed === EndorsementStatus.ENDORSED
|
{endorsed === EndorsementStatus.ENDORSED
|
||||||
? intl.formatMessage(messages.endorsedResponseCount, { num: sortedComments.length })
|
? intl.formatMessage(messages.endorsedResponseCount, { num: sortedComments.length })
|
||||||
: intl.formatMessage(messages.responseCount, { num: sortedComments.length })}
|
: intl.formatMessage(messages.responseCount, { num: sortedComments.length })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mx-4" role="list">
|
<div className="mx-4" role="list">
|
||||||
{sortedComments.map(comment => (
|
{sortedComments.map(comment => (
|
||||||
<Comment comment={comment} key={comment.id} postType={postType} isClosedPost={isClosed} />
|
<Comment comment={comment} key={comment.id} postType={postType} isClosedPost={isClosed} />
|
||||||
))}
|
))}
|
||||||
|
{!!sortedComments.length && !isClosed
|
||||||
|
&& <ResponseEditor postId={postId} addWrappingDiv />}
|
||||||
{hasMorePages && !isLoading && (
|
{hasMorePages && !isLoading && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleLoadMoreResponses}
|
onClick={handleLoadMoreResponses}
|
||||||
variant="link"
|
variant="link"
|
||||||
block="true"
|
block="true"
|
||||||
className="card p-4 mb-4 font-weight-500 font-size-14"
|
className="card p-4"
|
||||||
style={{
|
|
||||||
lineHeight: '20px',
|
|
||||||
}}
|
|
||||||
data-testid="load-more-comments"
|
data-testid="load-more-comments"
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.loadMoreResponses)}
|
{intl.formatMessage(messages.loadMoreResponses)}
|
||||||
@@ -115,8 +99,6 @@ function DiscussionCommentsView({
|
|||||||
<Spinner animation="border" variant="primary" />
|
<Spinner animation="border" variant="primary" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!sortedComments.length && !isClosed
|
|
||||||
&& <ResponseEditor postId={postId} addWrappingDiv />}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
@@ -134,74 +116,23 @@ DiscussionCommentsView.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function CommentsView({ intl }) {
|
function CommentsView({ intl }) {
|
||||||
const [isLoading, submitDispatch] = useDispatchWithState();
|
|
||||||
const { postId } = useParams();
|
const { postId } = useParams();
|
||||||
const thread = usePost(postId);
|
const thread = usePost(postId);
|
||||||
const history = useHistory();
|
const dispatch = useDispatch();
|
||||||
const location = useLocation();
|
|
||||||
const isOnDesktop = useIsOnDesktop();
|
|
||||||
const {
|
|
||||||
courseId, learnerUsername, category, topicId, page, inContext,
|
|
||||||
} = useContext(DiscussionContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!thread) { submitDispatch(fetchThread(postId, courseId, true)); }
|
|
||||||
}, [postId]);
|
|
||||||
|
|
||||||
if (!thread) {
|
if (!thread) {
|
||||||
if (!isLoading) {
|
dispatch(fetchThread(postId, true));
|
||||||
return (
|
|
||||||
<EmptyPage title={intl.formatMessage(messages.noThreadFound)} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Spinner animation="border" variant="primary" data-testid="loading-indicator" />
|
<Spinner animation="border" variant="primary" data-testid="loading-indicator" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isOnDesktop && (
|
<div className="discussion-comments d-flex flex-column m-4 p-4.5 card">
|
||||||
inContext ? (
|
|
||||||
<>
|
|
||||||
<div className="px-4 py-1.5 bg-white">
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
className="px-0 font-weight-light text-primary-500"
|
|
||||||
iconBefore={ArrowBack}
|
|
||||||
onClick={() => history.push(discussionsPath(PostsPages[page], {
|
|
||||||
courseId, learnerUsername, category, topicId,
|
|
||||||
})(location))}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.backAlt)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="border-bottom border-light-400" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
|
||||||
src={ArrowBack}
|
|
||||||
iconAs={Icon}
|
|
||||||
style={{ padding: '18px' }}
|
|
||||||
size="inline"
|
|
||||||
className="ml-4 mt-4"
|
|
||||||
onClick={() => history.push(discussionsPath(PostsPages[page], {
|
|
||||||
courseId, learnerUsername, category, topicId,
|
|
||||||
})(location))}
|
|
||||||
alt={intl.formatMessage(messages.backAlt)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<div className={classNames('discussion-comments d-flex flex-column card', {
|
|
||||||
'm-4 p-4.5': !inContext,
|
|
||||||
'p-4 rounded-0 border-0 mb-4': inContext,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Post post={thread} />
|
<Post post={thread} />
|
||||||
{!thread.closed && <ResponseEditor postId={postId} /> }
|
{!thread.closed && <ResponseEditor postId={postId} /> }
|
||||||
</div>
|
</div>
|
||||||
{thread.type === ThreadType.DISCUSSION && (
|
{thread.type === ThreadType.DISCUSSION
|
||||||
|
&& (
|
||||||
<DiscussionCommentsView
|
<DiscussionCommentsView
|
||||||
postId={postId}
|
postId={postId}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
@@ -209,7 +140,7 @@ function CommentsView({ intl }) {
|
|||||||
endorsed={EndorsementStatus.DISCUSSION}
|
endorsed={EndorsementStatus.DISCUSSION}
|
||||||
isClosed={thread.closed}
|
isClosed={thread.closed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{thread.type === ThreadType.QUESTION && (
|
{thread.type === ThreadType.QUESTION && (
|
||||||
<>
|
<>
|
||||||
<DiscussionCommentsView
|
<DiscussionCommentsView
|
||||||
|
|||||||
@@ -13,19 +13,16 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
|||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
import { executeThunk } from '../../test-utils';
|
import { executeThunk } from '../../test-utils';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import { getCourseConfigApiUrl } from '../data/api';
|
import { courseConfigApiUrl } from '../data/api';
|
||||||
import { fetchCourseConfig } from '../data/thunks';
|
import { fetchCourseConfig } from '../data/thunks';
|
||||||
import DiscussionContent from '../discussions-home/DiscussionContent';
|
import DiscussionContent from '../discussions-home/DiscussionContent';
|
||||||
import { getThreadsApiUrl } from '../posts/data/api';
|
import { threadsApiUrl } from '../posts/data/api';
|
||||||
import { fetchThreads } from '../posts/data/thunks';
|
import { fetchThreads } from '../posts/data/thunks';
|
||||||
import { getCommentsApiUrl } from './data/api';
|
import { commentsApiUrl } from './data/api';
|
||||||
|
|
||||||
import '../posts/data/__factories__';
|
import '../posts/data/__factories__';
|
||||||
import './data/__factories__';
|
import './data/__factories__';
|
||||||
|
|
||||||
const courseConfigApiUrl = getCourseConfigApiUrl();
|
|
||||||
const commentsApiUrl = getCommentsApiUrl();
|
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
const discussionPostId = 'thread-1';
|
const discussionPostId = 'thread-1';
|
||||||
const questionPostId = 'thread-2';
|
const questionPostId = 'thread-2';
|
||||||
const closedPostId = 'thread-2';
|
const closedPostId = 'thread-2';
|
||||||
@@ -105,7 +102,7 @@ function renderComponent(postId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('CommentsView', () => {
|
describe('CommentsView', () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
initializeMockApp({
|
initializeMockApp({
|
||||||
authenticatedUser: {
|
authenticatedUser: {
|
||||||
userId: 3,
|
userId: 3,
|
||||||
@@ -150,7 +147,7 @@ describe('CommentsView', () => {
|
|||||||
)];
|
)];
|
||||||
});
|
});
|
||||||
|
|
||||||
executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
|
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
|
||||||
mockAxiosReturnPagedComments();
|
mockAxiosReturnPagedComments();
|
||||||
mockAxiosReturnPagedCommentsResponses();
|
mockAxiosReturnPagedCommentsResponses();
|
||||||
});
|
});
|
||||||
@@ -452,9 +449,9 @@ describe('CommentsView', () => {
|
|||||||
describe('for discussion thread', () => {
|
describe('for discussion thread', () => {
|
||||||
const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments');
|
const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments');
|
||||||
|
|
||||||
it('shown post not found when post id does not belong to course', async () => {
|
it('shown spinner when post isn\'t loaded', async () => {
|
||||||
renderComponent('unloaded-id');
|
renderComponent('unloaded-id');
|
||||||
expect(await screen.findByText('Thread not found', { exact: true }))
|
expect(await screen.findByTestId('loading-indicator'))
|
||||||
.toBeInTheDocument();
|
.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -10,7 +10,6 @@ import { Button, useToggle } from '@edx/paragon';
|
|||||||
import HTMLLoader from '../../../components/HTMLLoader';
|
import HTMLLoader from '../../../components/HTMLLoader';
|
||||||
import { ContentActions } from '../../../data/constants';
|
import { ContentActions } from '../../../data/constants';
|
||||||
import { AlertBanner, DeleteConfirmation, EndorsedAlertBanner } from '../../common';
|
import { AlertBanner, DeleteConfirmation, EndorsedAlertBanner } from '../../common';
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import { selectBlackoutDate } from '../../data/selectors';
|
import { selectBlackoutDate } from '../../data/selectors';
|
||||||
import { fetchThread } from '../../posts/data/thunks';
|
import { fetchThread } from '../../posts/data/thunks';
|
||||||
import { inBlackoutDateRange } from '../../utils';
|
import { inBlackoutDateRange } from '../../utils';
|
||||||
@@ -40,9 +39,7 @@ function Comment({
|
|||||||
const hasMorePages = useSelector(selectCommentHasMorePages(comment.id));
|
const hasMorePages = useSelector(selectCommentHasMorePages(comment.id));
|
||||||
const currentPage = useSelector(selectCommentCurrentPage(comment.id));
|
const currentPage = useSelector(selectCommentCurrentPage(comment.id));
|
||||||
const blackoutDateRange = useSelector(selectBlackoutDate);
|
const blackoutDateRange = useSelector(selectBlackoutDate);
|
||||||
const {
|
|
||||||
courseId,
|
|
||||||
} = useContext(DiscussionContext);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If the comment has a parent comment, it won't have any children, so don't fetch them.
|
// If the comment has a parent comment, it won't have any children, so don't fetch them.
|
||||||
if (hasChildren && !currentPage && showFullThread) {
|
if (hasChildren && !currentPage && showFullThread) {
|
||||||
@@ -54,7 +51,7 @@ function Comment({
|
|||||||
[ContentActions.EDIT_CONTENT]: () => setEditing(true),
|
[ContentActions.EDIT_CONTENT]: () => setEditing(true),
|
||||||
[ContentActions.ENDORSE]: async () => {
|
[ContentActions.ENDORSE]: async () => {
|
||||||
await dispatch(editComment(comment.id, { endorsed: !comment.endorsed }, ContentActions.ENDORSE));
|
await dispatch(editComment(comment.id, { endorsed: !comment.endorsed }, ContentActions.ENDORSE));
|
||||||
await dispatch(fetchThread(comment.threadId, courseId));
|
await dispatch(fetchThread(comment.threadId));
|
||||||
},
|
},
|
||||||
[ContentActions.DELETE]: showDeleteConfirmation,
|
[ContentActions.DELETE]: showDeleteConfirmation,
|
||||||
[ContentActions.REPORT]: () => dispatch(editComment(comment.id, { flagged: !comment.abuseFlagged })),
|
[ContentActions.REPORT]: () => dispatch(editComment(comment.id, { flagged: !comment.abuseFlagged })),
|
||||||
@@ -83,7 +80,7 @@ function Comment({
|
|||||||
<CommentHeader comment={comment} actionHandlers={actionHandlers} postType={postType} />
|
<CommentHeader comment={comment} actionHandlers={actionHandlers} postType={postType} />
|
||||||
{isEditing
|
{isEditing
|
||||||
? (
|
? (
|
||||||
<CommentEditor comment={comment} onCloseEditor={() => setEditing(false)} formClasses="pt-3" />
|
<CommentEditor comment={comment} onCloseEditor={() => setEditing(false)} />
|
||||||
)
|
)
|
||||||
: <HTMLLoader cssClassName="comment-body pt-4 text-primary-500" componentId="comment" htmlNode={comment.renderedBody} />}
|
: <HTMLLoader cssClassName="comment-body pt-4 text-primary-500" componentId="comment" htmlNode={comment.renderedBody} />}
|
||||||
<CommentIcons
|
<CommentIcons
|
||||||
@@ -115,7 +112,7 @@ function Comment({
|
|||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.loadMoreComments)}
|
{intl.formatMessage(messages.loadMoreResponses)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isNested && showFullThread && (
|
{!isNested && showFullThread && (
|
||||||
@@ -133,11 +130,8 @@ function Comment({
|
|||||||
{(!isClosedPost && !inBlackoutDateRange(blackoutDateRange))
|
{(!isClosedPost && !inBlackoutDateRange(blackoutDateRange))
|
||||||
&& (
|
&& (
|
||||||
<Button
|
<Button
|
||||||
className="d-flex flex-grow mt-3 py-2 font-size-14"
|
className="d-flex flex-grow mt-4.5"
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
style={{
|
|
||||||
lineHeight: '20px',
|
|
||||||
}}
|
|
||||||
onClick={() => setReplying(true)}
|
onClick={() => setReplying(true)}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.addComment)}
|
{intl.formatMessage(messages.addComment)}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
selectModerationSettings,
|
selectModerationSettings,
|
||||||
selectUserHasModerationPrivileges,
|
selectUserHasModerationPrivileges,
|
||||||
selectUserIsGroupTa,
|
selectUserIsGroupTa,
|
||||||
selectUserIsStaff,
|
|
||||||
} from '../../data/selectors';
|
} from '../../data/selectors';
|
||||||
import { formikCompatibleHandler, isFormikFieldInvalid } from '../../utils';
|
import { formikCompatibleHandler, isFormikFieldInvalid } from '../../utils';
|
||||||
import { addComment, editComment } from '../data/thunks';
|
import { addComment, editComment } from '../data/thunks';
|
||||||
@@ -28,19 +27,16 @@ function CommentEditor({
|
|||||||
comment,
|
comment,
|
||||||
onCloseEditor,
|
onCloseEditor,
|
||||||
edit,
|
edit,
|
||||||
formClasses,
|
|
||||||
}) {
|
}) {
|
||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
const { authenticatedUser } = useContext(AppContext);
|
const { authenticatedUser } = useContext(AppContext);
|
||||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||||
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
||||||
const userIsStaff = useSelector(selectUserIsStaff);
|
|
||||||
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
||||||
const [submitting, dispatch] = useDispatchWithState();
|
const [submitting, dispatch] = useDispatchWithState();
|
||||||
|
|
||||||
const canDisplayEditReason = (reasonCodesEnabled && edit
|
const canDisplayEditReason = (reasonCodesEnabled && (userHasModerationPrivileges || userIsGroupTa)
|
||||||
&& (userHasModerationPrivileges || userIsGroupTa || userIsStaff)
|
&& edit && comment.author !== authenticatedUser.username
|
||||||
&& comment?.author !== authenticatedUser.username
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const editReasonCodeValidation = canDisplayEditReason && {
|
const editReasonCodeValidation = canDisplayEditReason && {
|
||||||
@@ -55,7 +51,7 @@ function CommentEditor({
|
|||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
comment: comment.rawBody,
|
comment: comment.rawBody,
|
||||||
editReasonCode: comment?.lastEdit?.reasonCode || (userIsStaff ? 'violates-guidelines' : ''),
|
editReasonCode: comment?.lastEdit?.reasonCode || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseEditor = (resetForm) => {
|
const handleCloseEditor = (resetForm) => {
|
||||||
@@ -98,7 +94,7 @@ function CommentEditor({
|
|||||||
handleChange,
|
handleChange,
|
||||||
resetForm,
|
resetForm,
|
||||||
}) => (
|
}) => (
|
||||||
<Form onSubmit={handleSubmit} className={formClasses}>
|
<Form onSubmit={handleSubmit}>
|
||||||
{canDisplayEditReason && (
|
{canDisplayEditReason && (
|
||||||
<Form.Group
|
<Form.Group
|
||||||
isInvalid={isFormikFieldInvalid('editReasonCode', {
|
isInvalid={isFormikFieldInvalid('editReasonCode', {
|
||||||
@@ -108,7 +104,7 @@ function CommentEditor({
|
|||||||
>
|
>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
name="editReasonCode"
|
name="editReasonCode"
|
||||||
className="mt-2 mr-0"
|
className="mt-2"
|
||||||
as="select"
|
as="select"
|
||||||
value={values.editReasonCode}
|
value={values.editReasonCode}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -185,12 +181,10 @@ CommentEditor.propTypes = {
|
|||||||
onCloseEditor: PropTypes.func.isRequired,
|
onCloseEditor: PropTypes.func.isRequired,
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
edit: PropTypes.bool,
|
edit: PropTypes.bool,
|
||||||
formClasses: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CommentEditor.defaultProps = {
|
CommentEditor.defaultProps = {
|
||||||
edit: true,
|
edit: true,
|
||||||
formClasses: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(CommentEditor);
|
export default injectIntl(CommentEditor);
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ import classNames from 'classnames';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { Avatar, Icon } from '@edx/paragon';
|
||||||
import {
|
import { CheckCircle, Verified } from '@edx/paragon/icons';
|
||||||
Avatar, Icon,
|
|
||||||
} from '@edx/paragon';
|
|
||||||
|
|
||||||
import { AvatarOutlineAndLabelColors, EndorsementStatus, ThreadType } from '../../../data/constants';
|
import { AvatarOutlineAndLabelColors, ThreadType } from '../../../data/constants';
|
||||||
import { AuthorLabel } from '../../common';
|
import { AuthorLabel } from '../../common';
|
||||||
import ActionsDropdown from '../../common/ActionsDropdown';
|
import ActionsDropdown from '../../common/ActionsDropdown';
|
||||||
import { useAlertBannerVisible } from '../../data/hooks';
|
import { useAlertBannerVisible } from '../../data/hooks';
|
||||||
import { selectAuthorAvatars } from '../../posts/data/selectors';
|
import { selectAuthorAvatars } from '../../posts/data/selectors';
|
||||||
import { useActions } from '../../utils';
|
|
||||||
import { commentShape } from './proptypes';
|
import { commentShape } from './proptypes';
|
||||||
|
|
||||||
function CommentHeader({
|
function CommentHeader({
|
||||||
@@ -27,20 +24,6 @@ function CommentHeader({
|
|||||||
const colorClass = AvatarOutlineAndLabelColors[comment.authorLabel];
|
const colorClass = AvatarOutlineAndLabelColors[comment.authorLabel];
|
||||||
const hasAnyAlert = useAlertBannerVisible(comment);
|
const hasAnyAlert = useAlertBannerVisible(comment);
|
||||||
|
|
||||||
const actions = useActions({
|
|
||||||
...comment,
|
|
||||||
postType,
|
|
||||||
});
|
|
||||||
const actionIcons = actions.find(({ action }) => action === EndorsementStatus.ENDORSED);
|
|
||||||
|
|
||||||
const handleIcons = (action) => {
|
|
||||||
const actionFunction = actionHandlers[action];
|
|
||||||
if (actionFunction) {
|
|
||||||
actionFunction();
|
|
||||||
} else {
|
|
||||||
logError(`Unknown or unimplemented action ${action}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('d-flex flex-row justify-content-between', {
|
<div className={classNames('d-flex flex-row justify-content-between', {
|
||||||
'mt-2': hasAnyAlert,
|
'mt-2': hasAnyAlert,
|
||||||
@@ -56,31 +39,14 @@ function CommentHeader({
|
|||||||
height: '32px',
|
height: '32px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AuthorLabel
|
<AuthorLabel author={comment.author} authorLabel={comment.authorLabel} labelColor={colorClass && `text-${colorClass}`} linkToProfile />
|
||||||
author={comment.author}
|
|
||||||
authorLabel={comment.authorLabel}
|
|
||||||
labelColor={colorClass && `text-${colorClass}`}
|
|
||||||
linkToProfile
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
|
|
||||||
{actionIcons && (
|
|
||||||
<span className="btn-icon btn-icon-sm mr-1 align-items-center">
|
<span className="btn-icon btn-icon-sm mr-1 align-items-center">
|
||||||
<Icon
|
{comment.endorsed && (postType === 'question'
|
||||||
data-testid="check-icon"
|
? <Icon src={CheckCircle} className="text-success" data-testid="check-icon" />
|
||||||
onClick={
|
: <Icon src={Verified} className="text-dark-500" data-testid="verified-icon" />)}
|
||||||
() => {
|
|
||||||
handleIcons(actionIcons.action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
src={actionIcons.icon}
|
|
||||||
className={['endorse', 'unendorse'].includes(actionIcons.id) ? 'text-dark-500' : 'text-success-500'}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
|
|
||||||
<ActionsDropdown
|
<ActionsDropdown
|
||||||
commentOrPost={{
|
commentOrPost={{
|
||||||
...comment,
|
...comment,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const mockComment = {
|
|||||||
author: 'abc123',
|
author: 'abc123',
|
||||||
authorLabel: 'ABC 123',
|
authorLabel: 'ABC 123',
|
||||||
endorsed: true,
|
endorsed: true,
|
||||||
editableFields: ['endorsed'],
|
editableFields: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Comment Header', () => {
|
describe('Comment Header', () => {
|
||||||
@@ -48,7 +48,7 @@ describe('Comment Header', () => {
|
|||||||
|
|
||||||
it('should render verified icon for endorsed discussion posts', () => {
|
it('should render verified icon for endorsed discussion posts', () => {
|
||||||
renderComponent(mockComment, 'discussion', {});
|
renderComponent(mockComment, 'discussion', {});
|
||||||
expect(screen.queryAllByTestId('check-icon')).toHaveLength(1);
|
expect(screen.queryAllByTestId('verified-icon')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('should render check icon for endorsed question posts', () => {
|
it('should render check icon for endorsed question posts', () => {
|
||||||
renderComponent(mockComment, 'question', {});
|
renderComponent(mockComment, 'question', {});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
|
|||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Button } from '@edx/paragon';
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import { selectBlackoutDate } from '../../data/selectors';
|
import { selectBlackoutDate } from '../../data/selectors';
|
||||||
import { inBlackoutDateRange } from '../../utils';
|
import { inBlackoutDateRange } from '../../utils';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
@@ -18,7 +17,6 @@ function ResponseEditor({
|
|||||||
intl,
|
intl,
|
||||||
addWrappingDiv,
|
addWrappingDiv,
|
||||||
}) {
|
}) {
|
||||||
const { inContext } = useContext(DiscussionContext);
|
|
||||||
const [addingResponse, setAddingResponse] = useState(false);
|
const [addingResponse, setAddingResponse] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,14 +37,7 @@ function ResponseEditor({
|
|||||||
)
|
)
|
||||||
: !inBlackoutDateRange(blackoutDateRange) && (
|
: !inBlackoutDateRange(blackoutDateRange) && (
|
||||||
<div className={classNames({ 'mb-4': addWrappingDiv }, 'actions d-flex')}>
|
<div className={classNames({ 'mb-4': addWrappingDiv }, 'actions d-flex')}>
|
||||||
<Button
|
<Button variant="primary" className="px-2.5 py-2" onClick={() => setAddingResponse(true)}>
|
||||||
variant="primary"
|
|
||||||
className={classNames('px-2.5 py-2 font-size-14', { 'w-100': inContext })}
|
|
||||||
onClick={() => setAddingResponse(true)}
|
|
||||||
style={{
|
|
||||||
lineHeight: '20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.addResponse)}
|
{intl.formatMessage(messages.addResponse)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ ensureConfig([
|
|||||||
'LMS_BASE_URL',
|
'LMS_BASE_URL',
|
||||||
], 'Comments API service');
|
], 'Comments API service');
|
||||||
|
|
||||||
export const getCommentsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/comments/`;
|
const apiBaseUrl = getConfig().LMS_BASE_URL;
|
||||||
|
|
||||||
|
export const commentsApiUrl = `${apiBaseUrl}/api/discussion/v1/comments/`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the comments for the specified thread.
|
* Returns all the comments for the specified thread.
|
||||||
@@ -34,7 +36,7 @@ export async function getThreadComments(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.get(getCommentsApiUrl(), { params });
|
.get(commentsApiUrl, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ export async function getCommentResponses(
|
|||||||
pageSize,
|
pageSize,
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const url = `${getCommentsApiUrl()}${commentId}/`;
|
const url = `${commentsApiUrl}${commentId}/`;
|
||||||
const params = snakeCaseObject({
|
const params = snakeCaseObject({
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
@@ -71,7 +73,7 @@ export async function getCommentResponses(
|
|||||||
*/
|
*/
|
||||||
export async function postComment(comment, threadId, parentId = null) {
|
export async function postComment(comment, threadId, parentId = null) {
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.post(getCommentsApiUrl(), snakeCaseObject({ threadId, raw_body: comment, parentId }));
|
.post(commentsApiUrl, snakeCaseObject({ threadId, raw_body: comment, parentId }));
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ export async function updateComment(commentId, {
|
|||||||
endorsed,
|
endorsed,
|
||||||
editReasonCode,
|
editReasonCode,
|
||||||
}) {
|
}) {
|
||||||
const url = `${getCommentsApiUrl()}${commentId}/`;
|
const url = `${commentsApiUrl}${commentId}/`;
|
||||||
const postData = snakeCaseObject({
|
const postData = snakeCaseObject({
|
||||||
raw_body: comment,
|
raw_body: comment,
|
||||||
voted,
|
voted,
|
||||||
@@ -111,7 +113,7 @@ export async function updateComment(commentId, {
|
|||||||
* @param {string} commentId ID of comment to delete
|
* @param {string} commentId ID of comment to delete
|
||||||
*/
|
*/
|
||||||
export async function deleteComment(commentId) {
|
export async function deleteComment(commentId) {
|
||||||
const url = `${getCommentsApiUrl()}${commentId}/`;
|
const url = `${commentsApiUrl}${commentId}/`;
|
||||||
await getAuthenticatedHttpClient()
|
await getAuthenticatedHttpClient()
|
||||||
.delete(url);
|
.delete(url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import { initializeMockApp } from '@edx/frontend-platform/testing';
|
|||||||
import { EndorsementStatus } from '../../../data/constants';
|
import { EndorsementStatus } from '../../../data/constants';
|
||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
import { getCommentsApiUrl } from './api';
|
import { commentsApiUrl } from './api';
|
||||||
import {
|
import {
|
||||||
addComment, editComment, fetchCommentResponses, fetchThreadComments, removeComment,
|
addComment, editComment, fetchCommentResponses, fetchThreadComments, removeComment,
|
||||||
} from './thunks';
|
} from './thunks';
|
||||||
|
|
||||||
import './__factories__';
|
import './__factories__';
|
||||||
|
|
||||||
const commentsApiUrl = getCommentsApiUrl();
|
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Content reported for staff to review',
|
defaultMessage: 'Content reported for staff to review',
|
||||||
description: 'Alert banner over comment that has been reported for abuse',
|
description: 'Alert banner over comment that has been reported for abuse',
|
||||||
},
|
},
|
||||||
backAlt: {
|
|
||||||
id: 'discussions.actions.back.alt',
|
|
||||||
defaultMessage: 'Back to list',
|
|
||||||
description: 'Back to Posts list button text',
|
|
||||||
},
|
|
||||||
responseCount: {
|
responseCount: {
|
||||||
id: 'discussions.comments.comment.responseCount',
|
id: 'discussions.comments.comment.responseCount',
|
||||||
defaultMessage: `{num, plural,
|
defaultMessage: `{num, plural,
|
||||||
@@ -182,11 +177,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: '{time} ago',
|
defaultMessage: '{time} ago',
|
||||||
description: 'Time text for endorse banner',
|
description: 'Time text for endorse banner',
|
||||||
},
|
},
|
||||||
noThreadFound: {
|
|
||||||
id: 'discussion.thread.notFound',
|
|
||||||
defaultMessage: 'Thread not found',
|
|
||||||
description: 'message to show on screen if the request thread is not found in course',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
@@ -6,7 +6,7 @@ import { useSelector } from 'react-redux';
|
|||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
import {
|
import {
|
||||||
Button, Dropdown, Icon, IconButton, ModalPopup, useToggle,
|
Button, Dropdown, Icon, IconButton, ModalPopup,
|
||||||
} from '@edx/paragon';
|
} from '@edx/paragon';
|
||||||
import { MoreHoriz } from '@edx/paragon/icons';
|
import { MoreHoriz } from '@edx/paragon/icons';
|
||||||
|
|
||||||
@@ -16,7 +16,6 @@ import { selectBlackoutDate } from '../data/selectors';
|
|||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { postShape } from '../posts/post/proptypes';
|
import { postShape } from '../posts/post/proptypes';
|
||||||
import { inBlackoutDateRange, useActions } from '../utils';
|
import { inBlackoutDateRange, useActions } from '../utils';
|
||||||
import { DiscussionContext } from './context';
|
|
||||||
|
|
||||||
function ActionsDropdown({
|
function ActionsDropdown({
|
||||||
intl,
|
intl,
|
||||||
@@ -24,10 +23,9 @@ function ActionsDropdown({
|
|||||||
disabled,
|
disabled,
|
||||||
actionHandlers,
|
actionHandlers,
|
||||||
}) {
|
}) {
|
||||||
const [isOpen, open, close] = useToggle(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
const [target, setTarget] = useState(null);
|
const dropdownIconRef = React.useRef(null);
|
||||||
const actions = useActions(commentOrPost);
|
const actions = useActions(commentOrPost);
|
||||||
const { inContext } = useContext(DiscussionContext);
|
|
||||||
const handleActions = (action) => {
|
const handleActions = (action) => {
|
||||||
const actionFunction = actionHandlers[action];
|
const actionFunction = actionHandlers[action];
|
||||||
if (actionFunction) {
|
if (actionFunction) {
|
||||||
@@ -44,19 +42,19 @@ function ActionsDropdown({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={open}
|
onClick={() => setOpen(!isOpen)}
|
||||||
alt={intl.formatMessage(messages.actionsAlt)}
|
alt={intl.formatMessage(messages.actionsAlt)}
|
||||||
src={MoreHoriz}
|
src={MoreHoriz}
|
||||||
iconAs={Icon}
|
iconAs={Icon}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
size="sm"
|
size="sm"
|
||||||
ref={setTarget}
|
ref={dropdownIconRef}
|
||||||
/>
|
/>
|
||||||
<ModalPopup
|
<ModalPopup
|
||||||
onClose={close}
|
onClose={() => setOpen(false)}
|
||||||
positionRef={target}
|
positionRef={dropdownIconRef}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
placement={inContext ? 'left' : 'auto-start'}
|
placement="auto-start"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="bg-white p-1 shadow d-flex flex-column"
|
className="bg-white p-1 shadow d-flex flex-column"
|
||||||
@@ -72,7 +70,7 @@ function ActionsDropdown({
|
|||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
size="inline"
|
size="inline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
close();
|
setOpen(false);
|
||||||
handleActions(action.action);
|
handleActions(action.action);
|
||||||
}}
|
}}
|
||||||
className="d-flex justify-content-start py-1.5 mr-4"
|
className="d-flex justify-content-start py-1.5 mr-4"
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ function AlertBanner({
|
|||||||
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
||||||
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
|
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
|
||||||
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
|
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
|
||||||
const canSeeReportedBanner = content?.abuseFlagged;
|
const isReportedByCurrentUser = getAuthenticatedUser().username === content?.abuseFlaggedBy;
|
||||||
|
const canSeeReportedBanner = (userHasModerationPrivileges || userIsGroupTa || isReportedByCurrentUser);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{canSeeReportedBanner && (
|
{content.abuseFlagged && canSeeReportedBanner && (
|
||||||
<Alert icon={Error} variant="danger" className="px-3 mb-2 py-10px shadow-none flex-fill">
|
<Alert icon={Error} variant="danger" className="px-3 mb-2 py-10px shadow-none flex-fill">
|
||||||
{intl.formatMessage(messages.abuseFlaggedMessage)}
|
{intl.formatMessage(messages.abuseFlaggedMessage)}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ function AuthorLabel({
|
|||||||
authorLabel,
|
authorLabel,
|
||||||
linkToProfile,
|
linkToProfile,
|
||||||
labelColor,
|
labelColor,
|
||||||
alert,
|
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { courseId } = useContext(DiscussionContext);
|
const { courseId } = useContext(DiscussionContext);
|
||||||
@@ -41,14 +40,14 @@ function AuthorLabel({
|
|||||||
const className = classNames('d-flex align-items-center', labelColor);
|
const className = classNames('d-flex align-items-center', labelColor);
|
||||||
|
|
||||||
const showUserNameAsLink = useShowLearnersTab()
|
const showUserNameAsLink = useShowLearnersTab()
|
||||||
&& linkToProfile && author && author !== intl.formatMessage(messages.anonymous);
|
&& linkToProfile && author && author !== messages.anonymous;
|
||||||
|
|
||||||
const labelContents = (
|
const labelContents = (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<span
|
<span
|
||||||
className={classNames('mr-1 font-size-14 font-style-normal font-family-inter font-weight-500', {
|
className={classNames('mr-1 font-size-14 font-style-normal font-family-inter font-weight-500', {
|
||||||
|
'text-primary-500': !authorLabelMessage && !isRetiredUser,
|
||||||
'text-gray-700': isRetiredUser,
|
'text-gray-700': isRetiredUser,
|
||||||
'text-primary-500': !authorLabelMessage && !isRetiredUser && !alert,
|
|
||||||
})}
|
})}
|
||||||
role="heading"
|
role="heading"
|
||||||
aria-level="2"
|
aria-level="2"
|
||||||
@@ -66,9 +65,8 @@ function AuthorLabel({
|
|||||||
)}
|
)}
|
||||||
{authorLabelMessage && (
|
{authorLabelMessage && (
|
||||||
<span
|
<span
|
||||||
className={classNames('mr-1 font-size-14 font-style-normal font-family-inter font-weight-500', {
|
className={classNames('mr-3 font-size-14 font-style-normal font-family-inter font-weight-500', {
|
||||||
'text-primary-500': !authorLabelMessage && !isRetiredUser && !alert,
|
'text-primary-500': !authorLabelMessage,
|
||||||
'text-gray-700': isRetiredUser,
|
|
||||||
})}
|
})}
|
||||||
style={{ marginLeft: '2px' }}
|
style={{ marginLeft: '2px' }}
|
||||||
>
|
>
|
||||||
@@ -99,14 +97,12 @@ AuthorLabel.propTypes = {
|
|||||||
authorLabel: PropTypes.string,
|
authorLabel: PropTypes.string,
|
||||||
linkToProfile: PropTypes.bool,
|
linkToProfile: PropTypes.bool,
|
||||||
labelColor: PropTypes.string,
|
labelColor: PropTypes.string,
|
||||||
alert: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorLabel.defaultProps = {
|
AuthorLabel.defaultProps = {
|
||||||
linkToProfile: false,
|
linkToProfile: false,
|
||||||
authorLabel: null,
|
authorLabel: null,
|
||||||
labelColor: '',
|
labelColor: '',
|
||||||
alert: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(AuthorLabel);
|
export default injectIntl(AuthorLabel);
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
|
|
||||||
import { initializeMockApp } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
|
||||||
|
|
||||||
import { initializeStore } from '../../store';
|
|
||||||
import { executeThunk } from '../../test-utils';
|
|
||||||
import { getCourseConfigApiUrl } from '../data/api';
|
|
||||||
import { fetchCourseConfig } from '../data/thunks';
|
|
||||||
import AuthorLabel from './AuthorLabel';
|
|
||||||
import { DiscussionContext } from './context';
|
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
|
||||||
const courseConfigApiUrl = getCourseConfigApiUrl();
|
|
||||||
let store;
|
|
||||||
let axiosMock;
|
|
||||||
let container;
|
|
||||||
|
|
||||||
function renderComponent(author, authorLabel, linkToProfile, labelColor) {
|
|
||||||
const wrapper = render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<DiscussionContext.Provider value={{ courseId }}>
|
|
||||||
<AuthorLabel
|
|
||||||
author={author}
|
|
||||||
authorLabel={authorLabel}
|
|
||||||
linkToProfile={linkToProfile}
|
|
||||||
labelColor={labelColor}
|
|
||||||
/>
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
container = wrapper.container;
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Author label', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
initializeMockApp({
|
|
||||||
authenticatedUser: {
|
|
||||||
userId: 3,
|
|
||||||
username: 'abc123',
|
|
||||||
administrator: true,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
store = initializeStore();
|
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
|
||||||
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
|
|
||||||
learners_tab_enabled: true,
|
|
||||||
has_moderation_privileges: true,
|
|
||||||
});
|
|
||||||
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
|
|
||||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe.each([
|
|
||||||
['anonymous', null, false, ''],
|
|
||||||
['ta_user', 'Community TA', true, 'text-TA-color'],
|
|
||||||
['retired__user', null, false, ''],
|
|
||||||
['staff_user', 'Staff', true, 'text-staff-color'],
|
|
||||||
['learner_user', null, false, ''],
|
|
||||||
])('for %s', (
|
|
||||||
author, authorLabel, linkToProfile, labelColor,
|
|
||||||
) => {
|
|
||||||
it('it has author name text',
|
|
||||||
async () => {
|
|
||||||
renderComponent(author, authorLabel, linkToProfile, labelColor);
|
|
||||||
const authorElement = container.querySelector('[role=heading]');
|
|
||||||
const authorName = author.startsWith('retired__user') ? '[Deactivated]' : author;
|
|
||||||
|
|
||||||
expect(authorElement).toHaveTextContent(authorName);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`it is "${!linkToProfile && 'not'}" clickable when linkToProfile is ${!!linkToProfile}`,
|
|
||||||
async () => {
|
|
||||||
renderComponent(author, authorLabel, linkToProfile, labelColor);
|
|
||||||
|
|
||||||
if (linkToProfile) {
|
|
||||||
expect(screen.queryByTestId('learner-posts-link')).toBeInTheDocument();
|
|
||||||
} else {
|
|
||||||
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`it has "${!linkToProfile && 'not'}" label text and label color when linkToProfile is ${!!linkToProfile}`,
|
|
||||||
async () => {
|
|
||||||
renderComponent(author, authorLabel, linkToProfile, labelColor);
|
|
||||||
const authorElement = container.querySelector('[role=heading]');
|
|
||||||
const labelElement = authorElement.parentNode.lastChild;
|
|
||||||
const label = ['TA', 'Staff'].includes(labelElement.textContent) && labelElement.textContent;
|
|
||||||
|
|
||||||
if (linkToProfile) {
|
|
||||||
expect(authorElement.parentNode).toHaveClass(labelColor);
|
|
||||||
expect(authorElement.parentNode.lastChild).toHaveTextContent(label);
|
|
||||||
} else {
|
|
||||||
expect(authorElement.parentNode.lastChild).not.toHaveTextContent(label, { exact: true });
|
|
||||||
expect(authorElement.parentNode).not.toHaveClass(labelColor, { exact: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -15,7 +15,7 @@ function DeleteConfirmation({
|
|||||||
onDelete,
|
onDelete,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ModalDialog title={title} isOpen={isOpen} hasCloseButton={false} onClose={onClose} zIndex={5000}>
|
<ModalDialog title={title} isOpen={isOpen} hasCloseButton={false} onClose={onClose}>
|
||||||
<ModalDialog.Header>
|
<ModalDialog.Header>
|
||||||
<ModalDialog.Title>
|
<ModalDialog.Title>
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -39,19 +39,14 @@ function EndorsedAlertBanner({
|
|||||||
)}
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
<span className="d-flex align-items-center mr-1 flex-wrap">
|
<span className="d-flex align-items-center mr-1 flex-wrap">
|
||||||
<span className="mr-1">
|
<span className="mr-2">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
isQuestion
|
isQuestion
|
||||||
? messages.answeredLabel
|
? messages.answeredLabel
|
||||||
: messages.endorsedLabel,
|
: messages.endorsedLabel,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<AuthorLabel
|
<AuthorLabel author={content.endorsedBy} authorLabel={content.endorsedByLabel} linkToProfile />
|
||||||
author={content.endorsedBy}
|
|
||||||
authorLabel={content.endorsedByLabel}
|
|
||||||
linkToProfile
|
|
||||||
alert={content.endorsed}
|
|
||||||
/>
|
|
||||||
{intl.formatMessage(messages.time, { time: timeago.format(content.endorsedAt, 'time-locale') })}
|
{intl.formatMessage(messages.time, { time: timeago.format(content.endorsedAt, 'time-locale') })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ ensureConfig([
|
|||||||
'LMS_BASE_URL',
|
'LMS_BASE_URL',
|
||||||
], 'Posts API service');
|
], 'Posts API service');
|
||||||
|
|
||||||
export const getCourseConfigApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
|
const apiBaseUrl = getConfig().LMS_BASE_URL;
|
||||||
|
|
||||||
|
export const courseConfigApiUrl = `${apiBaseUrl}/api/discussion/v1/courses/`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get discussions course config
|
* Get discussions course config
|
||||||
* @param {string} courseId
|
* @param {string} courseId
|
||||||
*/
|
*/
|
||||||
export async function getDiscussionsConfig(courseId) {
|
export async function getDiscussionsConfig(courseId) {
|
||||||
const url = `${getCourseConfigApiUrl()}${courseId}/`;
|
const url = `${courseConfigApiUrl}${courseId}/`;
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -24,7 +26,7 @@ export async function getDiscussionsConfig(courseId) {
|
|||||||
* @param {string} courseId
|
* @param {string} courseId
|
||||||
*/
|
*/
|
||||||
export async function getDiscussionsSettings(courseId) {
|
export async function getDiscussionsSettings(courseId) {
|
||||||
const url = `${getCourseConfigApiUrl()}${courseId}/settings`;
|
const url = `${courseConfigApiUrl}${courseId}/settings`;
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import { AppContext } from '@edx/frontend-platform/react';
|
|||||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||||
|
|
||||||
import { Routes } from '../../data/constants';
|
import { Routes } from '../../data/constants';
|
||||||
import { selectTopicsUnderCategory } from '../../data/selectors';
|
|
||||||
import { fetchCourseBlocks } from '../../data/thunks';
|
import { fetchCourseBlocks } from '../../data/thunks';
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import { clearRedirect } from '../posts/data';
|
import { clearRedirect } from '../posts/data';
|
||||||
import { selectTopics } from '../topics/data/selectors';
|
import { selectTopics } from '../topics/data/selectors';
|
||||||
import { fetchCourseTopics } from '../topics/data/thunks';
|
import { fetchCourseTopics } from '../topics/data/thunks';
|
||||||
@@ -23,7 +21,7 @@ import {
|
|||||||
selectModerationSettings,
|
selectModerationSettings,
|
||||||
selectPostThreadCount,
|
selectPostThreadCount,
|
||||||
selectUserHasModerationPrivileges,
|
selectUserHasModerationPrivileges,
|
||||||
selectUserIsGroupTa,
|
selectUserIsGroupTa, selectUserIsStaff, selectUserRoles,
|
||||||
} from './selectors';
|
} from './selectors';
|
||||||
import { fetchCourseConfig } from './thunks';
|
import { fetchCourseConfig } from './thunks';
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@ export function useCourseDiscussionData(courseId) {
|
|||||||
}, [courseId]);
|
}, [courseId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRedirectToThread(courseId, inContext) {
|
export function useRedirectToThread(courseId) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const redirectToThread = useSelector(
|
const redirectToThread = useSelector(
|
||||||
(state) => state.threads.redirectToThread,
|
(state) => state.threads.redirectToThread,
|
||||||
@@ -85,10 +83,9 @@ export function useRedirectToThread(courseId, inContext) {
|
|||||||
// stored in redirectToThread
|
// stored in redirectToThread
|
||||||
if (redirectToThread) {
|
if (redirectToThread) {
|
||||||
dispatch(clearRedirect());
|
dispatch(clearRedirect());
|
||||||
const newLocation = discussionsPath(Routes.COMMENTS.PAGES[inContext ? 'topics' : 'my-posts'], {
|
const newLocation = discussionsPath(Routes.COMMENTS.PAGES['my-posts'], {
|
||||||
courseId,
|
courseId,
|
||||||
postId: redirectToThread.threadId,
|
postId: redirectToThread.threadId,
|
||||||
topicId: redirectToThread.topicId,
|
|
||||||
})(location);
|
})(location);
|
||||||
history.push(newLocation);
|
history.push(newLocation);
|
||||||
}
|
}
|
||||||
@@ -96,8 +93,7 @@ export function useRedirectToThread(courseId, inContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useIsOnDesktop() {
|
export function useIsOnDesktop() {
|
||||||
const windowSize = useWindowSize();
|
return window.outerWidth >= breakpoints.large.minWidth;
|
||||||
return windowSize.width >= breakpoints.large.minWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIsOnXLDesktop() {
|
export function useIsOnXLDesktop() {
|
||||||
@@ -144,7 +140,8 @@ export const useAlertBannerVisible = (content) => {
|
|||||||
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
||||||
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
|
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
|
||||||
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
|
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
|
||||||
const canSeeReportedBanner = content.abuseFlagged;
|
const isReportedByCurrentUser = getAuthenticatedUser().username === content?.abuseFlaggedBy;
|
||||||
|
const canSeeReportedBanner = (userHasModerationPrivileges || userIsGroupTa || isReportedByCurrentUser);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(reasonCodesEnabled && canSeeLastEditOrClosedAlert && (content.lastEdit?.reason || content.closed))
|
(reasonCodesEnabled && canSeeLastEditOrClosedAlert && (content.lastEdit?.reason || content.closed))
|
||||||
@@ -152,25 +149,12 @@ export const useAlertBannerVisible = (content) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useShowLearnersTab = () => useSelector(selectLearnersTabEnabled);
|
export const useShowLearnersTab = () => {
|
||||||
|
const learnersTabEnabled = useSelector(selectLearnersTabEnabled);
|
||||||
/**
|
const userRoles = useSelector(selectUserRoles);
|
||||||
* React hook that gets the current topic ID from the current topic or category.
|
const isAdmin = useSelector(selectUserIsStaff);
|
||||||
* The topicId in the DiscussionContext only return the direct topicId from the URL.
|
const IsGroupTA = useSelector(selectUserIsGroupTa);
|
||||||
* If the URL has the current block ID it cannot get the topicID from that. This hook
|
const privileged = useSelector(selectUserHasModerationPrivileges);
|
||||||
* gets the topic ID from the URL if available, or from the current category otherwise.
|
const allowedUsers = isAdmin || IsGroupTA || privileged || (userRoles.includes('Student') && userRoles.length > 1);
|
||||||
* It only returns an ID if a single ID is available, if navigating a subsection it
|
return learnersTabEnabled && allowedUsers;
|
||||||
* returns null.
|
|
||||||
* @returns {null|string} A topic ID if a single one available in the current context.
|
|
||||||
*/
|
|
||||||
export const useCurrentDiscussionTopic = () => {
|
|
||||||
const { topicId, category } = useContext(DiscussionContext);
|
|
||||||
const topics = useSelector(selectTopicsUnderCategory)(category);
|
|
||||||
if (topicId) {
|
|
||||||
return topicId;
|
|
||||||
}
|
|
||||||
if (topics?.length === 1) {
|
|
||||||
return topics[0];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { render } from '@testing-library/react';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
|
|
||||||
import { initializeMockApp } from '@edx/frontend-platform';
|
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
|
||||||
|
|
||||||
import { initializeStore } from '../../store';
|
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import { useCurrentDiscussionTopic } from './hooks';
|
|
||||||
|
|
||||||
let store;
|
|
||||||
initializeMockApp();
|
|
||||||
describe('Hooks', () => {
|
|
||||||
describe('useCurrentDiscussionTopic', () => {
|
|
||||||
function ComponentWithHook() {
|
|
||||||
const topic = useCurrentDiscussionTopic();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{String(topic)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderComponent({ topicId, category }) {
|
|
||||||
return render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<DiscussionContext.Provider
|
|
||||||
value={{
|
|
||||||
topicId,
|
|
||||||
category,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ComponentWithHook />
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store = initializeStore({
|
|
||||||
blocks: {
|
|
||||||
blocks: {
|
|
||||||
'some-unit-key': { topics: ['some-topic-0'], parent: 'some-sequence-key' },
|
|
||||||
'some-sequence-key': { topics: ['some-topic-0'] },
|
|
||||||
'another-sequence-key': { topics: ['some-topic-1', 'some-topic-2'] },
|
|
||||||
'empty-key': { topics: [] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
config: { provider: 'openedx' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when topicId is in context', () => {
|
|
||||||
const { queryByText } = renderComponent({ topicId: 'some-topic' });
|
|
||||||
expect(queryByText('some-topic')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the category is a unit', () => {
|
|
||||||
const { queryByText } = renderComponent({ category: 'some-unit-key' });
|
|
||||||
expect(queryByText('some-topic-0')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the category is a sequence with one unit', () => {
|
|
||||||
const { queryByText } = renderComponent({ category: 'some-sequence-key' });
|
|
||||||
expect(queryByText('some-topic-0')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the category is a sequence with multiple units', () => {
|
|
||||||
const { queryByText } = renderComponent({ category: 'another-sequence-key' });
|
|
||||||
expect(queryByText('null')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the category is invalid', () => {
|
|
||||||
const { queryByText } = renderComponent({ category: 'invalid-key' });
|
|
||||||
expect(queryByText('null')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the category has no topics', () => {
|
|
||||||
const { queryByText } = renderComponent({ category: 'empty-key' });
|
|
||||||
expect(queryByText('null')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -22,8 +22,6 @@ export const selectDivisionSettings = state => state.config.settings;
|
|||||||
|
|
||||||
export const selectBlackoutDate = state => state.config.blackouts;
|
export const selectBlackoutDate = state => state.config.blackouts;
|
||||||
|
|
||||||
export const selectGroupAtSubsection = state => state.config.groupAtSubsection;
|
|
||||||
|
|
||||||
export const selectModerationSettings = state => ({
|
export const selectModerationSettings = state => ({
|
||||||
postCloseReasons: state.config.postCloseReasons,
|
postCloseReasons: state.config.postCloseReasons,
|
||||||
editReasons: state.config.editReasons,
|
editReasons: state.config.editReasons,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const configSlice = createSlice({
|
|||||||
allowAnonymous: false,
|
allowAnonymous: false,
|
||||||
allowAnonymousToPeers: false,
|
allowAnonymousToPeers: false,
|
||||||
userRoles: [],
|
userRoles: [],
|
||||||
groupAtSubsection: false,
|
|
||||||
hasModerationPrivileges: false,
|
hasModerationPrivileges: false,
|
||||||
isGroupTa: false,
|
isGroupTa: false,
|
||||||
isUserAdmin: false,
|
isUserAdmin: false,
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
|
||||||
import { PageBanner } from '@edx/paragon';
|
|
||||||
|
|
||||||
import { selectBlackoutDate } from '../data/selectors';
|
|
||||||
import messages from '../messages';
|
|
||||||
import { inBlackoutDateRange } from '../utils';
|
|
||||||
|
|
||||||
function BlackoutInformationBanner({
|
|
||||||
intl,
|
|
||||||
}) {
|
|
||||||
const isDiscussionsBlackout = inBlackoutDateRange(useSelector(selectBlackoutDate));
|
|
||||||
const [showBanner, setShowBanner] = useState(true);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageBanner
|
|
||||||
variant="accentB"
|
|
||||||
show={isDiscussionsBlackout && showBanner}
|
|
||||||
dismissible
|
|
||||||
onDismiss={() => setShowBanner(false)}
|
|
||||||
>
|
|
||||||
<div className="font-weight-500">
|
|
||||||
{intl.formatMessage(messages.blackoutDiscussionInformation)}
|
|
||||||
</div>
|
|
||||||
</PageBanner>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlackoutInformationBanner.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(BlackoutInformationBanner);
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
|
|
||||||
import { initializeMockApp } from '@edx/frontend-platform';
|
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
|
||||||
|
|
||||||
import { initializeStore } from '../../store';
|
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import { fetchConfigSuccess } from '../data/slices';
|
|
||||||
import messages from '../messages';
|
|
||||||
import BlackoutInformationBanner from './BlackoutInformationBanner';
|
|
||||||
|
|
||||||
let store;
|
|
||||||
let container;
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
|
||||||
let activeStartDate = new Date();
|
|
||||||
activeStartDate.setDate(activeStartDate.getDate() - 2);
|
|
||||||
let activeEndDate = new Date();
|
|
||||||
activeEndDate.setDate(activeEndDate.getDate() + 2);
|
|
||||||
activeStartDate = activeStartDate.toISOString();
|
|
||||||
activeEndDate = activeEndDate.toISOString();
|
|
||||||
|
|
||||||
const getConfigData = (blackouts = []) => ({
|
|
||||||
id: 'course-v1:edX+DemoX+Demo_Course',
|
|
||||||
userRoles: ['Admin', 'Student'],
|
|
||||||
hasModerationPrivileges: false,
|
|
||||||
isGroupTa: false,
|
|
||||||
isUserAdmin: false,
|
|
||||||
blackouts,
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderComponent() {
|
|
||||||
const wrapper = render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<DiscussionContext.Provider value={{ courseId }}>
|
|
||||||
<BlackoutInformationBanner />
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
container = wrapper.container;
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Blackout Information Banner', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
initializeMockApp({
|
|
||||||
authenticatedUser: {
|
|
||||||
userId: 3,
|
|
||||||
username: 'abc123',
|
|
||||||
administrator: false,
|
|
||||||
roles: ['Student'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{ blackouts: [], visibility: false },
|
|
||||||
{ blackouts: ['2021-12-31T10:15', '2021-12-31T10:20'], visibility: false },
|
|
||||||
{ blackouts: [{ start: activeStartDate, end: activeEndDate }], visibility: true },
|
|
||||||
{ blackouts: [{ start: activeEndDate, end: activeEndDate }], visibility: false },
|
|
||||||
])('Test Blackout Banner is visible on app load if blackout date is active', async ({ blackouts, visibility }) => {
|
|
||||||
store = initializeStore();
|
|
||||||
await store.dispatch(fetchConfigSuccess(getConfigData(blackouts)));
|
|
||||||
renderComponent();
|
|
||||||
if (visibility) {
|
|
||||||
const element = await screen.findByRole('alert');
|
|
||||||
expect(element).toBeInTheDocument();
|
|
||||||
expect(element).toHaveTextContent(messages.blackoutDiscussionInformation.defaultMessage);
|
|
||||||
} else {
|
|
||||||
const element = await screen.queryByRole('alert');
|
|
||||||
expect(element).not.toBeInTheDocument();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,20 +1,46 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Route, Switch } from 'react-router';
|
import { Route, Switch } from 'react-router';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Icon, IconButton } from '@edx/paragon';
|
||||||
|
import { ArrowBack } from '@edx/paragon/icons';
|
||||||
|
|
||||||
import { Routes } from '../../data/constants';
|
import { PostsPages, Routes } from '../../data/constants';
|
||||||
import { CommentsView } from '../comments';
|
import { CommentsView } from '../comments';
|
||||||
|
import { DiscussionContext } from '../common/context';
|
||||||
|
import { useIsOnDesktop } from '../data/hooks';
|
||||||
|
import messages from '../messages';
|
||||||
import { PostEditor } from '../posts';
|
import { PostEditor } from '../posts';
|
||||||
|
import { discussionsPath } from '../utils';
|
||||||
|
|
||||||
function DiscussionContent() {
|
function DiscussionContent({ intl }) {
|
||||||
|
const location = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
const postEditorVisible = useSelector((state) => state.threads.postEditorVisible);
|
const postEditorVisible = useSelector((state) => state.threads.postEditorVisible);
|
||||||
|
const isOnDesktop = useIsOnDesktop();
|
||||||
|
const {
|
||||||
|
courseId, learnerUsername, category, topicId, page,
|
||||||
|
} = useContext(DiscussionContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex bg-light-400 flex-column w-75 w-xs-100 w-xl-75 align-items-center">
|
<div className="d-flex bg-light-400 flex-column w-75 w-xs-100 w-xl-75 align-items-center">
|
||||||
<div className="d-flex flex-column w-100">
|
<div className="d-flex flex-column w-100">
|
||||||
|
{!isOnDesktop && (
|
||||||
|
<IconButton
|
||||||
|
src={ArrowBack}
|
||||||
|
iconAs={Icon}
|
||||||
|
style={{ padding: '18px' }}
|
||||||
|
size="inline"
|
||||||
|
className="ml-4 mt-4"
|
||||||
|
onClick={() => history.push(discussionsPath(PostsPages[page], {
|
||||||
|
courseId, learnerUsername, category, topicId,
|
||||||
|
})(location))}
|
||||||
|
alt={intl.formatMessage(messages.backAlt)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{postEditorVisible ? (
|
{postEditorVisible ? (
|
||||||
<Route path={Routes.POSTS.NEW_POST}>
|
<Route path={Routes.POSTS.NEW_POST}>
|
||||||
<PostEditor />
|
<PostEditor />
|
||||||
@@ -34,4 +60,8 @@ function DiscussionContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DiscussionContent.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default injectIntl(DiscussionContent);
|
export default injectIntl(DiscussionContent);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -7,10 +7,7 @@ import {
|
|||||||
Redirect, Route, Switch, useLocation,
|
Redirect, Route, Switch, useLocation,
|
||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
|
|
||||||
import { useWindowSize } from '@edx/paragon';
|
|
||||||
|
|
||||||
import { RequestStatus, Routes } from '../../data/constants';
|
import { RequestStatus, Routes } from '../../data/constants';
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import {
|
import {
|
||||||
useContainerSize, useIsOnDesktop, useIsOnXLDesktop, useShowLearnersTab,
|
useContainerSize, useIsOnDesktop, useIsOnXLDesktop, useShowLearnersTab,
|
||||||
} from '../data/hooks';
|
} from '../data/hooks';
|
||||||
@@ -27,29 +24,26 @@ export default function DiscussionSidebar({ displaySidebar, postActionBarRef })
|
|||||||
const redirectToLearnersTab = useShowLearnersTab();
|
const redirectToLearnersTab = useShowLearnersTab();
|
||||||
const sidebarRef = useRef(null);
|
const sidebarRef = useRef(null);
|
||||||
const postActionBarHeight = useContainerSize(postActionBarRef);
|
const postActionBarHeight = useContainerSize(postActionBarRef);
|
||||||
const { height: windowHeight } = useWindowSize();
|
|
||||||
const { inContext } = useContext(DiscussionContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sidebarRef && postActionBarHeight && !inContext) {
|
if (sidebarRef && postActionBarHeight) {
|
||||||
if (isOnDesktop) {
|
if (isOnDesktop) {
|
||||||
sidebarRef.current.style.maxHeight = `${windowHeight - postActionBarHeight}px`;
|
sidebarRef.current.style.maxHeight = `${document.body.offsetHeight - postActionBarHeight}px`;
|
||||||
}
|
}
|
||||||
sidebarRef.current.style.minHeight = `${windowHeight - postActionBarHeight}px`;
|
sidebarRef.current.style.minHeight = `${document.body.offsetHeight - postActionBarHeight}px`;
|
||||||
sidebarRef.current.style.top = `${postActionBarHeight}px`;
|
sidebarRef.current.style.top = `${postActionBarHeight}px`;
|
||||||
}
|
}
|
||||||
}, [sidebarRef, postActionBarHeight, inContext]);
|
}, [sidebarRef, postActionBarHeight]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={sidebarRef}
|
ref={sidebarRef}
|
||||||
className={classNames('flex-column position-sticky', {
|
className={classNames('flex-column min-content-height position-sticky', {
|
||||||
'd-none': !displaySidebar,
|
'd-none': !displaySidebar,
|
||||||
'd-flex overflow-auto': displaySidebar,
|
'd-flex overflow-auto': displaySidebar,
|
||||||
'w-100': !isOnDesktop,
|
'w-100': !isOnDesktop,
|
||||||
'sidebar-desktop-width': isOnDesktop && !isOnXLDesktop,
|
'sidebar-desktop-width': isOnDesktop && !isOnXLDesktop,
|
||||||
'w-25 sidebar-XL-width': isOnXLDesktop,
|
'w-25 sidebar-XL-width': isOnXLDesktop,
|
||||||
'min-content-height': !inContext,
|
|
||||||
})}
|
})}
|
||||||
data-testid="sidebar"
|
data-testid="sidebar"
|
||||||
>
|
>
|
||||||
@@ -60,11 +54,12 @@ export default function DiscussionSidebar({ displaySidebar, postActionBarRef })
|
|||||||
/>
|
/>
|
||||||
<Route path={Routes.TOPICS.PATH} component={TopicsView} />
|
<Route path={Routes.TOPICS.PATH} component={TopicsView} />
|
||||||
{redirectToLearnersTab && (
|
{redirectToLearnersTab && (
|
||||||
<Route path={Routes.LEARNERS.POSTS} component={LearnerPostsView} />
|
<Route path={Routes.LEARNERS.POSTS} component={LearnerPostsView} />
|
||||||
)}
|
)}
|
||||||
{redirectToLearnersTab && (
|
{redirectToLearnersTab && (
|
||||||
<Route path={Routes.LEARNERS.PATH} component={LearnersView} />
|
<Route path={Routes.LEARNERS.PATH} component={LearnersView} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{configStatus === RequestStatus.SUCCESSFUL && (
|
{configStatus === RequestStatus.SUCCESSFUL && (
|
||||||
<Redirect
|
<Redirect
|
||||||
from={Routes.DISCUSSIONS.PATH}
|
from={Routes.DISCUSSIONS.PATH}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
|||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import { fetchConfigSuccess } from '../data/slices';
|
import { fetchConfigSuccess } from '../data/slices';
|
||||||
import { getThreadsApiUrl } from '../posts/data/api';
|
import { threadsApiUrl } from '../posts/data/api';
|
||||||
import DiscussionSidebar from './DiscussionSidebar';
|
import DiscussionSidebar from './DiscussionSidebar';
|
||||||
|
|
||||||
import '../posts/data/__factories__';
|
import '../posts/data/__factories__';
|
||||||
@@ -21,7 +21,6 @@ import '../posts/data/__factories__';
|
|||||||
let store;
|
let store;
|
||||||
let container;
|
let container;
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
function renderComponent(displaySidebar = true, location = `/${courseId}/`) {
|
function renderComponent(displaySidebar = true, location = `/${courseId}/`) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
Route, Switch, useLocation, useRouteMatch,
|
Route, Switch, useLocation, useRouteMatch,
|
||||||
@@ -8,7 +7,6 @@ import {
|
|||||||
|
|
||||||
import Footer from '@edx/frontend-component-footer';
|
import Footer from '@edx/frontend-component-footer';
|
||||||
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
import { LearningHeader as Header } from '@edx/frontend-component-header';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
|
|
||||||
import { PostActionsBar } from '../../components';
|
import { PostActionsBar } from '../../components';
|
||||||
import { CourseTabsNavigation } from '../../components/NavigationBar';
|
import { CourseTabsNavigation } from '../../components/NavigationBar';
|
||||||
@@ -22,7 +20,6 @@ import { EmptyLearners, EmptyPosts, EmptyTopics } from '../empty-posts';
|
|||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { BreadcrumbMenu, LegacyBreadcrumbMenu, NavigationBar } from '../navigation';
|
import { BreadcrumbMenu, LegacyBreadcrumbMenu, NavigationBar } from '../navigation';
|
||||||
import { postMessageToParent } from '../utils';
|
import { postMessageToParent } from '../utils';
|
||||||
import BlackoutInformationBanner from './BlackoutInformationBanner';
|
|
||||||
import DiscussionContent from './DiscussionContent';
|
import DiscussionContent from './DiscussionContent';
|
||||||
import DiscussionSidebar from './DiscussionSidebar';
|
import DiscussionSidebar from './DiscussionSidebar';
|
||||||
import InformationBanner from './InformationsBanner';
|
import InformationBanner from './InformationsBanner';
|
||||||
@@ -40,7 +37,6 @@ export default function DiscussionsHome() {
|
|||||||
const { params: { path } } = useRouteMatch(`${Routes.DISCUSSIONS.PATH}/:path*`);
|
const { params: { path } } = useRouteMatch(`${Routes.DISCUSSIONS.PATH}/:path*`);
|
||||||
const { params } = useRouteMatch(ALL_ROUTES);
|
const { params } = useRouteMatch(ALL_ROUTES);
|
||||||
const isRedirectToLearners = useShowLearnersTab();
|
const isRedirectToLearners = useShowLearnersTab();
|
||||||
const isFeedbackBannerVisible = getConfig().DISPLAY_FEEDBACK_BANNER === 'true';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
courseId,
|
courseId,
|
||||||
@@ -50,13 +46,16 @@ export default function DiscussionsHome() {
|
|||||||
learnerUsername,
|
learnerUsername,
|
||||||
} = params;
|
} = params;
|
||||||
const inContext = new URLSearchParams(location.search).get('inContext') !== null;
|
const inContext = new URLSearchParams(location.search).get('inContext') !== null;
|
||||||
|
const inIframe = new URLSearchParams(location.search).get('inIframe')?.toLowerCase() === 'true';
|
||||||
// Display the content area if we are currently viewing/editing a post or creating one.
|
// Display the content area if we are currently viewing/editing a post or creating one.
|
||||||
const displayContentArea = postId || postEditorVisible || (learnerUsername && postId);
|
const displayContentArea = postId || postEditorVisible || (learnerUsername && postId);
|
||||||
let displaySidebar = useSidebarVisible();
|
let displaySidebar = useSidebarVisible();
|
||||||
|
|
||||||
const isOnDesktop = useIsOnDesktop();
|
const isOnDesktop = useIsOnDesktop();
|
||||||
|
|
||||||
const { courseNumber, courseTitle, org } = useSelector((state) => state.courseTabs);
|
const { courseNumber, courseTitle, org } = useSelector(
|
||||||
|
(state) => state.courseTabs,
|
||||||
|
);
|
||||||
if (displayContentArea) {
|
if (displayContentArea) {
|
||||||
// If the window is larger than a particular size, show the sidebar for navigating between posts/topics.
|
// If the window is larger than a particular size, show the sidebar for navigating between posts/topics.
|
||||||
// However, for smaller screens or embeds, only show the sidebar if the content area isn't displayed.
|
// However, for smaller screens or embeds, only show the sidebar if the content area isn't displayed.
|
||||||
@@ -65,7 +64,7 @@ export default function DiscussionsHome() {
|
|||||||
|
|
||||||
const provider = useSelector(selectDiscussionProvider);
|
const provider = useSelector(selectDiscussionProvider);
|
||||||
useCourseDiscussionData(courseId);
|
useCourseDiscussionData(courseId);
|
||||||
useRedirectToThread(courseId, inContext);
|
useRedirectToThread(courseId);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (path && path !== 'undefined') {
|
if (path && path !== 'undefined') {
|
||||||
postMessageToParent('discussions.navigate', { path });
|
postMessageToParent('discussions.navigate', { path });
|
||||||
@@ -83,50 +82,45 @@ export default function DiscussionsHome() {
|
|||||||
learnerUsername,
|
learnerUsername,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!inContext && <Header courseOrg={org} courseNumber={courseNumber} courseTitle={courseTitle} />}
|
{!inIframe && <Header courseOrg={org} courseNumber={courseNumber} courseTitle={courseTitle} />}
|
||||||
<main className="container-fluid d-flex flex-column p-0 w-100" id="main" tabIndex="-1">
|
<main className="container-fluid d-flex flex-column p-0 w-100" id="main" tabIndex="-1">
|
||||||
{!inContext && <CourseTabsNavigation activeTab="discussion" courseId={courseId} />}
|
{!inIframe
|
||||||
<div
|
&& <CourseTabsNavigation activeTab="discussion" courseId={courseId} />}
|
||||||
className={classNames('header-action-bar', { 'shadow-none border-light-300 border-bottom': inContext })}
|
<div className="header-action-bar" ref={postActionBarRef}>
|
||||||
ref={postActionBarRef}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={classNames('d-flex flex-row justify-content-between navbar fixed-top', {
|
className="d-flex flex-row justify-content-between navbar fixed-top"
|
||||||
'pl-4 pr-2.5 py-1.5': inContext,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{!inContext && <Route path={Routes.DISCUSSIONS.PATH} component={NavigationBar} />}
|
{!inContext && (
|
||||||
|
<Route path={Routes.DISCUSSIONS.PATH} component={NavigationBar} />
|
||||||
|
)}
|
||||||
<PostActionsBar inContext={inContext} />
|
<PostActionsBar inContext={inContext} />
|
||||||
</div>
|
</div>
|
||||||
{isFeedbackBannerVisible && <InformationBanner />}
|
<InformationBanner />
|
||||||
<BlackoutInformationBanner />
|
|
||||||
</div>
|
</div>
|
||||||
{!inContext && (
|
<Route
|
||||||
<Route
|
path={[Routes.POSTS.PATH, Routes.TOPICS.CATEGORY]}
|
||||||
path={[Routes.POSTS.PATH, Routes.TOPICS.CATEGORY]}
|
component={provider === DiscussionProvider.LEGACY ? LegacyBreadcrumbMenu : BreadcrumbMenu}
|
||||||
component={provider === DiscussionProvider.LEGACY ? LegacyBreadcrumbMenu : BreadcrumbMenu}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="d-flex flex-row">
|
<div className="d-flex flex-row">
|
||||||
<DiscussionSidebar displaySidebar={displaySidebar} postActionBarRef={postActionBarRef} />
|
<DiscussionSidebar displaySidebar={displaySidebar} postActionBarRef={postActionBarRef} />
|
||||||
{displayContentArea && <DiscussionContent />}
|
{displayContentArea && <DiscussionContent />}
|
||||||
{!displayContentArea && (
|
{!displayContentArea && (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={Routes.TOPICS.PATH} component={EmptyTopics} />
|
<Route path={Routes.TOPICS.PATH} component={EmptyTopics} />
|
||||||
<Route
|
<Route
|
||||||
path={Routes.POSTS.MY_POSTS}
|
path={Routes.POSTS.MY_POSTS}
|
||||||
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyMyPosts} />}
|
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyMyPosts} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={[Routes.POSTS.PATH, Routes.POSTS.ALL_POSTS, Routes.LEARNERS.POSTS]}
|
path={[Routes.POSTS.PATH, Routes.POSTS.ALL_POSTS, Routes.LEARNERS.POSTS]}
|
||||||
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyAllPosts} />}
|
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyAllPosts} />}
|
||||||
/>
|
/>
|
||||||
{isRedirectToLearners && <Route path={Routes.LEARNERS.PATH} component={EmptyLearners} /> }
|
{isRedirectToLearners && <Route path={Routes.LEARNERS.PATH} component={EmptyLearners} /> }
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{!inContext && <Footer />}
|
{!inIframe && <Footer />}
|
||||||
</DiscussionContext.Provider>
|
</DiscussionContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,10 +88,35 @@ describe('DiscussionsHome', () => {
|
|||||||
window.parent = parent;
|
window.parent = parent;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('header, course navigation bar and footer are visible', async () => {
|
describe.each([
|
||||||
renderComponent();
|
{
|
||||||
expect(screen.queryByRole('banner')).toBeInTheDocument();
|
queryParam: 'inIframe=True',
|
||||||
expect(document.getElementById('courseTabsNavigation')).toBeInTheDocument();
|
iframeView: true,
|
||||||
expect(screen.queryByRole('contentinfo')).toBeInTheDocument();
|
},
|
||||||
});
|
{
|
||||||
|
queryParam: 'inIframe=False',
|
||||||
|
iframeView: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParam: '',
|
||||||
|
iframeView: false,
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
'Header/Footer visibility',
|
||||||
|
({
|
||||||
|
queryParam,
|
||||||
|
iframeView,
|
||||||
|
}) => {
|
||||||
|
test(`inIframe query param ${queryParam}`, async () => {
|
||||||
|
renderComponent(`/${courseId}/topics?${queryParam}`);
|
||||||
|
if (iframeView) {
|
||||||
|
expect(screen.queryByRole('banner')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('contentinfo')).not.toBeInTheDocument();
|
||||||
|
} else {
|
||||||
|
expect(screen.queryByRole('banner')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('contentinfo')).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function InformationBanner({
|
|||||||
dismissible
|
dismissible
|
||||||
onDismiss={() => setShowBanner(false)}
|
onDismiss={() => setShowBanner(false)}
|
||||||
>
|
>
|
||||||
<div className="font-weight-500">
|
<div style={{ fontWeight: '500' }}>
|
||||||
{intl.formatMessage(messages.bannerMessage)}
|
{intl.formatMessage(messages.bannerMessage)}
|
||||||
{!hideLearnMoreButton
|
{!hideLearnMoreButton
|
||||||
&& (
|
&& (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
|||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
import { executeThunk } from '../../test-utils';
|
import { executeThunk } from '../../test-utils';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { getThreadsApiUrl } from '../posts/data/api';
|
import { threadsApiUrl } from '../posts/data/api';
|
||||||
import { fetchThreads } from '../posts/data/thunks';
|
import { fetchThreads } from '../posts/data/thunks';
|
||||||
import EmptyPosts from './EmptyPosts';
|
import EmptyPosts from './EmptyPosts';
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ import '../posts/data/__factories__';
|
|||||||
|
|
||||||
let store;
|
let store;
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
|
|
||||||
function renderComponent(location = `/${courseId}/`) {
|
function renderComponent(location = `/${courseId}/`) {
|
||||||
return render(
|
return render(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { initializeMockApp } from '@edx/frontend-platform';
|
|||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
import { getApiBaseUrl } from '../../data/constants';
|
import { API_BASE_URL } from '../../data/constants';
|
||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
import { executeThunk } from '../../test-utils';
|
import { executeThunk } from '../../test-utils';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
@@ -20,7 +20,7 @@ import '../topics/data/__factories__';
|
|||||||
|
|
||||||
let store;
|
let store;
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
|
const topicsApiUrl = `${API_BASE_URL}/api/discussion/v1/course_topics/${courseId}`;
|
||||||
|
|
||||||
function renderComponent(location = `/${courseId}/topics/`) {
|
function renderComponent(location = `/${courseId}/topics/`) {
|
||||||
return render(
|
return render(
|
||||||
|
|||||||
@@ -12,23 +12,17 @@ import {
|
|||||||
} from '@edx/paragon';
|
} from '@edx/paragon';
|
||||||
import { ArrowBack } from '@edx/paragon/icons';
|
import { ArrowBack } from '@edx/paragon/icons';
|
||||||
|
|
||||||
import {
|
import { RequestStatus, Routes } from '../../data/constants';
|
||||||
RequestStatus,
|
|
||||||
Routes,
|
|
||||||
} from '../../data/constants';
|
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import { selectUserHasModerationPrivileges, selectUserIsStaff } from '../data/selectors';
|
|
||||||
import {
|
import {
|
||||||
selectAllThreads,
|
selectAllThreads,
|
||||||
selectThreadNextPage,
|
selectThreadNextPage,
|
||||||
threadsLoadingStatus,
|
threadsLoadingStatus,
|
||||||
} from '../posts/data/selectors';
|
} from '../posts/data/selectors';
|
||||||
import { clearPostsPages } from '../posts/data/slices';
|
|
||||||
import NoResults from '../posts/NoResults';
|
import NoResults from '../posts/NoResults';
|
||||||
import { PostLink } from '../posts/post';
|
import { PostLink } from '../posts/post';
|
||||||
import { discussionsPath, filterPosts } from '../utils';
|
import { discussionsPath, filterPosts } from '../utils';
|
||||||
import { fetchUserPosts } from './data/thunks';
|
import { fetchUserPosts } from './data/thunks';
|
||||||
import LearnerPostFilterBar from './learner-post-filter-bar/LearnerPostFilterBar';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
function LearnerPostsView({ intl }) {
|
function LearnerPostsView({ intl }) {
|
||||||
@@ -38,28 +32,18 @@ function LearnerPostsView({ intl }) {
|
|||||||
|
|
||||||
const posts = useSelector(selectAllThreads);
|
const posts = useSelector(selectAllThreads);
|
||||||
const loadingStatus = useSelector(threadsLoadingStatus());
|
const loadingStatus = useSelector(threadsLoadingStatus());
|
||||||
const postFilter = useSelector(state => state.learners.postFilter);
|
|
||||||
const { courseId, learnerUsername: username } = useContext(DiscussionContext);
|
const { courseId, learnerUsername: username } = useContext(DiscussionContext);
|
||||||
const nextPage = useSelector(selectThreadNextPage());
|
const nextPage = useSelector(selectThreadNextPage());
|
||||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
|
||||||
const userIsStaff = useSelector(selectUserIsStaff);
|
|
||||||
|
|
||||||
const loadMorePosts = (pageNum = undefined) => {
|
|
||||||
const params = {
|
|
||||||
author: username,
|
|
||||||
page: pageNum,
|
|
||||||
filters: postFilter,
|
|
||||||
orderBy: postFilter.orderBy,
|
|
||||||
countFlagged: (userHasModerationPrivileges || userIsStaff) || undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch(fetchUserPosts(courseId, params));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(clearPostsPages());
|
dispatch(fetchUserPosts(courseId, username));
|
||||||
loadMorePosts();
|
}, [courseId, username]);
|
||||||
}, [courseId, postFilter, username]);
|
|
||||||
|
const loadMorePosts = () => (
|
||||||
|
dispatch(fetchUserPosts(courseId, username, {
|
||||||
|
page: nextPage,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const checkIsSelected = (id) => window.location.pathname.includes(id);
|
const checkIsSelected = (id) => window.location.pathname.includes(id);
|
||||||
const pinnedPosts = useMemo(() => filterPosts(posts, 'pinned'), [posts]);
|
const pinnedPosts = useMemo(() => filterPosts(posts, 'pinned'), [posts]);
|
||||||
@@ -94,8 +78,6 @@ function LearnerPostsView({ intl }) {
|
|||||||
<div style={{ padding: '18px' }} />
|
<div style={{ padding: '18px' }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-light-400 border border-light-300" />
|
<div className="bg-light-400 border border-light-300" />
|
||||||
<LearnerPostFilterBar />
|
|
||||||
<div className="border-bottom border-light-400" />
|
|
||||||
<div className="list-group list-group-flush">
|
<div className="list-group list-group-flush">
|
||||||
{postInstances(pinnedPosts)}
|
{postInstances(pinnedPosts)}
|
||||||
{postInstances(unpinnedPosts)}
|
{postInstances(unpinnedPosts)}
|
||||||
@@ -106,7 +88,7 @@ function LearnerPostsView({ intl }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
nextPage && loadingStatus === RequestStatus.SUCCESSFUL && (
|
nextPage && loadingStatus === RequestStatus.SUCCESSFUL && (
|
||||||
<Button onClick={() => loadMorePosts(nextPage)} variant="primary" size="md">
|
<Button onClick={() => loadMorePosts()} variant="primary" size="md">
|
||||||
{intl.formatMessage(messages.loadMore)}
|
{intl.formatMessage(messages.loadMore)}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import { MemoryRouter, Route } from 'react-router';
|
|
||||||
import { Factory } from 'rosie';
|
|
||||||
|
|
||||||
import { initializeMockApp } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
|
||||||
|
|
||||||
import { initializeStore } from '../../store';
|
|
||||||
import { executeThunk } from '../../test-utils';
|
|
||||||
import { DiscussionContext } from '../common/context';
|
|
||||||
import { getCourseConfigApiUrl } from '../data/api';
|
|
||||||
import { fetchCourseConfig } from '../data/thunks';
|
|
||||||
import { getCoursesApiUrl } from './data/api';
|
|
||||||
import LearnerPostsView from './LearnerPostsView';
|
|
||||||
|
|
||||||
import './data/__factories__';
|
|
||||||
|
|
||||||
let store;
|
|
||||||
let axiosMock;
|
|
||||||
const coursesApiUrl = getCoursesApiUrl();
|
|
||||||
const courseConfigApiUrl = getCourseConfigApiUrl();
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
|
||||||
const username = 'abc123';
|
|
||||||
|
|
||||||
function renderComponent(path = `/${courseId}/learners/${username}/posts`) {
|
|
||||||
return render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<DiscussionContext.Provider
|
|
||||||
value={{
|
|
||||||
learnerUsername: username,
|
|
||||||
courseId,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MemoryRouter initialEntries={[path]}>
|
|
||||||
<Route path={path}>
|
|
||||||
<LearnerPostsView />
|
|
||||||
</Route>
|
|
||||||
</MemoryRouter>
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('LearnerPostsView', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
initializeMockApp({
|
|
||||||
authenticatedUser: {
|
|
||||||
userId: 3,
|
|
||||||
username,
|
|
||||||
administrator: true,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
store = initializeStore();
|
|
||||||
Factory.resetAll();
|
|
||||||
const learnerPosts = Factory.build('learnerPosts', {}, {
|
|
||||||
abuseFlaggedCount: 1,
|
|
||||||
});
|
|
||||||
const apiUrl = `${coursesApiUrl}${courseId}/learner/`;
|
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
|
||||||
axiosMock.onGet(apiUrl, { username, count_flagged: true })
|
|
||||||
.reply(() => [200, learnerPosts]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Basic', () => {
|
|
||||||
test('Reported icon is visible to moderator for post with reported comment', async () => {
|
|
||||||
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
|
|
||||||
has_moderation_privileges: true,
|
|
||||||
});
|
|
||||||
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
|
|
||||||
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
|
|
||||||
await act(async () => {
|
|
||||||
renderComponent();
|
|
||||||
});
|
|
||||||
expect(screen.queryAllByTestId('reported-post')[0]).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Reported icon is not visible to learner for post with reported comment', async () => {
|
|
||||||
await renderComponent();
|
|
||||||
expect(screen.queryByTestId('reported-post')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -13,9 +13,9 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
|||||||
|
|
||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
import { executeThunk } from '../../test-utils';
|
import { executeThunk } from '../../test-utils';
|
||||||
import { getCourseConfigApiUrl } from '../data/api';
|
import { courseConfigApiUrl } from '../data/api';
|
||||||
import { fetchCourseConfig } from '../data/thunks';
|
import { fetchCourseConfig } from '../data/thunks';
|
||||||
import { getCoursesApiUrl, getUserProfileApiUrl } from './data/api';
|
import { coursesApiUrl, userProfileApiUrl } from './data/api';
|
||||||
import { fetchLearners } from './data/thunks';
|
import { fetchLearners } from './data/thunks';
|
||||||
import LearnersView from './LearnersView';
|
import LearnersView from './LearnersView';
|
||||||
|
|
||||||
@@ -23,9 +23,6 @@ import './data/__factories__';
|
|||||||
|
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
const coursesApiUrl = getCoursesApiUrl();
|
|
||||||
const courseConfigApiUrl = getCourseConfigApiUrl();
|
|
||||||
const userProfileApiUrl = getUserProfileApiUrl();
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
|
|
||||||
function renderComponent() {
|
function renderComponent() {
|
||||||
|
|||||||
@@ -86,63 +86,3 @@ Factory.define('learnersProfile')
|
|||||||
}));
|
}));
|
||||||
return profiles;
|
return profiles;
|
||||||
});
|
});
|
||||||
|
|
||||||
Factory.define('learnerPosts')
|
|
||||||
.option('abuseFlaggedCount', null, null)
|
|
||||||
.option('courseId', null, 'course-v1:edX+TestX+Test_Course')
|
|
||||||
.attr(
|
|
||||||
'results',
|
|
||||||
['abuseFlaggedCount', 'courseId'],
|
|
||||||
(abuseFlaggedCount, courseId) => {
|
|
||||||
const threads = [];
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
threads.push({
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
editable_fields: [
|
|
||||||
'abuse_flagged',
|
|
||||||
'following',
|
|
||||||
'group_id',
|
|
||||||
'raw_body',
|
|
||||||
'closed',
|
|
||||||
'read',
|
|
||||||
'title',
|
|
||||||
'topic_id',
|
|
||||||
'type',
|
|
||||||
'voted',
|
|
||||||
'pinned',
|
|
||||||
],
|
|
||||||
id: `post_id${i}`,
|
|
||||||
author: 'test_user',
|
|
||||||
author_label: 'Staff',
|
|
||||||
abuse_flagged: false,
|
|
||||||
can_delete: true,
|
|
||||||
voted: false,
|
|
||||||
vote_count: 1,
|
|
||||||
title: `Title ${i}`,
|
|
||||||
raw_body: `<p>body ${i}</p>`,
|
|
||||||
preview_body: `<p>body ${i}</p>`,
|
|
||||||
course_id: courseId,
|
|
||||||
group_id: null,
|
|
||||||
group_name: null,
|
|
||||||
abuse_flagged_count: abuseFlaggedCount,
|
|
||||||
following: false,
|
|
||||||
comment_count: 8,
|
|
||||||
unread_comment_count: 0,
|
|
||||||
endorsed_comment_list_url: null,
|
|
||||||
non_endorsed_comment_list_url: null,
|
|
||||||
read: false,
|
|
||||||
has_endorsed: false,
|
|
||||||
pinned: false,
|
|
||||||
topic_id: 'topic',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return threads;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.attr('pagination', [], () => ({
|
|
||||||
next: null,
|
|
||||||
prev: null,
|
|
||||||
count: 2,
|
|
||||||
num_pages: 1,
|
|
||||||
}));
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
import snakeCase from 'lodash/snakeCase';
|
import { ensureConfig, getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
import { ensureConfig, getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
ensureConfig([
|
ensureConfig([
|
||||||
'LMS_BASE_URL',
|
'LMS_BASE_URL',
|
||||||
], 'Posts API service');
|
], 'Posts API service');
|
||||||
|
|
||||||
export const getCoursesApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
|
const apiBaseUrl = getConfig().LMS_BASE_URL;
|
||||||
export const getUserProfileApiUrl = () => `${getConfig().LMS_BASE_URL}/api/user/v1/accounts`;
|
|
||||||
|
export const coursesApiUrl = `${apiBaseUrl}/api/discussion/v1/courses/`;
|
||||||
|
export const userProfileApiUrl = `${apiBaseUrl}/api/user/v1/accounts`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all the learners in the given course.
|
* Fetches all the learners in the given course.
|
||||||
@@ -18,7 +18,7 @@ export const getUserProfileApiUrl = () => `${getConfig().LMS_BASE_URL}/api/user/
|
|||||||
* @returns {Promise<{}>}
|
* @returns {Promise<{}>}
|
||||||
*/
|
*/
|
||||||
export async function getLearners(courseId, params) {
|
export async function getLearners(courseId, params) {
|
||||||
const url = `${getCoursesApiUrl()}${courseId}/activity_stats/`;
|
const url = `${coursesApiUrl}${courseId}/activity_stats/`;
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url, { params });
|
const { data } = await getAuthenticatedHttpClient().get(url, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ export async function getLearners(courseId, params) {
|
|||||||
* @param {string} usernames
|
* @param {string} usernames
|
||||||
*/
|
*/
|
||||||
export async function getUserProfiles(usernames) {
|
export async function getUserProfiles(usernames) {
|
||||||
const url = `${getUserProfileApiUrl()}?username=${usernames.join()}`;
|
const url = `${userProfileApiUrl}?username=${usernames.join()}`;
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -37,50 +37,18 @@ export async function getUserProfiles(usernames) {
|
|||||||
* Get the posts by a specific user in a course's discussions
|
* Get the posts by a specific user in a course's discussions
|
||||||
*
|
*
|
||||||
* @param {string} courseId Course ID of the course
|
* @param {string} courseId Course ID of the course
|
||||||
* @param {string} author
|
* @param {string} username Username of the user
|
||||||
* @param {number} page
|
* @param {number} page
|
||||||
* @param {number} pageSize
|
|
||||||
* @param {string} textSearch A search string to match.
|
|
||||||
* @param {ThreadOrdering} orderBy The results wil be sorted on this basis.
|
|
||||||
* @param {boolean} following If true, only threads followed by the current user will be returned.
|
|
||||||
* @param {boolean} flagged If true, only threads that have been reported will be returned.
|
|
||||||
* @param {string} threadType Can be 'discussion' or 'question'.
|
|
||||||
* @param {ThreadViewStatus} view Set to "unread" on "unanswered" to filter to only those statuses.
|
|
||||||
* @param {boolean} countFlagged If true, abuseFlaggedCount will be available.
|
|
||||||
* @param {number} cohort
|
|
||||||
* @returns API Response object in the format
|
* @returns API Response object in the format
|
||||||
* {
|
* {
|
||||||
* results: [array of posts],
|
* results: [array of posts],
|
||||||
* pagination: {count, num_pages, next, previous}
|
* pagination: {count, num_pages, next, previous}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export async function getUserPosts(courseId, {
|
export async function getUserPosts(courseId, username, { page }) {
|
||||||
page,
|
const learnerPostsApiUrl = `${coursesApiUrl}${courseId}/learner/`;
|
||||||
pageSize,
|
|
||||||
textSearch,
|
|
||||||
orderBy,
|
|
||||||
status,
|
|
||||||
author,
|
|
||||||
threadType,
|
|
||||||
countFlagged,
|
|
||||||
cohort,
|
|
||||||
} = {}) {
|
|
||||||
const learnerPostsApiUrl = `${getCoursesApiUrl()}${courseId}/learner/`;
|
|
||||||
|
|
||||||
const params = snakeCaseObject({
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
textSearch,
|
|
||||||
threadType,
|
|
||||||
orderBy: orderBy && snakeCase(orderBy),
|
|
||||||
status,
|
|
||||||
requestedFields: 'profile_image',
|
|
||||||
username: author,
|
|
||||||
countFlagged,
|
|
||||||
groupId: cohort,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.get(learnerPostsApiUrl, { params });
|
.get(learnerPostsApiUrl, { params: { username, page } });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
LearnersOrdering,
|
LearnersOrdering,
|
||||||
PostsStatusFilter,
|
|
||||||
RequestStatus,
|
RequestStatus,
|
||||||
ThreadOrdering,
|
|
||||||
ThreadType,
|
|
||||||
} from '../../../data/constants';
|
} from '../../../data/constants';
|
||||||
|
|
||||||
const learnersSlice = createSlice({
|
const learnersSlice = createSlice({
|
||||||
@@ -19,12 +16,6 @@ const learnersSlice = createSlice({
|
|||||||
totalPages: null,
|
totalPages: null,
|
||||||
totalLearners: null,
|
totalLearners: null,
|
||||||
sortedBy: LearnersOrdering.BY_LAST_ACTIVITY,
|
sortedBy: LearnersOrdering.BY_LAST_ACTIVITY,
|
||||||
postFilter: {
|
|
||||||
postType: ThreadType.ALL,
|
|
||||||
status: PostsStatusFilter.ALL,
|
|
||||||
orderBy: ThreadOrdering.BY_LAST_ACTIVITY,
|
|
||||||
cohort: '',
|
|
||||||
},
|
|
||||||
usernameSearch: null,
|
usernameSearch: null,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
@@ -56,10 +47,6 @@ const learnersSlice = createSlice({
|
|||||||
state.usernameSearch = payload;
|
state.usernameSearch = payload;
|
||||||
state.pages = [];
|
state.pages = [];
|
||||||
},
|
},
|
||||||
setPostFilter: (state, { payload }) => {
|
|
||||||
state.pages = [];
|
|
||||||
state.postFilter = payload;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,7 +57,6 @@ export const {
|
|||||||
fetchLearnersDenied,
|
fetchLearnersDenied,
|
||||||
setSortedBy,
|
setSortedBy,
|
||||||
setUsernameSearch,
|
setUsernameSearch,
|
||||||
setPostFilter,
|
|
||||||
} = learnersSlice.actions;
|
} = learnersSlice.actions;
|
||||||
|
|
||||||
export const learnersReducer = learnersSlice.reducer;
|
export const learnersReducer = learnersSlice.reducer;
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
import { camelCaseObject, snakeCaseObject } from '@edx/frontend-platform';
|
import { camelCaseObject, snakeCaseObject } from '@edx/frontend-platform';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
|
|
||||||
import {
|
|
||||||
PostsStatusFilter, ThreadType,
|
|
||||||
} from '../../../data/constants';
|
|
||||||
import {
|
import {
|
||||||
fetchLearnerThreadsRequest,
|
fetchLearnerThreadsRequest,
|
||||||
fetchThreadsDenied,
|
fetchThreadsDenied,
|
||||||
@@ -70,48 +67,15 @@ export function fetchLearners(courseId, {
|
|||||||
* @param page
|
* @param page
|
||||||
* @returns a promise that will update the state with the learner's posts
|
* @returns a promise that will update the state with the learner's posts
|
||||||
*/
|
*/
|
||||||
export function fetchUserPosts(courseId, {
|
export function fetchUserPosts(courseId, username, { page = 1 } = {}) {
|
||||||
orderBy,
|
|
||||||
filters = {},
|
|
||||||
page = 1,
|
|
||||||
author = null,
|
|
||||||
countFlagged,
|
|
||||||
} = {}) {
|
|
||||||
const options = {
|
|
||||||
orderBy,
|
|
||||||
page,
|
|
||||||
author,
|
|
||||||
countFlagged,
|
|
||||||
};
|
|
||||||
if (filters.status === PostsStatusFilter.UNREAD) {
|
|
||||||
options.status = 'unread';
|
|
||||||
}
|
|
||||||
if (filters.status === PostsStatusFilter.UNANSWERED) {
|
|
||||||
options.status = 'unanswered';
|
|
||||||
}
|
|
||||||
if (filters.status === PostsStatusFilter.REPORTED) {
|
|
||||||
options.status = 'flagged';
|
|
||||||
}
|
|
||||||
if (filters.status === PostsStatusFilter.UNRESPONDED) {
|
|
||||||
options.status = 'unresponded';
|
|
||||||
}
|
|
||||||
if (filters.postType !== ThreadType.ALL) {
|
|
||||||
options.threadType = filters.postType;
|
|
||||||
}
|
|
||||||
if (filters.search) {
|
|
||||||
options.textSearch = filters.search;
|
|
||||||
}
|
|
||||||
if (filters.cohort) {
|
|
||||||
options.cohort = filters.cohort;
|
|
||||||
}
|
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
dispatch(fetchLearnerThreadsRequest({ courseId, author }));
|
dispatch(fetchLearnerThreadsRequest({ courseId, author: username }));
|
||||||
|
|
||||||
const data = await getUserPosts(courseId, options);
|
const data = await getUserPosts(courseId, username, { page });
|
||||||
const normalisedData = normaliseThreads(camelCaseObject(data));
|
const normalisedData = normaliseThreads(camelCaseObject(data));
|
||||||
|
|
||||||
dispatch(fetchThreadsSuccess({ ...normalisedData, page, author }));
|
dispatch(fetchThreadsSuccess({ ...normalisedData, page, author: username }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (getHttpErrorStatus(error) === 403) {
|
if (getHttpErrorStatus(error) === 403) {
|
||||||
dispatch(fetchThreadsDenied());
|
dispatch(fetchThreadsDenied());
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import FilterBar from '../../../components/FilterBar';
|
|
||||||
import { selectCourseCohorts } from '../../cohorts/data/selectors';
|
|
||||||
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
|
|
||||||
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
|
||||||
import { setPostFilter } from '../data/slices';
|
|
||||||
|
|
||||||
function LearnerPostFilterBar() {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { courseId } = useParams();
|
|
||||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
|
||||||
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
|
||||||
const cohorts = useSelector(selectCourseCohorts);
|
|
||||||
const postFilter = useSelector(state => state.learners.postFilter);
|
|
||||||
|
|
||||||
const filtersToShow = [
|
|
||||||
{
|
|
||||||
name: 'postType',
|
|
||||||
filters: ['type-all', 'type-discussions', 'type-questions'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
filters: ['status-any', 'status-unread', 'status-unanswered', 'status-unresponded'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'orderBy',
|
|
||||||
filters: ['sort-activity', 'sort-comments', 'sort-votes'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (userHasModerationPrivileges || userIsGroupTa) {
|
|
||||||
filtersToShow[1].filters.splice(2, 0, 'status-reported');
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFilterChange = (event) => {
|
|
||||||
const { name, value } = event.currentTarget;
|
|
||||||
if (name === 'postType') {
|
|
||||||
if (postFilter.postType !== value) {
|
|
||||||
dispatch(setPostFilter({
|
|
||||||
...postFilter,
|
|
||||||
postType: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else if (name === 'status') {
|
|
||||||
if (postFilter.status !== value) {
|
|
||||||
dispatch(setPostFilter({
|
|
||||||
...postFilter,
|
|
||||||
status: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else if (name === 'orderBy') {
|
|
||||||
if (postFilter.orderBy !== value) {
|
|
||||||
dispatch(setPostFilter({
|
|
||||||
...postFilter,
|
|
||||||
orderBy: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else if (name === 'cohort') {
|
|
||||||
if (postFilter.cohort !== value) {
|
|
||||||
dispatch(setPostFilter({
|
|
||||||
...postFilter,
|
|
||||||
cohort: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (userHasModerationPrivileges && isEmpty(cohorts)) {
|
|
||||||
dispatch(fetchCourseCohorts(courseId));
|
|
||||||
}
|
|
||||||
}, [courseId, userHasModerationPrivileges]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FilterBar
|
|
||||||
filters={filtersToShow}
|
|
||||||
selectedFilters={postFilter}
|
|
||||||
onFilterChange={handleFilterChange}
|
|
||||||
showCohortsFilter={userHasModerationPrivileges || userIsGroupTa}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LearnerPostFilterBar;
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import {
|
|
||||||
act,
|
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
waitFor,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import { generatePath, MemoryRouter, Route } from 'react-router';
|
|
||||||
|
|
||||||
import { initializeMockApp } from '@edx/frontend-platform';
|
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
|
||||||
|
|
||||||
import { Routes } from '../../../data/constants';
|
|
||||||
import { initializeStore } from '../../../store';
|
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import LearnerPostFilterBar from './LearnerPostFilterBar';
|
|
||||||
|
|
||||||
let store;
|
|
||||||
const username = 'abc123';
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
|
||||||
const path = generatePath(
|
|
||||||
Routes.LEARNERS.POSTS,
|
|
||||||
{ courseId, learnerUsername: username },
|
|
||||||
);
|
|
||||||
|
|
||||||
function renderComponent() {
|
|
||||||
return render(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<DiscussionContext.Provider
|
|
||||||
value={{
|
|
||||||
learnerUsername: username,
|
|
||||||
courseId,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MemoryRouter initialEntries={[path]}>
|
|
||||||
<Route
|
|
||||||
path={Routes.LEARNERS.POSTS}
|
|
||||||
component={LearnerPostFilterBar}
|
|
||||||
/>
|
|
||||||
</MemoryRouter>
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('LearnerPostFilterBar', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
initializeMockApp({
|
|
||||||
authenticatedUser: {
|
|
||||||
userId: 3,
|
|
||||||
username,
|
|
||||||
administrator: true,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const initialData = {
|
|
||||||
config: {
|
|
||||||
hasModerationPrivileges: true,
|
|
||||||
isGroupTa: false,
|
|
||||||
},
|
|
||||||
cohorts: {
|
|
||||||
status: 'successful',
|
|
||||||
cohorts: [
|
|
||||||
{
|
|
||||||
name: 'Default Group',
|
|
||||||
id: 1,
|
|
||||||
userCount: 1,
|
|
||||||
groupId: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
store = initializeStore(initialData);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('checks if all filters are visible', async () => {
|
|
||||||
const { queryAllByRole } = await renderComponent();
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.click(queryAllByRole('button')[0]);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(queryAllByRole('radiogroup')).toHaveLength(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('checks if default values are selected', async () => {
|
|
||||||
const { queryAllByRole } = await renderComponent();
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.click(queryAllByRole('button')[0]);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
queryAllByRole('radiogroup')[0].querySelector('input[value="all"]'),
|
|
||||||
).toBeChecked();
|
|
||||||
expect(
|
|
||||||
queryAllByRole('radiogroup')[1].querySelector('input[value="statusAll"]'),
|
|
||||||
).toBeChecked();
|
|
||||||
expect(
|
|
||||||
queryAllByRole('radiogroup')[2].querySelector('input[value="lastActivityAt"]'),
|
|
||||||
).toBeChecked();
|
|
||||||
expect(
|
|
||||||
queryAllByRole('radiogroup')[3].querySelector('input[value=""]'),
|
|
||||||
).toBeChecked();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -103,12 +103,6 @@ function LearnerFilterBar({
|
|||||||
selected={currentSorting}
|
selected={currentSorting}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActionItem
|
|
||||||
id="sort-recency"
|
|
||||||
label={intl.formatMessage(messages.recentActivity)}
|
|
||||||
value={LearnersOrdering.BY_RECENCY}
|
|
||||||
selected={currentSorting}
|
|
||||||
/>
|
|
||||||
</Form.RadioSet>
|
</Form.RadioSet>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useSelector } from 'react-redux';
|
|||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
|
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
|
||||||
import { Edit, Report, ReportGmailerrorred } from '@edx/paragon/icons';
|
import { Edit, Report } from '@edx/paragon/icons';
|
||||||
|
|
||||||
import { QuestionAnswerOutline } from '../../../components/icons';
|
import { QuestionAnswerOutline, ReportGmailerrorred } from '../../../components/icons';
|
||||||
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { learnerShape } from './proptypes';
|
import { learnerShape } from './proptypes';
|
||||||
|
|||||||
@@ -38,14 +38,9 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Reported activity',
|
defaultMessage: 'Reported activity',
|
||||||
description: 'Text for learners sorting by reported activity',
|
description: 'Text for learners sorting by reported activity',
|
||||||
},
|
},
|
||||||
recentActivity: {
|
|
||||||
id: 'discussions.learner.recentActivity',
|
|
||||||
defaultMessage: 'Recent activity',
|
|
||||||
description: 'Text for learners sorting by recent activity',
|
|
||||||
},
|
|
||||||
sortFilterStatus: {
|
sortFilterStatus: {
|
||||||
id: 'discussions.learner.sortFilterStatus',
|
id: 'discussions.learner.sortFilterStatus',
|
||||||
defaultMessage: `All learners sorted by {sort, select,
|
defaultMessage: `All learners by {sort, select,
|
||||||
flagged {reported activity}
|
flagged {reported activity}
|
||||||
activity {most activity}
|
activity {most activity}
|
||||||
other {{sort}}
|
other {{sort}}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Actions menu',
|
defaultMessage: 'Actions menu',
|
||||||
description: 'Alt-text for dropdown button for actions related to a post or comment',
|
description: 'Alt-text for dropdown button for actions related to a post or comment',
|
||||||
},
|
},
|
||||||
|
backAlt: {
|
||||||
|
id: 'discussions.actions.back.alt',
|
||||||
|
defaultMessage: 'Back',
|
||||||
|
description: 'Text on button for back to posts list',
|
||||||
|
},
|
||||||
copyLink: {
|
copyLink: {
|
||||||
id: 'discussions.actions.copylink',
|
id: 'discussions.actions.copylink',
|
||||||
defaultMessage: 'Copy link',
|
defaultMessage: 'Copy link',
|
||||||
@@ -63,7 +68,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
markAnsweredAction: {
|
markAnsweredAction: {
|
||||||
id: 'discussions.actions.markAnswered',
|
id: 'discussions.actions.markAnswered',
|
||||||
defaultMessage: 'Mark as answered',
|
defaultMessage: 'Mark as Answered',
|
||||||
description: 'Action to mark a comment as answering a post',
|
description: 'Action to mark a comment as answering a post',
|
||||||
},
|
},
|
||||||
unmarkAnsweredAction: {
|
unmarkAnsweredAction: {
|
||||||
@@ -171,7 +176,7 @@ const messages = defineMessages({
|
|||||||
bannerMessage: {
|
bannerMessage: {
|
||||||
id: 'discussion.banner.welcomeMessage',
|
id: 'discussion.banner.welcomeMessage',
|
||||||
defaultMessage: '🎉 Welcome to the new and improved discussions experience!',
|
defaultMessage: '🎉 Welcome to the new and improved discussions experience!',
|
||||||
description: 'Information banner welcome text',
|
description: 'Author name displayed when a post is anonymous',
|
||||||
},
|
},
|
||||||
learnMoreBannerLink: {
|
learnMoreBannerLink: {
|
||||||
id: 'discussion.banner.learnMore',
|
id: 'discussion.banner.learnMore',
|
||||||
@@ -183,26 +188,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Share feedback',
|
defaultMessage: 'Share feedback',
|
||||||
description: 'Share feedback button to open feedback forms',
|
description: 'Share feedback button to open feedback forms',
|
||||||
},
|
},
|
||||||
blackoutDiscussionInformation: {
|
|
||||||
id: 'discussion.blackoutBanner.information',
|
|
||||||
defaultMessage: 'Blackout dates are currently active. Posting in discussions is unavailable at this time.',
|
|
||||||
description: 'Informative text when discussions blackout is active',
|
|
||||||
},
|
|
||||||
imageWarningMessage: {
|
|
||||||
id: 'discussions.editor.image.warning.message',
|
|
||||||
defaultMessage: 'Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions',
|
|
||||||
description: 'Modal message to tell image dimensions compatibility issue with legacy',
|
|
||||||
},
|
|
||||||
imageWarningModalTitle: {
|
|
||||||
id: 'discussions.editor.image.warning.title',
|
|
||||||
defaultMessage: 'Warning!',
|
|
||||||
description: 'Modal message title',
|
|
||||||
},
|
|
||||||
imageWarningDismissButton: {
|
|
||||||
id: 'discussions.editor.image.warning.dismiss',
|
|
||||||
defaultMessage: 'Ok',
|
|
||||||
description: 'Modal dismiss button text',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function BreadcrumbMenu() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="breadcrumb-menu d-flex flex-row bg-light-200 box-shadow-down-1 px-2.5 py-1">
|
<div className="breadcrumb-menu d-flex flex-row mt-2 mx-3">
|
||||||
<BreadcrumbDropdown
|
<BreadcrumbDropdown
|
||||||
currentItem={currentChapter}
|
currentItem={currentChapter}
|
||||||
showAllPath={discussionsPath(Routes.TOPICS.ALL, { courseId })}
|
showAllPath={discussionsPath(Routes.TOPICS.ALL, { courseId })}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
import { getBlocksAPIResponse } from '../../../data/__factories__';
|
import { getBlocksAPIResponse } from '../../../data/__factories__';
|
||||||
import { getBlocksAPIURL } from '../../../data/api';
|
import { blocksAPIURL } from '../../../data/api';
|
||||||
import { getApiBaseUrl, Routes } from '../../../data/constants';
|
import { API_BASE_URL, Routes } from '../../../data/constants';
|
||||||
import { fetchCourseBlocks } from '../../../data/thunks';
|
import { fetchCourseBlocks } from '../../../data/thunks';
|
||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
@@ -25,7 +25,7 @@ import { BreadcrumbMenu } from '../index';
|
|||||||
import '../../topics/data/__factories__';
|
import '../../topics/data/__factories__';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v2/course_topics/${courseId}`;
|
const topicsApiUrl = `${API_BASE_URL}/api/discussion/v2/course_topics/${courseId}`;
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ describe('BreadcrumbMenu', () => {
|
|||||||
Factory.resetAll();
|
Factory.resetAll();
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||||
blocksAPIResponse = getBlocksAPIResponse();
|
blocksAPIResponse = getBlocksAPIResponse();
|
||||||
axiosMock.onGet(getBlocksAPIURL())
|
axiosMock.onGet(blocksAPIURL)
|
||||||
.reply(200, blocksAPIResponse);
|
.reply(200, blocksAPIResponse);
|
||||||
await executeThunk(fetchCourseBlocks(courseId, 'test-user'), store.dispatch, store.getState);
|
await executeThunk(fetchCourseBlocks(courseId, 'test-user'), store.dispatch, store.getState);
|
||||||
const data = [
|
const data = [
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function LegacyBreadcrumbMenu() {
|
|||||||
const isNonCoursewareTopic = currentTopic && !currentCategory;
|
const isNonCoursewareTopic = currentTopic && !currentCategory;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="breadcrumb-menu d-flex flex-row bg-light-200 box-shadow-down-1 px-2.5 py-1">
|
<div className="breadcrumb-menu d-flex flex-row mt-2 mx-3">
|
||||||
{isNonCoursewareTopic ? (
|
{isNonCoursewareTopic ? (
|
||||||
<BreadcrumbDropdown
|
<BreadcrumbDropdown
|
||||||
currentItem={currentTopic}
|
currentItem={currentTopic}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { initializeMockApp } from '@edx/frontend-platform';
|
|||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
import { getApiBaseUrl, Routes } from '../../../data/constants';
|
import { API_BASE_URL, Routes } from '../../../data/constants';
|
||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
import { fetchCourseTopics } from '../../topics/data/thunks';
|
import { fetchCourseTopics } from '../../topics/data/thunks';
|
||||||
@@ -21,7 +21,7 @@ import { LegacyBreadcrumbMenu } from '../index';
|
|||||||
import '../../topics/data/__factories__';
|
import '../../topics/data/__factories__';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
|
const topicsApiUrl = `${API_BASE_URL}/api/discussion/v1/course_topics/${courseId}`;
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Button, Spinner } from '@edx/paragon';
|
|||||||
import { RequestStatus } from '../../data/constants';
|
import { RequestStatus } from '../../data/constants';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import { selectconfigLoadingStatus, selectUserHasModerationPrivileges, selectUserIsStaff } from '../data/selectors';
|
import { selectconfigLoadingStatus, selectUserHasModerationPrivileges, selectUserIsStaff } from '../data/selectors';
|
||||||
import { fetchUserPosts } from '../learners/data/thunks';
|
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { filterPosts } from '../utils';
|
import { filterPosts } from '../utils';
|
||||||
import {
|
import {
|
||||||
@@ -38,22 +37,16 @@ function PostsList({ posts, topics, intl }) {
|
|||||||
const userIsStaff = useSelector(selectUserIsStaff);
|
const userIsStaff = useSelector(selectUserIsStaff);
|
||||||
const configStatus = useSelector(selectconfigLoadingStatus);
|
const configStatus = useSelector(selectconfigLoadingStatus);
|
||||||
|
|
||||||
const loadThreads = (topicIds, pageNum = undefined) => {
|
const loadThreads = (topicIds, pageNum = undefined) => (
|
||||||
const params = {
|
dispatch(fetchThreads(courseId, {
|
||||||
|
topicIds,
|
||||||
orderBy,
|
orderBy,
|
||||||
filters,
|
filters,
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
author: showOwnPosts ? authenticatedUser.username : null,
|
author: showOwnPosts ? authenticatedUser.username : null,
|
||||||
countFlagged: (userHasModerationPrivileges || userIsStaff) || undefined,
|
countFlagged: userHasModerationPrivileges || userIsStaff,
|
||||||
topicIds,
|
}))
|
||||||
};
|
);
|
||||||
|
|
||||||
if (showOwnPosts && filters.search === '') {
|
|
||||||
dispatch(fetchUserPosts(courseId, params));
|
|
||||||
} else {
|
|
||||||
dispatch(fetchThreads(courseId, params));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (topics !== undefined && configStatus === RequestStatus.SUCCESSFUL) {
|
if (topics !== undefined && configStatus === RequestStatus.SUCCESSFUL) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import SearchInfo from '../../components/SearchInfo';
|
import SearchInfo from '../../components/SearchInfo';
|
||||||
import { selectCurrentCategoryGrouping, selectTopicsUnderCategory } from '../../data/selectors';
|
import { selectTopicsUnderCategory } from '../../data/selectors';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import {
|
import {
|
||||||
selectAllThreads,
|
selectAllThreads,
|
||||||
@@ -29,10 +29,7 @@ TopicPostsList.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function CategoryPostsList({ category }) {
|
function CategoryPostsList({ category }) {
|
||||||
const { inContext } = useContext(DiscussionContext);
|
const topicIds = useSelector(selectTopicsUnderCategory)(category);
|
||||||
const groupedCategory = useSelector(selectCurrentCategoryGrouping)(category);
|
|
||||||
// If grouping at subsection is enabled, only apply it when browsing discussions in context in the learning MFE.
|
|
||||||
const topicIds = useSelector(selectTopicsUnderCategory)(inContext ? groupedCategory : category);
|
|
||||||
const posts = useSelector(selectTopicThreads(topicIds));
|
const posts = useSelector(selectTopicThreads(topicIds));
|
||||||
return <PostsList posts={posts} topics={topicIds} />;
|
return <PostsList posts={posts} topics={topicIds} />;
|
||||||
}
|
}
|
||||||
@@ -49,7 +46,6 @@ function PostsView() {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const searchString = useSelector(({ threads }) => threads.filters.search);
|
const searchString = useSelector(({ threads }) => threads.filters.search);
|
||||||
const resultsFound = useSelector(({ threads }) => threads.totalThreads);
|
const resultsFound = useSelector(({ threads }) => threads.totalThreads);
|
||||||
const textSearchRewrite = useSelector(({ threads }) => threads.textSearchRewrite);
|
|
||||||
const loadingStatus = useSelector(({ threads }) => threads.status);
|
const loadingStatus = useSelector(({ threads }) => threads.status);
|
||||||
|
|
||||||
let postsListComponent;
|
let postsListComponent;
|
||||||
@@ -78,15 +74,9 @@ function PostsView() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="discussion-posts d-flex flex-column h-100">
|
<div className="discussion-posts d-flex flex-column h-100">
|
||||||
{searchString && (
|
{
|
||||||
<SearchInfo
|
searchString && <SearchInfo count={resultsFound} text={searchString} loadingStatus={loadingStatus} onClear={() => dispatch(setSearchQuery(''))} />
|
||||||
count={resultsFound}
|
}
|
||||||
text={searchString}
|
|
||||||
loadingStatus={loadingStatus}
|
|
||||||
onClear={() => dispatch(setSearchQuery(''))}
|
|
||||||
textSearchRewrite={textSearchRewrite}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<PostFilterBar />
|
<PostFilterBar />
|
||||||
<div className="border-bottom border-light-400" />
|
<div className="border-bottom border-light-400" />
|
||||||
<div className="list-group list-group-flush flex-fill" role="list" onKeyDown={e => handleKeyDown(e)}>
|
<div className="list-group list-group-flush flex-fill" role="list" onKeyDown={e => handleKeyDown(e)}>
|
||||||
|
|||||||
@@ -18,22 +18,18 @@ import { initializeStore } from '../../store';
|
|||||||
import { getCohortsApiUrl } from '../cohorts/data/api';
|
import { getCohortsApiUrl } from '../cohorts/data/api';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
import { fetchConfigSuccess } from '../data/slices';
|
import { fetchConfigSuccess } from '../data/slices';
|
||||||
import { getCoursesApiUrl } from '../learners/data/api';
|
import { threadsApiUrl } from './data/api';
|
||||||
import { getThreadsApiUrl } from './data/api';
|
|
||||||
import { PostsView } from './index';
|
import { PostsView } from './index';
|
||||||
|
|
||||||
import './data/__factories__';
|
import './data/__factories__';
|
||||||
import '../cohorts/data/__factories__';
|
import '../cohorts/data/__factories__';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
const coursesApiUrl = getCoursesApiUrl();
|
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
const username = 'abc123';
|
|
||||||
|
|
||||||
async function renderComponent({
|
async function renderComponent({
|
||||||
postId, topicId, category, myPosts, inContext = false,
|
postId, topicId, category, myPosts,
|
||||||
} = { myPosts: false }) {
|
} = { myPosts: false }) {
|
||||||
let path = generatePath(Routes.POSTS.ALL_POSTS, { courseId });
|
let path = generatePath(Routes.POSTS.ALL_POSTS, { courseId });
|
||||||
let page;
|
let page;
|
||||||
@@ -60,7 +56,6 @@ async function renderComponent({
|
|||||||
topicId,
|
topicId,
|
||||||
category,
|
category,
|
||||||
page,
|
page,
|
||||||
inContext,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -85,11 +80,17 @@ describe('PostsView', () => {
|
|||||||
initializeMockApp({
|
initializeMockApp({
|
||||||
authenticatedUser: {
|
authenticatedUser: {
|
||||||
userId: 3,
|
userId: 3,
|
||||||
username,
|
username: 'abc123',
|
||||||
administrator: true,
|
administrator: true,
|
||||||
roles: [],
|
roles: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store = initializeStore({
|
||||||
|
blocks: { blocks: { 'test-usage-key': { topics: ['some-topic-2', 'some-topic-0'] } } },
|
||||||
|
config: { hasModerationPrivileges: true },
|
||||||
|
});
|
||||||
|
store.dispatch(fetchConfigSuccess({}));
|
||||||
Factory.resetAll();
|
Factory.resetAll();
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||||
axiosMock.onGet(getCohortsApiUrl(courseId)).reply(200, Factory.buildList('cohort', 1));
|
axiosMock.onGet(getCohortsApiUrl(courseId)).reply(200, Factory.buildList('cohort', 1));
|
||||||
@@ -108,20 +109,8 @@ describe('PostsView', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupStore(data = {}) {
|
|
||||||
const storeData = {
|
|
||||||
blocks: { blocks: { 'test-usage-key': { topics: ['some-topic-2', 'some-topic-0'] } } },
|
|
||||||
config: { hasModerationPrivileges: true },
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
// console.log(storeData);
|
|
||||||
store = initializeStore(storeData);
|
|
||||||
store.dispatch(fetchConfigSuccess({}));
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Basic', () => {
|
describe('Basic', () => {
|
||||||
test('displays a list of all posts', async () => {
|
test('displays a list of all posts', async () => {
|
||||||
setupStore();
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await renderComponent();
|
await renderComponent();
|
||||||
});
|
});
|
||||||
@@ -129,62 +118,27 @@ describe('PostsView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('displays a list of user posts', async () => {
|
test('displays a list of user posts', async () => {
|
||||||
setupStore();
|
|
||||||
axiosMock.onGet(`${coursesApiUrl}${courseId}/learner/`, { username, count_flagged: true })
|
|
||||||
.reply(() => {
|
|
||||||
const threadAttrs = { previewBody: 'thread preview body', author: username };
|
|
||||||
return [200, Factory.build('threadsResult', {}, {
|
|
||||||
topicId: undefined,
|
|
||||||
count: threadCount,
|
|
||||||
threadAttrs,
|
|
||||||
pageSize: 6,
|
|
||||||
})];
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await renderComponent({ myPosts: true });
|
await renderComponent({ myPosts: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getAllByText('abc123')).toHaveLength(threadCount);
|
expect(screen.getAllByText('abc123')).toHaveLength(threadCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('displays a list of posts in a topic', async () => {
|
test('displays a list of posts in a topic', async () => {
|
||||||
setupStore();
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await renderComponent({ topicId: 'some-topic-1' });
|
await renderComponent({ topicId: 'some-topic-1' });
|
||||||
});
|
});
|
||||||
expect(screen.getAllByText(/this is thread-\d+ in topic some-topic-1/i)).toHaveLength(Math.ceil(threadCount / 3));
|
expect(screen.getAllByText(/this is thread-\d+ in topic some-topic-1/i)).toHaveLength(Math.ceil(threadCount / 3));
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([true, false])(
|
test('displays a list of posts in a category', async () => {
|
||||||
'displays a list of posts in a category with grouping at subsection = %s',
|
await act(async () => {
|
||||||
async (grouping) => {
|
await renderComponent({ category: 'test-usage-key' });
|
||||||
setupStore({
|
});
|
||||||
blocks: {
|
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-1}/i)).toHaveLength(0);
|
||||||
blocks: {
|
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-2/i)).toHaveLength(Math.ceil(threadCount / 3));
|
||||||
'test-usage-key': {
|
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-0/i)).toHaveLength(Math.ceil(threadCount / 3));
|
||||||
type: 'vertical',
|
});
|
||||||
topics: ['some-topic-2', 'some-topic-0'],
|
|
||||||
parent: 'test-seq-key',
|
|
||||||
},
|
|
||||||
'test-seq-key': { type: 'sequential', topics: ['some-topic-0', 'some-topic-1', 'some-topic-2'] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
config: { groupAtSubsection: grouping, hasModerationPrivileges: true, provider: 'openedx' },
|
|
||||||
});
|
|
||||||
await act(async () => {
|
|
||||||
await renderComponent({ category: 'test-usage-key', inContext: true, p: true });
|
|
||||||
});
|
|
||||||
const topicThreadCount = Math.ceil(threadCount / 3);
|
|
||||||
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-2/i))
|
|
||||||
.toHaveLength(topicThreadCount);
|
|
||||||
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-0/i))
|
|
||||||
.toHaveLength(topicThreadCount);
|
|
||||||
// When grouping is enabled, topic 1 will be shown, but not otherwise.
|
|
||||||
expect(screen.queryAllByText(/this is thread-\d+ in topic some-topic-1/i))
|
|
||||||
.toHaveLength(grouping ? topicThreadCount : 0);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Filtering', () => {
|
describe('Filtering', () => {
|
||||||
@@ -196,12 +150,11 @@ describe('PostsView', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
setupStore();
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await renderComponent();
|
await renderComponent();
|
||||||
});
|
});
|
||||||
dropDownButton = screen.getByRole('button', {
|
dropDownButton = screen.getByRole('button', {
|
||||||
name: /all posts sorted by recent activity/i,
|
name: /all posts by recent activity/i,
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fireEvent.click(dropDownButton);
|
fireEvent.click(dropDownButton);
|
||||||
@@ -213,7 +166,7 @@ describe('PostsView', () => {
|
|||||||
// 5 status filters: any, unread, following, reported, unanswered
|
// 5 status filters: any, unread, following, reported, unanswered
|
||||||
// 3 sort: activity, comments, likes
|
// 3 sort: activity, comments, likes
|
||||||
// 2 cohort: all groups, 1 api mock response cohort
|
// 2 cohort: all groups, 1 api mock response cohort
|
||||||
expect(screen.queryAllByRole('radio')).toHaveLength(14);
|
expect(screen.queryAllByRole('radio')).toHaveLength(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test that the cohorts filter works', async () => {
|
test('test that the cohorts filter works', async () => {
|
||||||
@@ -222,7 +175,7 @@ describe('PostsView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dropDownButton = screen.getByRole('button', {
|
dropDownButton = screen.getByRole('button', {
|
||||||
name: /All posts in Cohort 1 sorted by recent activity/i,
|
name: /All posts in Cohort 1 by recent activity/i,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(dropDownButton).toBeInTheDocument();
|
expect(dropDownButton).toBeInTheDocument();
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ ensureConfig([
|
|||||||
'LMS_BASE_URL',
|
'LMS_BASE_URL',
|
||||||
], 'Posts API service');
|
], 'Posts API service');
|
||||||
|
|
||||||
export const getThreadsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/threads/`;
|
const apiBaseUrl = getConfig().LMS_BASE_URL;
|
||||||
export const getCoursesApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
|
|
||||||
|
export const threadsApiUrl = `${apiBaseUrl}/api/discussion/v1/threads/`;
|
||||||
|
export const coursesApiUrl = `${apiBaseUrl}/api/discussion/v1/courses/`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all the threads in the given course and topic.
|
* Fetches all the threads in the given course and topic.
|
||||||
@@ -60,7 +62,7 @@ export async function getThreads(
|
|||||||
countFlagged,
|
countFlagged,
|
||||||
groupId: cohort,
|
groupId: cohort,
|
||||||
});
|
});
|
||||||
const { data } = await getAuthenticatedHttpClient().get(getThreadsApiUrl(), { params });
|
const { data } = await getAuthenticatedHttpClient().get(threadsApiUrl, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +71,9 @@ export async function getThreads(
|
|||||||
* @param {string} threadId
|
* @param {string} threadId
|
||||||
* @returns {Promise<{}>}
|
* @returns {Promise<{}>}
|
||||||
*/
|
*/
|
||||||
export async function getThread(threadId, courseId) {
|
export async function getThread(threadId) {
|
||||||
const params = { requested_fields: 'profile_image', course_id: courseId };
|
const params = { requested_fields: 'profile_image' };
|
||||||
const url = `${getThreadsApiUrl()}${threadId}/`;
|
const url = `${threadsApiUrl}${threadId}/`;
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url, { params });
|
const { data } = await getAuthenticatedHttpClient().get(url, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ export async function postThread(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.post(getThreadsApiUrl(), postData);
|
.post(threadsApiUrl, postData);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ export async function updateThread(threadId, {
|
|||||||
editReasonCode,
|
editReasonCode,
|
||||||
closeReasonCode,
|
closeReasonCode,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const url = `${getThreadsApiUrl()}${threadId}/`;
|
const url = `${threadsApiUrl}${threadId}/`;
|
||||||
const patchData = snakeCaseObject({
|
const patchData = snakeCaseObject({
|
||||||
topicId,
|
topicId,
|
||||||
abuse_flagged: flagged,
|
abuse_flagged: flagged,
|
||||||
@@ -175,7 +177,7 @@ export async function updateThread(threadId, {
|
|||||||
* @param {string} threadId
|
* @param {string} threadId
|
||||||
*/
|
*/
|
||||||
export async function deleteThread(threadId) {
|
export async function deleteThread(threadId) {
|
||||||
const url = `${getThreadsApiUrl()}${threadId}/`;
|
const url = `${threadsApiUrl}${threadId}/`;
|
||||||
await getAuthenticatedHttpClient()
|
await getAuthenticatedHttpClient()
|
||||||
.delete(url);
|
.delete(url);
|
||||||
}
|
}
|
||||||
@@ -189,7 +191,7 @@ export async function deleteThread(threadId) {
|
|||||||
* @returns {Promise<{ location: string }>}
|
* @returns {Promise<{ location: string }>}
|
||||||
*/
|
*/
|
||||||
export async function uploadFile(blob, filename, courseId, threadKey) {
|
export async function uploadFile(blob, filename, courseId, threadKey) {
|
||||||
const uploadUrl = `${getCoursesApiUrl()}${courseId}/upload`;
|
const uploadUrl = `${coursesApiUrl}${courseId}/upload`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('thread_key', threadKey);
|
formData.append('thread_key', threadKey);
|
||||||
formData.append('uploaded_file', blob, filename);
|
formData.append('uploaded_file', blob, filename);
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
|
|||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
||||||
|
|
||||||
import { getCoursesApiUrl, uploadFile } from './api';
|
import { coursesApiUrl, uploadFile } from './api';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
const coursesApiUrl = getCoursesApiUrl();
|
|
||||||
|
|
||||||
let axiosMock = null;
|
let axiosMock = null;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing';
|
|||||||
|
|
||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
import { getThreadsApiUrl } from './api';
|
import { threadsApiUrl } from './api';
|
||||||
import {
|
import {
|
||||||
createNewThread, fetchThread, fetchThreads, removeThread, updateExistingThread,
|
createNewThread, fetchThread, fetchThreads, removeThread, updateExistingThread,
|
||||||
} from './thunks';
|
} from './thunks';
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
import './__factories__';
|
import './__factories__';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
|
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
let store;
|
let store;
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ const threadsSlice = createSlice({
|
|||||||
nextPage: null,
|
nextPage: null,
|
||||||
totalPages: null,
|
totalPages: null,
|
||||||
totalThreads: null,
|
totalThreads: null,
|
||||||
textSearchRewrite: null,
|
|
||||||
postStatus: RequestStatus.SUCCESSFUL,
|
postStatus: RequestStatus.SUCCESSFUL,
|
||||||
filters: {
|
filters: {
|
||||||
status: PostsStatusFilter.ALL,
|
status: PostsStatusFilter.ALL,
|
||||||
@@ -61,9 +60,6 @@ const threadsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
state.status = RequestStatus.IN_PROGRESS;
|
state.status = RequestStatus.IN_PROGRESS;
|
||||||
},
|
},
|
||||||
clearPostsPages: (state) => {
|
|
||||||
state.pages = [];
|
|
||||||
},
|
|
||||||
fetchThreadsRequest: (state) => {
|
fetchThreadsRequest: (state) => {
|
||||||
state.status = RequestStatus.IN_PROGRESS;
|
state.status = RequestStatus.IN_PROGRESS;
|
||||||
},
|
},
|
||||||
@@ -84,7 +80,6 @@ const threadsSlice = createSlice({
|
|||||||
state.nextPage = (payload.page < payload.pagination.numPages) ? payload.page + 1 : null;
|
state.nextPage = (payload.page < payload.pagination.numPages) ? payload.page + 1 : null;
|
||||||
state.totalPages = payload.pagination.numPages;
|
state.totalPages = payload.pagination.numPages;
|
||||||
state.totalThreads = payload.pagination.count;
|
state.totalThreads = payload.pagination.count;
|
||||||
state.textSearchRewrite = payload.textSearchRewrite;
|
|
||||||
},
|
},
|
||||||
fetchThreadsFailed: (state) => {
|
fetchThreadsFailed: (state) => {
|
||||||
state.status = RequestStatus.FAILED;
|
state.status = RequestStatus.FAILED;
|
||||||
@@ -245,7 +240,6 @@ export const {
|
|||||||
showPostEditor,
|
showPostEditor,
|
||||||
hidePostEditor,
|
hidePostEditor,
|
||||||
clearRedirect,
|
clearRedirect,
|
||||||
clearPostsPages,
|
|
||||||
} = threadsSlice.actions;
|
} = threadsSlice.actions;
|
||||||
|
|
||||||
export const threadsReducer = threadsSlice.reducer;
|
export const threadsReducer = threadsSlice.reducer;
|
||||||
|
|||||||
@@ -120,9 +120,6 @@ export function fetchThreads(courseId, {
|
|||||||
if (filters.status === PostsStatusFilter.UNANSWERED) {
|
if (filters.status === PostsStatusFilter.UNANSWERED) {
|
||||||
options.view = 'unanswered';
|
options.view = 'unanswered';
|
||||||
}
|
}
|
||||||
if (filters.status === PostsStatusFilter.UNRESPONDED) {
|
|
||||||
options.view = 'unresponded';
|
|
||||||
}
|
|
||||||
if (filters.status === PostsStatusFilter.REPORTED) {
|
if (filters.status === PostsStatusFilter.REPORTED) {
|
||||||
options.flagged = true;
|
options.flagged = true;
|
||||||
}
|
}
|
||||||
@@ -141,7 +138,7 @@ export function fetchThreads(courseId, {
|
|||||||
const data = await getThreads(courseId, options);
|
const data = await getThreads(courseId, options);
|
||||||
const normalisedData = normaliseThreads(camelCaseObject(data), topicIds);
|
const normalisedData = normaliseThreads(camelCaseObject(data), topicIds);
|
||||||
dispatch(fetchThreadsSuccess({
|
dispatch(fetchThreadsSuccess({
|
||||||
...normalisedData, page, author, textSearchRewrite: data.text_search_rewrite,
|
...normalisedData, page, author,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (getHttpErrorStatus(error) === 403) {
|
if (getHttpErrorStatus(error) === 403) {
|
||||||
@@ -154,18 +151,18 @@ export function fetchThreads(courseId, {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchThread(threadId, courseId, isDirectLinkPost = false) {
|
export function fetchThread(threadId, isDirectLinkPost = false) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
dispatch(fetchThreadRequest({ threadId }));
|
dispatch(fetchThreadRequest({ threadId }));
|
||||||
const data = await getThread(threadId, courseId);
|
const data = await getThread(threadId);
|
||||||
if (isDirectLinkPost) {
|
if (isDirectLinkPost) {
|
||||||
dispatch(fetchThreadByDirectLinkSuccess({ ...normaliseThreads(camelCaseObject(data)), page: 1 }));
|
dispatch(fetchThreadByDirectLinkSuccess({ ...normaliseThreads(camelCaseObject(data)), page: 1 }));
|
||||||
} else {
|
} else {
|
||||||
dispatch(fetchThreadSuccess(normaliseThreads(camelCaseObject(data))));
|
dispatch(fetchThreadSuccess(normaliseThreads(camelCaseObject(data))));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (getHttpErrorStatus(error) === 403 || getHttpErrorStatus(error) === 404) {
|
if (getHttpErrorStatus(error) === 403) {
|
||||||
dispatch(fetchThreadDenied());
|
dispatch(fetchThreadDenied());
|
||||||
} else {
|
} else {
|
||||||
dispatch(fetchThreadFailed());
|
dispatch(fetchThreadFailed());
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
@@ -11,8 +10,7 @@ import {
|
|||||||
import { Close } from '@edx/paragon/icons';
|
import { Close } from '@edx/paragon/icons';
|
||||||
|
|
||||||
import Search from '../../../components/Search';
|
import Search from '../../../components/Search';
|
||||||
import { RequestStatus } from '../../../data/constants';
|
import { selectBlackoutDate } from '../../data/selectors';
|
||||||
import { selectBlackoutDate, selectconfigLoadingStatus } from '../../data/selectors';
|
|
||||||
import { inBlackoutDateRange, postMessageToParent } from '../../utils';
|
import { inBlackoutDateRange, postMessageToParent } from '../../utils';
|
||||||
import { showPostEditor } from '../data';
|
import { showPostEditor } from '../data';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -24,37 +22,38 @@ function PostActionsBar({
|
|||||||
inContext,
|
inContext,
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const loadingStatus = useSelector(selectconfigLoadingStatus);
|
|
||||||
const blackoutDateRange = useSelector(selectBlackoutDate);
|
|
||||||
|
|
||||||
const handleCloseInContext = () => {
|
const handleCloseInContext = () => {
|
||||||
postMessageToParent('learning.events.sidebar.close');
|
postMessageToParent('learning.events.sidebar.close');
|
||||||
};
|
};
|
||||||
|
const blackoutDateRange = useSelector(selectBlackoutDate);
|
||||||
return (
|
return (
|
||||||
<div className={classNames('d-flex justify-content-end flex-grow-1', { 'py-1': !inContext })}>
|
<div className="d-flex justify-content-end py-1 flex-grow-1">
|
||||||
{!inContext && <Search />}
|
{!inContext && (
|
||||||
|
<>
|
||||||
|
<Search />
|
||||||
|
<div className="border-right border-light-400 mx-3" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{inContext && (
|
{inContext && (
|
||||||
<h4 className="d-flex flex-grow-1 font-weight-bold my-0 py-0 align-self-center">
|
<h4 className="d-flex flex-grow-1 font-weight-bold my-0 py-0 align-self-center">
|
||||||
{intl.formatMessage(messages.title)}
|
{intl.formatMessage(messages.title)}
|
||||||
</h4>
|
</h4>
|
||||||
)}
|
)}
|
||||||
{(!inBlackoutDateRange(blackoutDateRange) && loadingStatus === RequestStatus.SUCCESSFUL) && (
|
{
|
||||||
<>
|
!inBlackoutDateRange(blackoutDateRange) && (
|
||||||
{!inContext && <div className="border-right border-light-400 mx-3" />}
|
|
||||||
<Button
|
<Button
|
||||||
variant={inContext ? 'plain' : 'brand'}
|
variant={inContext ? 'plain' : 'brand'}
|
||||||
className={classNames('my-0', { 'p-0': inContext })}
|
className="my-0"
|
||||||
onClick={() => dispatch(showPostEditor())}
|
onClick={() => dispatch(showPostEditor())}
|
||||||
size={inContext ? 'md' : 'sm'}
|
size="sm"
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.addAPost)}
|
{intl.formatMessage(messages.addAPost)}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
)
|
||||||
)}
|
}
|
||||||
{inContext && (
|
{inContext && (
|
||||||
<>
|
<>
|
||||||
<div className="border-right border-light-300 mr-2 ml-3.5 my-2" />
|
<div className="border-right mr-3 ml-4" />
|
||||||
<IconButton
|
<IconButton
|
||||||
src={Close}
|
src={Close}
|
||||||
iconAs={Icon}
|
iconAs={Icon}
|
||||||
@@ -63,6 +62,7 @@ function PostActionsBar({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Showing {count} results for "{text}"',
|
defaultMessage: 'Showing {count} results for "{text}"',
|
||||||
description: 'Message displayed when user performs a search',
|
description: 'Message displayed when user performs a search',
|
||||||
},
|
},
|
||||||
searchRewriteInfo: {
|
|
||||||
id: 'discussions.actionBar.searchRewriteInfo',
|
|
||||||
defaultMessage: 'No results found for "{searchString}". Showing {count} results for "{textSearchRewrite}".',
|
|
||||||
description: 'Message displayed when user performs a search and search query is rewritten because matching results are not found',
|
|
||||||
},
|
|
||||||
searchInfoSearching: {
|
searchInfoSearching: {
|
||||||
id: 'discussions.actionBar.searchInfoSearching',
|
id: 'discussions.actionBar.searchInfoSearching',
|
||||||
defaultMessage: 'Searching...',
|
defaultMessage: 'Searching...',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { AppContext } from '@edx/frontend-platform/react';
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
import {
|
import {
|
||||||
Button, Card, Form, Spinner, StatefulButton,
|
Button, Card, Form, Spinner, StatefulButton,
|
||||||
@@ -23,8 +23,6 @@ import PostPreviewPane from '../../../components/PostPreviewPane';
|
|||||||
import { useDispatchWithState } from '../../../data/hooks';
|
import { useDispatchWithState } from '../../../data/hooks';
|
||||||
import { selectCourseCohorts } from '../../cohorts/data/selectors';
|
import { selectCourseCohorts } from '../../cohorts/data/selectors';
|
||||||
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
|
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import { useCurrentDiscussionTopic } from '../../data/hooks';
|
|
||||||
import {
|
import {
|
||||||
selectAnonymousPostingConfig,
|
selectAnonymousPostingConfig,
|
||||||
selectDivisionSettings,
|
selectDivisionSettings,
|
||||||
@@ -33,7 +31,6 @@ import {
|
|||||||
selectUserIsGroupTa,
|
selectUserIsGroupTa,
|
||||||
selectUserIsStaff,
|
selectUserIsStaff,
|
||||||
} from '../../data/selectors';
|
} from '../../data/selectors';
|
||||||
import { EmptyPage } from '../../empty-posts';
|
|
||||||
import { selectCoursewareTopics, selectNonCoursewareIds, selectNonCoursewareTopics } from '../../topics/data/selectors';
|
import { selectCoursewareTopics, selectNonCoursewareIds, selectNonCoursewareTopics } from '../../topics/data/selectors';
|
||||||
import {
|
import {
|
||||||
discussionsPath, formikCompatibleHandler, isFormikFieldInvalid, useCommentsPagePath,
|
discussionsPath, formikCompatibleHandler, isFormikFieldInvalid, useCommentsPagePath,
|
||||||
@@ -80,9 +77,9 @@ DiscussionPostType.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function PostEditor({
|
function PostEditor({
|
||||||
|
intl,
|
||||||
editExisting,
|
editExisting,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
|
||||||
const { authenticatedUser } = useContext(AppContext);
|
const { authenticatedUser } = useContext(AppContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
@@ -92,9 +89,10 @@ function PostEditor({
|
|||||||
const commentsPagePath = useCommentsPagePath();
|
const commentsPagePath = useCommentsPagePath();
|
||||||
const {
|
const {
|
||||||
courseId,
|
courseId,
|
||||||
|
topicId,
|
||||||
postId,
|
postId,
|
||||||
} = useParams();
|
} = useParams();
|
||||||
const topicId = useCurrentDiscussionTopic();
|
|
||||||
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
|
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
|
||||||
const nonCoursewareIds = useSelector(selectNonCoursewareIds);
|
const nonCoursewareIds = useSelector(selectNonCoursewareIds);
|
||||||
const coursewareTopics = useSelector(selectCoursewareTopics);
|
const coursewareTopics = useSelector(selectCoursewareTopics);
|
||||||
@@ -106,11 +104,9 @@ function PostEditor({
|
|||||||
const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig);
|
const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig);
|
||||||
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
||||||
const userIsStaff = useSelector(selectUserIsStaff);
|
const userIsStaff = useSelector(selectUserIsStaff);
|
||||||
const { category, inContext } = useContext(DiscussionContext);
|
|
||||||
|
|
||||||
const canDisplayEditReason = (reasonCodesEnabled && editExisting
|
const canDisplayEditReason = (reasonCodesEnabled && editExisting
|
||||||
&& (userHasModerationPrivileges || userIsGroupTa || userIsStaff)
|
&& (userHasModerationPrivileges || userIsGroupTa || userIsStaff) && post?.author !== authenticatedUser.username
|
||||||
&& post?.author !== authenticatedUser.username
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const editReasonCodeValidation = canDisplayEditReason && {
|
const editReasonCodeValidation = canDisplayEditReason && {
|
||||||
@@ -138,7 +134,7 @@ function PostEditor({
|
|||||||
follow: isEmpty(post?.following) ? true : post?.following,
|
follow: isEmpty(post?.following) ? true : post?.following,
|
||||||
anonymous: allowAnonymous ? false : undefined,
|
anonymous: allowAnonymous ? false : undefined,
|
||||||
anonymousToPeers: allowAnonymousToPeers ? false : undefined,
|
anonymousToPeers: allowAnonymousToPeers ? false : undefined,
|
||||||
editReasonCode: post?.lastEdit?.reasonCode || (userIsStaff ? 'violates-guidelines' : ''),
|
editReasonCode: post?.lastEdit?.reasonCode || '',
|
||||||
cohort: post?.cohort || 'default',
|
cohort: post?.cohort || 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,7 +146,6 @@ function PostEditor({
|
|||||||
topicId,
|
topicId,
|
||||||
postId,
|
postId,
|
||||||
learnerUsername: post?.author,
|
learnerUsername: post?.author,
|
||||||
category,
|
|
||||||
})(location);
|
})(location);
|
||||||
history.push(newLocation);
|
history.push(newLocation);
|
||||||
}
|
}
|
||||||
@@ -194,25 +189,16 @@ function PostEditor({
|
|||||||
dispatch(fetchCourseCohorts(courseId));
|
dispatch(fetchCourseCohorts(courseId));
|
||||||
}
|
}
|
||||||
if (editExisting) {
|
if (editExisting) {
|
||||||
dispatchSubmit(fetchThread(postId, courseId));
|
dispatch(fetchThread(postId));
|
||||||
}
|
}
|
||||||
}, [courseId, editExisting]);
|
}, [courseId, editExisting]);
|
||||||
|
|
||||||
if (editExisting && !post) {
|
if (editExisting && !post) {
|
||||||
if (submitting) {
|
return (
|
||||||
return (
|
<div className="m-4 card p-4 align-items-center">
|
||||||
<div className="m-4 card p-4 align-items-center">
|
<Spinner animation="border" variant="primary" />
|
||||||
<Spinner animation="border" variant="primary" />
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!submitting) {
|
|
||||||
return (
|
|
||||||
<EmptyPage
|
|
||||||
title={intl.formatMessage(messages.noThreadFound)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
@@ -296,20 +282,15 @@ function PostEditor({
|
|||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
aria-describedby="topicAreaInput"
|
aria-describedby="topicAreaInput"
|
||||||
floatingLabel={intl.formatMessage(messages.topicArea)}
|
floatingLabel={intl.formatMessage(messages.topicArea)}
|
||||||
disabled={inContext}
|
|
||||||
>
|
>
|
||||||
{nonCoursewareTopics.map(topic => (
|
{nonCoursewareTopics.map(topic => (
|
||||||
<option
|
<option key={topic.id} value={topic.id}>{topic.name}</option>
|
||||||
key={topic.id}
|
|
||||||
value={topic.id}
|
|
||||||
>{topic.name || intl.formatMessage(messages.unnamedSubTopics)}
|
|
||||||
</option>
|
|
||||||
))}
|
))}
|
||||||
{coursewareTopics.map(categoryObj => (
|
{coursewareTopics.map(category => (
|
||||||
<optgroup label={categoryObj.name || intl.formatMessage(messages.unnamedTopics)} key={categoryObj.id}>
|
<optgroup label={category.name} key={category.id}>
|
||||||
{categoryObj.topics.map(subtopic => (
|
{category.topics.map(subtopic => (
|
||||||
<option key={subtopic.id} value={subtopic.id}>
|
<option key={subtopic.id} value={subtopic.id}>
|
||||||
{subtopic.name || intl.formatMessage(messages.unnamedSubTopics)}
|
{subtopic.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
@@ -369,7 +350,7 @@ function PostEditor({
|
|||||||
name="editReasonCode"
|
name="editReasonCode"
|
||||||
className="m-0"
|
className="m-0"
|
||||||
as="select"
|
as="select"
|
||||||
value={values.editReasonCode}
|
value={userIsStaff ? 'violates-guidelines' : values.editReasonCode}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
aria-describedby="editReasonCodeInput"
|
aria-describedby="editReasonCodeInput"
|
||||||
@@ -458,6 +439,7 @@ function PostEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
PostEditor.propTypes = {
|
PostEditor.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
editExisting: PropTypes.bool,
|
editExisting: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -465,4 +447,4 @@ PostEditor.defaultProps = {
|
|||||||
editExisting: false,
|
editExisting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PostEditor;
|
export default injectIntl(PostEditor);
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ import { initializeMockApp } from '@edx/frontend-platform';
|
|||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
import { getApiBaseUrl, Routes } from '../../../data/constants';
|
import { API_BASE_URL, Routes } from '../../../data/constants';
|
||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
import { getCohortsApiUrl } from '../../cohorts/data/api';
|
import { getCohortsApiUrl } from '../../cohorts/data/api';
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import { fetchCourseTopics } from '../../topics/data/thunks';
|
import { fetchCourseTopics } from '../../topics/data/thunks';
|
||||||
import { getThreadsApiUrl } from '../data/api';
|
import { threadsApiUrl } from '../data/api';
|
||||||
import { fetchThread } from '../data/thunks';
|
import { fetchThread } from '../data/thunks';
|
||||||
import { PostEditor } from '../index';
|
import { PostEditor } from '../index';
|
||||||
|
|
||||||
@@ -28,8 +27,7 @@ import '../../topics/data/__factories__';
|
|||||||
import '../data/__factories__';
|
import '../data/__factories__';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
|
const topicsApiUrl = `${API_BASE_URL}/api/discussion/v1/course_topics/${courseId}`;
|
||||||
const threadsApiUrl = getThreadsApiUrl();
|
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
@@ -38,15 +36,11 @@ async function renderComponent(editExisting = false, location = `/${courseId}/po
|
|||||||
await render(
|
await render(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<AppProvider store={store}>
|
<AppProvider store={store}>
|
||||||
<DiscussionContext.Provider
|
<MemoryRouter initialEntries={[location]}>
|
||||||
value={{ courseId, category: null }}
|
<Route path={path}>
|
||||||
>
|
<PostEditor editExisting={editExisting} />
|
||||||
<MemoryRouter initialEntries={[location]}>
|
</Route>
|
||||||
<Route path={path}>
|
</MemoryRouter>
|
||||||
<PostEditor editExisting={editExisting} />
|
|
||||||
</Route>
|
|
||||||
</MemoryRouter>
|
|
||||||
</DiscussionContext.Provider>
|
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -116,26 +116,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Show Preview',
|
defaultMessage: 'Show Preview',
|
||||||
description: 'show preview button text to allow user to see their post content.',
|
description: 'show preview button text to allow user to see their post content.',
|
||||||
},
|
},
|
||||||
actionsAlt: {
|
|
||||||
id: 'discussions.actions.label',
|
|
||||||
defaultMessage: 'Actions menu',
|
|
||||||
description: 'Button to see actions for a post or comment',
|
|
||||||
},
|
|
||||||
unnamedTopics: {
|
|
||||||
id: 'discussions.topic.noName.label',
|
|
||||||
defaultMessage: 'Unnamed category',
|
|
||||||
description: 'display string for topics with missing names',
|
|
||||||
},
|
|
||||||
unnamedSubTopics: {
|
|
||||||
id: 'discussions.subtopic.noName.label',
|
|
||||||
defaultMessage: 'Unnamed subcategory',
|
|
||||||
description: 'display string for topics with missing names',
|
|
||||||
},
|
|
||||||
noThreadFound: {
|
|
||||||
id: 'discussion.thread.notFound',
|
|
||||||
defaultMessage: 'Thread not found',
|
|
||||||
description: 'message to show on screen if the request thread is not found in course',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
useContext, useEffect, useMemo, useState,
|
|
||||||
} from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -20,7 +18,6 @@ import {
|
|||||||
} from '../../../data/constants';
|
} from '../../../data/constants';
|
||||||
import { selectCourseCohorts } from '../../cohorts/data/selectors';
|
import { selectCourseCohorts } from '../../cohorts/data/selectors';
|
||||||
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
|
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
|
||||||
import { DiscussionContext } from '../../common/context';
|
|
||||||
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
||||||
import {
|
import {
|
||||||
setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter,
|
setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter,
|
||||||
@@ -28,7 +25,7 @@ import {
|
|||||||
import { selectThreadFilters, selectThreadSorting } from '../data/selectors';
|
import { selectThreadFilters, selectThreadSorting } from '../data/selectors';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const ActionItem = ({
|
const ActionItem = ({
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
@@ -63,7 +60,6 @@ function PostFilterBar({
|
|||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { courseId } = useParams();
|
const { courseId } = useParams();
|
||||||
const { page } = useContext(DiscussionContext);
|
|
||||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||||
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
||||||
const currentSorting = useSelector(selectThreadSorting());
|
const currentSorting = useSelector(selectThreadSorting());
|
||||||
@@ -99,10 +95,6 @@ function PostFilterBar({
|
|||||||
// You can't filter discussions by unanswered so switch type to questions
|
// You can't filter discussions by unanswered so switch type to questions
|
||||||
dispatch(setPostsTypeFilter(ThreadType.QUESTION));
|
dispatch(setPostsTypeFilter(ThreadType.QUESTION));
|
||||||
}
|
}
|
||||||
if (value === PostsStatusFilter.UNRESPONDED && currentType !== ThreadType.DISCUSSION) {
|
|
||||||
// You can't filter questions by not responded so switch type to discussion
|
|
||||||
dispatch(setPostsTypeFilter(ThreadType.DISCUSSION));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (name === 'sort') {
|
if (name === 'sort') {
|
||||||
dispatch(setSortedBy(value));
|
dispatch(setSortedBy(value));
|
||||||
@@ -188,14 +180,12 @@ function PostFilterBar({
|
|||||||
value={PostsStatusFilter.UNREAD}
|
value={PostsStatusFilter.UNREAD}
|
||||||
selected={currentFilters.status}
|
selected={currentFilters.status}
|
||||||
/>
|
/>
|
||||||
{page !== 'my-posts' && (
|
<ActionItem
|
||||||
<ActionItem
|
id="status-following"
|
||||||
id="status-following"
|
label={intl.formatMessage(messages.filterFollowing)}
|
||||||
label={intl.formatMessage(messages.filterFollowing)}
|
value={PostsStatusFilter.FOLLOWING}
|
||||||
value={PostsStatusFilter.FOLLOWING}
|
selected={currentFilters.status}
|
||||||
selected={currentFilters.status}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(userHasModerationPrivileges || userIsGroupTa) && (
|
{(userHasModerationPrivileges || userIsGroupTa) && (
|
||||||
<ActionItem
|
<ActionItem
|
||||||
id="status-reported"
|
id="status-reported"
|
||||||
@@ -210,12 +200,6 @@ function PostFilterBar({
|
|||||||
value={PostsStatusFilter.UNANSWERED}
|
value={PostsStatusFilter.UNANSWERED}
|
||||||
selected={currentFilters.status}
|
selected={currentFilters.status}
|
||||||
/>
|
/>
|
||||||
<ActionItem
|
|
||||||
id="status-unresponded"
|
|
||||||
label={intl.formatMessage(messages.filterUnresponded)}
|
|
||||||
value={PostsStatusFilter.UNRESPONDED}
|
|
||||||
selected={currentFilters.status}
|
|
||||||
/>
|
|
||||||
</Form.RadioSet>
|
</Form.RadioSet>
|
||||||
<Form.RadioSet
|
<Form.RadioSet
|
||||||
name="sort"
|
name="sort"
|
||||||
|
|||||||
@@ -46,11 +46,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Unanswered',
|
defaultMessage: 'Unanswered',
|
||||||
description: 'Option in dropdown to filter to unanswered posts',
|
description: 'Option in dropdown to filter to unanswered posts',
|
||||||
},
|
},
|
||||||
filterUnresponded: {
|
|
||||||
id: 'discussions.posts.status.filter.unresponded',
|
|
||||||
defaultMessage: 'Not responded',
|
|
||||||
description: 'Option in dropdown to filter to unresponded posts',
|
|
||||||
},
|
|
||||||
myPosts: {
|
myPosts: {
|
||||||
id: 'discussions.posts.filter.myPosts',
|
id: 'discussions.posts.filter.myPosts',
|
||||||
defaultMessage: 'My posts',
|
defaultMessage: 'My posts',
|
||||||
@@ -98,7 +93,6 @@ const messages = defineMessages({
|
|||||||
statusFollowing {followed}
|
statusFollowing {followed}
|
||||||
statusReported {reported}
|
statusReported {reported}
|
||||||
statusUnanswered {unanswered}
|
statusUnanswered {unanswered}
|
||||||
statusUnresponded {unresponded}
|
|
||||||
other {{status}}
|
other {{status}}
|
||||||
} {type, select,
|
} {type, select,
|
||||||
discussion {discussions}
|
discussion {discussions}
|
||||||
@@ -109,10 +103,10 @@ const messages = defineMessages({
|
|||||||
all {}
|
all {}
|
||||||
group {in {cohort}}
|
group {in {cohort}}
|
||||||
other {{cohortType}}
|
other {{cohortType}}
|
||||||
} sorted by {sort, select,
|
} by {sort, select,
|
||||||
lastActivityAt {recent activity}
|
lastActivityAt {recent activity}
|
||||||
commentCount {most activity}
|
commentCount {most activity}
|
||||||
voteCount {most likes}
|
voteCount {most votes}
|
||||||
other {{sort}}
|
other {{sort}}
|
||||||
}`,
|
}`,
|
||||||
description: 'Status message showing current sorting and filtering status',
|
description: 'Status message showing current sorting and filtering status',
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ function ClosePostReasonModal({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
hasCloseButton={false}
|
hasCloseButton={false}
|
||||||
zIndex={5000}
|
isFullscreenOnMobile
|
||||||
|
isFullscreenScroll
|
||||||
>
|
>
|
||||||
<ModalDialog.Header>
|
<ModalDialog.Header>
|
||||||
<ModalDialog.Title>
|
<ModalDialog.Title>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, useToggle } from '@edx/paragon';
|
import { Hyperlink, useToggle } from '@edx/paragon';
|
||||||
|
|
||||||
@@ -36,9 +35,6 @@ function Post({
|
|||||||
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
|
||||||
const [isDeleting, showDeleteConfirmation, hideDeleteConfirmation] = useToggle(false);
|
const [isDeleting, showDeleteConfirmation, hideDeleteConfirmation] = useToggle(false);
|
||||||
const [isClosing, showClosePostModal, hideClosePostModal] = useToggle(false);
|
const [isClosing, showClosePostModal, hideClosePostModal] = useToggle(false);
|
||||||
|
|
||||||
const postURL = new URL(`${getConfig().PUBLIC_PATH}${courseId}/posts/${post.id}`, window.location.origin);
|
|
||||||
|
|
||||||
const actionHandlers = {
|
const actionHandlers = {
|
||||||
[ContentActions.EDIT_CONTENT]: () => history.push({
|
[ContentActions.EDIT_CONTENT]: () => history.push({
|
||||||
...location,
|
...location,
|
||||||
@@ -54,7 +50,7 @@ function Post({
|
|||||||
dispatch(updateExistingThread(post.id, { closed: true }));
|
dispatch(updateExistingThread(post.id, { closed: true }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ContentActions.COPY_LINK]: () => { navigator.clipboard.writeText(postURL.href); },
|
[ContentActions.COPY_LINK]: () => { navigator.clipboard.writeText(`${window.location.origin}/${courseId}/posts/${post.id}`); },
|
||||||
[ContentActions.PIN]: () => dispatch(updateExistingThread(post.id, { pinned: !post.pinned })),
|
[ContentActions.PIN]: () => dispatch(updateExistingThread(post.id, { pinned: !post.pinned })),
|
||||||
[ContentActions.REPORT]: () => dispatch(updateExistingThread(post.id, { flagged: !post.abuseFlagged })),
|
[ContentActions.REPORT]: () => dispatch(updateExistingThread(post.id, { flagged: !post.abuseFlagged })),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ function PostFooter({
|
|||||||
</span>
|
</span>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
<span
|
<span
|
||||||
className="text-gray-700 mx-1.5 font-weight-500"
|
className="text-light-700 mx-1.5 font-weight-500"
|
||||||
style={{ fontSize: '16px' }}
|
style={{ fontSize: '16px' }}
|
||||||
>
|
>
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<span title={post.createdAt} className="text-gray-700">
|
<span title={post.createdAt} className="text-gray-500">
|
||||||
{timeago.format(post.createdAt, 'time-locale')}
|
{timeago.format(post.createdAt, 'time-locale')}
|
||||||
</span>
|
</span>
|
||||||
{!preview && post.closed
|
{!preview && post.closed
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function PostHeader({
|
|||||||
</div>
|
</div>
|
||||||
{!preview
|
{!preview
|
||||||
&& (
|
&& (
|
||||||
<div className="ml-auto d-flex">
|
<div className="ml-auto d-flex align-items-center">
|
||||||
<ActionsDropdown commentOrPost={post} actionHandlers={actionHandlers} />
|
<ActionsDropdown commentOrPost={post} actionHandlers={actionHandlers} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/* eslint-disable react/no-unknown-property */
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Badge, Icon, Truncate } from '@edx/paragon';
|
import { Badge, Icon } from '@edx/paragon';
|
||||||
import { CheckCircle } from '@edx/paragon/icons';
|
|
||||||
|
|
||||||
import { PushPin } from '../../../components/icons';
|
import { PushPin } from '../../../components/icons';
|
||||||
import { AvatarOutlineAndLabelColors, Routes, ThreadType } from '../../../data/constants';
|
import { AvatarOutlineAndLabelColors, Routes, ThreadType } from '../../../data/constants';
|
||||||
import AuthorLabel from '../../common/AuthorLabel';
|
import AuthorLabel from '../../common/AuthorLabel';
|
||||||
import { DiscussionContext } from '../../common/context';
|
import { DiscussionContext } from '../../common/context';
|
||||||
|
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
|
||||||
import { discussionsPath, isPostPreviewAvailable } from '../../utils';
|
import { discussionsPath, isPostPreviewAvailable } from '../../utils';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import PostFooter from './PostFooter';
|
import PostFooter from './PostFooter';
|
||||||
@@ -22,10 +22,11 @@ import { postShape } from './proptypes';
|
|||||||
function PostLink({
|
function PostLink({
|
||||||
post,
|
post,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
intl,
|
||||||
|
learnerTab,
|
||||||
showDivider,
|
showDivider,
|
||||||
idx,
|
idx,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
|
||||||
const {
|
const {
|
||||||
page,
|
page,
|
||||||
postId,
|
postId,
|
||||||
@@ -41,9 +42,12 @@ function PostLink({
|
|||||||
category,
|
category,
|
||||||
learnerUsername,
|
learnerUsername,
|
||||||
});
|
});
|
||||||
|
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||||
|
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
||||||
const showAnsweredBadge = post.hasEndorsed && post.type === ThreadType.QUESTION;
|
const showAnsweredBadge = post.hasEndorsed && post.type === ThreadType.QUESTION;
|
||||||
const authorLabelColor = AvatarOutlineAndLabelColors[post.authorLabel];
|
const authorLabelColor = AvatarOutlineAndLabelColors[post.authorLabel];
|
||||||
const canSeeReportedBadge = post.abuseFlagged || post.abuseFlaggedCount;
|
const postReported = post.abuseFlagged || post.abuseFlaggedCount;
|
||||||
|
const canSeeReportedBadge = postReported && (userHasModerationPrivileges || userIsGroupTa);
|
||||||
const read = post.read || (!post.read && post.commentCount !== post.unreadCommentCount);
|
const read = post.read || (!post.read && post.commentCount !== post.unreadCommentCount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -56,10 +60,10 @@ function PostLink({
|
|||||||
}
|
}
|
||||||
to={linkUrl}
|
to={linkUrl}
|
||||||
onClick={() => isSelected(post.id)}
|
onClick={() => isSelected(post.id)}
|
||||||
style={{ lineHeight: '22px' }}
|
style={{ lineHeight: '21px' }}
|
||||||
aria-current={isSelected(post.id) ? 'page' : undefined}
|
aria-current={isSelected(post.id) ? 'page' : undefined}
|
||||||
role="option"
|
role="option"
|
||||||
tabIndex={(isSelected(post.id) || idx === 0) ? 0 : -1}
|
tabindex={(isSelected(post.id) || idx === 0) ? 0 : -1}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@@ -75,28 +79,20 @@ function PostLink({
|
|||||||
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
|
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
|
||||||
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
||||||
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
|
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
|
||||||
<Truncate lines={1} className="mr-1.5" whiteSpace>
|
<div
|
||||||
<span
|
className={
|
||||||
class={
|
classNames('text-truncate font-weight-500 font-size-14 text-primary-500 font-style-normal font-family-inter',
|
||||||
classNames('font-weight-500 font-size-14 text-primary-500 font-style-normal font-family-inter align-bottom',
|
{ 'font-weight-bolder': !read })
|
||||||
{ 'font-weight-bolder': !read })
|
}
|
||||||
}
|
>
|
||||||
>
|
{post.title}
|
||||||
{post.title}
|
</div>
|
||||||
</span>
|
|
||||||
<span class="align-bottom"> </span>
|
|
||||||
<span
|
|
||||||
class="text-gray-700 font-weight-normal font-size-14 font-style-normal font-family-inter align-bottom"
|
|
||||||
>
|
|
||||||
{isPostPreviewAvailable(post.previewBody)
|
|
||||||
? post.previewBody
|
|
||||||
: intl.formatMessage(messages.postWithoutPreview)}
|
|
||||||
</span>
|
|
||||||
</Truncate>
|
|
||||||
{showAnsweredBadge && (
|
{showAnsweredBadge && (
|
||||||
<Icon src={CheckCircle} className="text-success font-weight-500 ml-auto badge-padding" data-testid="check-icon">
|
<Badge variant="success" className="font-weight-500 ml-auto badge-padding">
|
||||||
<span className="sr-only">{' '}answered</span>
|
{intl.formatMessage(messages.answered)}
|
||||||
</Icon>
|
<span className="sr-only">{' '}answered</span>
|
||||||
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canSeeReportedBadge && (
|
{canSeeReportedBadge && (
|
||||||
@@ -122,8 +118,19 @@ function PostLink({
|
|||||||
author={post.author || intl.formatMessage(messages.anonymous)}
|
author={post.author || intl.formatMessage(messages.anonymous)}
|
||||||
authorLabel={post.authorLabel}
|
authorLabel={post.authorLabel}
|
||||||
labelColor={authorLabelColor && `text-${authorLabelColor}`}
|
labelColor={authorLabelColor && `text-${authorLabelColor}`}
|
||||||
|
linkToProfile={!learnerTab && post.author}
|
||||||
/>
|
/>
|
||||||
<PostFooter post={post} preview intl={intl} showNewCountLabel={read} />
|
<div
|
||||||
|
className="text-truncate text-primary-500 font-weight-normal font-size-14 font-style-normal font-family-inter"
|
||||||
|
style={{ maxHeight: '1.5rem' }}
|
||||||
|
>
|
||||||
|
{isPostPreviewAvailable(post.previewBody)
|
||||||
|
? post.previewBody
|
||||||
|
: intl.formatMessage(messages.postWithoutPreview)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<PostFooter post={post} preview intl={intl} showNewCountLabel={read} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!showDivider && post.pinned && <div className="pt-1 bg-light-500 border-top border-light-700" />}
|
{!showDivider && post.pinned && <div className="pt-1 bg-light-500 border-top border-light-700" />}
|
||||||
@@ -135,13 +142,16 @@ function PostLink({
|
|||||||
PostLink.propTypes = {
|
PostLink.propTypes = {
|
||||||
post: postShape.isRequired,
|
post: postShape.isRequired,
|
||||||
isSelected: PropTypes.func.isRequired,
|
isSelected: PropTypes.func.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
learnerTab: PropTypes.bool,
|
||||||
showDivider: PropTypes.bool,
|
showDivider: PropTypes.bool,
|
||||||
idx: PropTypes.number,
|
idx: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
PostLink.defaultProps = {
|
PostLink.defaultProps = {
|
||||||
|
learnerTab: false,
|
||||||
showDivider: true,
|
showDivider: true,
|
||||||
idx: -1,
|
idx: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PostLink;
|
export default injectIntl(PostLink);
|
||||||
|
|||||||
@@ -11,21 +11,20 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
|||||||
import { initializeStore } from '../../../store';
|
import { initializeStore } from '../../../store';
|
||||||
import { executeThunk } from '../../../test-utils';
|
import { executeThunk } from '../../../test-utils';
|
||||||
import { DiscussionContext } from '../../common/context';
|
import { DiscussionContext } from '../../common/context';
|
||||||
import { getCourseConfigApiUrl } from '../../data/api';
|
import { courseConfigApiUrl } from '../../data/api';
|
||||||
import { fetchCourseConfig } from '../../data/thunks';
|
import { fetchCourseConfig } from '../../data/thunks';
|
||||||
import PostLink from './PostLink';
|
import PostLink from './PostLink';
|
||||||
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
const courseConfigApiUrl = getCourseConfigApiUrl();
|
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
function renderComponent(post) {
|
function renderComponent(post, learnerTab = false) {
|
||||||
return render(
|
return render(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<AppProvider store={store}>
|
<AppProvider store={store}>
|
||||||
<DiscussionContext.Provider value={{ courseId }}>
|
<DiscussionContext.Provider value={{ courseId }}>
|
||||||
<PostLink post={post} key={post.id} isSelected={() => true} />
|
<PostLink post={post} key={post.id} isSelected={() => true} learnerTab={learnerTab} />
|
||||||
</DiscussionContext.Provider>
|
</DiscussionContext.Provider>
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
@@ -104,10 +103,27 @@ describe('Post username', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
'anonymous',
|
true,
|
||||||
'test-user',
|
false,
|
||||||
])('is not clickable for %s user', async (user) => {
|
])('is a clickable link %s', async (leanerTab) => {
|
||||||
renderComponent({ ...mockPost, author: user });
|
renderComponent(mockPost, leanerTab);
|
||||||
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
|
|
||||||
|
if (leanerTab) {
|
||||||
|
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
|
||||||
|
} else {
|
||||||
|
expect(screen.queryByTestId('learner-posts-link')).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
])('is only clickable if user is not anonymous', async (isAnonymous) => {
|
||||||
|
renderComponent({ ...mockPost, author: isAnonymous ? null : 'test-user' });
|
||||||
|
if (isAnonymous) {
|
||||||
|
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
|
||||||
|
} else {
|
||||||
|
expect(screen.queryByTestId('learner-posts-link')).toBeInTheDocument();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,19 +22,15 @@ function CourseWideTopics() {
|
|||||||
const { category } = useParams();
|
const { category } = useParams();
|
||||||
const filter = useSelector(selectTopicFilter);
|
const filter = useSelector(selectTopicFilter);
|
||||||
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
|
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
|
||||||
const filteredNonCoursewareTopics = nonCoursewareTopics.filter(item => (filter
|
return (nonCoursewareTopics && category === undefined) && nonCoursewareTopics.filter(
|
||||||
? item.name.toLowerCase().includes(filter)
|
item => (filter
|
||||||
: true
|
? item.name.toLowerCase()
|
||||||
));
|
.includes(filter)
|
||||||
|
: true
|
||||||
return (nonCoursewareTopics && category === undefined)
|
),
|
||||||
&& filteredNonCoursewareTopics.map((topic, index) => (
|
)
|
||||||
<Topic
|
.map(topic => (
|
||||||
topic={topic}
|
<Topic topic={topic} key={topic.id} />
|
||||||
key={topic.id}
|
|
||||||
index={index}
|
|
||||||
showDivider={(filteredNonCoursewareTopics.length - 1) !== index}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,20 +76,6 @@ function TopicsView() {
|
|||||||
const { courseId } = useContext(DiscussionContext);
|
const { courseId } = useContext(DiscussionContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
|
||||||
const { key } = event;
|
|
||||||
if (key !== 'ArrowDown' && key !== 'ArrowUp') { return; }
|
|
||||||
const option = event.target;
|
|
||||||
|
|
||||||
let selectedOption;
|
|
||||||
if (key === 'ArrowDown') { selectedOption = option.nextElementSibling; }
|
|
||||||
if (key === 'ArrowUp') { selectedOption = option.previousElementSibling; }
|
|
||||||
|
|
||||||
if (selectedOption) {
|
|
||||||
selectedOption.focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't load till the provider information is available
|
// Don't load till the provider information is available
|
||||||
if (provider) {
|
if (provider) {
|
||||||
@@ -107,19 +89,19 @@ function TopicsView() {
|
|||||||
}, [topicFilter]);
|
}, [topicFilter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="discussion-topics d-flex flex-column h-100" data-testid="topics-view">
|
<div className="d-flex flex-column flex-fill">
|
||||||
{topicFilter && (
|
<div
|
||||||
<SearchInfo
|
className="discussion-topics card"
|
||||||
text={topicFilter}
|
data-testid="topics-view"
|
||||||
count={filteredTopicsCount}
|
>
|
||||||
loadingStatus={loadingStatus}
|
{
|
||||||
onClear={() => dispatch(setFilter(''))}
|
topicFilter && <SearchInfo text={topicFilter} count={filteredTopicsCount} loadingStatus={loadingStatus} onClear={() => dispatch(setFilter(''))} />
|
||||||
/>
|
}
|
||||||
)}
|
<div className="list-group list-group-flush">
|
||||||
<div className="list-group list-group-flush flex-fill" role="list" onKeyDown={e => handleKeyDown(e)}>
|
<CourseWideTopics />
|
||||||
<CourseWideTopics />
|
{provider === DiscussionProvider.OPEN_EDX && <CoursewareTopics />}
|
||||||
{provider === DiscussionProvider.OPEN_EDX && <CoursewareTopics />}
|
{provider === DiscussionProvider.LEGACY && <LegacyCoursewareTopics />}
|
||||||
{provider === DiscussionProvider.LEGACY && <LegacyCoursewareTopics />}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
filteredTopicsCount === 0
|
filteredTopicsCount === 0
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
import { getBlocksAPIResponse } from '../../data/__factories__';
|
import { getBlocksAPIResponse } from '../../data/__factories__';
|
||||||
import { getBlocksAPIURL } from '../../data/api';
|
import { blocksAPIURL } from '../../data/api';
|
||||||
import { DiscussionProvider, getApiBaseUrl } from '../../data/constants';
|
import { API_BASE_URL, DiscussionProvider } from '../../data/constants';
|
||||||
import { selectSequences } from '../../data/selectors';
|
import { selectSequences } from '../../data/selectors';
|
||||||
import { fetchCourseBlocks } from '../../data/thunks';
|
import { fetchCourseBlocks } from '../../data/thunks';
|
||||||
import { initializeStore } from '../../store';
|
import { initializeStore } from '../../store';
|
||||||
@@ -26,8 +26,8 @@ import './data/__factories__';
|
|||||||
|
|
||||||
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
const courseId = 'course-v1:edX+DemoX+Demo_Course';
|
||||||
|
|
||||||
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
|
const topicsApiUrl = `${API_BASE_URL}/api/discussion/v1/course_topics/${courseId}`;
|
||||||
const topicsv2ApiUrl = `${getApiBaseUrl()}/api/discussion/v2/course_topics/${courseId}`;
|
const topicsv2ApiUrl = `${API_BASE_URL}/api/discussion/v2/course_topics/${courseId}`;
|
||||||
let store;
|
let store;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
let lastLocation;
|
let lastLocation;
|
||||||
@@ -113,7 +113,7 @@ describe('TopicsView', () => {
|
|||||||
axiosMock
|
axiosMock
|
||||||
.onGet(topicsv2ApiUrl)
|
.onGet(topicsv2ApiUrl)
|
||||||
.reply(200, data);
|
.reply(200, data);
|
||||||
axiosMock.onGet(getBlocksAPIURL())
|
axiosMock.onGet(blocksAPIURL)
|
||||||
.reply(200, getBlocksAPIResponse(true));
|
.reply(200, getBlocksAPIResponse(true));
|
||||||
axiosMock.onAny().networkError();
|
axiosMock.onAny().networkError();
|
||||||
await executeThunk(fetchCourseBlocks(courseId, 'abc123'), store.dispatch, store.getState);
|
await executeThunk(fetchCourseBlocks(courseId, 'abc123'), store.dispatch, store.getState);
|
||||||
@@ -159,7 +159,7 @@ describe('TopicsView', () => {
|
|||||||
renderComponent();
|
renderComponent();
|
||||||
const archivedTopicGroup = screen.queryAllByTestId('topic-group').pop();
|
const archivedTopicGroup = screen.queryAllByTestId('topic-group').pop();
|
||||||
expect(archivedTopicGroup).toHaveTextContent(/archived/i);
|
expect(archivedTopicGroup).toHaveTextContent(/archived/i);
|
||||||
const archivedTopicLinks = within(archivedTopicGroup).queryAllByRole('option');
|
const archivedTopicLinks = within(archivedTopicGroup).queryAllByRole('link');
|
||||||
expect(archivedTopicLinks).toHaveLength(2);
|
expect(archivedTopicLinks).toHaveLength(2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
import { getApiBaseUrl } from '../../../data/constants';
|
import { API_BASE_URL } from '../../../data/constants';
|
||||||
|
|
||||||
export async function getCourseTopics(courseId, topicIds) {
|
export async function getCourseTopics(courseId, topicIds) {
|
||||||
const url = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
|
const url = `${API_BASE_URL}/api/discussion/v1/course_topics/${courseId}`;
|
||||||
const params = {};
|
const params = {};
|
||||||
if (topicIds) {
|
if (topicIds) {
|
||||||
params.topic_id = topicIds.join(',');
|
params.topic_id = topicIds.join(',');
|
||||||
@@ -15,7 +15,7 @@ export async function getCourseTopics(courseId, topicIds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCourseTopicsV2(courseId, topicIds) {
|
export async function getCourseTopicsV2(courseId, topicIds) {
|
||||||
const url = `${getApiBaseUrl()}/api/discussion/v2/course_topics/${courseId}`;
|
const url = `${API_BASE_URL}/api/discussion/v2/course_topics/${courseId}`;
|
||||||
const params = {};
|
const params = {};
|
||||||
if (topicIds) {
|
if (topicIds) {
|
||||||
params.topic_id = topicIds.join(',');
|
params.topic_id = topicIds.join(',');
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const selectCoursewareTopics = createSelector(
|
|||||||
: sequences.map(sequence => ({
|
: sequences.map(sequence => ({
|
||||||
id: sequence.id,
|
id: sequence.id,
|
||||||
name: sequence.displayName,
|
name: sequence.displayName,
|
||||||
topics: sequence.topics.map(topicId => ({ id: topicId, name: topics[topicId]?.name || 'unnamed' })),
|
topics: sequence.topics.map(topicId => ({ id: topicId, name: topics[topicId].name })),
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -45,7 +45,6 @@ export const selectNonCoursewareTopics = state => state.topics.nonCoursewareIds.
|
|||||||
|
|
||||||
export const selectTopic = topicId => state => state.topics.topics[topicId];
|
export const selectTopic = topicId => state => state.topics.topics[topicId];
|
||||||
|
|
||||||
export const selectTopicsById = topicIds => state => topicIds.map(topicId => state.topics.topics[topicId])
|
export const selectTopicsById = topicIds => state => topicIds.map(topicId => state.topics.topics[topicId]);
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
export const topicsLoadingStatus = state => state.topics.status;
|
export const topicsLoadingStatus = state => state.topics.status;
|
||||||
|
|||||||
@@ -1,34 +1,6 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
discussions: {
|
|
||||||
id: 'discussions.topics.discussions',
|
|
||||||
defaultMessage: `{count, plural,
|
|
||||||
=0 {Discussion}
|
|
||||||
one {# Discussion}
|
|
||||||
other {# Discussions}
|
|
||||||
}`,
|
|
||||||
description: 'Display tooltip text used to indicate how many posts type are discussion',
|
|
||||||
},
|
|
||||||
questions: {
|
|
||||||
id: 'discussions.topics.questions',
|
|
||||||
defaultMessage: `{count, plural,
|
|
||||||
=0 {Question}
|
|
||||||
one {# Question}
|
|
||||||
other {# Questions}
|
|
||||||
}`,
|
|
||||||
description: 'Display tooltip text used to indicate how many posts type are questions',
|
|
||||||
},
|
|
||||||
reported: {
|
|
||||||
id: 'discussions.topics.reported',
|
|
||||||
defaultMessage: '{reported} reported',
|
|
||||||
description: 'Display tooltip text used to indicate how many posts are reported',
|
|
||||||
},
|
|
||||||
previouslyReported: {
|
|
||||||
id: 'discussions.topics.previouslyReported',
|
|
||||||
defaultMessage: '{previouslyReported} previously reported',
|
|
||||||
description: 'Display tooltip text used to indicate how many posts are previously reported',
|
|
||||||
},
|
|
||||||
sortedBy: {
|
sortedBy: {
|
||||||
id: 'discussions.topics.sort.message',
|
id: 'discussions.topics.sort.message',
|
||||||
defaultMessage: 'Sorted by {sortBy}',
|
defaultMessage: 'Sorted by {sortBy}',
|
||||||
@@ -59,16 +31,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Archived',
|
defaultMessage: 'Archived',
|
||||||
description: 'Heading for displaying topics that are archived.',
|
description: 'Heading for displaying topics that are archived.',
|
||||||
},
|
},
|
||||||
unnamedTopicCategories: {
|
|
||||||
id: 'discussions.topics.unnamed.label',
|
|
||||||
defaultMessage: 'Unnamed category',
|
|
||||||
description: 'Text to display in place of topic name if topic name is empty',
|
|
||||||
},
|
|
||||||
unnamedTopicSubCategories: {
|
|
||||||
id: 'discussions.subtopics.unnamed.label',
|
|
||||||
defaultMessage: 'Unnamed subcategory',
|
|
||||||
description: 'Text to display in place of topic name if topic name is empty',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -4,13 +4,10 @@ import PropTypes from 'prop-types';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { Routes } from '../../../data/constants';
|
import { Routes } from '../../../data/constants';
|
||||||
import { DiscussionContext } from '../../common/context';
|
import { DiscussionContext } from '../../common/context';
|
||||||
import { discussionsPath } from '../../utils';
|
import { discussionsPath } from '../../utils';
|
||||||
import { selectTopicFilter } from '../data/selectors';
|
import { selectTopicFilter } from '../data/selectors';
|
||||||
import messages from '../messages';
|
|
||||||
import Topic, { topicShape } from './topic/Topic';
|
import Topic, { topicShape } from './topic/Topic';
|
||||||
|
|
||||||
function TopicGroupBase({
|
function TopicGroupBase({
|
||||||
@@ -18,56 +15,50 @@ function TopicGroupBase({
|
|||||||
groupTitle,
|
groupTitle,
|
||||||
linkToGroup,
|
linkToGroup,
|
||||||
topics,
|
topics,
|
||||||
intl,
|
|
||||||
}) {
|
}) {
|
||||||
const { courseId } = useContext(DiscussionContext);
|
const { courseId } = useContext(DiscussionContext);
|
||||||
const filter = useSelector(selectTopicFilter);
|
const filter = useSelector(selectTopicFilter);
|
||||||
const hasTopics = topics.length > 0;
|
const hasTopics = topics.length > 0;
|
||||||
const matchesFilter = filter
|
const matchesFilter = filter
|
||||||
? groupTitle?.toLowerCase().includes(filter)
|
? groupTitle?.toLowerCase()
|
||||||
|
.includes(filter)
|
||||||
: true;
|
: true;
|
||||||
|
const topicElements = topics.filter(
|
||||||
const filteredTopicElements = topics.filter(
|
topic => (
|
||||||
topic => (filter
|
filter
|
||||||
? (topic.name.toLowerCase().includes(filter) || matchesFilter)
|
? (topic.name.toLowerCase()
|
||||||
: true
|
.includes(filter) || matchesFilter)
|
||||||
|
: true
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
.map(topic => (<Topic topic={topic} key={topic.id} />));
|
||||||
const hasFilteredSubtopics = (filteredTopicElements.length > 0);
|
const hasFilteredSubtopics = (topicElements.length > 0);
|
||||||
if (!hasTopics || (!matchesFilter && !hasFilteredSubtopics)) {
|
if (!hasTopics || (!matchesFilter && !hasFilteredSubtopics)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="discussion-topic-group d-flex flex-column text-primary-500"
|
className="discussion-topic-group d-flex flex-column"
|
||||||
data-category-id={groupId}
|
data-category-id={groupId}
|
||||||
data-testid="topic-group"
|
data-testid="topic-group"
|
||||||
>
|
>
|
||||||
<div className="pt-2.5 px-4 font-weight-bold">
|
{linkToGroup
|
||||||
{linkToGroup && groupId
|
? (
|
||||||
? (
|
<Link
|
||||||
<Link
|
className="list-group-item p-4 text-primary-500"
|
||||||
className="text-decoration-none text-primary-500"
|
to={discussionsPath(Routes.TOPICS.CATEGORY, {
|
||||||
to={discussionsPath(Routes.TOPICS.CATEGORY, {
|
courseId,
|
||||||
courseId,
|
category: groupId,
|
||||||
category: groupId,
|
})}
|
||||||
})}
|
>
|
||||||
>
|
{groupTitle}
|
||||||
{groupTitle}
|
</Link>
|
||||||
</Link>
|
) : (
|
||||||
) : (
|
<span className="list-group-item p-4 text-primary-500">
|
||||||
groupTitle || intl.formatMessage(messages.unnamedTopicCategories)
|
{groupTitle}
|
||||||
)}
|
</span>
|
||||||
</div>
|
)}
|
||||||
{filteredTopicElements.map((topic, index) => (
|
{topicElements}
|
||||||
<Topic
|
|
||||||
topic={topic}
|
|
||||||
key={topic.id}
|
|
||||||
index={index}
|
|
||||||
showDivider={(filteredTopicElements.length - 1) !== index}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -77,11 +68,10 @@ TopicGroupBase.propTypes = {
|
|||||||
groupTitle: PropTypes.string.isRequired,
|
groupTitle: PropTypes.string.isRequired,
|
||||||
topics: PropTypes.arrayOf(topicShape).isRequired,
|
topics: PropTypes.arrayOf(topicShape).isRequired,
|
||||||
linkToGroup: PropTypes.bool,
|
linkToGroup: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TopicGroupBase.defaultProps = {
|
TopicGroupBase.defaultProps = {
|
||||||
linkToGroup: true,
|
linkToGroup: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(TopicGroupBase);
|
export default TopicGroupBase;
|
||||||
|
|||||||
@@ -2,120 +2,65 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { Icon } from '@edx/paragon';
|
||||||
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
|
import { Error as ErrorIcon, Help, PostOutline } from '@edx/paragon/icons';
|
||||||
import { HelpOutline, PostOutline, Report } from '@edx/paragon/icons';
|
|
||||||
|
|
||||||
import { Routes } from '../../../../data/constants';
|
import { Routes } from '../../../../data/constants';
|
||||||
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../../data/selectors';
|
|
||||||
import { discussionsPath } from '../../../utils';
|
import { discussionsPath } from '../../../utils';
|
||||||
import messages from '../../messages';
|
|
||||||
|
|
||||||
function Topic({
|
function Topic({ topic }) {
|
||||||
topic,
|
|
||||||
showDivider,
|
|
||||||
index,
|
|
||||||
intl,
|
|
||||||
}) {
|
|
||||||
const { courseId } = useParams();
|
const { courseId } = useParams();
|
||||||
const topicUrl = discussionsPath(Routes.TOPICS.TOPIC, {
|
const topicUrl = discussionsPath(Routes.TOPICS.TOPIC, {
|
||||||
courseId,
|
courseId,
|
||||||
topicId: topic.id,
|
topicId: topic.id,
|
||||||
});
|
});
|
||||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
const icons = [
|
||||||
const userIsGroupTa = useSelector(selectUserIsGroupTa);
|
{
|
||||||
const { inactiveFlags, activeFlags } = topic;
|
key: 'discussions',
|
||||||
const canSeeReportedStats = (activeFlags || inactiveFlags) && (userHasModerationPrivileges || userIsGroupTa);
|
icon: PostOutline,
|
||||||
const isSelected = (id) => window.location.pathname.includes(id);
|
count: topic.threadCounts?.discussion || 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'questions',
|
||||||
|
icon: Help,
|
||||||
|
count: topic.threadCounts?.question || 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={
|
className="discussion-topic d-flex flex-column list-group-item px-4 py-3 text-primary-500"
|
||||||
classNames('discussion-topic p-0 text-decoration-none text-primary-500', {
|
|
||||||
'border-bottom border-light-400': showDivider,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
data-topic-id={topic.id}
|
data-topic-id={topic.id}
|
||||||
to={topicUrl}
|
to={topicUrl}
|
||||||
onClick={() => isSelected(topic.id)}
|
|
||||||
aria-current={isSelected(topic.id) ? 'page' : undefined}
|
|
||||||
role="option"
|
|
||||||
tabIndex={(isSelected(topic.id) || index === 0) ? 0 : -1}
|
|
||||||
>
|
>
|
||||||
<div className="d-flex flex-row pt-2.5 pb-2 px-4">
|
<div className="topic-name">
|
||||||
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
|
{topic.name}
|
||||||
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
</div>
|
||||||
<div className="topic-name text-truncate">
|
<div className="d-flex mt-3">
|
||||||
{topic.name || intl.formatMessage(messages.unnamedTopicSubCategories)}
|
{
|
||||||
</div>
|
icons.map(({
|
||||||
</div>
|
key,
|
||||||
<div className="d-flex align-items-center mt-2.5" style={{ marginBottom: '2px' }}>
|
icon,
|
||||||
<OverlayTrigger
|
count,
|
||||||
overlay={(
|
}) => (
|
||||||
<Tooltip>
|
<div className="mr-4 d-flex align-items-center" key={key}>
|
||||||
<div className="d-flex flex-column align-items-start">
|
<Icon className="mr-2" src={icon} />
|
||||||
{intl.formatMessage(messages.discussions, {
|
{/* Reserve some space for larger counts */}
|
||||||
count: topic.threadCounts?.discussion || 0,
|
<span>
|
||||||
})}
|
{count}
|
||||||
</div>
|
</span>
|
||||||
</Tooltip>
|
</div>
|
||||||
)}
|
))
|
||||||
>
|
}
|
||||||
<div className="d-flex align-items-center mr-3.5">
|
{topic?.flags && (
|
||||||
<Icon src={PostOutline} className="icon-size mr-2" />
|
<div className="d-flex align-items-center">
|
||||||
{topic.threadCounts?.discussion || 0}
|
<Icon className="mr-2" src={ErrorIcon} />
|
||||||
</div>
|
{topic.flags}
|
||||||
</OverlayTrigger>
|
</div>
|
||||||
<OverlayTrigger
|
)}
|
||||||
overlay={(
|
|
||||||
<Tooltip>
|
|
||||||
<div className="d-flex flex-column align-items-start">
|
|
||||||
{intl.formatMessage(messages.questions, {
|
|
||||||
count: topic.threadCounts?.question || 0,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="d-flex align-items-center mr-3.5">
|
|
||||||
<Icon src={HelpOutline} className="icon-size mr-2" />
|
|
||||||
{topic.threadCounts?.question || 0}
|
|
||||||
</div>
|
|
||||||
</OverlayTrigger>
|
|
||||||
{Boolean(canSeeReportedStats) && (
|
|
||||||
<OverlayTrigger
|
|
||||||
overlay={(
|
|
||||||
<Tooltip>
|
|
||||||
<div className="d-flex flex-column align-items-start">
|
|
||||||
{Boolean(activeFlags) && (
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage(messages.reported, { reported: activeFlags })}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{Boolean(inactiveFlags) && (
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage(messages.previouslyReported, { previouslyReported: inactiveFlags })}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<Icon src={Report} className="icon-size mr-2 text-danger" />
|
|
||||||
{activeFlags}{Boolean(inactiveFlags) && `/${inactiveFlags}`}
|
|
||||||
</div>
|
|
||||||
</OverlayTrigger>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{!showDivider && <div className="divider pt-1 bg-light-500 border-top border-light-700" />}
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -128,15 +73,7 @@ export const topicShape = PropTypes.shape({
|
|||||||
flags: PropTypes.number,
|
flags: PropTypes.number,
|
||||||
});
|
});
|
||||||
Topic.propTypes = {
|
Topic.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
|
||||||
topic: topicShape.isRequired,
|
topic: topicShape.isRequired,
|
||||||
showDivider: PropTypes.bool,
|
|
||||||
index: PropTypes.number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Topic.defaultProps = {
|
export default Topic;
|
||||||
showDivider: true,
|
|
||||||
index: -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(Topic);
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import { generatePath, useRouteMatch } from 'react-router';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import {
|
import {
|
||||||
CheckCircle,
|
Delete, Edit, Pin, QuestionAnswer, Report, VerifiedBadge,
|
||||||
CheckCircleOutline,
|
|
||||||
Delete, Edit, Pin, QuestionAnswer, Report, Verified, VerifiedOutline,
|
|
||||||
} from '@edx/paragon/icons';
|
} from '@edx/paragon/icons';
|
||||||
|
|
||||||
import { InsertLink } from '../components/icons';
|
import { InsertLink } from '../components/icons';
|
||||||
@@ -103,7 +101,7 @@ export const ACTIONS_LIST = [
|
|||||||
{
|
{
|
||||||
id: 'endorse',
|
id: 'endorse',
|
||||||
action: ContentActions.ENDORSE,
|
action: ContentActions.ENDORSE,
|
||||||
icon: VerifiedOutline,
|
icon: VerifiedBadge,
|
||||||
label: messages.endorseAction,
|
label: messages.endorseAction,
|
||||||
conditions: {
|
conditions: {
|
||||||
endorsed: false,
|
endorsed: false,
|
||||||
@@ -113,7 +111,7 @@ export const ACTIONS_LIST = [
|
|||||||
{
|
{
|
||||||
id: 'unendorse',
|
id: 'unendorse',
|
||||||
action: ContentActions.ENDORSE,
|
action: ContentActions.ENDORSE,
|
||||||
icon: Verified,
|
icon: VerifiedBadge,
|
||||||
label: messages.unendorseAction,
|
label: messages.unendorseAction,
|
||||||
conditions: {
|
conditions: {
|
||||||
endorsed: true,
|
endorsed: true,
|
||||||
@@ -123,7 +121,7 @@ export const ACTIONS_LIST = [
|
|||||||
{
|
{
|
||||||
id: 'answer',
|
id: 'answer',
|
||||||
action: ContentActions.ENDORSE,
|
action: ContentActions.ENDORSE,
|
||||||
icon: CheckCircleOutline,
|
icon: VerifiedBadge,
|
||||||
label: messages.markAnsweredAction,
|
label: messages.markAnsweredAction,
|
||||||
conditions: {
|
conditions: {
|
||||||
endorsed: false,
|
endorsed: false,
|
||||||
@@ -133,7 +131,7 @@ export const ACTIONS_LIST = [
|
|||||||
{
|
{
|
||||||
id: 'unanswer',
|
id: 'unanswer',
|
||||||
action: ContentActions.ENDORSE,
|
action: ContentActions.ENDORSE,
|
||||||
icon: CheckCircle,
|
icon: VerifiedBadge,
|
||||||
label: messages.unmarkAnsweredAction,
|
label: messages.unmarkAnsweredAction,
|
||||||
conditions: {
|
conditions: {
|
||||||
endorsed: true,
|
endorsed: true,
|
||||||
@@ -185,7 +183,6 @@ export function useActions(content) {
|
|||||||
.every(condition => condition === true)
|
.every(condition => condition === true)
|
||||||
: true
|
: true
|
||||||
);
|
);
|
||||||
|
|
||||||
return ACTIONS_LIST.filter(
|
return ACTIONS_LIST.filter(
|
||||||
({
|
({
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"discussions.comments.comment.addResponse": "Add a response",
|
"discussions.comments.comment.addResponse": "Add a response",
|
||||||
"discussions.comments.comment.addComment": "Add a comment",
|
"discussions.comments.comment.addComment": "Add a comment",
|
||||||
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
|
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
|
||||||
"discussions.actions.back.alt": "Back to list",
|
|
||||||
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
|
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
|
||||||
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
|
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
|
||||||
"discussions.comments.comment.loadMoreComments": "Load more comments",
|
"discussions.comments.comment.loadMoreComments": "Load more comments",
|
||||||
@@ -43,11 +42,9 @@
|
|||||||
"discussions.learner.activityForLearner": "Activity for {username}",
|
"discussions.learner.activityForLearner": "Activity for {username}",
|
||||||
"discussions.learner.mostActivity": "Most activity",
|
"discussions.learner.mostActivity": "Most activity",
|
||||||
"discussions.learner.reportedActivity": "Reported activity",
|
"discussions.learner.reportedActivity": "Reported activity",
|
||||||
"discussions.learner.recentActivity": "Recent activity",
|
"discussions.learner.sortFilterStatus": "All learners by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
|
||||||
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
|
|
||||||
"discussion.learner.allActivity": "All activity",
|
|
||||||
"discussion.learner.posts": "Posts",
|
|
||||||
"discussions.actions.button.alt": "Actions menu",
|
"discussions.actions.button.alt": "Actions menu",
|
||||||
|
"discussions.actions.back.alt": "Back",
|
||||||
"discussions.actions.copylink": "Copy link",
|
"discussions.actions.copylink": "Copy link",
|
||||||
"discussions.actions.unpin": "Unpin",
|
"discussions.actions.unpin": "Unpin",
|
||||||
"discussions.actions.close": "Close",
|
"discussions.actions.close": "Close",
|
||||||
@@ -56,7 +53,7 @@
|
|||||||
"discussions.actions.unreport": "Unreport",
|
"discussions.actions.unreport": "Unreport",
|
||||||
"discussions.actions.endorse": "Endorse",
|
"discussions.actions.endorse": "Endorse",
|
||||||
"discussions.actions.unendorse": "Unendorse",
|
"discussions.actions.unendorse": "Unendorse",
|
||||||
"discussions.actions.markAnswered": "Mark as answered",
|
"discussions.actions.markAnswered": "Mark as Answered",
|
||||||
"discussions.actions.unMarkAnswered": "Unmark as answered",
|
"discussions.actions.unMarkAnswered": "Unmark as answered",
|
||||||
"discussions.delete.confirmation.button.cancel": "Cancel",
|
"discussions.delete.confirmation.button.cancel": "Cancel",
|
||||||
"discussions.delete.confirmation.button.delete": "Delete",
|
"discussions.delete.confirmation.button.delete": "Delete",
|
||||||
@@ -80,10 +77,6 @@
|
|||||||
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
|
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
|
||||||
"discussion.banner.learnMore": "Learn more",
|
"discussion.banner.learnMore": "Learn more",
|
||||||
"discussion.banner.shareFeedback": "Share feedback",
|
"discussion.banner.shareFeedback": "Share feedback",
|
||||||
"discussion.blackoutBanner.information": "Blackout dates are currently active. Posting in discussions is unavailable at this time.",
|
|
||||||
"discussions.editor.image.warning.message": "Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions",
|
|
||||||
"discussions.editor.image.warning.title": "Warning!",
|
|
||||||
"discussions.editor.image.warning.dismiss": "Ok",
|
|
||||||
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
|
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
|
||||||
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
|
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
|
||||||
"discussions.navigation.navigationBar.allPosts": "All posts",
|
"discussions.navigation.navigationBar.allPosts": "All posts",
|
||||||
@@ -92,9 +85,8 @@
|
|||||||
"discussions.navigation.navigationBar.learners": "Learners",
|
"discussions.navigation.navigationBar.learners": "Learners",
|
||||||
"discussions.app.title": "Discussions",
|
"discussions.app.title": "Discussions",
|
||||||
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
|
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
|
||||||
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
|
"discussions.posts.actionBar.search": "{page, select,\n topics {Find a topic}\n posts {Search all posts}\n learners {Search learner}\n myPosts {Search all posts}\n other {{page}}\n }",
|
||||||
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
|
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
|
||||||
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
|
|
||||||
"discussions.actionBar.searchInfoSearching": "Searching...",
|
"discussions.actionBar.searchInfoSearching": "Searching...",
|
||||||
"discussions.actionBar.clearSearch": "Clear results",
|
"discussions.actionBar.clearSearch": "Clear results",
|
||||||
"discussion.posts.actionBar.add": "Add a post",
|
"discussion.posts.actionBar.add": "Add a post",
|
||||||
@@ -123,8 +115,6 @@
|
|||||||
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
|
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
|
||||||
"discussions.editor.posts.editReasonCode": "Reason for editing",
|
"discussions.editor.posts.editReasonCode": "Reason for editing",
|
||||||
"discussions.editor.posts.showPreview.button": "Show Preview",
|
"discussions.editor.posts.showPreview.button": "Show Preview",
|
||||||
"discussions.topic.noName.label": "Unnamed category",
|
|
||||||
"discussions.subtopic.noName.label": "Unnamed subcategory",
|
|
||||||
"discussions.posts.filter.showALl": "Show all",
|
"discussions.posts.filter.showALl": "Show all",
|
||||||
"discussions.posts.filter.discussions": "Discussions",
|
"discussions.posts.filter.discussions": "Discussions",
|
||||||
"discussions.posts.filter.questions": "Questions",
|
"discussions.posts.filter.questions": "Questions",
|
||||||
@@ -134,7 +124,6 @@
|
|||||||
"discussions.posts.status.filter.following": "Following",
|
"discussions.posts.status.filter.following": "Following",
|
||||||
"discussions.posts.status.filter.reported": "Reported",
|
"discussions.posts.status.filter.reported": "Reported",
|
||||||
"discussions.posts.status.filter.unanswered": "Unanswered",
|
"discussions.posts.status.filter.unanswered": "Unanswered",
|
||||||
"discussions.posts.status.filter.unresponded": "Not responded",
|
|
||||||
"discussions.posts.filter.myPosts": "My posts",
|
"discussions.posts.filter.myPosts": "My posts",
|
||||||
"discussions.posts.filter.myDiscussions": "My discussions",
|
"discussions.posts.filter.myDiscussions": "My discussions",
|
||||||
"discussions.posts.filter.myQuestions": "My questions",
|
"discussions.posts.filter.myQuestions": "My questions",
|
||||||
@@ -142,7 +131,7 @@
|
|||||||
"discussions.posts.sort.lastActivity": "Recent activity",
|
"discussions.posts.sort.lastActivity": "Recent activity",
|
||||||
"discussions.posts.sort.commentCount": "Most activity",
|
"discussions.posts.sort.commentCount": "Most activity",
|
||||||
"discussions.posts.sort.voteCount": "Most likes",
|
"discussions.posts.sort.voteCount": "Most likes",
|
||||||
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
|
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most votes}\n other {{sort}}\n }",
|
||||||
"discussions.post.author.anonymous": "anonymous",
|
"discussions.post.author.anonymous": "anonymous",
|
||||||
"discussions.post.lastResponse": "Last response {time}",
|
"discussions.post.lastResponse": "Last response {time}",
|
||||||
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
|
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
|
||||||
@@ -167,16 +156,10 @@
|
|||||||
"discussions.post.editedBy": "Edited by",
|
"discussions.post.editedBy": "Edited by",
|
||||||
"discussions.post.editReason": "Reason",
|
"discussions.post.editReason": "Reason",
|
||||||
"discussions.post.postWithoutPreview": "No preview available",
|
"discussions.post.postWithoutPreview": "No preview available",
|
||||||
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
|
|
||||||
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
|
|
||||||
"discussions.topics.reported": "{reported} reported",
|
|
||||||
"discussions.topics.previouslyReported": "{previouslyReported} previously reported",
|
|
||||||
"discussions.topics.sort.message": "Sorted by {sortBy}",
|
"discussions.topics.sort.message": "Sorted by {sortBy}",
|
||||||
"discussions.topics.sort.lastActivity": "Recent activity",
|
"discussions.topics.sort.lastActivity": "Recent activity",
|
||||||
"discussions.topics.sort.commentCount": "Most activity",
|
"discussions.topics.sort.commentCount": "Most activity",
|
||||||
"discussions.topics.sort.courseStructure": "Course Structure",
|
"discussions.topics.sort.courseStructure": "Course Structure",
|
||||||
"discussions.topics.find.label": "Search topics",
|
"discussions.topics.find.label": "Find a topic",
|
||||||
"discussions.topics.archived.label": "Archived",
|
"discussions.topics.archived.label": "Archived"
|
||||||
"discussions.topics.unnamed.label": "Unnamed category",
|
|
||||||
"discussions.subtopics.unnamed.label": "Unnamed subcategory"
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
"discussions.comments.comment.addResponse": "Add a response",
|
"discussions.comments.comment.addResponse": "Add a response",
|
||||||
"discussions.comments.comment.addComment": "Add a comment",
|
"discussions.comments.comment.addComment": "Add a comment",
|
||||||
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
|
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
|
||||||
"discussions.actions.back.alt": "Back to list",
|
|
||||||
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
|
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
|
||||||
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
|
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
|
||||||
"discussions.comments.comment.loadMoreComments": "Load more comments",
|
"discussions.comments.comment.loadMoreComments": "Load more comments",
|
||||||
@@ -43,11 +42,9 @@
|
|||||||
"discussions.learner.activityForLearner": "Activity for {username}",
|
"discussions.learner.activityForLearner": "Activity for {username}",
|
||||||
"discussions.learner.mostActivity": "Most activity",
|
"discussions.learner.mostActivity": "Most activity",
|
||||||
"discussions.learner.reportedActivity": "Reported activity",
|
"discussions.learner.reportedActivity": "Reported activity",
|
||||||
"discussions.learner.recentActivity": "Recent activity",
|
"discussions.learner.sortFilterStatus": "All learners by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
|
||||||
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
|
|
||||||
"discussion.learner.allActivity": "All activity",
|
|
||||||
"discussion.learner.posts": "Posts",
|
|
||||||
"discussions.actions.button.alt": "Actions menu",
|
"discussions.actions.button.alt": "Actions menu",
|
||||||
|
"discussions.actions.back.alt": "Back",
|
||||||
"discussions.actions.copylink": "Copy link",
|
"discussions.actions.copylink": "Copy link",
|
||||||
"discussions.actions.unpin": "Unpin",
|
"discussions.actions.unpin": "Unpin",
|
||||||
"discussions.actions.close": "Close",
|
"discussions.actions.close": "Close",
|
||||||
@@ -56,7 +53,7 @@
|
|||||||
"discussions.actions.unreport": "Unreport",
|
"discussions.actions.unreport": "Unreport",
|
||||||
"discussions.actions.endorse": "Endorse",
|
"discussions.actions.endorse": "Endorse",
|
||||||
"discussions.actions.unendorse": "Unendorse",
|
"discussions.actions.unendorse": "Unendorse",
|
||||||
"discussions.actions.markAnswered": "Mark as answered",
|
"discussions.actions.markAnswered": "Mark as Answered",
|
||||||
"discussions.actions.unMarkAnswered": "Unmark as answered",
|
"discussions.actions.unMarkAnswered": "Unmark as answered",
|
||||||
"discussions.delete.confirmation.button.cancel": "Cancel",
|
"discussions.delete.confirmation.button.cancel": "Cancel",
|
||||||
"discussions.delete.confirmation.button.delete": "Delete",
|
"discussions.delete.confirmation.button.delete": "Delete",
|
||||||
@@ -80,10 +77,6 @@
|
|||||||
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
|
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
|
||||||
"discussion.banner.learnMore": "Learn more",
|
"discussion.banner.learnMore": "Learn more",
|
||||||
"discussion.banner.shareFeedback": "Share feedback",
|
"discussion.banner.shareFeedback": "Share feedback",
|
||||||
"discussion.blackoutBanner.information": "Blackout dates are currently active. Posting in discussions is unavailable at this time.",
|
|
||||||
"discussions.editor.image.warning.message": "Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions",
|
|
||||||
"discussions.editor.image.warning.title": "Warning!",
|
|
||||||
"discussions.editor.image.warning.dismiss": "Ok",
|
|
||||||
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
|
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
|
||||||
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
|
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
|
||||||
"discussions.navigation.navigationBar.allPosts": "All posts",
|
"discussions.navigation.navigationBar.allPosts": "All posts",
|
||||||
@@ -92,9 +85,8 @@
|
|||||||
"discussions.navigation.navigationBar.learners": "Learners",
|
"discussions.navigation.navigationBar.learners": "Learners",
|
||||||
"discussions.app.title": "Discussions",
|
"discussions.app.title": "Discussions",
|
||||||
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
|
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
|
||||||
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
|
"discussions.posts.actionBar.search": "{page, select,\n topics {Find a topic}\n posts {Search all posts}\n learners {Search learner}\n myPosts {Search all posts}\n other {{page}}\n }",
|
||||||
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
|
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
|
||||||
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
|
|
||||||
"discussions.actionBar.searchInfoSearching": "Searching...",
|
"discussions.actionBar.searchInfoSearching": "Searching...",
|
||||||
"discussions.actionBar.clearSearch": "Clear results",
|
"discussions.actionBar.clearSearch": "Clear results",
|
||||||
"discussion.posts.actionBar.add": "Add a post",
|
"discussion.posts.actionBar.add": "Add a post",
|
||||||
@@ -123,8 +115,6 @@
|
|||||||
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
|
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
|
||||||
"discussions.editor.posts.editReasonCode": "Reason for editing",
|
"discussions.editor.posts.editReasonCode": "Reason for editing",
|
||||||
"discussions.editor.posts.showPreview.button": "Show Preview",
|
"discussions.editor.posts.showPreview.button": "Show Preview",
|
||||||
"discussions.topic.noName.label": "Unnamed category",
|
|
||||||
"discussions.subtopic.noName.label": "Unnamed subcategory",
|
|
||||||
"discussions.posts.filter.showALl": "Show all",
|
"discussions.posts.filter.showALl": "Show all",
|
||||||
"discussions.posts.filter.discussions": "Discussions",
|
"discussions.posts.filter.discussions": "Discussions",
|
||||||
"discussions.posts.filter.questions": "Questions",
|
"discussions.posts.filter.questions": "Questions",
|
||||||
@@ -134,7 +124,6 @@
|
|||||||
"discussions.posts.status.filter.following": "Following",
|
"discussions.posts.status.filter.following": "Following",
|
||||||
"discussions.posts.status.filter.reported": "Reported",
|
"discussions.posts.status.filter.reported": "Reported",
|
||||||
"discussions.posts.status.filter.unanswered": "Unanswered",
|
"discussions.posts.status.filter.unanswered": "Unanswered",
|
||||||
"discussions.posts.status.filter.unresponded": "Not responded",
|
|
||||||
"discussions.posts.filter.myPosts": "My posts",
|
"discussions.posts.filter.myPosts": "My posts",
|
||||||
"discussions.posts.filter.myDiscussions": "My discussions",
|
"discussions.posts.filter.myDiscussions": "My discussions",
|
||||||
"discussions.posts.filter.myQuestions": "My questions",
|
"discussions.posts.filter.myQuestions": "My questions",
|
||||||
@@ -142,7 +131,7 @@
|
|||||||
"discussions.posts.sort.lastActivity": "Recent activity",
|
"discussions.posts.sort.lastActivity": "Recent activity",
|
||||||
"discussions.posts.sort.commentCount": "Most activity",
|
"discussions.posts.sort.commentCount": "Most activity",
|
||||||
"discussions.posts.sort.voteCount": "Most likes",
|
"discussions.posts.sort.voteCount": "Most likes",
|
||||||
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
|
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most votes}\n other {{sort}}\n }",
|
||||||
"discussions.post.author.anonymous": "anonymous",
|
"discussions.post.author.anonymous": "anonymous",
|
||||||
"discussions.post.lastResponse": "Last response {time}",
|
"discussions.post.lastResponse": "Last response {time}",
|
||||||
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
|
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
|
||||||
@@ -167,16 +156,10 @@
|
|||||||
"discussions.post.editedBy": "Edited by",
|
"discussions.post.editedBy": "Edited by",
|
||||||
"discussions.post.editReason": "Reason",
|
"discussions.post.editReason": "Reason",
|
||||||
"discussions.post.postWithoutPreview": "No preview available",
|
"discussions.post.postWithoutPreview": "No preview available",
|
||||||
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
|
|
||||||
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
|
|
||||||
"discussions.topics.reported": "{reported} reported",
|
|
||||||
"discussions.topics.previouslyReported": "{previouslyReported} previously reported",
|
|
||||||
"discussions.topics.sort.message": "Sorted by {sortBy}",
|
"discussions.topics.sort.message": "Sorted by {sortBy}",
|
||||||
"discussions.topics.sort.lastActivity": "Recent activity",
|
"discussions.topics.sort.lastActivity": "Recent activity",
|
||||||
"discussions.topics.sort.commentCount": "Most activity",
|
"discussions.topics.sort.commentCount": "Most activity",
|
||||||
"discussions.topics.sort.courseStructure": "Course Structure",
|
"discussions.topics.sort.courseStructure": "Course Structure",
|
||||||
"discussions.topics.find.label": "Search topics",
|
"discussions.topics.find.label": "Find a topic",
|
||||||
"discussions.topics.archived.label": "Archived",
|
"discussions.topics.archived.label": "Archived"
|
||||||
"discussions.topics.unnamed.label": "Unnamed category",
|
|
||||||
"discussions.subtopics.unnamed.label": "Unnamed subcategory"
|
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user