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 !== '' && (
-
- )}
-
- {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 = ({