UNPKG

armo-editor

Version:

React text editor component.

174 lines (145 loc) 4.59 kB
// Based on these two files: // https://github.com/JedWatson/react-codemirror/blob/master/src/Codemirror.js // https://github.com/FormidableLabs/component-playground/blob/master/src/components/editor.jsx import styles from './Editor.less' import ExecutionEnvironment from 'exenv' import PropTypes from 'prop-types' import React, { Component } from 'react' import ReactDOM from 'react-dom' import cx from 'classnames' import debounce from 'lodash.debounce' import createPrefixer from 'utils/createPrefixer' let codeMirror if (ExecutionEnvironment.canUseDOM) { codeMirror = require('codemirror') require("codemirror/mode/jsx/jsx") require("codemirror/mode/css/css") require("codemirror/mode/markdown/markdown") } const prefix = createPrefixer('controls', 'Editor') const OPTION_PROPS = [ 'readOnly', 'lineNumbers', 'lineWrapping', 'mode', 'theme', ] function normalizeLineEndings (str) { if (!str) return str; return str.replace(/\r\n|\r/g, '\n'); } @prefix.withName('Editor') export default class Editor extends Component { static propTypes = { theme: PropTypes.string, readOnly: PropTypes.bool, fitToContent: PropTypes.bool, value: PropTypes.string, selectedLines: PropTypes.array, onChange: PropTypes.func, mode: PropTypes.oneOf(['jsx', 'css']), lineNumbers: PropTypes.bool, lineWrapping: PropTypes.bool, style: PropTypes.object, className: PropTypes.string, } static defaultProps = { theme: "monokai", fitToContent: false, } state = { isFocused: false, } constructor(props) { super(props) this.componentWillReceiveProps = debounce(this.componentWillReceiveProps, 0) } componentDidMount() { const options = { matchBrackets: true, smartIndent: false, tabSize: 2, indentWithTabs: false, extraKeys: { Tab: function(cm) { var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(spaces); } }, } for (let key of OPTION_PROPS) { options[key] = this.props[key] } if (this.props.fitToContent) { options.viewportMargin = Infinity } this.codeMirror = codeMirror.fromTextArea(this.textareaNode, options); this.codeMirror.on('change', this.handleChange); this.codeMirror.on('focus', this.handleFocus.bind(this, true)); this.codeMirror.on('blur', this.handleFocus.bind(this, false)); this.codeMirror.on('scroll', this.handleScroll); this.codeMirror.on('scrollCursorIntoView', this.handleScrollIntoView); this.codeMirror.setValue(this.props.defaultValue || this.props.value || ''); } componentWillReceiveProps(nextProps) { if (this.codeMirror && nextProps.value !== undefined && normalizeLineEndings(this.codeMirror.getValue()) !== normalizeLineEndings(nextProps.value)) { if (this.props.preserveScrollPosition) { var prevScrollPosition = this.codeMirror.getScrollInfo(); this.codeMirror.setValue(nextProps.value); this.codeMirror.scrollTo(prevScrollPosition.left, prevScrollPosition.top); } else { this.codeMirror.setValue(nextProps.value); } } for (let key of OPTION_PROPS) { const prop = nextProps[key] if (prop !== this.props[key]) { this.codeMirror.setOption(key, prop) } } } componentWillUnmount() { // is there a lighter-weight way to remove the cm instance? if (this.codeMirror) { this.codeMirror.toTextArea(); } } highlightSelectedLines = () => { if (Array.isArray(this.props.selectedLines)) { this.props.selectedLines.forEach(lineNumber => this.codeMirror.addLineClass(lineNumber, "wrap", "CodeMirror-activeline-background")) } } focus() { if (this.codeMirror) { this.codeMirror.focus() } } render() { const { focused } = this.state return ( <div className={cx({ focused })}> <textarea ref={this.receiveTextareaRef} defaultValue={this.props.value} autoComplete="off" /> </div> ) } receiveTextareaRef = (ref) => this.textareaNode = ref handleFocus(isFocused) { this.setState({ isFocused }) } handleChange = (doc, change) => { if (!this.props.readOnly && this.props.onChange && change.origin !== 'setValue') { this.props.onChange(doc.getValue()) } } handleScroll = (codeMirror) => { this.props.onScroll && this.props.onScroll(codeMirror.getScrollInfo()) } handleScrollIntoView = (codeMirror, e) => { e.preventDefault() } }