2016-11-01 16:55:21 -07:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { EditorState } from 'prosemirror-state';
|
|
|
|
import { EditorView } from 'prosemirror-view';
|
|
|
|
import history from 'prosemirror-history';
|
|
|
|
import {
|
|
|
|
blockQuoteRule, orderedListRule, bulletListRule, codeBlockRule, headingRule,
|
|
|
|
inputRules, allInputRules,
|
|
|
|
} from 'prosemirror-inputrules';
|
|
|
|
import { keymap } from 'prosemirror-keymap';
|
|
|
|
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
|
|
|
import { baseKeymap, setBlockType, toggleMark } from 'prosemirror-commands';
|
|
|
|
import { buildKeymap } from './keymap';
|
|
|
|
import Toolbar from '../Toolbar';
|
|
|
|
import styles from './index.css';
|
|
|
|
|
|
|
|
function buildInputRules(schema) {
|
|
|
|
let result = [], type;
|
|
|
|
if (type = schema.nodes.blockquote) result.push(blockQuoteRule(type));
|
|
|
|
if (type = schema.nodes.ordered_list) result.push(orderedListRule(type));
|
|
|
|
if (type = schema.nodes.bullet_list) result.push(bulletListRule(type));
|
|
|
|
if (type = schema.nodes.code_block) result.push(codeBlockRule(type));
|
|
|
|
if (type = schema.nodes.heading) result.push(headingRule(type, 6));
|
|
|
|
return result;
|
|
|
|
}
|
2016-10-03 16:57:48 +02:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
export default class Editor extends Component {
|
2016-08-11 11:27:09 -03:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2016-11-01 16:55:21 -07:00
|
|
|
this.state = {};
|
|
|
|
}
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
componentDidMount() {
|
|
|
|
this.view = new EditorView(this.ref, {
|
|
|
|
state: EditorState.create({
|
|
|
|
doc: defaultMarkdownParser.parse(this.props.value || ''),
|
|
|
|
schema,
|
|
|
|
plugins: [
|
|
|
|
inputRules({
|
|
|
|
rules: allInputRules.concat(buildInputRules(schema)),
|
|
|
|
}),
|
2016-11-01 17:25:37 -07:00
|
|
|
keymap(buildKeymap(schema)),
|
2016-11-01 16:55:21 -07:00
|
|
|
keymap(baseKeymap),
|
|
|
|
history.history(),
|
2016-11-01 17:25:37 -07:00
|
|
|
keymap({
|
|
|
|
'Mod-z': history.undo,
|
|
|
|
'Mod-y': history.redo,
|
|
|
|
}),
|
2016-11-01 16:55:21 -07:00
|
|
|
],
|
2016-10-18 12:32:39 -02:00
|
|
|
}),
|
2016-11-01 16:55:21 -07:00
|
|
|
onAction: this.handleAction,
|
|
|
|
});
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleAction = (action) => {
|
|
|
|
const newState = this.view.state.applyAction(action);
|
|
|
|
switch (action.type) {
|
|
|
|
case 'selection':
|
|
|
|
this.handleSelection(newState);
|
|
|
|
default:
|
|
|
|
const md = defaultMarkdownSerializer.serialize(newState.doc);
|
|
|
|
this.props.onChange(md);
|
|
|
|
}
|
|
|
|
this.view.updateState(newState);
|
|
|
|
this.view.focus();
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleSelection = (state) => {
|
|
|
|
const { selection } = state;
|
|
|
|
if (selection.from === selection.to) {
|
|
|
|
const pos = this.view.coordsAtPos(selection.from);
|
|
|
|
const editorPos = this.view.content.getBoundingClientRect();
|
|
|
|
const selectionPosition = { top: pos.top - editorPos.top, left: pos.left - editorPos.left };
|
|
|
|
this.setState({ showToolbar: false, selectionPosition });
|
2016-08-11 11:27:09 -03:00
|
|
|
} else {
|
2016-11-01 16:55:21 -07:00
|
|
|
this.setState({ showToolbar: true });
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleRef = (ref) => {
|
|
|
|
this.ref = ref;
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleHeader = level => (
|
|
|
|
() => {
|
2016-11-01 17:51:49 -07:00
|
|
|
const state = this.view.state;
|
|
|
|
const { $from, to, node } = state.selection;
|
|
|
|
let nodeType = schema.nodes.heading;
|
|
|
|
let attrs = { level };
|
|
|
|
let inHeader = node && node.hasMarkup(nodeType, attrs);
|
|
|
|
if (!inHeader) {
|
|
|
|
inHeader = to <= $from.end() && $from.parent.hasMarkup(nodeType, attrs);
|
|
|
|
}
|
|
|
|
if (inHeader) {
|
|
|
|
nodeType = schema.nodes.paragraph;
|
|
|
|
attrs = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
const command = setBlockType(nodeType, { level });
|
|
|
|
command(state, this.handleAction);
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
2016-11-01 16:55:21 -07:00
|
|
|
);
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleBold = () => {
|
|
|
|
const command = toggleMark(schema.marks.strong);
|
|
|
|
command(this.view.state, this.handleAction);
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleItalic = () => {
|
|
|
|
const command = toggleMark(schema.marks.em);
|
|
|
|
command(this.view.state, this.handleAction);
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleToggle = () => {
|
|
|
|
this.props.onMode('raw');
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
|
|
|
render() {
|
2016-11-01 16:55:21 -07:00
|
|
|
const { showToolbar, selectionPosition } = this.state;
|
|
|
|
|
|
|
|
return (<div className={styles.editor}>
|
|
|
|
<Toolbar
|
|
|
|
isOpen={showToolbar}
|
|
|
|
selectionPosition={selectionPosition}
|
|
|
|
onH1={this.handleHeader(1)}
|
|
|
|
onH2={this.handleHeader(2)}
|
|
|
|
onBold={this.handleBold}
|
|
|
|
onItalic={this.handleItalic}
|
|
|
|
onLink={this.handleLink}
|
|
|
|
onToggleMode={this.handleToggle}
|
|
|
|
/>
|
|
|
|
<div ref={this.handleRef} />
|
|
|
|
</div>);
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
|
|
|
}
|