diff --git a/client/package-lock.json b/client/package-lock.json
index fba9845639..775d03bb41 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -16612,6 +16612,39 @@
}
}
},
+ "react-hotkeys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz",
+ "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==",
+ "requires": {
+ "prop-types": "^15.6.1"
+ },
+ "dependencies": {
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "react-is": {
+ "version": "16.9.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
+ "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw=="
+ }
+ }
+ },
"react-identicons": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/react-identicons/-/react-identicons-1.1.7.tgz",
diff --git a/client/package.json b/client/package.json
index 846f2f0251..8eb26b0134 100644
--- a/client/package.json
+++ b/client/package.json
@@ -53,6 +53,7 @@
"react-final-form": "^6.3.0",
"react-ga": "^2.6.0",
"react-helmet": "^5.2.1",
+ "react-hotkeys": "^2.0.0",
"react-identicons": "^1.1.7",
"react-instantsearch-dom": "^5.7.0",
"react-monaco-editor": "^0.30.1",
diff --git a/client/src/templates/Challenges/classic/Editor.js b/client/src/templates/Challenges/classic/Editor.js
index c35bf32f08..1ab4070708 100644
--- a/client/src/templates/Challenges/classic/Editor.js
+++ b/client/src/templates/Challenges/classic/Editor.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
+import { navigate } from 'gatsby';
import { executeChallenge, updateFile } from '../redux';
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
@@ -17,6 +18,8 @@ const propTypes = {
executeChallenge: PropTypes.func.isRequired,
ext: PropTypes.string,
fileKey: PropTypes.string,
+ nextChallengePath: PropTypes.string.isRequired,
+ prevChallengePath: PropTypes.string.isRequired,
theme: PropTypes.string,
updateFile: PropTypes.func.isRequired
};
@@ -119,6 +122,28 @@ class Editor extends Component {
],
run: this.props.executeChallenge
});
+ this._editor.addAction({
+ id: 'navigate-prev',
+ label: 'Navigate to previous challenge',
+ keybindings: [
+ /* eslint-disable no-bitwise */
+ monaco.KeyMod.chord(
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.PageDown
+ )
+ ],
+ run: () => navigate(this.props.prevChallengePath)
+ });
+ this._editor.addAction({
+ id: 'navigate-next',
+ label: 'Navigate to next challenge',
+ keybindings: [
+ /* eslint-disable no-bitwise */
+ monaco.KeyMod.chord(
+ monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.PageUp
+ )
+ ],
+ run: () => navigate(this.props.nextChallengePath)
+ });
};
onChange = editorValue => {
diff --git a/client/src/templates/Challenges/classic/Show.js b/client/src/templates/Challenges/classic/Show.js
index 98f2900472..eb56ea0d79 100644
--- a/client/src/templates/Challenges/classic/Show.js
+++ b/client/src/templates/Challenges/classic/Show.js
@@ -69,7 +69,12 @@ const propTypes = {
initTests: PropTypes.func.isRequired,
output: PropTypes.string,
pageContext: PropTypes.shape({
- challengeMeta: PropTypes.object
+ challengeMeta: PropTypes.shape({
+ id: PropTypes.string,
+ introPath: PropTypes.string,
+ nextChallengePath: PropTypes.string,
+ prevChallengePath: PropTypes.string
+ })
}),
tests: PropTypes.arrayOf(
PropTypes.shape({
@@ -214,10 +219,23 @@ class ShowClassic extends Component {
}
renderEditor() {
- const { files } = this.props;
+ const {
+ files,
+ pageContext: {
+ challengeMeta: { prevChallengePath, nextChallengePath }
+ }
+ } = this.props;
+
const challengeFile = first(Object.keys(files).map(key => files[key]));
return (
- challengeFile &&