Skip to content

Commit

Permalink
Site: Add live editor (#430)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwerty678908 authored and e1emeb0t committed Jun 19, 2017
1 parent 9f639b5 commit e8ae80e
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 99 deletions.
32 changes: 32 additions & 0 deletions libs/editor/codemirrorConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const options = {
value: '',
mode: 'jsx',
theme: 'react',
keyMap: 'sublime',
indentUnit: 2,
lineNumbers: true,
dragDrop: false,
showCursorWhenSelecting: true,
autoCloseBrackets: true,
matchTags: {
bothTags: true,
},
extraKeys: {
'Tab': 'indentMore',
'Cmd-/': (cm) => {
cm.listSelections().forEach(() => {
cm.toggleComment({ lineComment: '//' })
})
},
},
}

export const requireAddons = () => {
require('codemirror/mode/jsx/jsx')
require('codemirror/keymap/sublime')
require('codemirror/addon/fold/xml-fold') // required for matchtags
require('codemirror/addon/edit/matchtags')
require('codemirror/addon/edit/closebrackets')
require('codemirror/addon/comment/comment')
require('codemirror/addon/selection/active-line')
}
29 changes: 29 additions & 0 deletions libs/editor/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import 'codemirror/lib/codemirror.css'
import { options, requireAddons } from './codemirrorConfig'
import './style.scss'

export default class Editor extends Component {
componentDidMount() {
const { onChange, readOnly, value } = this.props

requireAddons()
const Codemirror = require('codemirror')
this.cm = Codemirror(this.editor, Object.assign(options, { readOnly }))
this.cm.on('changes', cm => {
onChange && onChange(cm.getValue())
})
this.cm.setValue(value)
}

render() {
return <div className="editor" ref={ref => (this.editor = ref)} />
}
}

Editor.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
readOnly: PropTypes.bool
}
67 changes: 67 additions & 0 deletions libs/editor/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.cm-s-react {
font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace;
font-size: 13px;
line-height: 20px;
color: #484848;
}

.cm-s-react .CodeMirror-linenumber {
color: #D8D8D8;
padding: 0 3px 0 3px;
font-size: 10px;
line-height: 22px;
}

.cm-s-react .CodeMirror-gutters {
background: white;
border-left: 4px solid rgba(238,238,238,1);
border-right: 0px;
}

.cm-s-react span.cm-keyword { color: #1990B8; }
.cm-s-react span.cm-atom { color: #C92C2C; }
.cm-s-react span.cm-number { color: #C92C2C; }
.cm-s-react span.cm-variable { color: black; }
.cm-s-react span.cm-variable-2 { color: #0000C0; }
.cm-s-react span.cm-variable-3 { color: #0000C0; }
.cm-s-react span.cm-property { color: black; }
.cm-s-react span.cm-operator { color: black; }
.cm-s-react span.cm-comment { color: #7D8B99; }
.cm-s-react span.cm-string { color: #2F9C0A; }
.cm-s-react span.cm-string-2 { color: #2F9C0A; }
.cm-s-react span.cm-link { color: #C92C2C; }

.cm-s-react .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-react .CodeMirror-matchingtag { background: transparent; }
.cm-s-react .cm-tag.CodeMirror-matchingtag:not(.cm-bracket) { text-decoration: underline; }

@keyframes cm-line-warning {
0% { background-color: white; }
66% { background-color: white; }
100% { background-color: #FFDADA; }
}

.cm-s-react .cm-line-error {
background-color: #FFDADA;
animation: cm-line-warning 0.5s;
}

/* read-only styles */

.read-only .CodeMirror, .read-only .CodeMirror-gutters {
background: rgb(238,238,238);
}

.read-only .CodeMirror-linenumber {
color: #BCBCBC;
}

.CodeMirror {
height: auto;
}
.CodeMirror-gutters {
z-index: 0;
}
.CodeMirror, .CodeMirror-gutters {
background-color: #f9fafc !important;
}
153 changes: 86 additions & 67 deletions libs/markdown/canvas.jsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,127 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import marked from 'marked';
import { transform } from 'babel-standalone';
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import marked from 'marked'
import { transform } from 'babel-standalone'
import Editor from '../editor'

export default class Canvas extends React.Component {
constructor(props) {
super(props);
super(props)

this.playerId = `player-${parseInt(Math.random() * 1e9)}`
this.document = this.props.children.match(/([^]*)\n?(```[^]+```)/)
this.description = marked(this.document[1])
this.source = this.document[2].match(/```(.*)\n([^]+)```/)

this.state = {
showBlock: false
};
}
}

componentDidMount() {
this.renderSource();
}

componentDidUpdate() {
this.renderSource();
this.renderSource(this.source[2])
}

getHeight() {
return Math.max(this.refs.highlight.offsetHeight, this.refs.description && this.refs.description.offsetHeight || 0);
get height() {
return Math.max(
this.refs.editor.offsetHeight,
(this.refs.description && this.refs.description.offsetHeight) || 0
)
}

blockControl() {
this.setState({
showBlock: !this.state.showBlock
});
})
}

renderSource() {
if (this.shouldUpdate) {
const div = this.refs.source;
renderSource(value) {
import('../../src')
.then(Element => {
const args = ['context', 'React', 'ReactDOM']
const argv = [this, React, ReactDOM]

if (div instanceof HTMLElement) {
require(['../../src'], Element => {
const args = ['context', 'React'], argv = [this, React];
for (const key in Element) {
args.push(key)
argv.push(Element[key])
}

for (const key in Element) {
args.push(key);
argv.push(Element[key]);
return {
args,
argv
}
})
.then(({ args, argv }) => {
const code = transform(
`
class Demo extends React.Component {
${value}
}
ReactDOM.render(<Demo {...context.props} />, document.getElementById('${this.playerId}'))
`,
{
presets: ['es2015', 'react']
}
).code

args.push(this.component);
args.push(code)

ReactDOM.unmountComponentAtNode(div);
ReactDOM.render(new Function(...args).apply(null, argv), div);
});
}
}
new Function(...args).apply(null, argv)

delete this.shouldUpdate;
this.source[2] = value
})
.catch(e => console.log(e))
}

render() {
const document = this.props.children.match(/([^]*)\n?(```[^]+```)/);
const source = document[2].match(/```(.*)\n([^]+)```/);
const description = marked(document[1]);
const highlight = marked(document[2]);
const component = transform(`
class Demo extends React.Component {
${source[2]}
}
__rtn = (function() {
return <Demo {...context.props} />
})();
`, {
presets: ['es2015', 'react']
}).code.replace('__rtn = ', 'return ');

this.shouldUpdate = component != this.component || this.component === undefined;
this.component = component;

return (
<div className={`demo-block demo-box demo-${this.props.name}`}>
<div className="source" ref="source"></div>
<div className="meta" style={{
height: this.state.showBlock ? this.getHeight() : 0
}}>
{description && <div ref="description" className="description" dangerouslySetInnerHTML={{ __html: description }}></div>}
<div ref="highlight" className="highlight" dangerouslySetInnerHTML={{ __html: highlight }}></div>
<div className="source" id={this.playerId} />
<div
className="meta"
style={{
height: this.state.showBlock ? this.height : 0
}}
>
{this.description &&
<div
ref="description"
className="description"
dangerouslySetInnerHTML={{ __html: this.description }}
/>}
<div ref="editor">
<Editor
value={this.source[2]}
onChange={code => this.renderSource(code)}
/>
</div>

</div>
{
this.state.showBlock ?
<div className="demo-block-control" onClick={this.blockControl.bind(this)}>
<i className="el-icon-caret-top"></i><span>{this.props.locale.hide}</span>
</div>
:
<div className="demo-block-control" onClick={this.blockControl.bind(this)}>
<i className="el-icon-caret-bottom"></i><span>{this.props.locale.show}</span>
{this.state.showBlock
? <div
className="demo-block-control"
onClick={this.blockControl.bind(this)}
>
<i className="el-icon-caret-top" />
<span>{this.props.locale.hide}</span>
</div>
}
: <div
className="demo-block-control"
onClick={this.blockControl.bind(this)}
>
<i className="el-icon-caret-bottom" />
<span>{this.props.locale.show}</span>
</div>}
</div>
)
}
}

Canvas.propTypes = {
locale: PropTypes.object
};
}

Canvas.defaultProps = {
locale: {}
};
}
4 changes: 0 additions & 4 deletions libs/markdown/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ export default class Markdown extends React.Component {
this.renderDOM();
}

componentDidUpdate() {
this.renderDOM();
}

renderDOM() {
for (const [id, component] of this.components) {
const div = document.getElementById(id);
Expand Down
Loading

0 comments on commit e8ae80e

Please sign in to comment.