Files
freeCodeCamp/common/app/routes/Challenges/views/Modern/Editor.jsx

147 lines
3.4 KiB
JavaScript
Raw Normal View History

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import Codemirror from 'react-codemirror';
import NoSSR from 'react-no-ssr';
import MouseTrap from 'mousetrap';
import ns from './ns.json';
import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
import {
executeChallenge,
modernEditorUpdated,
challengeMetaSelector
} from '../../redux';
import { createFileSelector } from '../../../../files';
const envProps = typeof window !== 'undefined' ? Object.keys(window) : [];
const options = {
lint: {
esversion: 6,
predef: envProps
},
lineNumbers: true,
mode: 'javascript',
runnable: true,
matchBrackets: true,
autoCloseBrackets: true,
scrollbarStyle: 'null',
lineWrapping: true,
gutters: [ 'CodeMirror-lint-markers' ]
};
const mapStateToProps = createSelector(
createFileSelector((_, { fileKey }) => fileKey || ''),
challengeMetaSelector,
(
file,
{ mode }
) => ({
content: file.contents || '// Happy Coding!',
file: file,
mode: file.ext || mode || 'javascript'
})
);
const mapDispatchToProps = {
executeChallenge,
modernEditorUpdated
};
const propTypes = {
content: PropTypes.string,
executeChallenge: PropTypes.func.isRequired,
fileKey: PropTypes.string,
mode: PropTypes.string,
modernEditorUpdated: PropTypes.func.isRequired
};
export class Editor extends PureComponent {
createOptions = createSelector(
state => state.executeChallenge,
state => state.mode,
(executeChallenge, mode) => ({
...options,
mode,
// JSHint only works with javascript
// we will need to switch to eslint to make this work with jsx
lint: mode === 'javascript' ? options.lint : false,
extraKeys: {
Esc() {
document.activeElement.blur();
},
Tab(cm) {
if (cm.somethingSelected()) {
return cm.indentSelection('add');
}
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
return cm.replaceSelection(spaces);
},
'Shift-Tab': function(cm) {
return cm.indentSelection('subtract');
},
'Ctrl-Enter': function() {
executeChallenge();
return false;
},
'Cmd-Enter': function() {
executeChallenge();
return false;
},
'Ctrl-/': function(cm) {
cm.toggleComment();
},
'Cmd-/': function(cm) {
cm.toggleComment();
}
}
})
);
componentDidMount() {
MouseTrap.bind('e', () => {
this.refs.editor.focus();
}, 'keyup');
}
componentWillUnmount() {
MouseTrap.unbind('e', 'keyup');
}
render() {
const {
content,
modernEditorUpdated,
executeChallenge,
fileKey,
mode
} = this.props;
return (
<div
className={ `${ns}-editor` }
role='main'
>
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
<Codemirror
onChange={ content => modernEditorUpdated(fileKey, content) }
options={ this.createOptions({ executeChallenge, mode }) }
ref='editor'
value={ content }
/>
</NoSSR>
</div>
);
}
}
Editor.displayName = 'Editor';
Editor.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Editor);