plugin parsing for rich text editors
This commit is contained in:
parent
986e36c137
commit
ae52a14cb1
@ -1,16 +1,21 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import RawEditor from './MarkdownControlElements/RawEditor';
|
import RawEditor from './MarkdownControlElements/RawEditor';
|
||||||
import VisualEditor from './MarkdownControlElements/VisualEditor';
|
import VisualEditor from './MarkdownControlElements/VisualEditor';
|
||||||
|
import { processEditorPlugins } from './richText';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { switchVisualMode } from '../../actions/editor';
|
import { switchVisualMode } from '../../actions/editor';
|
||||||
|
|
||||||
class MarkdownControl extends React.Component {
|
class MarkdownControl extends React.Component {
|
||||||
constructor(props) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props, context);
|
||||||
this.useVisualEditor = this.useVisualEditor.bind(this);
|
this.useVisualEditor = this.useVisualEditor.bind(this);
|
||||||
this.useRawEditor = this.useRawEditor.bind(this);
|
this.useRawEditor = this.useRawEditor.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
processEditorPlugins(this.context.plugins.editor);
|
||||||
|
}
|
||||||
|
|
||||||
useVisualEditor() {
|
useVisualEditor() {
|
||||||
this.props.switchVisualMode(true);
|
this.props.switchVisualMode(true);
|
||||||
}
|
}
|
||||||
@ -70,6 +75,10 @@ MarkdownControl.propTypes = {
|
|||||||
value: PropTypes.node,
|
value: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MarkdownControl.contextTypes = {
|
||||||
|
plugins: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({ editor: state.editor }),
|
state => ({ editor: state.editor }),
|
||||||
{ switchVisualMode }
|
{ switchVisualMode }
|
||||||
|
@ -88,15 +88,17 @@ export default class BlockTypesMenu extends Component {
|
|||||||
renderBlockTypeButton(type, icon) {
|
renderBlockTypeButton(type, icon) {
|
||||||
const onClick = e => this.handleBlockTypeClick(e, type);
|
const onClick = e => this.handleBlockTypeClick(e, type);
|
||||||
return (
|
return (
|
||||||
<Icon type={icon} onClick={onClick} className={styles.icon} />
|
<Icon key={type} type={icon} onClick={onClick} className={styles.icon} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMenu() {
|
renderMenu() {
|
||||||
|
const { plugins } = this.props;
|
||||||
if (this.state.expanded) {
|
if (this.state.expanded) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.menu}>
|
<div className={styles.menu}>
|
||||||
{this.renderBlockTypeButton('hr', 'dot-3')}
|
{this.renderBlockTypeButton('hr', 'dot-3')}
|
||||||
|
{plugins.map(plugin => this.renderBlockTypeButton(plugin.id, plugin.icon))}
|
||||||
<Icon type="picture" onClick={this.handleFileUploadClick} className={styles.icon} />
|
<Icon type="picture" onClick={this.handleFileUploadClick} className={styles.icon} />
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@ -134,6 +136,7 @@ export default class BlockTypesMenu extends Component {
|
|||||||
|
|
||||||
BlockTypesMenu.propTypes = {
|
BlockTypesMenu.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
plugins: PropTypes.array.isRequired,
|
||||||
position: PropTypes.shape({
|
position: PropTypes.shape({
|
||||||
top: PropTypes.number.isRequired,
|
top: PropTypes.number.isRequired,
|
||||||
left: PropTypes.number.isRequired
|
left: PropTypes.number.isRequired
|
||||||
|
@ -3,9 +3,9 @@ import _ from 'lodash';
|
|||||||
import { Editor, Raw } from 'slate';
|
import { Editor, Raw } from 'slate';
|
||||||
import position from 'selection-position';
|
import position from 'selection-position';
|
||||||
import MarkupIt, { SlateUtils } from 'markup-it';
|
import MarkupIt, { SlateUtils } from 'markup-it';
|
||||||
import getSyntax from '../syntax';
|
|
||||||
import { emptyParagraphBlock } from '../constants';
|
import { emptyParagraphBlock } from '../constants';
|
||||||
import { DEFAULT_NODE, SCHEMA } from './schema';
|
import { DEFAULT_NODE, SCHEMA } from './schema';
|
||||||
|
import { getNodes, getSyntaxes, getPlugins } from '../../richText';
|
||||||
import StylesMenu from './StylesMenu';
|
import StylesMenu from './StylesMenu';
|
||||||
import BlockTypesMenu from './BlockTypesMenu';
|
import BlockTypesMenu from './BlockTypesMenu';
|
||||||
import styles from './index.css';
|
import styles from './index.css';
|
||||||
@ -17,12 +17,8 @@ class VisualEditor extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.getMedia = this.getMedia.bind(this);
|
SCHEMA.nodes = _.merge(SCHEMA.nodes, getNodes());
|
||||||
const MarkdownSyntax = getSyntax(this.getMedia);
|
this.markdown = new MarkupIt(getSyntaxes().markdown);
|
||||||
this.markdown = new MarkupIt(MarkdownSyntax);
|
|
||||||
|
|
||||||
this.customImageNodeRenderer = this.customImageNodeRenderer.bind(this);
|
|
||||||
SCHEMA.nodes['mediaproxy'] = this.customImageNodeRenderer;
|
|
||||||
|
|
||||||
this.blockEdit = false;
|
this.blockEdit = false;
|
||||||
this.menuPositions = {
|
this.menuPositions = {
|
||||||
@ -295,6 +291,7 @@ class VisualEditor extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<BlockTypesMenu
|
<BlockTypesMenu
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
plugins={getPlugins()}
|
||||||
position={this.menuPositions.blockTypesMenu}
|
position={this.menuPositions.blockTypesMenu}
|
||||||
onClickBlock={this.handleBlockTypeClick}
|
onClickBlock={this.handleBlockTypeClick}
|
||||||
onClickImage={this.handleImageClick}
|
onClickImage={this.handleImageClick}
|
||||||
|
69
src/components/Widgets/richText.js
Normal file
69
src/components/Widgets/richText.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List } from 'immutable';
|
||||||
|
import MarkupIt from 'markup-it';
|
||||||
|
import markdownSyntax from 'markup-it/syntaxes/markdown';
|
||||||
|
import htmlSyntax from 'markup-it/syntaxes/html';
|
||||||
|
import { Icon } from '../UI';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All Rich text widgets (Markdown, for example) should use Slate for text editing and
|
||||||
|
* MarkupIt to convert between structured formats (Slate JSON, Markdown, HTML, etc.).
|
||||||
|
* This module Processes and provides Slate nodes and MarkupIt syntaxes augmented with plugins
|
||||||
|
*/
|
||||||
|
|
||||||
|
let processedPlugins = List([]);
|
||||||
|
|
||||||
|
|
||||||
|
const nodes = {};
|
||||||
|
const augmentedMarkdownSyntax = markdownSyntax;
|
||||||
|
const augmentedHTMLSyntax = htmlSyntax;
|
||||||
|
|
||||||
|
function processEditorPlugins(plugins) {
|
||||||
|
// Since the plugin list is immutable, a simple comparisson is enough
|
||||||
|
// to determine whether we need to process again.
|
||||||
|
if (plugins === processedPlugins) return;
|
||||||
|
|
||||||
|
plugins.forEach(plugin => {
|
||||||
|
const markdownRule = MarkupIt.Rule(plugin.id)
|
||||||
|
.regExp(plugin.pattern, function(state, match) { return plugin.fromBlock(match); })
|
||||||
|
.toText(function(state, token) { return plugin.toBlock(token.getData()); });
|
||||||
|
|
||||||
|
const htmlRule = MarkupIt.Rule(plugin.id)
|
||||||
|
.regExp(plugin.pattern, function(state, match) { return plugin.fromBlock(match); })
|
||||||
|
.toText(function(state, token) { return plugin.toPreview(token.getData()); });
|
||||||
|
|
||||||
|
const nodeRenderer = (props) => {
|
||||||
|
/* eslint react/prop-types: 0 */
|
||||||
|
const { node, state } = props;
|
||||||
|
const isFocused = state.selection.hasEdgeIn(node);
|
||||||
|
const className = isFocused ? 'plugin active' : 'plugin';
|
||||||
|
return (
|
||||||
|
<div {...props.attributes} className={className}>
|
||||||
|
<Icon type={plugin.icon}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
augmentedMarkdownSyntax.addInlineRules(markdownRule);
|
||||||
|
augmentedHTMLSyntax.addInlineRules(htmlRule);
|
||||||
|
nodes[plugin.id] = nodeRenderer;
|
||||||
|
});
|
||||||
|
|
||||||
|
processedPlugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlugins() {
|
||||||
|
return processedPlugins.map(plugin => (
|
||||||
|
{ id: plugin.id, icon: plugin.icon }
|
||||||
|
)).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodes() {
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSyntaxes() {
|
||||||
|
return { markdown: augmentedMarkdownSyntax, html:augmentedHTMLSyntax };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { processEditorPlugins, getNodes, getSyntaxes, getPlugins };
|
Loading…
x
Reference in New Issue
Block a user