Raw text editor (with markdown highlight)
This commit is contained in:
parent
7405ae8f63
commit
994d969247
@ -76,6 +76,7 @@
|
|||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"markup-it": "git+https://github.com/cassiozen/markup-it.git",
|
"markup-it": "git+https://github.com/cassiozen/markup-it.git",
|
||||||
"pluralize": "^3.0.0",
|
"pluralize": "^3.0.0",
|
||||||
|
"prismjs": "^1.5.1",
|
||||||
"react-portal": "^2.2.1",
|
"react-portal": "^2.2.1",
|
||||||
"selection-position": "^1.0.0",
|
"selection-position": "^1.0.0",
|
||||||
"slate": "^0.12.2"
|
"slate": "^0.12.2"
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import { Editor, Plain, Mark } from 'slate';
|
||||||
|
import Prism from 'prismjs';
|
||||||
|
import marks from './prismMarkdown';
|
||||||
|
import styles from './index.css';
|
||||||
|
|
||||||
|
const MARKS = {
|
||||||
|
'highlight-comment': {
|
||||||
|
opacity: '0.33'
|
||||||
|
},
|
||||||
|
'highlight-important': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#006',
|
||||||
|
},
|
||||||
|
'highlight-keyword': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#006',
|
||||||
|
},
|
||||||
|
'highlight-url': {
|
||||||
|
color: '#006',
|
||||||
|
},
|
||||||
|
'highlight-punctuation': {
|
||||||
|
color: '#006',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Prism.languages.markdown = Prism.languages.extend('markup', {});
|
||||||
|
Prism.languages.insertBefore('markdown', 'prolog', marks);
|
||||||
|
Prism.languages.markdown['bold'].inside['url'] = Prism.util.clone(Prism.languages.markdown['url']);
|
||||||
|
Prism.languages.markdown['italic'].inside['url'] = Prism.util.clone(Prism.languages.markdown['url']);
|
||||||
|
Prism.languages.markdown['bold'].inside['italic'] = Prism.util.clone(Prism.languages.markdown['italic']);
|
||||||
|
Prism.languages.markdown['italic'].inside['bold'] = Prism.util.clone(Prism.languages.markdown['bold']);
|
||||||
|
|
||||||
|
class RawEditor extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const content = props.value ? Plain.deserialize(props.value) : Plain.deserialize('');
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
state: content
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleDocumentChange = this.handleDocumentChange.bind(this);
|
||||||
|
this.renderMark = this.renderMark.bind(this);
|
||||||
|
this.renderDecorations = this.renderDecorations.bind(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slate keeps track of selections, scroll position etc.
|
||||||
|
* So, onChange gets dispatched on every interaction (click, arrows, everything...)
|
||||||
|
* It also have an onDocumentChange, that get's dispached only when the actual
|
||||||
|
* content changes
|
||||||
|
*/
|
||||||
|
handleChange(state) {
|
||||||
|
this.setState({ state });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDocumentChange(document, state) {
|
||||||
|
const content = Plain.serialize(state, { terse: true });
|
||||||
|
this.props.onChange(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMark(mark) {
|
||||||
|
return MARKS[mark.type] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDecorations(text, block) {
|
||||||
|
let characters = text.characters.asMutable();
|
||||||
|
const string = text.text;
|
||||||
|
const grammar = Prism.languages.markdown;
|
||||||
|
const tokens = Prism.tokenize(string, grammar);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (typeof token == 'string') {
|
||||||
|
offset += token.length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = offset + token.matchedStr.length;
|
||||||
|
const name = token.alias || token.type;
|
||||||
|
const type = `highlight-${name}`;
|
||||||
|
|
||||||
|
for (let i = offset; i < length; i++) {
|
||||||
|
let char = characters.get(i);
|
||||||
|
let { marks } = char;
|
||||||
|
marks = marks.add(Mark.create({ type }));
|
||||||
|
char = char.merge({ marks });
|
||||||
|
characters = characters.set(i, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return characters.asImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
placeholder={'Enter some rich text...'}
|
||||||
|
state={this.state.state}
|
||||||
|
renderMark={this.renderMark}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onDocumentChange={this.handleDocumentChange}
|
||||||
|
renderDecorations={this.renderDecorations}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RawEditor;
|
||||||
|
|
||||||
|
RawEditor.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
value: PropTypes.node,
|
||||||
|
};
|
@ -0,0 +1,116 @@
|
|||||||
|
const marks = {
|
||||||
|
'blockquote': {
|
||||||
|
// > ...
|
||||||
|
pattern: /^>(?:[\t ]*>)*/m,
|
||||||
|
alias: 'punctuation'
|
||||||
|
},
|
||||||
|
'code': [
|
||||||
|
{
|
||||||
|
// Prefixed by 4 spaces or 1 tab
|
||||||
|
pattern: /^(?: {4}|\t).+/m,
|
||||||
|
alias: 'keyword'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// `code`
|
||||||
|
// ``code``
|
||||||
|
pattern: /``.+?``|`[^`\n]+`/,
|
||||||
|
alias: 'keyword'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'title': [
|
||||||
|
{
|
||||||
|
// title 1
|
||||||
|
// =======
|
||||||
|
|
||||||
|
// title 2
|
||||||
|
// -------
|
||||||
|
pattern: /\w+.*(?:\r?\n|\r)(?:==+|--+)/,
|
||||||
|
alias: 'important',
|
||||||
|
inside: {
|
||||||
|
punctuation: /==+$|--+$/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// # title 1
|
||||||
|
// ###### title 6
|
||||||
|
pattern: /(^\s*)#+.+/m,
|
||||||
|
lookbehind: true,
|
||||||
|
alias: 'important',
|
||||||
|
inside: {
|
||||||
|
punctuation: /^#+|#+$/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'hr': {
|
||||||
|
// ***
|
||||||
|
// ---
|
||||||
|
// * * *
|
||||||
|
// -----------
|
||||||
|
pattern: /(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,
|
||||||
|
lookbehind: true,
|
||||||
|
alias: 'punctuation'
|
||||||
|
},
|
||||||
|
'list': {
|
||||||
|
// * item
|
||||||
|
// + item
|
||||||
|
// - item
|
||||||
|
// 1. item
|
||||||
|
pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
|
||||||
|
lookbehind: true,
|
||||||
|
alias: 'punctuation'
|
||||||
|
},
|
||||||
|
'url-reference': {
|
||||||
|
// [id]: http://example.com "Optional title"
|
||||||
|
// [id]: http://example.com 'Optional title'
|
||||||
|
// [id]: http://example.com (Optional title)
|
||||||
|
// [id]: <http://example.com> "Optional title"
|
||||||
|
pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
|
||||||
|
inside: {
|
||||||
|
'variable': {
|
||||||
|
pattern: /^(!?\[)[^\]]+/,
|
||||||
|
lookbehind: true
|
||||||
|
},
|
||||||
|
'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
|
||||||
|
'punctuation': /^[\[\]!:]|[<>]/
|
||||||
|
},
|
||||||
|
alias: 'url'
|
||||||
|
},
|
||||||
|
'bold': {
|
||||||
|
// **strong**
|
||||||
|
// __strong__
|
||||||
|
|
||||||
|
// Allow only one line break
|
||||||
|
pattern: /(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
|
||||||
|
lookbehind: true,
|
||||||
|
inside: {
|
||||||
|
'punctuation': /^\*\*|^__|\*\*$|__$/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'italic': {
|
||||||
|
// *em*
|
||||||
|
// _em_
|
||||||
|
|
||||||
|
// Allow only one line break
|
||||||
|
pattern: /(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
|
||||||
|
lookbehind: true,
|
||||||
|
inside: {
|
||||||
|
'punctuation': /^[*_]|[*_]$/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'url': {
|
||||||
|
// [example](http://example.com "Optional title")
|
||||||
|
// [example] [id]
|
||||||
|
pattern: /!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,
|
||||||
|
inside: {
|
||||||
|
'variable': {
|
||||||
|
pattern: /(!?\[)[^\]]+(?=\]$)/,
|
||||||
|
lookbehind: true
|
||||||
|
},
|
||||||
|
'string': {
|
||||||
|
pattern: /"(?:\\.|[^"\\])*"(?=\)$)/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default marks;
|
Loading…
x
Reference in New Issue
Block a user