diff --git a/.eslintrc.js b/.eslintrc.js index 06e92c1d56..6fc94024bf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,8 @@ module.exports = createConfig( 'template-curly-spacing': 'off', 'react-hooks/exhaustive-deps': 'off', 'no-restricted-exports': 'off', + // There is no reason to disallow this syntax anymore; we don't use regenerator-runtime in new browsers + 'no-restricted-syntax': 'off', }, settings: { // Import URLs should be resolved using aliases diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9186f7421b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Adding new check for github-actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/README.rst b/README.rst index 0b2d30be71..4588f3c5e3 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -frontend-app-course-authoring -############################# +frontend-app-authoring +###################### |license-badge| |status-badge| |codecov-badge| @@ -7,9 +7,9 @@ frontend-app-course-authoring Purpose ******* -This is the Course Authoring micro-frontend, currently under development by `2U `_. +This implements most of the frontend for **Open edX Studio**, allowing authors to create and edit courses, libraries, and their learning components. -Its purpose is to provide both a framework and UI for new or replacement React-based authoring features outside ``edx-platform``. You can find the current set described below. +A few parts of Studio still default to the `"legacy" pages defined in edx-platform `_, but those are rapidly being deprecated and replaced with the React- and Paragon-based pages defined here. Getting Started diff --git a/package-lock.json b/package-lock.json index 14f7b3f317..88b7ed9a72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", + "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-footer": "git+ssh://github.com/open-craft/frontend-component-footer#5daf1acdc56b9b7426fb9e2fe772efb9a54431ab", "@edx/frontend-component-header": "git+ssh://github.com/open-craft/frontend-component-header#f764d85bacb1d25c1c57823bb80fea2e24160b75", "@edx/frontend-enterprise-hotjar": "^2.0.0", @@ -34,6 +35,7 @@ "@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams", "@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki", "@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary", + "@openedx/frontend-build": "^14.0.14", "@openedx/frontend-plugin-framework": "^1.2.1", "@openedx/paragon": "git+ssh://github.com/open-craft/paragon#d5642191a21d532cd6fbe13519dfecd9e1518601", "@redux-devtools/extension": "^3.3.0", @@ -69,7 +71,6 @@ "react-transition-group": "4.4.5", "redux": "4.0.5", "redux-logger": "^3.0.6", - "redux-mock-store": "^1.5.4", "redux-thunk": "^2.4.1", "reselect": "^4.1.5", "start": "^5.1.0", @@ -80,31 +81,22 @@ "yup": "0.31.1" }, "devDependencies": { - "@edx/browserslist-config": "1.2.0", "@edx/react-unit-test-utils": "3.0.0", - "@edx/reactifex": "^1.0.3", "@edx/stylelint-config-edx": "2.3.3", "@edx/typescript-config": "^1.0.1", - "@openedx/frontend-build": "^14.0.14", "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.2.1", "@types/lodash": "^4.17.7", - "axios": "^0.28.0", "axios-mock-adapter": "1.22.0", "eslint-import-resolver-webpack": "^0.13.8", "fetch-mock-jest": "^1.5.1", - "glob": "7.2.3", "husky": "7.0.4", "jest-canvas-mock": "^2.5.2", "jest-expect-message": "^1.1.3", "react-test-renderer": "17.0.2", - "reactifex": "1.1.1", - "ts-loader": "^9.5.1" - }, - "peerDependencies": { - "decode-uri-component": ">=0.2.2" + "redux-mock-store": "^1.5.4" } }, "node_modules/@adobe/css-tools": { @@ -2256,12 +2248,12 @@ }, "node_modules/@edx/browserslist-config": { "version": "1.2.0", - "dev": true, "license": "AGPL-3.0" }, "node_modules/@edx/eslint-config": { - "version": "4.1.0", - "dev": true, + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@edx/eslint-config/-/eslint-config-4.2.0.tgz", + "integrity": "sha512-2wuIw49uyj6gRwS74qJ8WhBU+X2FOP4uot40sthIC4YU9qCM7WJOcOuAhkRPP1FvZKd3UQH3gZM7eJ85xzDBqA==", "license": "MIT", "peerDependencies": { "@typescript-eslint/eslint-plugin": "^5.62.0", @@ -2423,7 +2415,9 @@ } }, "node_modules/@edx/openedx-atlas": { - "version": "0.6.1", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.6.2.tgz", + "integrity": "sha512-28Q8vzJDMS4wUxdkbIUBQpzWJ3HTdMaGlaEhFjrVGfuZkh++1AG6Tn/7FMD88cegalYAkphu530VQCHEkMZQhw==", "license": "AGPL-3.0", "bin": { "atlas": "atlas" @@ -2479,26 +2473,6 @@ "deep-equal": "^2.0.5" } }, - "node_modules/@edx/reactifex": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^0.21.1", - "yargs": "^17.1.1" - }, - "bin": { - "edx_reactifex": "main.js" - } - }, - "node_modules/@edx/reactifex/node_modules/axios": { - "version": "0.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, "node_modules/@edx/stylelint-config-edx": { "version": "2.3.3", "dev": true, @@ -2997,6 +2971,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3005,6 +2980,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" }, @@ -3016,6 +2992,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz", "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" }, @@ -3027,6 +3004,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" }, @@ -3038,6 +3016,7 @@ "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" }, @@ -3733,8 +3712,9 @@ "link": true }, "node_modules/@openedx/frontend-build": { - "version": "14.0.15", - "dev": true, + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@openedx/frontend-build/-/frontend-build-14.1.2.tgz", + "integrity": "sha512-pLTcY/BLjCFn2qsBmUY0on2qB4PNShlg8scTYjrsz5fCASuSAJk000jOF5ys6bg6i8krSOIJlcZXPgrq0ywsUg==", "license": "AGPL-3.0", "dependencies": { "@babel/cli": "7.24.8", @@ -3745,7 +3725,7 @@ "@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/preset-env": "7.24.8", "@babel/preset-react": "7.24.7", - "@edx/eslint-config": "4.1.0", + "@edx/eslint-config": "4.2.0", "@edx/new-relic-source-map-webpack-plugin": "2.1.0", "@edx/typescript-config": "1.1.0", "@formatjs/cli": "^6.0.3", @@ -3783,6 +3763,7 @@ "jest": "29.6.1", "jest-environment-jsdom": "29.6.1", "mini-css-extract-plugin": "1.6.2", + "parse5": "7.1.2", "postcss": "8.4.39", "postcss-custom-media": "10.0.8", "postcss-loader": "7.3.4", @@ -3802,7 +3783,8 @@ "webpack-bundle-analyzer": "^4.10.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.10.0" + "webpack-merge": "^5.10.0", + "webpack-remove-empty-scripts": "1.0.4" }, "bin": { "fedx-scripts": "bin/fedx-scripts.js" @@ -3813,7 +3795,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/@eslint/js": { "version": "8.44.0", - "dev": true, + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3821,12 +3804,14 @@ }, "node_modules/@openedx/frontend-build/node_modules/argparse": { "version": "2.0.1", - "dev": true, + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/@openedx/frontend-build/node_modules/eslint": { "version": "8.44.0", - "dev": true, + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -3881,7 +3866,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/eslint-plugin-import": { "version": "2.27.5", - "dev": true, + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "license": "MIT", "dependencies": { "array-includes": "^3.1.6", @@ -3909,7 +3895,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", - "dev": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -3917,7 +3904,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", - "dev": true, + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -3928,7 +3916,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/eslint-scope": { "version": "7.2.2", - "dev": true, + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -3943,7 +3932,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "dev": true, + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3954,7 +3944,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/find-up": { "version": "5.0.0", - "dev": true, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -3969,7 +3960,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/glob-parent": { "version": "6.0.2", - "dev": true, + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -3980,7 +3972,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/globals": { "version": "13.24.0", - "dev": true, + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -3994,7 +3987,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/jest": { "version": "29.6.1", - "dev": true, + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "license": "MIT", "dependencies": { "@jest/core": "^29.6.1", @@ -4019,7 +4013,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/jest-environment-jsdom": { "version": "29.6.1", - "dev": true, + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.1.tgz", + "integrity": "sha512-PoY+yLaHzVRhVEjcVKSfJ7wXmJW4UqPYNhR05h7u/TK0ouf6DmRNZFBL/Z00zgQMyWGMBXn69/FmOvhEJu8cIw==", "license": "MIT", "dependencies": { "@jest/environment": "^29.6.1", @@ -4045,7 +4040,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/js-yaml": { "version": "4.1.0", - "dev": true, + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4056,7 +4052,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/locate-path": { "version": "6.0.0", - "dev": true, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -4070,7 +4067,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/p-locate": { "version": "5.0.0", - "dev": true, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -4084,7 +4082,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/ts-jest": { "version": "29.1.4", - "dev": true, + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz", + "integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==", "license": "MIT", "dependencies": { "bs-logger": "0.x", @@ -4130,7 +4129,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/ts-jest/node_modules/semver": { "version": "7.6.3", - "dev": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4141,7 +4141,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/type-fest": { "version": "0.20.2", - "dev": true, + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -4152,7 +4153,8 @@ }, "node_modules/@openedx/frontend-build/node_modules/yargs-parser": { "version": "21.1.1", - "dev": true, + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" @@ -4195,9 +4197,9 @@ } }, "node_modules/@openedx/paragon": { - "version": "20.19.0", - "resolved": "git+ssh://git@github.com/open-craft/paragon.git#d5642191a21d532cd6fbe13519dfecd9e1518601", - "integrity": "sha512-UxmmfDb1GXoOQHLl2PgtaoVkfqRV/0leUX1y+RmJjfHlwRa+27YyIt2DQ7yGqCZ72Kl1ahD6OfxY8n73fKSGJQ==", + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.8.1.tgz", + "integrity": "sha512-lm2x0tvNZrtJvp0L+cjvLLmkE9NoUbNIzt9L1FaOx9g92gf8rFVgq4aadq7IVAjN12HW19/QJMEJaQ0SVsvY2A==", "license": "Apache-2.0", "workspaces": [ "example", @@ -4248,6 +4250,8 @@ }, "node_modules/@openedx/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==", "license": "MIT", "dependencies": { "prop-types": "^15.8.1" @@ -4259,6 +4263,8 @@ }, "node_modules/@openedx/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==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4266,6 +4272,9 @@ }, "node_modules/@openedx/paragon/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -4283,6 +4292,8 @@ }, "node_modules/@openedx/paragon/node_modules/minimatch": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4293,6 +4304,8 @@ }, "node_modules/@openedx/paragon/node_modules/react-responsive": { "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz", + "integrity": "sha512-iagCqVrw4QSjhxKp3I/YK6+ODkWY6G+YPElvdYKiUUbywwh9Ds0M7r26Fj2/7dWFFbOpcGnJE6uE7aMck8j5Qg==", "license": "MIT", "dependencies": { "hyphenate-style-name": "^1.0.0", @@ -4309,6 +4322,8 @@ }, "node_modules/@openedx/paragon/node_modules/uuid": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -5953,6 +5968,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-1.5.2.tgz", + "integrity": "sha512-T3vUABrcgSj/HXv27P+A/JxGk5b/ydx0JjN3lgjBTC2iZUFxQGjh43zCzLSbU4C1QTgmx9oaPeWNJFM+auI8qw==", + "license": "ISC", + "engines": { + "node": ">=12.13" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/biodiscus" + } + }, "node_modules/anymatch": { "version": "3.1.3", "dev": true, @@ -6244,6 +6272,7 @@ "version": "0.28.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -6876,8 +6905,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "dev": true, + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "funding": [ { "type": "opencollective", @@ -9937,7 +9967,9 @@ } }, "node_modules/frontend-components-tinymce-advanced-plugins": { - "version": "1.0.3", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/frontend-components-tinymce-advanced-plugins/-/frontend-components-tinymce-advanced-plugins-1.0.4.tgz", + "integrity": "sha512-3PzEaOa9k1csUsVqvrJ11LXiUgu804lax0sq6eWmirtfYMJNYrajUEwW4REKTnpQrv1ByDSkVV/kaporN0PkWA==", "license": "AGPL-3.0", "dependencies": { "tinymce": "^5.10.4" @@ -12815,6 +12847,7 @@ }, "node_modules/lodash.isplainobject": { "version": "4.0.6", + "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { @@ -18128,14 +18161,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/reactifex": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "bin": { - "reactifex": "main.js" - } - }, "node_modules/read-pkg": { "version": "6.0.0", "dev": true, @@ -18317,6 +18342,7 @@ }, "node_modules/redux-mock-store": { "version": "1.5.4", + "dev": true, "license": "MIT", "dependencies": { "lodash.isplainobject": "^4.0.6" @@ -20364,56 +20390,6 @@ "node": ">=12" } }, - "node_modules/ts-loader": { - "version": "9.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/enhanced-resolve": { - "version": "5.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.6.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/tapable": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "dev": true, @@ -21268,6 +21244,25 @@ "node": ">=10.0.0" } }, + "node_modules/webpack-remove-empty-scripts": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/webpack-remove-empty-scripts/-/webpack-remove-empty-scripts-1.0.4.tgz", + "integrity": "sha512-W/Vd94oNXMsQam+W9G+aAzGgFlX1aItcJpkG3byuHGDaxyK3H17oD/b5RcqS/ZHzStIKepksdLDznejDhDUs+Q==", + "license": "ISC", + "dependencies": { + "ansis": "1.5.2" + }, + "engines": { + "node": ">=12.14" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/biodiscus" + }, + "peerDependencies": { + "webpack": ">=5.32.0" + } + }, "node_modules/webpack-sources": { "version": "1.4.3", "dev": true, diff --git a/package.json b/package.json index 33675c4042..ca812c87dd 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", + "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-footer": "git+ssh://github.com/open-craft/frontend-component-footer#5daf1acdc56b9b7426fb9e2fe772efb9a54431ab", "@edx/frontend-component-header": "git+ssh://github.com/open-craft/frontend-component-header#f764d85bacb1d25c1c57823bb80fea2e24160b75", "@edx/frontend-enterprise-hotjar": "^2.0.0", @@ -62,6 +63,7 @@ "@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams", "@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki", "@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary", + "@openedx/frontend-build": "^14.0.14", "@openedx/frontend-plugin-framework": "^1.2.1", "@openedx/paragon": "git+ssh://github.com/open-craft/paragon#d5642191a21d532cd6fbe13519dfecd9e1518601", "@redux-devtools/extension": "^3.3.0", @@ -97,7 +99,6 @@ "react-transition-group": "4.4.5", "redux": "4.0.5", "redux-logger": "^3.0.6", - "redux-mock-store": "^1.5.4", "redux-thunk": "^2.4.1", "reselect": "^4.1.5", "start": "^5.1.0", @@ -108,30 +109,21 @@ "yup": "0.31.1" }, "devDependencies": { - "@edx/browserslist-config": "1.2.0", "@edx/react-unit-test-utils": "3.0.0", - "@edx/reactifex": "^1.0.3", "@edx/stylelint-config-edx": "2.3.3", "@edx/typescript-config": "^1.0.1", - "@openedx/frontend-build": "^14.0.14", "@testing-library/jest-dom": "5.17.0", "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.2.1", "@types/lodash": "^4.17.7", - "axios": "^0.28.0", "axios-mock-adapter": "1.22.0", "eslint-import-resolver-webpack": "^0.13.8", "fetch-mock-jest": "^1.5.1", - "glob": "7.2.3", "husky": "7.0.4", "jest-canvas-mock": "^2.5.2", "jest-expect-message": "^1.1.3", "react-test-renderer": "17.0.2", - "reactifex": "1.1.1", - "ts-loader": "^9.5.1" - }, - "peerDependencies": { - "decode-uri-component": ">=0.2.2" + "redux-mock-store": "^1.5.4" } } diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index 5d3d5e37b3..41da0bc232 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -11,6 +11,7 @@ import { fetchCourseDetail } from './data/thunks'; import { useModel } from './generic/model-store'; import NotFoundAlert from './generic/NotFoundAlert'; import PermissionDeniedAlert from './generic/PermissionDeniedAlert'; +import { fetchStudioHomeData } from './studio-home/data/thunks'; import { getCourseAppsApiStatus } from './pages-and-resources/data/selectors'; import { RequestStatus } from './data/constants'; import Loading from './generic/Loading'; @@ -22,6 +23,10 @@ const CourseAuthoringPage = ({ courseId, children }) => { dispatch(fetchCourseDetail(courseId)); }, [courseId]); + useEffect(() => { + dispatch(fetchStudioHomeData()); + }, []); + const courseDetail = useModel('courseDetails', courseId); const courseNumber = courseDetail ? courseDetail.number : null; diff --git a/src/CourseAuthoringRoutes.jsx b/src/CourseAuthoringRoutes.jsx index 51599317e6..0c9d2a1680 100644 --- a/src/CourseAuthoringRoutes.jsx +++ b/src/CourseAuthoringRoutes.jsx @@ -88,7 +88,7 @@ const CourseAuthoringRoutes = () => { /> } + element={} /> { - const { blockType, blockId } = useParams(); - return ( -
- -
- ); -}; -EditorContainer.propTypes = { - courseId: PropTypes.string.isRequired, -}; - -export default EditorContainer; diff --git a/src/editors/EditorContainer.test.jsx b/src/editors/EditorContainer.test.jsx index a6186050ae..d57d14c6b1 100644 --- a/src/editors/EditorContainer.test.jsx +++ b/src/editors/EditorContainer.test.jsx @@ -10,7 +10,7 @@ jest.mock('react-router', () => ({ }), })); -const props = { courseId: 'cOuRsEId' }; +const props = { learningContextId: 'cOuRsEId' }; describe('Editor Container', () => { describe('snapshots', () => { diff --git a/src/editors/EditorContainer.tsx b/src/editors/EditorContainer.tsx new file mode 100644 index 0000000000..fc6fa417c1 --- /dev/null +++ b/src/editors/EditorContainer.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { getConfig } from '@edx/frontend-platform'; + +import EditorPage from './EditorPage'; + +interface Props { + /** Course ID or Library ID */ + learningContextId: string; + /** Event handler for when user cancels out of the editor page */ + onClose?: () => void; + /** Event handler called after when user saves their changes using an editor */ + afterSave?: () => (newData: Record) => void; +} + +const EditorContainer: React.FC = ({ + learningContextId, + onClose, + afterSave, +}) => { + const { blockType, blockId } = useParams(); + if (blockType === undefined || blockId === undefined) { + // istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker. + return
Error: missing URL parameters
; + } + if (!!onClose !== !!afterSave) { + /* istanbul ignore next */ + throw new Error('You must specify both onClose and afterSave or neither.'); + // These parameters are a bit messy so I'm trying to help make it more + // consistent here. For example, if you specify onClose, then returnFunction + // is only called if the save is successful. But if you leave onClose + // undefined, then returnFunction is called in either case, and with + // different arguments. The underlying EditorPage should be refactored to + // have more clear events like onCancel and onSaveSuccess + } + return ( +
+ +
+ ); +}; + +export default EditorContainer; diff --git a/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap b/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap index 49598b47ee..02c89e55d7 100644 --- a/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap @@ -51,6 +51,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and /> diff --git a/src/editors/containers/EditorContainer/messages.js b/src/editors/containers/EditorContainer/messages.js index b8301ca810..a6f1754fb2 100644 --- a/src/editors/containers/EditorContainer/messages.js +++ b/src/editors/containers/EditorContainer/messages.js @@ -12,6 +12,11 @@ const messages = defineMessages({ defaultMessage: 'Are you sure you want to exit the editor? Any unsaved changes will be lost.', description: 'Description text for modal confirming cancellation', }, + exitButtonAlt: { + id: 'authoring.editorContainer.exitButton.alt', + defaultMessage: 'Exit the editor', + description: 'Alt text for the Exit button', + }, okButtonLabel: { id: 'authoring.editorContainer.okButton.label', defaultMessage: 'OK', diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx index 8ac30a31c4..57012064ac 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx @@ -25,6 +25,9 @@ const AnswerOption = ({ intl, // redux problemType, + images, + isLibrary, + learningContextId, }) => { const dispatch = useDispatch(); const removeAnswer = hooks.removeAnswer({ answer, dispatch }); @@ -47,6 +50,11 @@ const AnswerOption = ({ setContent={setAnswerTitle} placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)} id={`answer-${answer.id}`} + {...{ + images, + isLibrary, + learningContextId, + }} /> ); } @@ -106,6 +114,11 @@ const AnswerOption = ({ setSelectedFeedback={setSelectedFeedback} setUnselectedFeedback={setUnselectedFeedback} intl={intl} + {...{ + images, + isLibrary, + learningContextId, + }} /> @@ -135,10 +148,16 @@ AnswerOption.propTypes = { intl: intlShape.isRequired, // redux problemType: PropTypes.string.isRequired, + images: PropTypes.shape({}).isRequired, + learningContextId: PropTypes.string.isRequired, + isLibrary: PropTypes.bool.isRequired, }; export const mapStateToProps = (state) => ({ problemType: selectors.problem.problemType(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), + learningContextId: selectors.app.learningContextId(state), }); export const mapDispatchToProps = {}; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.jsx index 7ef58f6d9a..59c05a67e5 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.jsx @@ -10,12 +10,18 @@ jest.mock('../../../../../data/redux', () => ({ default: jest.fn(), selectors: { problem: { - answers: jest.fn(state => ({ answers: state })), problemType: jest.fn(state => ({ problemType: state })), }, + app: { + images: jest.fn(state => ({ images: state })), + isLibrary: jest.fn(state => ({ isLibrary: state })), + learningContextId: jest.fn(state => ({ learningContextId: state })), + }, }, thunkActions: { - video: jest.fn(), + video: { + importTranscripts: jest.fn(), + }, }, })); @@ -49,6 +55,9 @@ describe('AnswerOption', () => { intl: { formatMessage }, // redux problemType: 'multiplechoiceresponse', + images: {}, + isLibrary: false, + learningContextId: 'course+org+run', }; describe('render', () => { test('snapshot: renders correct option with feedback', () => { @@ -72,5 +81,20 @@ describe('AnswerOption', () => { mapStateToProps(testState).problemType, ).toEqual(selectors.problem.problemType(testState)); }); + test('images from app.images', () => { + expect( + mapStateToProps(testState).images, + ).toEqual(selectors.app.images(testState)); + }); + test('learningContextId from app.learningContextId', () => { + expect( + mapStateToProps(testState).learningContextId, + ).toEqual(selectors.app.learningContextId(testState)); + }); + test('isLibrary from app.isLibrary', () => { + expect( + mapStateToProps(testState).isLibrary, + ).toEqual(selectors.app.isLibrary(testState)); + }); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/__snapshots__/AnswerOption.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/__snapshots__/AnswerOption.test.jsx.snap index 171a2ab104..d1443d903a 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/__snapshots__/AnswerOption.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/__snapshots__/AnswerOption.test.jsx.snap @@ -30,6 +30,9 @@ exports[`AnswerOption render snapshot: renders correct option with feedback 1`] error={false} errorMessage={null} id="answer-A" + images={{}} + isLibrary={false} + learningContextId="course+org+run" placeholder="Enter an answer" setContent={[Function]} value="Answer 1" @@ -44,11 +47,14 @@ exports[`AnswerOption render snapshot: renders correct option with feedback 1`] "title": "Answer 1", } } + images={{}} intl={ { "formatMessage": [Function], } } + isLibrary={false} + learningContextId="course+org+run" problemType="multiplechoiceresponse" setSelectedFeedback={[Function]} setUnselectedFeedback={[Function]} @@ -121,11 +127,14 @@ exports[`AnswerOption render snapshot: renders correct option with numeric input "title": "Answer 1", } } + images={{}} intl={ { "formatMessage": [Function], } } + isLibrary={false} + learningContextId="course+org+run" problemType="numericalresponse" setSelectedFeedback={[Function]} setUnselectedFeedback={[Function]} @@ -213,11 +222,14 @@ exports[`AnswerOption render snapshot: renders correct option with numeric input "unselectedFeedback": "unselected feedback", } } + images={{}} intl={ { "formatMessage": [Function], } } + isLibrary={false} + learningContextId="course+org+run" problemType="numericalresponse" setSelectedFeedback={[Function]} setUnselectedFeedback={[Function]} @@ -276,6 +288,9 @@ exports[`AnswerOption render snapshot: renders correct option with selected unse error={false} errorMessage={null} id="answer-A" + images={{}} + isLibrary={false} + learningContextId="course+org+run" placeholder="Enter an answer" setContent={[Function]} value="Answer 1" @@ -291,11 +306,14 @@ exports[`AnswerOption render snapshot: renders correct option with selected unse "unselectedFeedback": "unselected feedback", } } + images={{}} intl={ { "formatMessage": [Function], } } + isLibrary={false} + learningContextId="course+org+run" problemType="choiceresponse" setSelectedFeedback={[Function]} setUnselectedFeedback={[Function]} diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.jsx index c75cafe2ff..91522d3aa6 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.jsx @@ -12,12 +12,18 @@ export const FeedbackBox = ({ problemType, setSelectedFeedback, setUnselectedFeedback, + images, + isLibrary, + learningContextId, // injected intl, }) => { const props = { answer, intl, + images, + isLibrary, + learningContextId, }; return ((problemType === ProblemTypeKeys.MULTISELECT) ? ( @@ -61,6 +67,9 @@ FeedbackBox.propTypes = { setAnswer: PropTypes.func.isRequired, setSelectedFeedback: PropTypes.func.isRequired, setUnselectedFeedback: PropTypes.func.isRequired, + images: PropTypes.shape({}).isRequired, + learningContextId: PropTypes.string.isRequired, + isLibrary: PropTypes.bool.isRequired, intl: intlShape.isRequired, }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.test.jsx index 885cb7a6d2..2c1fa2dd5c 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackBox.test.jsx @@ -8,6 +8,9 @@ const answerWithFeedback = { selectedFeedback: 'some feedback', unselectedFeedback: 'unselectedFeedback', problemType: 'sOMepRObleM', + images: {}, + isLibrary: false, + learningContextId: 'course+org+run', }; const props = { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.jsx index e22102f5a6..4a84656ea6 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.jsx @@ -15,6 +15,9 @@ const FeedbackControl = ({ answer, intl, type, + images, + isLibrary, + learningContextId, }) => ( @@ -31,6 +34,11 @@ const FeedbackControl = ({ value={feedback} setContent={onChange} placeholder={intl.formatMessage(messages.feedbackPlaceholder)} + {...{ + images, + isLibrary, + learningContextId, + }} /> ); @@ -41,6 +49,9 @@ FeedbackControl.propTypes = { labelMessageBoldUnderline: PropTypes.string.isRequired, answer: answerOptionProps.isRequired, type: PropTypes.string.isRequired, + images: PropTypes.shape({}).isRequired, + learningContextId: PropTypes.string.isRequired, + isLibrary: PropTypes.bool.isRequired, intl: intlShape.isRequired, }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.test.jsx index 7fb51bf491..1dd77266eb 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/FeedbackControl.test.jsx @@ -18,6 +18,9 @@ const props = { onChange: jest.fn(), labelMessage: 'msg', labelMessageBoldUnderline: 'msg', + images: {}, + isLibrary: false, + learningContextId: 'course+org+run', }; describe('FeedbackControl component', () => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackBox.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackBox.test.jsx.snap index bdc6ada0b2..ec31f4becb 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackBox.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackBox.test.jsx.snap @@ -9,6 +9,9 @@ exports[`FeedbackBox component renders as expected with a multi select problem 1 { "correct": true, "id": "A", + "images": {}, + "isLibrary": false, + "learningContextId": "course+org+run", "problemType": "sOMepRObleM", "selectedFeedback": "some feedback", "title": "Answer 1", @@ -39,6 +42,9 @@ exports[`FeedbackBox component renders as expected with a multi select problem 1 { "correct": true, "id": "A", + "images": {}, + "isLibrary": false, + "learningContextId": "course+org+run", "problemType": "sOMepRObleM", "selectedFeedback": "some feedback", "title": "Answer 1", @@ -76,6 +82,9 @@ exports[`FeedbackBox component renders as expected with a numeric input problem { "correct": true, "id": "A", + "images": {}, + "isLibrary": false, + "learningContextId": "course+org+run", "problemType": "sOMepRObleM", "selectedFeedback": "some feedback", "title": "Answer 1", @@ -113,6 +122,9 @@ exports[`FeedbackBox component renders as expected with default props 1`] = ` { "correct": true, "id": "A", + "images": {}, + "isLibrary": false, + "learningContextId": "course+org+run", "problemType": "sOMepRObleM", "selectedFeedback": "some feedback", "title": "Answer 1", diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackControl.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackControl.test.jsx.snap index c84124f16b..614505617b 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackControl.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/components/Feedback/__snapshots__/FeedbackControl.test.jsx.snap @@ -29,6 +29,9 @@ exports[`FeedbackControl component renders 1`] = ` error={false} errorMessage={null} id="undefinedFeedback-A" + images={{}} + isLibrary={false} + learningContextId="course+org+run" placeholder={null} setContent={[MockFunction]} value="feedback" diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js index b1e9585cf6..a2ac1b44ea 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js @@ -42,22 +42,22 @@ export const setAnswerTitle = ({ dispatch(actions.problem.updateAnswer({ id: answer.id, hasSingleAnswer, title })); }; -export const setSelectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (e) => { - if (e.target) { +export const setSelectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (value) => { + if (value) { dispatch(actions.problem.updateAnswer({ id: answer.id, hasSingleAnswer, - selectedFeedback: e.target.value, + selectedFeedback: value, })); } }; -export const setUnselectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (e) => { - if (e.target) { +export const setUnselectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (value) => { + if (value) { dispatch(actions.problem.updateAnswer({ id: answer.id, hasSingleAnswer, - unselectedFeedback: e.target.value, + unselectedFeedback: value, })); } }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.test.js index 9614e635f3..028682249a 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.test.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.test.js @@ -130,12 +130,12 @@ describe('Answer Options Hooks', () => { const answer = { id: 'A' }; const hasSingleAnswer = false; const dispatch = useDispatch(); - const e = { target: { value: 'string' } }; - module.setSelectedFeedback({ answer, hasSingleAnswer, dispatch })(e); + const value = 'string'; + module.setSelectedFeedback({ answer, hasSingleAnswer, dispatch })(value); expect(dispatch).toHaveBeenCalledWith(actions.problem.updateAnswer({ id: answer.id, hasSingleAnswer, - selectedFeedback: e.target.value, + selectedFeedback: value, })); }); }); @@ -144,12 +144,12 @@ describe('Answer Options Hooks', () => { const answer = { id: 'A' }; const hasSingleAnswer = false; const dispatch = useDispatch(); - const e = { target: { value: 'string' } }; - module.setUnselectedFeedback({ answer, hasSingleAnswer, dispatch })(e); + const value = 'string'; + module.setUnselectedFeedback({ answer, hasSingleAnswer, dispatch })(value); expect(dispatch).toHaveBeenCalledWith(actions.problem.updateAnswer({ id: answer.id, hasSingleAnswer, - unselectedFeedback: e.target.value, + unselectedFeedback: value, })); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap index 8a9deb9303..3cb07a6ce0 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap @@ -22,13 +22,21 @@ exports[`SolutionWidget render snapshot: renders correct default 1`] = ` id="authoring.problemEditor.solutionwidget.solutionDescriptionText" /> - <[object Object] + `; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx index 16bdbab9a7..bf4d5b6ccb 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx @@ -12,6 +12,8 @@ const ExplanationWidget = ({ // redux settings, learningContextId, + images, + isLibrary, // injected intl, }) => { @@ -39,6 +41,11 @@ const ExplanationWidget = ({ setEditorRef={setEditorRef} minHeight={150} placeholder={intl.formatMessage(messages.placeholder)} + {...{ + images, + isLibrary, + learningContextId, + }} /> ); @@ -49,12 +56,16 @@ ExplanationWidget.propTypes = { // eslint-disable-next-line settings: PropTypes.any.isRequired, learningContextId: PropTypes.string.isRequired, + images: PropTypes.shape({}).isRequired, + isLibrary: PropTypes.bool.isRequired, // injected intl: intlShape.isRequired, }; export const mapStateToProps = (state) => ({ settings: selectors.problem.settings(state), learningContextId: selectors.app.learningContextId(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), }); export const ExplanationWidgetInternal = ExplanationWidget; // For testing only diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx index 062330c190..294e06ac55 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx @@ -14,6 +14,8 @@ jest.mock('../../../../../data/redux', () => ({ }, app: { learningContextId: jest.fn(state => ({ learningContextId: state })), + images: jest.fn(state => ({ images: state })), + isLibrary: jest.fn(state => ({ isLibrary: state })), }, }, thunkActions: { @@ -35,6 +37,8 @@ describe('SolutionWidget', () => { const props = { settings: { solutionExplanation: 'This is my solution' }, learningContextId: 'course+org+run', + images: {}, + isLibrary: false, // injected intl: { formatMessage }, }; @@ -51,5 +55,15 @@ describe('SolutionWidget', () => { test('learningContextId from app.learningContextId', () => { expect(mapStateToProps(testState).learningContextId).toEqual(selectors.app.learningContextId(testState)); }); + test('images from app.images', () => { + expect( + mapStateToProps(testState).images, + ).toEqual(selectors.app.images(testState)); + }); + test('isLibrary from app.isLibrary', () => { + expect( + mapStateToProps(testState).isLibrary, + ).toEqual(selectors.app.isLibrary(testState)); + }); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap index 891bf8846f..d4fc2c7dac 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap @@ -13,13 +13,21 @@ exports[`QuestionWidget render snapshot: renders correct default 1`] = ` id="authoring.questionwidget.question.questionWidgetTitle" /> - <[object Object] + `; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx index a0ecde82a1..f1bc9f11d6 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx @@ -12,6 +12,8 @@ const QuestionWidget = ({ // redux question, learningContextId, + images, + isLibrary, // injected intl, }) => { @@ -36,6 +38,11 @@ const QuestionWidget = ({ setEditorRef={setEditorRef} minHeight={150} placeholder={intl.formatMessage(messages.placeholder)} + {...{ + images, + isLibrary, + learningContextId, + }} /> ); @@ -45,12 +52,16 @@ QuestionWidget.propTypes = { // redux question: PropTypes.string.isRequired, learningContextId: PropTypes.string.isRequired, + images: PropTypes.shape({}).isRequired, + isLibrary: PropTypes.bool.isRequired, // injected intl: intlShape.isRequired, }; export const mapStateToProps = (state) => ({ question: selectors.problem.question(state), learningContextId: selectors.app.learningContextId(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), }); export const QuestionWidgetInternal = QuestionWidget; // For testing only diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx index c867da970a..da4df5a7ae 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx @@ -16,6 +16,8 @@ jest.mock('../../../../../data/redux', () => ({ selectors: { app: { learningContextId: jest.fn(state => ({ learningContextId: state })), + images: jest.fn(state => ({ images: state })), + isLibrary: jest.fn(state => ({ isLibrary: state })), }, problem: { question: jest.fn(state => ({ question: state })), @@ -41,6 +43,8 @@ describe('QuestionWidget', () => { question: 'This is my question', updateQuestion: jest.fn(), learningContextId: 'course+org+run', + images: {}, + isLibrary: false, // injected intl: { formatMessage }, }; @@ -57,5 +61,15 @@ describe('QuestionWidget', () => { test('learningContextId from app.learningContextId', () => { expect(mapStateToProps(testState).learningContextId).toEqual(selectors.app.learningContextId(testState)); }); + test('images from app.images', () => { + expect( + mapStateToProps(testState).images, + ).toEqual(selectors.app.images(testState)); + }); + test('isLibrary from app.isLibrary', () => { + expect( + mapStateToProps(testState).isLibrary, + ).toEqual(selectors.app.isLibrary(testState)); + }); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap index 2042e3f432..0fcfc6ed06 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap @@ -22,6 +22,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget for Advanced className="mt-3" > @@ -106,6 +109,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = ` className="mt-3" > @@ -190,6 +196,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced className="mt-3" > diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx index 6c858b7361..095c44eddd 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx @@ -36,6 +36,9 @@ const SettingsWidget = ({ updateField, updateAnswer, defaultSettings, + images, + isLibrary, + learningContextId, }) => { const { isAdvancedCardsVisible, showAdvancedCards } = showAdvancedSettingsCards(); @@ -85,7 +88,16 @@ const SettingsWidget = ({ />
- +
{feedbackCard()}
@@ -172,6 +184,9 @@ SettingsWidget.propTypes = { showResetButton: PropTypes.bool, rerandomize: PropTypes.string, }).isRequired, + images: PropTypes.shape({}).isRequired, + learningContextId: PropTypes.string.isRequired, + isLibrary: PropTypes.bool.isRequired, // eslint-disable-next-line settings: PropTypes.any.isRequired, }; @@ -183,6 +198,9 @@ const mapStateToProps = (state) => ({ blockTitle: selectors.app.blockTitle(state), correctAnswerCount: selectors.problem.correctAnswerCount(state), defaultSettings: selectors.problem.defaultSettings(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), + learningContextId: selectors.app.learningContextId(state), }); export const mapDispatchToProps = { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx index a432a5730d..1630753c05 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx @@ -30,6 +30,9 @@ describe('SettingsWidget', () => { showanswer: 'finished', showResetButton: false, }, + images: {}, + isLibrary: false, + learningContextId: 'course+org+run', }; describe('behavior', () => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.jsx index 55817d51a1..31fa88ca63 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.jsx @@ -16,6 +16,9 @@ const HintRow = ({ handleChange, handleDelete, id, + images, + isLibrary, + learningContextId, // injected intl, }) => ( @@ -26,6 +29,11 @@ const HintRow = ({ setContent={handleChange} placeholder={intl.formatMessage(messages.hintInputLabel)} id={`hint-${id}`} + {...{ + images, + isLibrary, + learningContextId, + }} />
@@ -45,6 +53,9 @@ HintRow.propTypes = { handleChange: PropTypes.func.isRequired, handleDelete: PropTypes.func.isRequired, id: PropTypes.string.isRequired, + images: PropTypes.shape({}).isRequired, + learningContextId: PropTypes.string.isRequired, + isLibrary: PropTypes.bool.isRequired, // injected intl: intlShape.isRequired, }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.test.jsx index 3d7673ec9f..89ec54b234 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintRow.test.jsx @@ -11,6 +11,9 @@ describe('HintRow', () => { handleDelete: jest.fn(), id: '0', intl: { formatMessage }, + images: {}, + isLibrary: false, + learningContextId: 'course+org+run', }; describe('snapshot', () => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx index cdb399d91f..dc7a059073 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx @@ -12,6 +12,9 @@ const HintsCard = ({ hints, problemType, updateSettings, + images, + isLibrary, + learningContextId, // inject intl, }) => { @@ -31,7 +34,12 @@ const HintsCard = ({ key={hint.id} id={hint.id} value={hint.value} - {...hintsRowHooks(hint.id, hints, updateSettings)} + {...{ + ...hintsRowHooks(hint.id, hints, updateSettings), + images, + isLibrary, + learningContextId, + }} /> ))}
@@ -73,11 +77,7 @@ exports[`TextEditor snapshots loaded, raw editor 1`] = ` onClose={[MockFunction hooks.nullMethod]} show={false} > - + Error: Could Not Load Text Content - + Error: Could Not Load Text Content
- + Error: Could Not Load Text Content - <[object Object] +
@@ -216,13 +216,10 @@ exports[`TextEditor snapshots renders static images with relative paths 1`] = ` onClose={[MockFunction hooks.nullMethod]} show={false} > - + Error: Could Not Load Text Content - <[object Object] + " editorRef={ { @@ -233,9 +230,16 @@ exports[`TextEditor snapshots renders static images with relative paths 1`] = ` } editorType="text" height="100%" + id={null} + images={{}} initializeEditor={[MockFunction args.intializeEditor]} + isLibrary={null} + learningContextId="course+org+run" + lmsEndpointUrl="" minHeight={500} + onChange={[Function]} setEditorRef={[MockFunction hooks.prepareEditorRef.setEditorRef]} + studioEndpointUrl="" />
diff --git a/src/editors/containers/TextEditor/index.jsx b/src/editors/containers/TextEditor/index.jsx index 830f3b510a..c4b2215e93 100644 --- a/src/editors/containers/TextEditor/index.jsx +++ b/src/editors/containers/TextEditor/index.jsx @@ -6,7 +6,7 @@ import { Spinner, Toast, } from '@openedx/paragon'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { actions, selectors } from '../../data/redux'; import { RequestKeys } from '../../data/constants/requests'; @@ -28,6 +28,8 @@ const TextEditor = ({ initializeEditor, blockFinished, learningContextId, + images, + isLibrary, // inject intl, }) => { @@ -59,6 +61,11 @@ const TextEditor = ({ minHeight={500} height="100%" initializeEditor={initializeEditor} + {...{ + images, + isLibrary, + learningContextId, + }} /> ); }; @@ -71,7 +78,7 @@ const TextEditor = ({ >
- + { intl.formatMessage(messages.couldNotLoadTextContext) } {(!blockFinished) @@ -104,7 +111,9 @@ TextEditor.propTypes = { initializeEditor: PropTypes.func.isRequired, showRawEditor: PropTypes.bool.isRequired, blockFinished: PropTypes.bool, - learningContextId: PropTypes.string.isRequired, + learningContextId: PropTypes.string, // This should be required but is NULL when the store is in initial state :/ + images: PropTypes.shape({}).isRequired, + isLibrary: PropTypes.bool.isRequired, // inject intl: intlShape.isRequired, }; @@ -115,6 +124,8 @@ export const mapStateToProps = (state) => ({ showRawEditor: selectors.app.showRawEditor(state), blockFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchBlock }), learningContextId: selectors.app.learningContextId(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), }); export const mapDispatchToProps = { diff --git a/src/editors/containers/TextEditor/index.test.jsx b/src/editors/containers/TextEditor/index.test.jsx index ac337f68f7..3d6caeab8a 100644 --- a/src/editors/containers/TextEditor/index.test.jsx +++ b/src/editors/containers/TextEditor/index.test.jsx @@ -57,6 +57,7 @@ jest.mock('../../data/redux', () => ({ lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })), studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })), showRawEditor: jest.fn(state => ({ showRawEditor: state })), + images: jest.fn(state => ({ images: state })), isLibrary: jest.fn(state => ({ isLibrary: state })), learningContextId: jest.fn(state => ({ learningContextId: state })), }, @@ -82,6 +83,7 @@ describe('TextEditor', () => { showRawEditor: false, blockFinished: true, learningContextId: 'course+org+run', + images: {}, // inject intl: { formatMessage }, }; @@ -129,6 +131,11 @@ describe('TextEditor', () => { mapStateToProps(testState).learningContextId, ).toEqual(selectors.app.learningContextId(testState)); }); + test('images from app.images', () => { + expect( + mapStateToProps(testState).images, + ).toEqual(selectors.app.images(testState)); + }); }); describe('mapDispatchToProps', () => { diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap index 87b43324f1..fd175bb110 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap @@ -1,46 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ThumbnailWidget snapshots snapshots: renders as expected where thumbnail uploads are allowed 1`] = ` - - - - - - - - - Image used as thumbnail for video - - -`; +exports[`ThumbnailWidget snapshots snapshots: renders as expected where thumbnail uploads are allowed 1`] = `null`; exports[`ThumbnailWidget snapshots snapshots: renders as expected where videoId is valid 1`] = ` `; -exports[`ThumbnailWidget snapshots snapshots: renders as expected with default props 1`] = ` - - - - - - - - -
- -
- -
-
- - -
-
-`; +exports[`ThumbnailWidget snapshots snapshots: renders as expected with default props 1`] = `null`; exports[`ThumbnailWidget snapshots snapshots: renders as expected with isLibrary true 1`] = `null`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/index.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/index.jsx index 72226c50cf..47e66306c4 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/index.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/index.jsx @@ -61,7 +61,7 @@ const ThumbnailWidget = ({ } return intl.formatMessage(messages.unavailableSubtitle); }; - return (!isLibrary ? ( + return (!isLibrary && edxVideo ? ( - {(allowThumbnailUpload && edxVideo) ? null : ( + {!allowThumbnailUpload && ( @@ -90,7 +90,7 @@ const ThumbnailWidget = ({ src={thumbnailSrc || thumbnail} alt={intl.formatMessage(messages.thumbnailAltText)} /> - {(allowThumbnailUpload && edxVideo) ? ( + {allowThumbnailUpload && ( - ) : null } + )} ) : ( @@ -115,7 +115,7 @@ const ThumbnailWidget = ({ iconBefore={FileUpload} onClick={fileInput.click} variant="link" - disabled={!(allowThumbnailUpload && edxVideo)} + disabled={!allowThumbnailUpload} > diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx index fc3100c38f..a527e3404d 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx @@ -39,8 +39,16 @@ export const sourceHooks = ({ dispatch, previousVideoId, setAlert }) => ({ export const fallbackHooks = ({ fallbackVideos, dispatch }) => ({ addFallbackVideo: () => dispatch(actions.video.updateField({ fallbackVideos: [...fallbackVideos, ''] })), + /** + * Deletes the first occurrence of the given videoUrl from the fallbackVideos list + * @param {string} videoUrl - the video URL to delete + */ deleteFallbackVideo: (videoUrl) => { - const updatedFallbackVideos = fallbackVideos.splice(fallbackVideos.indexOf(videoUrl), 1); + const index = fallbackVideos.findIndex(video => video === videoUrl); + const updatedFallbackVideos = [ + ...fallbackVideos.slice(0, index), + ...fallbackVideos.slice(index + 1), + ]; dispatch(actions.video.updateField({ fallbackVideos: updatedFallbackVideos })); }, }); diff --git a/src/editors/data/redux/app/selectors.js b/src/editors/data/redux/app/selectors.js index 9976eee19f..43e7d6861a 100644 --- a/src/editors/data/redux/app/selectors.js +++ b/src/editors/data/redux/app/selectors.js @@ -40,12 +40,35 @@ export const returnUrl = createSelector( ), ); +export const isLibrary = createSelector( + [ + module.simpleSelectors.learningContextId, + module.simpleSelectors.blockId, + ], + (learningContextId, blockId) => { + if (learningContextId && learningContextId.startsWith('library-v1')) { + return true; + } + if (blockId && blockId.startsWith('lb:')) { + return true; + } + return false; + }, +); + export const isInitialized = createSelector( [ module.simpleSelectors.unitUrl, module.simpleSelectors.blockValue, + module.isLibrary, ], - (unitUrl, blockValue) => !!(unitUrl && blockValue), + (unitUrl, blockValue, isLibraryBlock) => { + if (isLibraryBlock) { + return !!blockValue; + } + + return !!blockValue && !!unitUrl; + }, ); export const displayTitle = createSelector( @@ -77,22 +100,6 @@ export const analytics = createSelector( ), ); -export const isLibrary = createSelector( - [ - module.simpleSelectors.learningContextId, - module.simpleSelectors.blockId, - ], - (learningContextId, blockId) => { - if (learningContextId && learningContextId.startsWith('library-v1')) { - return true; - } - if (blockId && blockId.startsWith('lb:')) { - return true; - } - return false; - }, -); - export default { ...simpleSelectors, isInitialized, diff --git a/src/editors/data/redux/app/selectors.test.js b/src/editors/data/redux/app/selectors.test.js index 33a022e0b6..061a0b5dd4 100644 --- a/src/editors/data/redux/app/selectors.test.js +++ b/src/editors/data/redux/app/selectors.test.js @@ -78,24 +78,40 @@ describe('app selectors unit tests', () => { }); }); describe('isInitialized selector', () => { - it('is memoized based on unitUrl, editorInitialized, and blockValue', () => { + it('is memoized based on editorInitialized, unitUrl, isLibrary and blockValue', () => { expect(selectors.isInitialized.preSelectors).toEqual([ simpleSelectors.unitUrl, simpleSelectors.blockValue, + selectors.isLibrary, ]); }); - it('returns true iff unitUrl, blockValue, and editorInitialized are all truthy', () => { - const { cb } = selectors.isInitialized; - const truthy = { - url: { url: 'data' }, - blockValue: { block: 'value' }, - }; + describe('for library blocks', () => { + it('returns true if blockValue, and editorInitialized are truthy', () => { + const { cb } = selectors.isInitialized; + const truthy = { + blockValue: { block: 'value' }, + }; - [ - [[null, truthy.blockValue], false], - [[truthy.url, null], false], - [[truthy.url, truthy.blockValue], true], - ].map(([args, expected]) => expect(cb(...args)).toEqual(expected)); + [ + [[null, truthy.blockValue, true], true], + [[null, null, true], false], + ].map(([args, expected]) => expect(cb(...args)).toEqual(expected)); + }); + }); + describe('for course blocks', () => { + it('returns true if blockValue, unitUrl, and editorInitialized are truthy', () => { + const { cb } = selectors.isInitialized; + const truthy = { + blockValue: { block: 'value' }, + unitUrl: { url: 'data' }, + }; + + [ + [[null, truthy.blockValue, false], false], + [[truthy.unitUrl, null, false], false], + [[truthy.unitUrl, truthy.blockValue, false], true], + ].map(([args, expected]) => expect(cb(...args)).toEqual(expected)); + }); }); }); describe('displayTitle', () => { diff --git a/src/editors/data/redux/thunkActions/app.js b/src/editors/data/redux/thunkActions/app.js index fa50c91a06..2e210bb80d 100644 --- a/src/editors/data/redux/thunkActions/app.js +++ b/src/editors/data/redux/thunkActions/app.js @@ -89,7 +89,9 @@ export const initialize = (data) => (dispatch) => { const editorType = data.blockType; dispatch(actions.app.initialize(data)); dispatch(module.fetchBlock()); - dispatch(module.fetchUnit()); + if (data.blockId?.startsWith('block-v1:')) { + dispatch(module.fetchUnit()); + } switch (editorType) { case 'problem': dispatch(module.fetchImages({ pageNumber: 0 })); @@ -100,7 +102,12 @@ export const initialize = (data) => (dispatch) => { dispatch(module.fetchCourseDetails()); break; case 'html': - dispatch(module.fetchImages({ pageNumber: 0 })); + if (data.learningContextId?.startsWith('lib:')) { + // eslint-disable-next-line no-console + console.log('Not fetching image assets - not implemented yet for content libraries.'); + } else { + dispatch(module.fetchImages({ pageNumber: 0 })); + } break; default: break; diff --git a/src/editors/data/redux/thunkActions/app.test.js b/src/editors/data/redux/thunkActions/app.test.js index 2c962b2853..5cf52f7941 100644 --- a/src/editors/data/redux/thunkActions/app.test.js +++ b/src/editors/data/redux/thunkActions/app.test.js @@ -187,7 +187,6 @@ describe('app thunkActions', () => { expect(dispatch.mock.calls).toEqual([ [actions.app.initialize(testValue)], [thunkActions.fetchBlock()], - [thunkActions.fetchUnit()], ]); thunkActions.fetchBlock = fetchBlock; thunkActions.fetchUnit = fetchUnit; @@ -216,6 +215,8 @@ describe('app thunkActions', () => { const data = { ...testValue, blockType: 'html', + blockId: 'block-v1:UniversityX+PHYS+1+type@problem+block@123', + learningContextId: 'course-v1:UniversityX+PHYS+1', }; thunkActions.initialize(data)(dispatch); expect(dispatch.mock.calls).toEqual([ @@ -251,6 +252,8 @@ describe('app thunkActions', () => { const data = { ...testValue, blockType: 'problem', + blockId: 'block-v1:UniversityX+PHYS+1+type@problem+block@123', + learningContextId: 'course-v1:UniversityX+PHYS+1', }; thunkActions.initialize(data)(dispatch); expect(dispatch.mock.calls).toEqual([ @@ -286,6 +289,8 @@ describe('app thunkActions', () => { const data = { ...testValue, blockType: 'video', + blockId: 'block-v1:UniversityX+PHYS+1+type@problem+block@123', + learningContextId: 'course-v1:UniversityX+PHYS+1', }; thunkActions.initialize(data)(dispatch); expect(dispatch.mock.calls).toEqual([ diff --git a/src/editors/data/services/cms/urls.js b/src/editors/data/services/cms/urls.js index de4e9f158a..d101ff9241 100644 --- a/src/editors/data/services/cms/urls.js +++ b/src/editors/data/services/cms/urls.js @@ -38,11 +38,7 @@ export const blockAncestor = ({ studioEndpointUrl, blockId }) => { if (blockId.includes('block-v1')) { return `${block({ studioEndpointUrl, blockId })}?fields=ancestorInfo`; } - // this url only need to get info to build the return url, which isn't used by V2 blocks - // (temporary) don't throw error, just return empty url. it will fail it's network connection but otherwise - // the app will run - // throw new Error('Block ancestor not available (and not needed) for V2 blocks'); - return ''; + throw new Error('Block ancestor not available (and not needed) for V2 blocks'); }; export const blockStudioView = ({ studioEndpointUrl, blockId }) => ( diff --git a/src/editors/data/services/cms/urls.test.js b/src/editors/data/services/cms/urls.test.js index bbaf1cbcff..94b39828c7 100644 --- a/src/editors/data/services/cms/urls.test.js +++ b/src/editors/data/services/cms/urls.test.js @@ -95,14 +95,9 @@ describe('cms url methods', () => { expect(blockAncestor({ studioEndpointUrl, blockId })) .toEqual(`${block({ studioEndpointUrl, blockId })}?fields=ancestorInfo`); }); - // This test will probably be used in the future - // it('throws error with studioEndpointUrl, v2 blockId and ancestor query', () => { - // expect(() => { blockAncestor({ studioEndpointUrl, blockId: v2BlockId }); }) - // .toThrow('Block ancestor not available (and not needed) for V2 blocks'); - // }); - it('returns blank url with studioEndpointUrl, v2 blockId and ancestor query', () => { - expect(blockAncestor({ studioEndpointUrl, blockId: v2BlockId })) - .toEqual(''); + it('throws error with studioEndpointUrl, v2 blockId and ancestor query', () => { + expect(() => { blockAncestor({ studioEndpointUrl, blockId: v2BlockId }); }) + .toThrow('Block ancestor not available (and not needed) for V2 blocks'); }); }); describe('blockStudioView', () => { diff --git a/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap b/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap index d28c1dd97c..a0bf40260a 100644 --- a/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap @@ -1,17 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TinyMceWidget snapshots ImageUploadModal is not rendered 1`] = ` - + - + `; exports[`TinyMceWidget snapshots SourcecodeModal is not rendered 1`] = ` - + @@ -139,21 +119,11 @@ exports[`TinyMceWidget snapshots SourcecodeModal is not rendered 1`] = ` id="sOMeiD" onEditorChange={[Function]} /> - + `; exports[`TinyMceWidget snapshots renders as expected with default behavior 1`] = ` - + @@ -228,5 +198,5 @@ exports[`TinyMceWidget snapshots renders as expected with default behavior 1`] = id="sOMeiD" onEditorChange={[Function]} /> - + `; diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index 2353a158ed..b74488b4b6 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -4,6 +4,7 @@ import { useCallback, useEffect, } from 'react'; +import { getConfig } from '@edx/frontend-platform'; import { getLocale, isRtl } from '@edx/frontend-platform/i18n'; import { a11ycheckerCss } from 'frontend-components-tinymce-advanced-plugins'; import { isEmpty } from 'lodash'; @@ -234,8 +235,6 @@ export const editorConfig = ({ setEditorRef, editorContentHtml, images, - lmsEndpointUrl, - studioEndpointUrl, isLibrary, placeholder, initializeEditor, @@ -247,6 +246,8 @@ export const editorConfig = ({ minHeight, learningContextId, }) => { + const lmsEndpointUrl = getConfig().LMS_BASE_URL; + const studioEndpointUrl = getConfig().STUDIO_BASE_URL; const { toolbar, config, diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.test.js b/src/editors/sharedComponents/TinyMceWidget/hooks.test.js index 7f9ee94bcb..bc7cb3f904 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.test.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.test.js @@ -1,4 +1,5 @@ import 'CourseAuthoring/editors/setupEditorTest'; +import { getConfig } from '@edx/frontend-platform'; import { MockUseState } from '../../testUtils'; import * as tinyMCE from '../../data/constants/tinyMCE'; @@ -125,7 +126,7 @@ describe('TinyMceEditor hooks', () => { const setImage = jest.fn(); const updateContent = jest.fn(); const editorType = 'expandable'; - const lmsEndpointUrl = 'sOmEvaLue.cOm'; + const lmsEndpointUrl = getConfig().LMS_BASE_URL; const editor = { ui: { registry: { addButton, addToggleButton, addIcon } }, on: jest.fn(), @@ -190,7 +191,7 @@ describe('TinyMceEditor hooks', () => { describe('replaceStaticWithAsset', () => { const initialContent = `test`; const learningContextId = 'course-v1:org+test+run'; - const lmsEndpointUrl = 'sOmEvaLue.cOm'; + const lmsEndpointUrl = getConfig().LMS_BASE_URL; it('returns updated src for text editor to update content', () => { const expected = `test`; const actual = module.replaceStaticWithAsset({ initialContent, learningContextId }); @@ -216,7 +217,7 @@ describe('TinyMceEditor hooks', () => { describe('setAssetToStaticUrl', () => { it('returns content with updated img links', () => { const editorValue = ` testing link`; - const lmsEndpointUrl = 'sOmEvaLue.cOm'; + const lmsEndpointUrl = getConfig().LMS_BASE_URL; const content = module.setAssetToStaticUrl({ editorValue, lmsEndpointUrl }); expect(content).toEqual(' testing link'); }); @@ -226,8 +227,8 @@ describe('TinyMceEditor hooks', () => { const props = { editorContentHtml: null, editorType: 'text', - lmsEndpointUrl: 'sOmEuRl.cOm', - studioEndpointUrl: 'sOmEoThEruRl.cOm', + lmsEndpointUrl: getConfig().LMS_BASE_URL, + studioEndpointUrl: getConfig().STUDIO_BASE_URL, images: mockImagesRef, isLibrary: false, learningContextId: 'course+org+run', diff --git a/src/editors/sharedComponents/TinyMceWidget/index.jsx b/src/editors/sharedComponents/TinyMceWidget/index.jsx index 601af1bf87..b311e863a0 100644 --- a/src/editors/sharedComponents/TinyMceWidget/index.jsx +++ b/src/editors/sharedComponents/TinyMceWidget/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Provider, connect } from 'react-redux'; import PropTypes from 'prop-types'; import { Editor } from '@tinymce/tinymce-react'; +import { getConfig } from '@edx/frontend-platform'; import 'tinymce'; import 'tinymce/themes/silver'; @@ -9,8 +9,6 @@ import 'tinymce/skins/ui/oxide/skin.css'; import 'tinymce/icons/default'; import 'frontend-components-tinymce-advanced-plugins'; -import store from '../../data/store'; -import { selectors } from '../../data/redux'; import ImageUploadModal from '../ImageUploadModal'; import SourceCodeModal from '../SourceCodeModal'; import * as hooks from './hooks'; @@ -42,41 +40,37 @@ const TinyMceWidget = ({ disabled, id, editorContentHtml, // editorContent in html form - // redux learningContextId, images, isLibrary, - lmsEndpointUrl, - studioEndpointUrl, onChange, ...editorConfig }) => { const { isImgOpen, openImgModal, closeImgModal } = hooks.imgModalToggle(); const { isSourceCodeOpen, openSourceCodeModal, closeSourceCodeModal } = hooks.sourceCodeModalToggle(editorRef); const { imagesRef } = hooks.useImages({ images, editorContentHtml }); - const imageSelection = hooks.selectedImage(null); return ( - - {isLibrary ? null : ( + <> + {!isLibrary && ( )} - {editorType === 'text' ? ( + {editorType === 'text' && ( - ) : null} + )} - + ); }; TinyMceWidget.defaultProps = { isLibrary: null, editorType: null, editorRef: null, - lmsEndpointUrl: null, - studioEndpointUrl: null, + lmsEndpointUrl: '', + studioEndpointUrl: '', images: null, id: null, disabled: false, @@ -116,7 +108,7 @@ TinyMceWidget.defaultProps = { ...editorConfigDefaultProps, }; TinyMceWidget.propTypes = { - learningContextId: PropTypes.string, + learningContextId: PropTypes.string.isRequired, editorType: PropTypes.string, isLibrary: PropTypes.bool, images: PropTypes.shape({}), @@ -131,13 +123,5 @@ TinyMceWidget.propTypes = { ...editorConfigPropTypes, }; -export const mapStateToProps = (state) => ({ - images: selectors.app.images(state), - lmsEndpointUrl: selectors.app.lmsEndpointUrl(state), - studioEndpointUrl: selectors.app.studioEndpointUrl(state), - isLibrary: selectors.app.isLibrary(state), - learningContextId: selectors.app.learningContextId(state), -}); - export const TinyMceWidgetInternal = TinyMceWidget; // For testing only -export default (connect(mapStateToProps)(TinyMceWidget)); +export default TinyMceWidget; diff --git a/src/editors/sharedComponents/TinyMceWidget/index.test.jsx b/src/editors/sharedComponents/TinyMceWidget/index.test.jsx index c0064c2d2b..741e196ad3 100644 --- a/src/editors/sharedComponents/TinyMceWidget/index.test.jsx +++ b/src/editors/sharedComponents/TinyMceWidget/index.test.jsx @@ -1,10 +1,9 @@ import React from 'react'; import { shallow } from '@edx/react-unit-test-utils'; -import { selectors } from '../../data/redux'; import SourceCodeModal from '../SourceCodeModal'; import ImageUploadModal from '../ImageUploadModal'; import { imgModalToggle, sourceCodeModalToggle } from './hooks'; -import { TinyMceWidgetInternal as TinyMceWidget, mapStateToProps } from '.'; +import { TinyMceWidgetInternal as TinyMceWidget } from '.'; const staticUrl = '/assets/sOmEaSsET'; @@ -22,20 +21,6 @@ jest.mock('@tinymce/tinymce-react', () => { jest.mock('../ImageUploadModal', () => 'ImageUploadModal'); jest.mock('../SourceCodeModal', () => 'SourceCodeModal'); -jest.mock('../../data/redux', () => ({ - __esModule: true, - default: jest.fn(), - selectors: { - app: { - lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })), - studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })), - isLibrary: jest.fn(state => ({ isLibrary: state })), - images: jest.fn(state => ({ images: state })), - learningContextId: jest.fn(state => ({ learningContextId: state })), - }, - }, -})); - jest.mock('./hooks', () => ({ editorConfig: jest.fn(args => ({ editorConfig: args })), imgModalToggle: jest.fn(() => ({ @@ -56,15 +41,6 @@ jest.mock('./hooks', () => ({ useImages: jest.fn(() => ({ imagesRef: { current: [{ externalUrl: staticUrl }] } })), })); -jest.mock('react-redux', () => ({ - Provider: 'Provider', - connect: (mapStateToProp, mapDispatchToProps) => (component) => ({ - mapStateToProp, - mapDispatchToProps, - component, - }), -})); - describe('TinyMceWidget', () => { const props = { editorType: 'text', @@ -103,32 +79,4 @@ describe('TinyMceWidget', () => { expect(wrapper.instance.findByType(ImageUploadModal).length).toBe(0); }); }); - describe('mapStateToProps', () => { - const testState = { A: 'pple', B: 'anana', C: 'ucumber' }; - test('lmsEndpointUrl from app.lmsEndpointUrl', () => { - expect( - mapStateToProps(testState).lmsEndpointUrl, - ).toEqual(selectors.app.lmsEndpointUrl(testState)); - }); - test('studioEndpointUrl from app.studioEndpointUrl', () => { - expect( - mapStateToProps(testState).studioEndpointUrl, - ).toEqual(selectors.app.studioEndpointUrl(testState)); - }); - test('images from app.images', () => { - expect( - mapStateToProps(testState).images, - ).toEqual(selectors.app.images(testState)); - }); - test('isLibrary from app.isLibrary', () => { - expect( - mapStateToProps(testState).isLibrary, - ).toEqual(selectors.app.isLibrary(testState)); - }); - test('learningContextId from app.learningContextId', () => { - expect( - mapStateToProps(testState).learningContextId, - ).toEqual(selectors.app.learningContextId(testState)); - }); - }); }); diff --git a/src/files-and-videos/files-page/FileValidationModal.jsx b/src/files-and-videos/files-page/FileValidationModal.jsx index f3a45deeb3..1e1a310b39 100644 --- a/src/files-and-videos/files-page/FileValidationModal.jsx +++ b/src/files-and-videos/files-page/FileValidationModal.jsx @@ -32,6 +32,7 @@ const FileValidationModal = ({ title={intl.formatMessage(messages.overwriteModalTitle)} isOpen={isOpen} onClose={close} + isOverflowVisible={false} > diff --git a/src/frontend-platform.d.ts b/src/frontend-platform.d.ts new file mode 100644 index 0000000000..d9b2f7f91b --- /dev/null +++ b/src/frontend-platform.d.ts @@ -0,0 +1,41 @@ +// frontend-platform currently doesn't provide types... do it ourselves for i18n module at least. +// We can remove this in the future when we migrate to frontend-shell, or when frontend-platform gets types +// (whichever comes first). + +declare module '@edx/frontend-platform/i18n' { + // eslint-disable-next-line import/no-extraneous-dependencies + import { injectIntl as _injectIntl } from 'react-intl'; + /** @deprecated Use useIntl() hook instead. */ + export const injectIntl: typeof _injectIntl; + /** @deprecated Use useIntl() hook instead. */ + export const intlShape: any; + + // eslint-disable-next-line import/no-extraneous-dependencies + export { + createIntl, + FormattedDate, + FormattedTime, + FormattedRelativeTime, + FormattedNumber, + FormattedPlural, + FormattedMessage, + defineMessages, + IntlProvider, + useIntl, + } from 'react-intl'; + + // Other exports from the i18n module: + export const configure: any; + export const getPrimaryLanguageSubtag: (code: string) => string; + export const getLocale: (locale?: string) => string; + export const getMessages: any; + export const isRtl: (locale?: string) => boolean; + export const handleRtl: any; + export const mergeMessages: any; + export const LOCALE_CHANGED: any; + export const LOCALE_TOPIC: any; + export const getCountryList: any; + export const getCountryMessages: any; + export const getLanguageList: any; + export const getLanguageMessages: any; +} diff --git a/src/generic/FormikControl.jsx b/src/generic/FormikControl.tsx similarity index 62% rename from src/generic/FormikControl.jsx rename to src/generic/FormikControl.tsx index 048ad991ab..e3d56af966 100644 --- a/src/generic/FormikControl.jsx +++ b/src/generic/FormikControl.tsx @@ -1,16 +1,25 @@ -/* eslint-disable react/jsx-no-useless-fragment */ +import React from 'react'; import { Form } from '@openedx/paragon'; import { getIn, useFormikContext } from 'formik'; -import PropTypes from 'prop-types'; -import React from 'react'; import FormikErrorFeedback from './FormikErrorFeedback'; -const FormikControl = ({ +interface Props { + name: string; + label?: React.ReactElement; + help?: React.ReactElement; + className?: string; + controlClasses?: string; + value: string | number; +} + +const FormikControl: React.FC> = ({ name, - label, - help, - className, - controlClasses, + // eslint-disable-next-line react/jsx-no-useless-fragment + label = <>, + // eslint-disable-next-line react/jsx-no-useless-fragment + help = <>, + className = '', + controlClasses = 'pb-2', ...params }) => { const { @@ -39,23 +48,4 @@ const FormikControl = ({ ); }; -FormikControl.propTypes = { - name: PropTypes.string.isRequired, - label: PropTypes.element, - help: PropTypes.element, - className: PropTypes.string, - controlClasses: PropTypes.string, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]).isRequired, -}; - -FormikControl.defaultProps = { - help: <>, - label: <>, - className: '', - controlClasses: 'pb-2', -}; - export default FormikControl; diff --git a/src/generic/WysiwygEditor.jsx b/src/generic/WysiwygEditor.jsx index dd326181cf..c321cca52b 100644 --- a/src/generic/WysiwygEditor.jsx +++ b/src/generic/WysiwygEditor.jsx @@ -1,32 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { connect, Provider, useSelector } from 'react-redux'; -import { createStore } from 'redux'; -import { getConfig } from '@edx/frontend-platform'; +import { useSelector } from 'react-redux'; import TinyMceWidget, { prepareEditorRef } from '../editors/sharedComponents/TinyMceWidget'; import { DEFAULT_EMPTY_WYSIWYG_VALUE } from '../constants'; -const store = createStore(() => ({})); - export const SUPPORTED_TEXT_EDITORS = { text: 'text', expandable: 'expandable', }; -const mapStateToProps = () => ({ - images: {}, - lmsEndpointUrl: getConfig().LMS_BASE_URL, - studioEndpointUrl: getConfig().STUDIO_BASE_URL, - isLibrary: true, - onEditorChange: () => ({}), -}); -const Editor = connect(mapStateToProps)(TinyMceWidget); - export const WysiwygEditor = ({ initialValue, editorType, onChange, minHeight, }) => { - // const courseId = "course+test+test+test" const { editorRef, refReady, setEditorRef } = prepareEditorRef(); const { courseId } = useSelector((state) => state.courseDetail); const isEquivalentCodeExtraSpaces = (first, second) => { @@ -61,20 +47,21 @@ export const WysiwygEditor = ({ } return ( - - ({})} - learningContextId={courseId} - /> - + ({})} + learningContextId={courseId} + images={{}} + isLibrary + onEditorChange={() => ({})} + /> ); }; diff --git a/src/generic/block-type-utils/constants.ts b/src/generic/block-type-utils/constants.ts index 9b6cee0993..792ab33ace 100644 --- a/src/generic/block-type-utils/constants.ts +++ b/src/generic/block-type-utils/constants.ts @@ -51,6 +51,7 @@ export const STRUCTURAL_TYPE_ICONS: Record = { vertical: UNIT_TYPE_ICONS_MAP.vertical, sequential: Folder, chapter: Folder, + collection: Folder, }; export const COMPONENT_TYPE_STYLE_COLOR_MAP = { diff --git a/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.jsx b/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.jsx index cffbf53f24..36bafc5974 100644 --- a/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.jsx +++ b/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.jsx @@ -16,7 +16,7 @@ import TypeaheadDropdown from '../../editors/sharedComponents/TypeaheadDropdown' import AlertMessage from '../alert-message'; import { STATEFUL_BUTTON_STATES } from '../../constants'; -import { RequestStatus } from '../../data/constants'; +import { RequestStatus, TOTAL_LENGTH_KEY } from '../../data/constants'; import { getSavingStatus } from '../data/selectors'; import { getStudioHomeData } from '../../studio-home/data/selectors'; import { updatePostErrors } from '../data/slice'; @@ -132,6 +132,8 @@ const CreateOrRerunCourseForm = ({ }, ]; + const errorMessage = errors[TOTAL_LENGTH_KEY] || postErrors?.errMsg; + const createButtonState = { labels: { default: intl.formatMessage(isCreateNewCourse ? messages.createButton : messages.rerunCreateButton), @@ -202,11 +204,11 @@ const CreateOrRerunCourseForm = ({ return (
- {showErrorBanner ? ( + {(errors[TOTAL_LENGTH_KEY] || showErrorBanner) ? (