diff --git a/.babelrc b/.babelrc index f3ee889..3a27f8c 100644 --- a/.babelrc +++ b/.babelrc @@ -10,41 +10,11 @@ "transform-object-assign", "transform-flow-strip-types", "@babel/plugin-proposal-class-properties", - "@babel/plugin-transform-runtime", - ["prismjs", { - "languages": [ - "csharp", - "css", - "go", - "groovy", - "java", - "javascript", - "kotlin", - "markdown", - "pascal", - "php", - "python", - "rust", - "scala", - "typescript", - "bash", - "cpp", - "haskell", - "cshtml", - "arduino", - "prolog", - "bash", - "sql", - "bison" - ], - "theme": "default", - "css": true - }] + "@babel/plugin-transform-runtime" ], "env": { "production": { "plugins": [ - "@babel/plugin-transform-react-inline-elements" ] } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22e9d8b..2f66e76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - node-version: ['20'] + node-version: ['22', '24'] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 diff --git a/bin/dev.js b/bin/dev.js index 7e1123a..2c2de19 100644 --- a/bin/dev.js +++ b/bin/dev.js @@ -21,7 +21,8 @@ const app = new Express(); app.set('view engine', 'ejs'); app.use(urlPrefix, Express.static(path.join(__dirname, '../public'))); -app.get('*', (req, res) => { +// 'splat' is the name of the * wildcard (new in Express 5.x) +app.get('*splat', (req, res) => { res.render('index', { html: '', head: { diff --git a/config/webpack.config-dev.js b/config/webpack.config-dev.js index e5346e9..4a0dd66 100644 --- a/config/webpack.config-dev.js +++ b/config/webpack.config-dev.js @@ -25,14 +25,19 @@ const gitRevisionPlugin = new GitRevisionPlugin({ }); export default { - devtool: process.env.NODE_ENV === 'development' ? 'eval-source-map' : 'none', + // switch the source map generation when debugging + // note, we used 'eval-source-map' before, but since webpack 5.100, it breaks the build + // (causes 'SyntaxError: redeclaration of function normalize') + // devtool: 'inline-source-map', + devtool: false, // turn it off completely + entry: path.join(__dirname, '..', 'src/client.js'), output: { filename: 'bundle.js', path: path.join(__dirname, '..', 'public'), publicPath: '/public/', }, - mode: 'development', + mode: process.env.NODE_ENV, resolve: { alias: { moment: 'moment/moment.js', diff --git a/config/webpack.server.js b/config/webpack.server.js index fe5cbd0..3ecd710 100644 --- a/config/webpack.server.js +++ b/config/webpack.server.js @@ -23,9 +23,8 @@ export default { resolve: { alias: { moment: 'moment/moment.js', - 'react-ace': 'react-ace-bad', // a hack to rid ourselves of ace module in server mode }, - fallback: { 'react-ace-bad': false }, + fallback: {}, }, target: 'node', mode: process.env.NODE_ENV, diff --git a/eslint.config.mjs b/eslint.config.mjs index 4748079..8e4ce17 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -41,7 +41,6 @@ export default [{ globals: { ...globals.browser, ...globals.node, - ...globals.mocha, __DEVELOPMENT__: true, __CLIENT__: true, __SERVER__: true, @@ -61,7 +60,7 @@ export default [{ 'import/ignore': ['node_modules', '.less$'], react: { - version: '18.3.1', + version: '19.2.0', }, }, diff --git a/package.json b/package.json index 09dabef..653fddd 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,6 @@ "build": "npm run clean && npm run build:server && npm run build:client", "build:test": "npm run clean && npm run build:server && npm run build:client", "lint": "eslint src", - "test": "mocha --recursive --timeout 2000 --require ./test/setup.js --require mock-local-storage", - "test:mocha": "mocha --recursive --timeout 2000 --require @babel/register --require ./test/setup.js --require mock-local-storage", - "test:watch": "mocha --recursive --timeout 2000 --require @babel/register --require ./test/setup.js --require mock-local-storage --watch --reporter nyan", "dev": "babel-node bin/dev.js --max-old-space-size=4096", "start": "node bin/server.mjs", "deploy": "mkdir -p ./prod && mkdir -p ./prod/etc && cp -rf ./views ./prod && cp -rf ./bin ./prod && cp -n ./etc/env.json.example ./prod/etc/env.json && rm -rf ./prod/public && cp -rf ./public ./prod", @@ -27,64 +24,52 @@ "format": "prettier --config .prettierrc --write \"src/**/*.js\"" }, "dependencies": { - "@babel/plugin-transform-react-inline-elements": "^7.25.9", - "@babel/runtime": "^7.26.0", - "@formatjs/intl-pluralrules": "^5.4.2", - "@formatjs/intl-relativetimeformat": "^11.4.9", - "@fortawesome/fontawesome-free": "^6.7.2", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-brands-svg-icons": "^6.7.2", - "@fortawesome/free-regular-svg-icons": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@fortawesome/react-fontawesome": "^0.2.2", + "@babel/plugin-transform-react-inline-elements": "^7.27.1", + "@babel/runtime": "^7.28.4", + "@formatjs/intl-pluralrules": "^5.4.6", + "@formatjs/intl-relativetimeformat": "^11.4.13", + "@fortawesome/fontawesome-free": "^7.1.0", + "@fortawesome/fontawesome-svg-core": "^7.1.0", + "@fortawesome/free-brands-svg-icons": "^7.1.0", + "@fortawesome/free-regular-svg-icons": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", + "@fortawesome/react-fontawesome": "^3.1.0", "@iktakahiro/markdown-it-katex": "^4.0.1", - "@reduxjs/toolkit": "^2.5.0", - "admin-lte": "4.0.0-beta3", - "babel-plugin-formatjs": "^10.5.34", - "bluebird": "^3.7.2", + "@reduxjs/toolkit": "^2.9.2", + "admin-lte": "4.0.0-rc4", + "babel-plugin-formatjs": "^10.5.41", "browser-cookies": "^1.2.0", "buffer": "^6.0.3", - "chai-immutable": "^2.1.0", "classnames": "^2.5.1", "cookie-parser": "^1.4.7", "cross-fetch": "^4.1.0", "css-loader": "^7.1.2", - "deep-equal": "^2.2.3", "ejs": "^3.1.10", - "express": "^4.21.2", + "express": "^5.1.0", "file-saver": "^2.0.5", "final-form": "^5.0.0", "flat": "^6.0.1", "font-awesome-animation": "^1.1.1", - "glob": "^11.0.1", - "global": "^4.4.0", + "glob": "^11.0.3", "highlight.js": "^11.11.1", - "immutable": "^5.0.3", + "immutable": "^5.1.4", "jwt-decode": "^4.0.0", "markdown-it": "^14.1.0", "moment": "^2.30.1", - "pretty-ms": "^9.2.0", - "prismjs": "^1.29.0", "prop-types": "^15.8.1", - "react": "^19.0.0", - "react-ace": "^13.0.0", - "react-bootstrap": "2.10.8", + "react": "^19.2.0", + "react-bootstrap": "2.10.10", "react-collapse": "^5.1.1", "react-copy-to-clipboard": "^5.1.0", "react-datetime": "^3.3.1", - "react-diff-viewer": "^3.1.1", - "react-dom": "^19.0.0", - "react-dropzone": "^14.3.5", + "react-dom": "^19.2.0", "react-final-form": "^7.0.0", - "react-height": "^3.0.2", "react-helmet": "^6.1.0", "react-immutable-proptypes": "^2.2.0", - "react-intl": "7.1.4", - "react-motion": "^0.5.2", + "react-intl": "7.1.14", "react-redux": "^9.2.0", - "react-router": "^7.1.3", - "react-router-dom": "^7.1.3", - "react-syntax-highlighter": "^15.6.1", + "react-router": "^7.9.5", + "react-router-dom": "^7.9.5", "react-toggle": "4.1.3", "redux": "^5.0.1", "redux-actions": "^3.0.3", @@ -93,80 +78,64 @@ "redux-storage-decorator-filter": "^1.1.8", "redux-storage-engine-localstorage": "^1.1.4", "reselect": "^5.1.1", - "serialize-javascript": "^6.0.2", "statuscode": "0.0.0", - "validator": "^13.12.0", - "viz.js": "^2.1.2" + "validator": "^13.15.20" }, "devDependencies": { - "@babel/cli": "^7.26.4", - "@babel/core": "^7.26.0", - "@babel/eslint-parser": "^7.26.5", - "@babel/eslint-plugin": "^7.25.9", - "@babel/node": "^7.26.0", + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.5", + "@babel/eslint-parser": "^7.28.5", + "@babel/eslint-plugin": "^7.27.1", + "@babel/node": "^7.28.0", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.26.0", - "@babel/preset-react": "^7.26.3", - "@babel/register": "^7.25.9", - "@eslint/compat": "^1.2.5", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.18.0", + "@babel/plugin-transform-runtime": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-react": "^7.28.5", + "@babel/register": "^7.28.3", + "@eslint/compat": "^1.4.1", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.38.0", "@formatjs/cli-lib": "^6.6.6", - "async": "^3.2.6", - "babel-loader": "^9.2.1", - "babel-plugin-prismjs": "^2.1.0", + "babel-loader": "^10.0.0", "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-object-assign": "^6.22.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-1": "^6.24.1", "babel-regenerator-runtime": "^6.5.0", - "chai": "^5.1.2", - "chai-spies": "^1.1.0", "colors": "^1.4.0", - "core-js": "^3.40.0", "css-loader": "^7.1.2", "css-modules-require-hook": "^4.2.3", - "dotenv": "^16.4.7", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", + "dotenv": "^17.2.3", + "eslint": "^9.38.0", + "eslint-config-prettier": "^10.1.8", "eslint-config-standard": "17.1.0", "eslint-config-standard-react": "13.0.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-n": "^17.15.1", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-n": "^17.23.1", "eslint-plugin-node": "11.1.0", "eslint-plugin-promise": "^7.2.1", - "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-standard": "^5.0.0", - "esm": "^3.2.25", - "fetch-mock": "^12.2.0", "file-loader": "^6.2.0", "git-revision-webpack-plugin": "^5.0.0", - "globals": "^15.14.0", + "globals": "^16.4.0", "image-webpack-loader": "^8.1.0", "isomorphic-style-loader": "^5.4.0", - "jsdom": "^26.0.0", - "json-loader": "^0.5.7", - "less": "^4.2.2", - "less-loader": "^12.2.0", - "mini-css-extract-plugin": "^2.9.2", - "mocha": "^11.1.0", - "mocha-lcov-reporter": "^1.3.0", - "mock-local-storage": "^1.1.24", - "postcss-loader": "^8.1.1", - "prettier": "^3.4.2", + "less": "^4.4.2", + "less-loader": "^12.3.0", + "mini-css-extract-plugin": "^2.9.4", + "postcss-loader": "^8.2.0", + "prettier": "^3.6.2", "react-intl-translations-manager": "^5.0.3", - "request": "^2.88.2", - "scroll-behavior": "^0.11.0", "strip-loader": "^0.1.2", "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.11", - "webpack": "^5.97.1", + "terser-webpack-plugin": "^5.3.14", + "webpack": "5.102.1", "webpack-cli": "^6.0.1", - "webpack-dev-middleware": "^7.4.2", - "webpack-dev-server": "^5.2.0", + "webpack-dev-middleware": "^7.4.5", + "webpack-dev-server": "^5.2.2", "webpack-isomorphic-tools": "^4.0.0" }, "packageManager": "yarn@3.2.1" diff --git a/src/client.js b/src/client.js index 125e645..295418b 100644 --- a/src/client.js +++ b/src/client.js @@ -13,48 +13,6 @@ import App from './containers/App/index.js'; import 'admin-lte/dist/js/adminlte.js'; -// Patch for ACE editor (it has complex loading) -import ace from 'ace-builds'; -import 'ace-builds/webpack-resolver.js'; -import 'ace-builds/src-noconflict/theme-monokai.js'; -import 'ace-builds/src-noconflict/theme-github.js'; -import 'ace-builds/src-noconflict/mode-c_cpp.js'; -import 'ace-builds/src-noconflict/mode-csharp.js'; -import 'ace-builds/src-noconflict/mode-css.js'; -import 'ace-builds/src-noconflict/mode-groovy.js'; -import 'ace-builds/src-noconflict/mode-html.js'; -import 'ace-builds/src-noconflict/mode-kotlin.js'; -import 'ace-builds/src-noconflict/mode-java.js'; -import 'ace-builds/src-noconflict/mode-javascript.js'; -import 'ace-builds/src-noconflict/mode-makefile.js'; -import 'ace-builds/src-noconflict/mode-markdown.js'; -import 'ace-builds/src-noconflict/mode-pascal.js'; -import 'ace-builds/src-noconflict/mode-php.js'; -import 'ace-builds/src-noconflict/mode-python.js'; -import 'ace-builds/src-noconflict/mode-rust.js'; -import 'ace-builds/src-noconflict/mode-scala.js'; -import 'ace-builds/src-noconflict/mode-typescript.js'; -import 'ace-builds/src-noconflict/keybinding-vim.js'; - -// override of worker paths, so they load properly -const ACE_CDN_PREFIX = 'https://cdn.jsdelivr.net/npm/ace-builds@1.4.12/src-noconflict/'; -ace.config.set('basePath', ACE_CDN_PREFIX); -const KNOWN_ACE_WORKERS = { - base_worker: 'worker-base', - css_worker: 'worker-css', - html_worker: 'worker-html', - javascript_worker: 'worker-javascript', - php_worker: 'worker-php', - xml_worker: 'worker-xml', -}; -Object.keys(KNOWN_ACE_WORKERS).forEach(key => { - ace.config.setModuleUrl(`ace/mode/${key}`, `${ACE_CDN_PREFIX}${KNOWN_ACE_WORKERS[key]}.js`); -}); - -// set Prismjs to manual mode (if present) -window.Prism = window.Prism || {}; -window.Prism.manual = true; - // load the initial state form the server - if any let state; const ini = window.__INITIAL_STATE__; diff --git a/src/components/buttons/DeleteButton/DeleteButton.js b/src/components/buttons/DeleteButton/DeleteButton.js deleted file mode 100644 index 4bdda09..0000000 --- a/src/components/buttons/DeleteButton/DeleteButton.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; - -import Button from '../../widgets/TheButton'; -import OptionalTooltipWrapper from '../../widgets/OptionalTooltipWrapper'; -import Confirm from '../../widgets/Confirm'; -import { isReady, isLoading, isDeleting, isDeleted } from '../../../redux/helpers/resourceManager'; -import { DeleteIcon, FailureIcon, LoadingIcon, SuccessIcon } from '../../icons'; - -// Button states - reflecting resource states -const states = { - DELETED: 'deleted', - LOADING: 'loading', - READY: 'ready', - DELETING: 'deleting', - FAILED: 'failed', -}; - -const getState = resource => { - if (!resource || isDeleted(resource)) return states.DELETED; - if (isLoading(resource)) return states.LOADING; - if (isReady(resource)) return states.READY; - if (isDeleting(resource)) return states.DELETING; - return states.FAILED; -}; - -const stateIcons = captionAsTooltip => { - const iconProps = { gapLeft: captionAsTooltip ? 1 : 0, gapRight: captionAsTooltip ? 1 : 2 }; - return { - [states.DELETED]: , - [states.LOADING]: , - [states.READY]: , - [states.DELETING]: , - [states.FAILED]: , - }; -}; - -const stateLabels = { - [states.DELETED]: , - [states.LOADING]: , - [states.READY]: , - [states.DELETING]: , - [states.FAILED]: , -}; - -const DeleteButtonInternal = ({ id, icon, label, disabled, small, captionAsTooltip, ...props }) => ( - - - -); - -const DeleteButton = ({ - id, - resource, - resourceless = false, - deleteAction, - disabled = false, - small = true, - captionAsTooltip = false, - question = ( - - ), - ...props -}) => { - const state = resourceless ? states.READY : getState(resource); - const icon = stateIcons(captionAsTooltip)[state]; - const label = stateLabels[state]; - - return state === states.READY ? ( - - - - ) : ( - - ); -}; - -DeleteButtonInternal.propTypes = { - id: PropTypes.string, - icon: PropTypes.element, - label: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), - disabled: PropTypes.bool, - small: PropTypes.bool, - captionAsTooltip: PropTypes.bool, -}; - -DeleteButton.propTypes = { - id: PropTypes.string, - resource: ImmutablePropTypes.map, - resourceless: PropTypes.bool, - question: PropTypes.any, - deleteAction: PropTypes.func.isRequired, - disabled: PropTypes.bool, - small: PropTypes.bool, - captionAsTooltip: PropTypes.bool, -}; - -export default DeleteButton; diff --git a/src/components/buttons/DeleteButton/index.js b/src/components/buttons/DeleteButton/index.js deleted file mode 100644 index 63efd8b..0000000 --- a/src/components/buttons/DeleteButton/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import DeleteButton from './DeleteButton.js'; -export default DeleteButton; diff --git a/src/components/buttons/JoinGroupButton/JoinGroupButton.js b/src/components/buttons/JoinGroupButton/JoinGroupButton.js deleted file mode 100644 index 5356702..0000000 --- a/src/components/buttons/JoinGroupButton/JoinGroupButton.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/TheButton'; -import Icon from '../../icons'; - -const JoinGroupButton = ({ onClick, ...props }) => ( - -); - -JoinGroupButton.propTypes = { - onClick: PropTypes.func.isRequired, -}; - -export default JoinGroupButton; diff --git a/src/components/buttons/JoinGroupButton/index.js b/src/components/buttons/JoinGroupButton/index.js deleted file mode 100644 index 604ee0d..0000000 --- a/src/components/buttons/JoinGroupButton/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import JoinGroupButton from './JoinGroupButton.js'; -export default JoinGroupButton; diff --git a/src/components/buttons/LeaveGroupButton/LeaveGroupButton.js b/src/components/buttons/LeaveGroupButton/LeaveGroupButton.js deleted file mode 100644 index 307e9b5..0000000 --- a/src/components/buttons/LeaveGroupButton/LeaveGroupButton.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/TheButton'; -import Icon from '../../icons'; -import Confirm from '../../widgets/Confirm'; - -const LeaveGroupButton = ({ onClick, ...props }) => ( - - }> - - -); - -LeaveGroupButton.propTypes = { - onClick: PropTypes.func.isRequired, -}; - -export default LeaveGroupButton; diff --git a/src/components/buttons/LeaveGroupButton/index.js b/src/components/buttons/LeaveGroupButton/index.js deleted file mode 100644 index 4f9ba43..0000000 --- a/src/components/buttons/LeaveGroupButton/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import LeaveGroupButton from './LeaveGroupButton.js'; -export default LeaveGroupButton; diff --git a/src/components/buttons/RemoveFromGroupButton/RemoveFromGroupButton.js b/src/components/buttons/RemoveFromGroupButton/RemoveFromGroupButton.js deleted file mode 100644 index ec3c25b..0000000 --- a/src/components/buttons/RemoveFromGroupButton/RemoveFromGroupButton.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/TheButton'; -import Icon from '../../icons'; -import Confirm from '../../widgets/Confirm'; - -const RemoveFromGroupButton = ({ onClick, ...props }) => ( - - }> - - -); - -RemoveFromGroupButton.propTypes = { - onClick: PropTypes.func.isRequired, -}; - -export default RemoveFromGroupButton; diff --git a/src/components/buttons/RemoveFromGroupButton/index.js b/src/components/buttons/RemoveFromGroupButton/index.js deleted file mode 100644 index 9879079..0000000 --- a/src/components/buttons/RemoveFromGroupButton/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import RemoveFromGroupButton from './RemoveFromGroupButton.js'; -export default RemoveFromGroupButton; diff --git a/src/components/helpers/LocalizedNames/LocalizedExerciseName.js b/src/components/helpers/LocalizedNames/LocalizedExerciseName.js deleted file mode 100644 index 127397d..0000000 --- a/src/components/helpers/LocalizedNames/LocalizedExerciseName.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl } from 'react-intl'; - -import Icon from '../../icons'; -import { getLocalizedName, getOtherLocalizedNames } from '../../../helpers/localizedData.js'; - -const LocalizedExerciseName = ({ entity, noNameMessage = '??', intl: { locale } }) => { - const otherNames = getOtherLocalizedNames(entity, locale); - const name = getLocalizedName(entity, locale); - return name ? ( - - {name} - {otherNames.length > 0 && ( - - n.name).join(', ')} - tooltip={otherNames.map((name, i) => ( -
- {name.name} [{name.locale}] -
- ))} - /> -
- )} -
- ) : ( - {noNameMessage} - ); -}; - -LocalizedExerciseName.propTypes = { - entity: PropTypes.object, - noNameMessage: PropTypes.any, - intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired, -}; - -export default injectIntl(LocalizedExerciseName); diff --git a/src/components/helpers/LocalizedNames/LocalizedGroupName.js b/src/components/helpers/LocalizedNames/LocalizedGroupName.js deleted file mode 100644 index c26b03c..0000000 --- a/src/components/helpers/LocalizedNames/LocalizedGroupName.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl } from 'react-intl'; - -import Icon from '../../icons'; -import { getLocalizedName, getOtherLocalizedNames } from '../../../helpers/localizedData.js'; - -const LocalizedGroupName = ({ entity, translations = false, intl: { locale } }) => { - const otherNames = getOtherLocalizedNames(entity, locale); - return ( - <> - {getLocalizedName(entity, locale)} - {translations && otherNames.length > 0 && ( - - n.name).join(', ')} - tooltip={otherNames.map((name, i) => ( -
- {name.name} [{name.locale}] -
- ))} - /> -
- )} - - ); -}; - -LocalizedGroupName.propTypes = { - entity: PropTypes.object, - translations: PropTypes.bool, - intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired, -}; - -export default injectIntl(LocalizedGroupName); diff --git a/src/components/helpers/LocalizedNames/index.js b/src/components/helpers/LocalizedNames/index.js deleted file mode 100644 index d736b74..0000000 --- a/src/components/helpers/LocalizedNames/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as LocalizedExerciseName } from './LocalizedExerciseName.js'; -export { default as LocalizedGroupName } from './LocalizedGroupName.js'; diff --git a/src/components/helpers/LocalizedTexts/LocalizedTexts.css b/src/components/helpers/LocalizedTexts/LocalizedTexts.css deleted file mode 100644 index ba8c14e..0000000 --- a/src/components/helpers/LocalizedTexts/LocalizedTexts.css +++ /dev/null @@ -1,3 +0,0 @@ -.tab-pane img { - max-width: 100%; -} diff --git a/src/components/helpers/LocalizedTexts/LocalizedTexts.js b/src/components/helpers/LocalizedTexts/LocalizedTexts.js deleted file mode 100644 index a4c1661..0000000 --- a/src/components/helpers/LocalizedTexts/LocalizedTexts.js +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import { Card, Tab, Nav } from 'react-bootstrap'; - -import Icon from '../../icons'; -import Markdown from '../../widgets/Markdown'; -import Callout from '../../widgets/Callout'; -import InsetPanel from '../../widgets/InsetPanel'; -import { knownLocales } from '../../../helpers/localizedData.js'; -import { UrlContext } from '../../../helpers/contexts.js'; - -import './LocalizedTexts.css'; - -const LocalizedTexts = ({ locales = [], noLocalesMessage = null }) => { - const localeTabs = knownLocales - .map(locale => locales.find(l => l.locale === locale)) - .filter(tabData => tabData && (tabData.text || tabData.link || tabData.studentHint)); - - if (localeTabs.length === 0) { - return noLocalesMessage ? {noLocalesMessage} : null; - } - - return ( - - {({ lang = 'en' }) => ( - locale === lang) || localeTabs.length === 0 ? lang : localeTabs[0].locale - } - id="localized-texts"> - - - - {localeTabs.map(({ locale, text, link = '', studentHint = null }) => ( - - - {link && link !== '' && ( -
- -

- -

- - - {link} - -
-
- )} - - {text.trim() !== '' && } - - {!text.trim() && !link && ( - - - - )} -
- - {studentHint && studentHint !== '' && ( - -

- -

- -
- )} -
- ))} -
-
-
- )} -
- ); -}; - -LocalizedTexts.propTypes = { - locales: PropTypes.arrayOf( - PropTypes.shape({ - locale: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - }) - ), - noLocalesMessage: PropTypes.any, -}; - -export default LocalizedTexts; diff --git a/src/components/helpers/LocalizedTexts/index.js b/src/components/helpers/LocalizedTexts/index.js deleted file mode 100644 index 3b304f5..0000000 --- a/src/components/helpers/LocalizedTexts/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import LocalizedTexts from './LocalizedTexts.js'; -export default LocalizedTexts; diff --git a/src/components/helpers/SimpleTextSearch/SimpleTextSearch.js b/src/components/helpers/SimpleTextSearch/SimpleTextSearch.js deleted file mode 100644 index da75f81..0000000 --- a/src/components/helpers/SimpleTextSearch/SimpleTextSearch.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import { FormGroup, FormLabel, FormControl, InputGroup } from 'react-bootstrap'; -import { LoadingIcon, SearchIcon, WarningIcon } from '../../icons'; -import Button from '../../widgets/TheButton'; - -class SimpleTextSearch extends Component { - state = { query: this.props.query, lastPropsQuery: this.props.query }; - - queryChangeHandler = ev => { - this.setState({ query: ev.target.value }); - }; - - static getDerivedStateFromProps(props, state) { - return props.query !== state.lastPropsQuery ? { query: props.query, lastPropsQuery: props.query } : null; - } - - render() { - const { id = 'simpleTextSearch', onSubmit, isLoading, hasFailed } = this.props; - - return ( -
- - - : - - - - - - -
- ); - } -} - -SimpleTextSearch.propTypes = { - id: PropTypes.string, - query: PropTypes.string, - onSubmit: PropTypes.func.isRequired, - isLoading: PropTypes.bool, - hasFailed: PropTypes.bool, -}; - -export default SimpleTextSearch; diff --git a/src/components/helpers/SimpleTextSearch/index.js b/src/components/helpers/SimpleTextSearch/index.js deleted file mode 100644 index 0a3dbfc..0000000 --- a/src/components/helpers/SimpleTextSearch/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SimpleTextSearch from './SimpleTextSearch.js'; -export default SimpleTextSearch; diff --git a/src/components/helpers/escapeString.js b/src/components/helpers/escapeString.js deleted file mode 100644 index 6fc0c79..0000000 --- a/src/components/helpers/escapeString.js +++ /dev/null @@ -1,23 +0,0 @@ -const escapeString = string => - ('' + string).replace(/["'\\\n\r\u2028\u2029]/g, character => { - // Escape all characters not included in SingleStringCharacters and - // DoubleStringCharacters on - // http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4 - switch (character) { - case '"': - case "'": // eslint-disable-line quotes - case '\\': - return '\\' + character; - // Four possible LineTerminator characters need to be escaped: - case '\n': - return '\\n'; - case '\r': - return '\\r'; - case '\u2028': - return '\\u2028'; - case '\u2029': - return '\\u2029'; - } - }); - -export default escapeString; diff --git a/src/components/layout/Navigation/Navigation.js b/src/components/layout/Navigation/Navigation.js deleted file mode 100644 index 4e7bf6b..0000000 --- a/src/components/layout/Navigation/Navigation.js +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Card } from 'react-bootstrap'; -import { Link, useLocation } from 'react-router-dom'; - -import UsersNameContainer from '../../../containers/UsersNameContainer'; - -import * as styles from './Navigation.less'; - -const defaultLinkMatch = (link, pathname, search) => link === pathname + search; - -const NavigationLink = ({ - link, - href, - caption, - icon = null, - location: { pathname, search }, - className, - match = defaultLinkMatch, -}) => - match(link, pathname, search) ? ( - - {icon} - {caption} - - ) : href ? ( - - {icon} - {caption} - - ) : ( - - {icon} - {caption} - - ); - -NavigationLink.propTypes = { - link: PropTypes.string, - href: PropTypes.string, - caption: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - icon: PropTypes.element, - className: PropTypes.string, - match: PropTypes.func, - location: PropTypes.shape({ - pathname: PropTypes.string.isRequired, - search: PropTypes.string.isRequired, - }).isRequired, -}; - -const Navigation = ({ - groupId = null, - exerciseId = null, - assignmentId = null, - shadowId = null, - pipelineId = null, - userId = null, - emphasizeUser = false, - links, - secondaryLinks, - titlePrefix = null, - titleSuffix = null, -}) => { - const location = useLocation(); - links = Array.isArray(links) ? links.filter(link => link) : []; - secondaryLinks = Array.isArray(secondaryLinks) ? secondaryLinks.filter(link => link) : []; - const onlyUser = Boolean(userId && !groupId && !exerciseId && !assignmentId && !shadowId); - - return ( - - - - {userId && emphasizeUser && ( -
- -
- )} - - {titlePrefix && {titlePrefix}} - - {userId && !emphasizeUser && ( - - )} - - {titleSuffix && {titleSuffix}} -
-
- - {(links.length > 0 || secondaryLinks.length > 0) && ( - - {links.map(link => ( - - ))} - - {secondaryLinks.map(link => ( - - ))} - - )} -
- ); -}; - -Navigation.propTypes = { - groupId: PropTypes.string, - exerciseId: PropTypes.string, - assignmentId: PropTypes.string, - shadowId: PropTypes.string, - pipelineId: PropTypes.string, - userId: PropTypes.string, - emphasizeUser: PropTypes.bool, - links: PropTypes.array, - secondaryLinks: PropTypes.array, - titlePrefix: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), - titleSuffix: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), -}; - -export default Navigation; diff --git a/src/components/layout/Navigation/Navigation.less b/src/components/layout/Navigation/Navigation.less deleted file mode 100644 index 5d18065..0000000 --- a/src/components/layout/Navigation/Navigation.less +++ /dev/null @@ -1,36 +0,0 @@ -.title { - padding-left: -2rem; -} - -.title > * { - font-size: 120%; - -} - -.title > span { - margin-right: 2rem; -} - -.title > span:last-child { - margin-right: 0; -} - -.links { - padding-left: -1rem; - font-size: 90%; -} - -.links > a, .links > strong { - margin-right: 1rem; -} - -.links > a.secondary, .links > strong.secodary { - margin-right: 0; - margin-left: 1rem; - float: right; -} - -.links > strong { - color: var(--success); - text-shadow: 0.1rem 0.1rem 0.3rem #aaa; -} diff --git a/src/components/layout/Navigation/UserNavigation.js b/src/components/layout/Navigation/UserNavigation.js deleted file mode 100644 index 6652937..0000000 --- a/src/components/layout/Navigation/UserNavigation.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -import Navigation from './Navigation.js'; -import withLinks from '../../../helpers/withLinks.js'; -import { DashboardIcon, EditIcon, UserIcon } from '../../icons'; - -const UserNavigation = ({ - userId, - canViewDetail = false, - canEdit = false, - isLoggedInUser = false, - links: { DASHBOARD_URI, USER_URI_FACTORY, EDIT_USER_URI_FACTORY }, -}) => ( - , - link: DASHBOARD_URI, - icon: , - }, - canViewDetail && { - caption: , - link: USER_URI_FACTORY(userId), - icon: , - }, - canEdit && { - caption: , - link: EDIT_USER_URI_FACTORY(userId), - icon: , - }, - ]} - /> -); - -UserNavigation.propTypes = { - userId: PropTypes.string.isRequired, - canViewDetail: PropTypes.bool, - canEdit: PropTypes.bool, - isLoggedInUser: PropTypes.bool, - links: PropTypes.object.isRequired, -}; - -export default withLinks(UserNavigation); diff --git a/src/components/layout/Navigation/index.js b/src/components/layout/Navigation/index.js deleted file mode 100644 index 920f4c5..0000000 --- a/src/components/layout/Navigation/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as UserNavigation } from './UserNavigation.js'; diff --git a/src/components/layout/Sidebar/Sidebar.js b/src/components/layout/Sidebar/Sidebar.js index 9a8282f..1922b93 100644 --- a/src/components/layout/Sidebar/Sidebar.js +++ b/src/components/layout/Sidebar/Sidebar.js @@ -61,6 +61,7 @@ const Sidebar = ({