Make editor plugins work in preview

This commit is contained in:
Mathias Biilmann Christensen 2016-10-30 23:38:12 -07:00
parent 378be79a76
commit aca88ef441
3 changed files with 61 additions and 45 deletions

View File

@ -1,6 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import MarkupIt, { Syntax, BLOCKS, STYLES, ENTITIES } from 'markup-it'; import MarkupIt, { Syntax, BLOCKS, STYLES, ENTITIES } from 'markup-it';
import { omit } from 'lodash'; import { omit } from 'lodash';
import registry from '../../lib/registry';
const defaultSchema = { const defaultSchema = {
[BLOCKS.DOCUMENT]: 'article', [BLOCKS.DOCUMENT]: 'article',
@ -44,43 +45,16 @@ function sanitizeProps(props) {
return omit(props, notAllowedAttributes); return omit(props, notAllowedAttributes);
} }
function renderToken(schema, token, index = 0, key = '0') {
const type = token.get('type');
const data = token.get('data');
const text = token.get('text');
const tokens = token.get('tokens');
const nodeType = schema[type];
key = `${ key }.${ index }`;
// Only render if type is registered as renderer
if (typeof nodeType !== 'undefined') {
let children = null;
if (tokens.size) {
children = tokens.map((token, idx) => renderToken(schema, token, idx, key));
} else if (type === 'text') {
children = text;
}
if (nodeType !== null) {
let props = { key, token };
if (typeof nodeType !== 'function') {
props = { key, ...sanitizeProps(data.toJS()) };
}
// If this is a react element
return React.createElement(nodeType, props, children);
} else {
// If this is a text node
return children;
}
}
return null;
}
export default class MarkupItReactRenderer extends React.Component { export default class MarkupItReactRenderer extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { syntax } = props; const { syntax } = props;
this.parser = new MarkupIt(syntax); this.parser = new MarkupIt(syntax);
this.plugins = {};
registry.getEditorComponents().forEach((component) => {
this.plugins[component.get('id')] = component;
});
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -89,10 +63,51 @@ export default class MarkupItReactRenderer extends React.Component {
} }
} }
renderToken(schema, token, index = 0, key = '0') {
const type = token.get('type');
const data = token.get('data');
const text = token.get('text');
const tokens = token.get('tokens');
const nodeType = schema[type];
key = `${ key }.${ index }`;
// Only render if type is registered as renderer
if (typeof nodeType !== 'undefined') {
let children = null;
if (tokens.size) {
children = tokens.map((token, idx) => this.renderToken(schema, token, idx, key));
} else if (type === 'text') {
children = text;
}
if (nodeType !== null) {
let props = { key, token };
if (typeof nodeType !== 'function') {
props = { key, ...sanitizeProps(data.toJS()) };
}
// If this is a react element
return React.createElement(nodeType, props, children);
} else {
// If this is a text node
return children;
}
}
const plugin = this.plugins[token.get('type')];
if (plugin) {
const output = plugin.toPreview(token.get('data').toJS());
return output instanceof React.Component ?
output :
<span dangerouslySetInnerHTML={{ __html: output}} />;
}
return null;
}
render() { render() {
const { value, schema } = this.props; const { value, schema } = this.props;
const content = this.parser.toContent(value); const content = this.parser.toContent(value);
return renderToken({ ...defaultSchema, ...schema }, content.get('token')); return this.renderToken({ ...defaultSchema, ...schema }, content.get('token'));
} }
} }

View File

@ -3,6 +3,7 @@
left: -18px; left: -18px;
display: none; display: none;
width: 100%; width: 100%;
z-index: 1000;
} }
.visible { .visible {

View File

@ -65,18 +65,17 @@ function getCleanPaste(e) {
}); });
} }
const buildtInPlugins = fromJS([{ const buildtInPlugins = [{
label: 'Image', label: 'Image',
id: 'image', id: 'image',
fromBlock: (data) => { fromBlock: match => match && {
const m = data.match(/^!\[([^\]]+)\]\(([^\)]+)\)$/); image: match[2],
return m && { alt: match[1],
image: m[2],
alt: m[1],
};
}, },
toBlock: data => `![${ data.alt }](${ data.image })`, toBlock: data => `![${ data.alt }](${ data.image })`,
toPreview: data => `<img src="${ data.image }" alt="${ data.alt }" />`, toPreview: (data) => {
return <img src={data.image} alt={data.alt} />;
},
pattern: /^!\[([^\]]+)\]\(([^\)]+)\)$/, pattern: /^!\[([^\]]+)\]\(([^\)]+)\)$/,
fields: [{ fields: [{
label: 'Image', label: 'Image',
@ -86,14 +85,15 @@ const buildtInPlugins = fromJS([{
label: 'Alt Text', label: 'Alt Text',
name: 'alt', name: 'alt',
}], }],
}]); }];
buildtInPlugins.forEach(plugin => registry.registerEditorComponent(plugin));
export default class RawEditor extends React.Component { export default class RawEditor extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const plugins = registry.getEditorComponents(); const plugins = registry.getEditorComponents();
this.state = { this.state = {
plugins: buildtInPlugins.concat(plugins), plugins: plugins,
}; };
this.shortcuts = { this.shortcuts = {
meta: { meta: {
@ -161,7 +161,7 @@ export default class RawEditor extends React.Component {
} }
replaceSelection(chars) { replaceSelection(chars) {
const { value } = this.props; const value = this.props.value || '';
const selection = this.getSelection(); const selection = this.getSelection();
const newSelection = Object.assign({}, selection); const newSelection = Object.assign({}, selection);
const beforeSelection = value.substr(0, selection.start); const beforeSelection = value.substr(0, selection.start);
@ -172,7 +172,7 @@ export default class RawEditor extends React.Component {
} }
toggleHeader(header) { toggleHeader(header) {
const { value } = this.props; const value = this.props.value || '';
const selection = this.getSelection(); const selection = this.getSelection();
const newSelection = Object.assign({}, selection); const newSelection = Object.assign({}, selection);
const lastNewline = value.lastIndexOf('\n', selection.start); const lastNewline = value.lastIndexOf('\n', selection.start);
@ -234,7 +234,7 @@ export default class RawEditor extends React.Component {
}; };
handleSelection = () => { handleSelection = () => {
const { value } = this.props; const value = this.props.value || '';
const selection = this.getSelection(); const selection = this.getSelection();
if (selection.start !== selection.end && !HAS_LINE_BREAK.test(selection.selected)) { if (selection.start !== selection.end && !HAS_LINE_BREAK.test(selection.selected)) {
try { try {