2016-11-04 11:04:54 -07:00
|
|
|
import React, { Component, PropTypes } from 'react';
|
2017-06-19 17:15:59 -04:00
|
|
|
import { Map, List } from 'immutable';
|
|
|
|
import { Editor as SlateEditor, Html as SlateHtml, Raw as SlateRaw} from 'slate';
|
2017-06-09 23:49:14 -04:00
|
|
|
import unified from 'unified';
|
|
|
|
import markdownToRemark from 'remark-parse';
|
2017-06-19 17:15:59 -04:00
|
|
|
import remarkToRehype from 'remark-rehype';
|
|
|
|
import rehypeToHtml from 'rehype-stringify';
|
2017-06-09 23:49:14 -04:00
|
|
|
import remarkToMarkdown from 'remark-stringify';
|
2017-06-19 17:15:59 -04:00
|
|
|
import htmlToRehype from 'rehype-parse';
|
|
|
|
import rehypeToRemark from 'rehype-remark';
|
2017-06-23 14:42:40 -04:00
|
|
|
import registry from '../../../../../lib/registry';
|
|
|
|
import { createAssetProxy } from '../../../../../valueObjects/AssetProxy';
|
|
|
|
import {
|
|
|
|
remarkParseConfig,
|
|
|
|
remarkStringifyConfig,
|
|
|
|
rehypeParseConfig,
|
|
|
|
rehypeStringifyConfig,
|
|
|
|
} from '../../unifiedConfig';
|
2017-05-22 14:04:24 -04:00
|
|
|
import { buildKeymap } from './keymap';
|
|
|
|
import createMarkdownParser from './parser';
|
|
|
|
import Toolbar from '../Toolbar/Toolbar';
|
2017-06-23 14:42:40 -04:00
|
|
|
import { Sticky } from '../../../../UI/Sticky/Sticky';
|
2017-05-22 14:04:24 -04:00
|
|
|
import styles from './index.css';
|
2016-11-01 16:55:21 -07:00
|
|
|
|
2017-06-22 17:39:52 -04:00
|
|
|
/**
|
|
|
|
* Slate can serialize to html, but we persist the value as markdown. Serializing
|
|
|
|
* the html to markdown on every keystroke is a big perf hit, so we'll register
|
|
|
|
* functions to perform those actions only when necessary, such as after loading
|
|
|
|
* and before persisting.
|
|
|
|
*/
|
2017-06-22 17:35:47 -04:00
|
|
|
registry.registerWidgetValueSerializer('markdown', {
|
2017-06-21 13:49:43 -04:00
|
|
|
serialize: value => unified()
|
2017-06-23 14:42:40 -04:00
|
|
|
.use(htmlToRehype, rehypeParseConfig)
|
2017-06-21 13:49:43 -04:00
|
|
|
.use(htmlToRehype)
|
|
|
|
.use(rehypeToRemark)
|
2017-06-23 14:42:40 -04:00
|
|
|
.use(remarkToMarkdown, remarkStringifyConfig)
|
2017-06-21 13:49:43 -04:00
|
|
|
.processSync(value)
|
|
|
|
.contents,
|
|
|
|
deserialize: value => unified()
|
2017-06-23 14:42:40 -04:00
|
|
|
.use(markdownToRemark, remarkParseConfig)
|
2017-06-21 13:49:43 -04:00
|
|
|
.use(remarkToRehype)
|
2017-06-23 14:42:40 -04:00
|
|
|
.use(rehypeToHtml, rehypeStringifyConfig)
|
2017-06-21 13:49:43 -04:00
|
|
|
.processSync(value)
|
|
|
|
.contents
|
|
|
|
});
|
|
|
|
|
2016-11-01 17:58:19 -07:00
|
|
|
function processUrl(url) {
|
|
|
|
if (url.match(/^(https?:\/\/|mailto:|\/)/)) {
|
|
|
|
return url;
|
|
|
|
}
|
2017-03-16 20:45:46 -04:00
|
|
|
if (url.match(/^[^/]+\.[^/]+/)) {
|
2016-11-01 17:58:19 -07:00
|
|
|
return `https://${ url }`;
|
|
|
|
}
|
|
|
|
return `/${ url }`;
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
const DEFAULT_NODE = 'paragraph';
|
2016-11-01 17:58:19 -07:00
|
|
|
|
2016-11-01 23:31:20 -07:00
|
|
|
function schemaWithPlugins(schema, plugins) {
|
|
|
|
let nodeSpec = schema.nodeSpec;
|
|
|
|
plugins.forEach((plugin) => {
|
|
|
|
const attrs = {};
|
2017-05-22 14:04:24 -04:00
|
|
|
plugin.get('fields').forEach((field) => {
|
|
|
|
attrs[field.get('name')] = { default: null };
|
2016-11-01 23:31:20 -07:00
|
|
|
});
|
2017-05-22 14:04:24 -04:00
|
|
|
nodeSpec = nodeSpec.addToEnd(`plugin_${ plugin.get('id') }`, {
|
2016-11-01 23:31:20 -07:00
|
|
|
attrs,
|
2017-05-22 14:04:24 -04:00
|
|
|
group: 'block',
|
2017-05-22 14:34:49 -04:00
|
|
|
parseDOM: [{
|
|
|
|
tag: 'div[data-plugin]',
|
|
|
|
getAttrs(dom) {
|
|
|
|
return JSON.parse(dom.getAttribute('data-plugin'));
|
2016-11-01 23:31:20 -07:00
|
|
|
},
|
2017-05-22 14:34:49 -04:00
|
|
|
}],
|
2016-11-01 23:31:20 -07:00
|
|
|
toDOM(node) {
|
2017-05-22 14:34:49 -04:00
|
|
|
return ['div', { 'data-plugin': JSON.stringify(node.attrs) }, plugin.get('label')];
|
2016-11-01 23:31:20 -07:00
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return new Schema({
|
|
|
|
nodes: nodeSpec,
|
|
|
|
marks: schema.markSpec,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function createSerializer(schema, plugins) {
|
|
|
|
const serializer = Object.create(defaultMarkdownSerializer);
|
|
|
|
plugins.forEach((plugin) => {
|
2017-05-22 14:04:24 -04:00
|
|
|
serializer.nodes[`plugin_${ plugin.get('id') }`] = (state, node) => {
|
|
|
|
const toBlock = plugin.get('toBlock');
|
2017-01-10 22:23:22 -02:00
|
|
|
state.write(`${ toBlock.call(plugin, node.attrs) }\n\n`);
|
2016-11-01 23:31:20 -07:00
|
|
|
};
|
|
|
|
});
|
|
|
|
return serializer;
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
const BLOCK_TAGS = {
|
|
|
|
p: 'paragraph',
|
|
|
|
li: 'list-item',
|
|
|
|
ul: 'bulleted-list',
|
|
|
|
ol: 'numbered-list',
|
|
|
|
blockquote: 'quote',
|
|
|
|
pre: 'code',
|
|
|
|
h1: 'heading-one',
|
|
|
|
h2: 'heading-two',
|
|
|
|
h3: 'heading-three',
|
|
|
|
h4: 'heading-four',
|
|
|
|
h5: 'heading-five',
|
2017-06-27 12:21:34 -04:00
|
|
|
h6: 'heading-six',
|
2017-06-19 17:15:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const MARK_TAGS = {
|
|
|
|
strong: 'bold',
|
|
|
|
em: 'italic',
|
|
|
|
u: 'underline',
|
|
|
|
s: 'strikethrough',
|
2017-06-27 12:21:34 -04:00
|
|
|
del: 'strikethrough',
|
2017-06-19 17:15:59 -04:00
|
|
|
code: 'code'
|
|
|
|
}
|
|
|
|
|
2017-06-21 16:53:54 -04:00
|
|
|
const BLOCK_COMPONENTS = {
|
|
|
|
'paragraph': props => <p>{props.children}</p>,
|
|
|
|
'list-item': props => <li {...props.attributes}>{props.children}</li>,
|
2017-06-19 17:15:59 -04:00
|
|
|
'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>,
|
2017-06-21 16:53:54 -04:00
|
|
|
'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>,
|
|
|
|
'quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>,
|
|
|
|
'code': props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
2017-06-19 17:15:59 -04:00
|
|
|
'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>,
|
|
|
|
'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
|
|
|
|
'heading-three': props => <h3 {...props.attributes}>{props.children}</h3>,
|
|
|
|
'heading-four': props => <h4 {...props.attributes}>{props.children}</h4>,
|
|
|
|
'heading-five': props => <h5 {...props.attributes}>{props.children}</h5>,
|
|
|
|
'heading-six': props => <h6 {...props.attributes}>{props.children}</h6>,
|
2017-06-27 12:21:34 -04:00
|
|
|
'image': props => {
|
|
|
|
const data = props.node && props.node.get('data');
|
|
|
|
const src = data && data.get('src', props.src);
|
|
|
|
const alt = data && data.get('alt', props.alt);
|
|
|
|
return <img src={src} alt={alt} {...props.attributes}/>;
|
|
|
|
},
|
2017-06-19 17:15:59 -04:00
|
|
|
};
|
|
|
|
|
2017-06-21 16:53:54 -04:00
|
|
|
const NODE_COMPONENTS = {
|
|
|
|
...BLOCK_COMPONENTS,
|
|
|
|
'link': props => {
|
|
|
|
const href = props.node && props.node.getIn(['data', 'href']) || props.href;
|
|
|
|
return <a href={href} {...props.attributes}>{props.children}</a>;
|
|
|
|
},
|
2017-06-27 12:21:34 -04:00
|
|
|
};
|
2017-06-21 16:53:54 -04:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
const MARK_COMPONENTS = {
|
|
|
|
bold: props => <strong>{props.children}</strong>,
|
|
|
|
italic: props => <em>{props.children}</em>,
|
|
|
|
underlined: props => <u>{props.children}</u>,
|
2017-06-21 16:53:54 -04:00
|
|
|
strikethrough: props => <s>{props.children}</s>,
|
|
|
|
code: props => <code>{props.children}</code>,
|
2017-06-19 17:15:59 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
const RULES = [
|
|
|
|
{
|
|
|
|
deserialize(el, next) {
|
|
|
|
const block = BLOCK_TAGS[el.tagName]
|
|
|
|
if (!block) return
|
|
|
|
return {
|
|
|
|
kind: 'block',
|
|
|
|
type: block,
|
|
|
|
nodes: next(el.children)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
serialize(entity, children) {
|
2017-06-27 12:21:34 -04:00
|
|
|
if (['bulleted-list', 'numbered-list'].includes(entity.type)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-06-21 16:53:54 -04:00
|
|
|
const component = BLOCK_COMPONENTS[entity.type]
|
2017-06-19 17:15:59 -04:00
|
|
|
if (!component) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return component({ children });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
deserialize(el, next) {
|
|
|
|
const mark = MARK_TAGS[el.tagName]
|
|
|
|
if (!mark) return
|
|
|
|
return {
|
|
|
|
kind: 'mark',
|
|
|
|
type: mark,
|
|
|
|
nodes: next(el.children)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
serialize(entity, children) {
|
|
|
|
const component = MARK_COMPONENTS[entity.type]
|
|
|
|
if (!component) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return component({ children });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Special case for code blocks, which need to grab the nested children.
|
|
|
|
deserialize(el, next) {
|
|
|
|
if (el.tagName != 'pre') return
|
|
|
|
const code = el.children[0]
|
|
|
|
const children = code && code.tagName == 'code'
|
|
|
|
? code.children
|
|
|
|
: el.children
|
|
|
|
|
|
|
|
return {
|
|
|
|
kind: 'block',
|
|
|
|
type: 'code',
|
|
|
|
nodes: next(children)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2017-06-27 12:21:34 -04:00
|
|
|
{
|
|
|
|
deserialize(el, next) {
|
|
|
|
if (el.tagName != 'img') return
|
|
|
|
return {
|
|
|
|
kind: 'inline',
|
|
|
|
type: 'image',
|
|
|
|
nodes: [],
|
|
|
|
data: {
|
|
|
|
src: el.attribs.src,
|
|
|
|
alt: el.attribs.alt,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
serialize(entity, children) {
|
|
|
|
if (entity.type !== 'image') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const data = entity.get('data');
|
|
|
|
const props = {
|
|
|
|
src: data.get('src'),
|
|
|
|
alt: data.get('alt'),
|
|
|
|
attributes: data.get('attributes'),
|
|
|
|
};
|
|
|
|
return NODE_COMPONENTS.image(props);
|
|
|
|
}
|
|
|
|
},
|
2017-06-19 17:15:59 -04:00
|
|
|
{
|
|
|
|
// Special case for links, to grab their href.
|
|
|
|
deserialize(el, next) {
|
|
|
|
if (el.tagName != 'a') return
|
|
|
|
return {
|
|
|
|
kind: 'inline',
|
|
|
|
type: 'link',
|
|
|
|
nodes: next(el.children),
|
|
|
|
data: {
|
|
|
|
href: el.attribs.href
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2017-06-21 16:53:54 -04:00
|
|
|
serialize(entity, children) {
|
|
|
|
if (entity.type !== 'link') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const data = entity.get('data');
|
|
|
|
const props = {
|
|
|
|
href: data.get('href'),
|
|
|
|
attributes: data.get('attributes'),
|
|
|
|
children,
|
|
|
|
};
|
|
|
|
return NODE_COMPONENTS.link(props);
|
|
|
|
}
|
2017-06-19 17:15:59 -04:00
|
|
|
},
|
2017-06-27 12:21:34 -04:00
|
|
|
{
|
|
|
|
serialize(entity, children) {
|
|
|
|
if (!['bulleted-list', 'unordered-list'].includes(entity.type)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return NODE_COMPONENTS[entity.type]({ children });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
const serializer = new SlateHtml({ rules: RULES });
|
|
|
|
|
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 23:31:20 -07:00
|
|
|
const plugins = registry.getEditorComponents();
|
2017-06-27 12:21:34 -04:00
|
|
|
console.log(this.props.value);
|
2016-11-01 23:31:20 -07:00
|
|
|
this.state = {
|
2017-06-21 13:49:43 -04:00
|
|
|
editorState: serializer.deserialize(this.props.value || '<p></p>'),
|
2017-06-19 17:15:59 -04:00
|
|
|
schema: {
|
|
|
|
nodes: NODE_COMPONENTS,
|
|
|
|
marks: MARK_COMPONENTS,
|
|
|
|
},
|
2016-11-01 23:31:20 -07:00
|
|
|
plugins,
|
|
|
|
};
|
2016-11-01 16:55:21 -07:00
|
|
|
}
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-23 17:04:17 -04:00
|
|
|
handlePaste = (e, data, state) => {
|
|
|
|
if (data.type !== 'html' || data.isShift) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const fragment = serializer.deserialize(data.html).document;
|
|
|
|
return state.transform().insertFragment(fragment).apply();
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
handleDocumentChange = (doc, editorState) => {
|
|
|
|
const html = serializer.serialize(editorState);
|
2017-06-21 13:49:43 -04:00
|
|
|
this.props.onChange(html);
|
2017-06-19 17:15:59 -04:00
|
|
|
};
|
2017-07-23 19:38:05 +02:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
hasMark = type => this.state.editorState.marks.some(mark => mark.type === type);
|
|
|
|
hasBlock = type => this.state.editorState.blocks.some(node => node.type === type);
|
2017-07-23 19:38:05 +02:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
handleKeyDown = (e, data, state) => {
|
|
|
|
if (!data.isMod) {
|
|
|
|
return;
|
2017-07-23 19:38:05 +02:00
|
|
|
}
|
2017-06-19 17:15:59 -04:00
|
|
|
const marks = {
|
|
|
|
b: 'bold',
|
|
|
|
i: 'italic',
|
|
|
|
u: 'underlined',
|
2017-06-27 12:21:34 -04:00
|
|
|
s: 'strikethrough',
|
2017-06-19 17:15:59 -04:00
|
|
|
'`': 'code',
|
|
|
|
};
|
2017-07-23 19:38:05 +02:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
const mark = marks[data.key];
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
if (mark) {
|
|
|
|
state = state.transform().toggleMark(mark).apply();
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
2017-06-19 17:15:59 -04:00
|
|
|
return;
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
handleMarkClick = (event, type) => {
|
|
|
|
event.preventDefault();
|
2017-06-21 17:08:53 -04:00
|
|
|
const resolvedState = this.state.editorState.transform().focus().toggleMark(type).apply();
|
2017-06-19 17:15:59 -04:00
|
|
|
this.ref.onChange(resolvedState);
|
|
|
|
this.setState({ editorState: resolvedState });
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
handleBlockClick = (event, type) => {
|
|
|
|
event.preventDefault();
|
|
|
|
let { editorState } = this.state;
|
2017-06-21 17:08:53 -04:00
|
|
|
const transform = editorState.transform().focus();
|
2017-06-19 17:15:59 -04:00
|
|
|
const doc = editorState.document;
|
|
|
|
const isList = this.hasBlock('list-item')
|
|
|
|
|
|
|
|
// Handle everything except list buttons.
|
|
|
|
if (!['bulleted-list', 'numbered-list'].includes(type)) {
|
|
|
|
const isActive = this.hasBlock(type);
|
|
|
|
const transformed = transform.setBlock(isActive ? DEFAULT_NODE : type);
|
|
|
|
|
|
|
|
if (isList) {
|
|
|
|
transformed
|
|
|
|
.unwrapBlock('bulleted-list')
|
|
|
|
.unwrapBlock('numbered-list');
|
2017-03-18 14:13:22 -07:00
|
|
|
}
|
2017-06-19 17:15:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle the extra wrapping required for list buttons.
|
|
|
|
else {
|
|
|
|
const isType = editorState.blocks.some(block => {
|
|
|
|
return !!doc.getClosest(block.key, parent => parent.type === type);
|
|
|
|
});
|
2017-03-02 12:02:55 -08:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
if (isList && isType) {
|
|
|
|
transform
|
|
|
|
.setBlock(DEFAULT_NODE)
|
|
|
|
.unwrapBlock('bulleted-list')
|
|
|
|
.unwrapBlock('numbered-list');
|
|
|
|
} else if (isList) {
|
|
|
|
transform
|
|
|
|
.unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
|
|
|
|
.wrapBlock(type);
|
|
|
|
} else {
|
|
|
|
transform
|
|
|
|
.setBlock('list-item')
|
|
|
|
.wrapBlock(type);
|
|
|
|
}
|
2017-05-22 14:34:49 -04:00
|
|
|
}
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-21 17:08:53 -04:00
|
|
|
const resolvedState = transform.apply();
|
2017-06-19 17:15:59 -04:00
|
|
|
this.ref.onChange(resolvedState);
|
|
|
|
this.setState({ editorState: resolvedState });
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
|
|
|
|
2016-11-01 17:58:19 -07:00
|
|
|
handleLink = () => {
|
|
|
|
let url = null;
|
2016-11-01 23:31:20 -07:00
|
|
|
if (!markActive(this.view.state, this.state.schema.marks.link)) {
|
2017-03-16 20:45:46 -04:00
|
|
|
url = prompt('Link URL:'); // eslint-disable-line no-alert
|
2016-11-01 17:58:19 -07:00
|
|
|
}
|
2017-05-22 14:34:49 -04:00
|
|
|
const command = toggleMark(this.state.schema.marks.link, { href: url ? processUrl(url) : null });
|
2016-11-01 17:58:19 -07:00
|
|
|
command(this.view.state, this.handleAction);
|
|
|
|
};
|
|
|
|
|
2017-04-18 11:09:14 -04:00
|
|
|
handlePluginSubmit = (plugin, data) => {
|
2016-11-01 23:31:20 -07:00
|
|
|
const { schema } = this.state;
|
2017-05-22 14:04:24 -04:00
|
|
|
const nodeType = schema.nodes[`plugin_${ plugin.get('id') }`];
|
2017-06-19 17:15:59 -04:00
|
|
|
//this.view.props.onAction(this.view.state.tr.replaceSelectionWith(nodeType.create(data.toJS())).action());
|
2016-11-01 23:31:20 -07:00
|
|
|
};
|
|
|
|
|
2016-12-27 23:18:37 -08:00
|
|
|
handleDragEnter = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
this.setState({ dragging: true });
|
|
|
|
};
|
|
|
|
|
|
|
|
handleDragLeave = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
this.setState({ dragging: false });
|
|
|
|
};
|
|
|
|
|
|
|
|
handleDragOver = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
|
|
|
|
handleDrop = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
this.setState({ dragging: false });
|
|
|
|
|
|
|
|
const { schema } = this.state;
|
|
|
|
|
|
|
|
const nodes = [];
|
|
|
|
|
|
|
|
if (e.dataTransfer.files && e.dataTransfer.files.length) {
|
|
|
|
Array.from(e.dataTransfer.files).forEach((file) => {
|
2017-05-22 14:34:49 -04:00
|
|
|
createAssetProxy(file.name, file)
|
|
|
|
.then((assetProxy) => {
|
2017-01-10 22:23:22 -02:00
|
|
|
this.props.onAddAsset(assetProxy);
|
2017-05-22 14:04:24 -04:00
|
|
|
if (file.type.split('/')[0] === 'image') {
|
2017-01-10 22:23:22 -02:00
|
|
|
nodes.push(
|
2017-05-22 14:34:49 -04:00
|
|
|
schema.nodes.image.create({ src: assetProxy.public_path, alt: file.name })
|
2017-01-10 22:23:22 -02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
nodes.push(
|
2017-05-22 14:34:49 -04:00
|
|
|
schema.marks.link.create({ href: assetProxy.public_path, title: file.name })
|
2017-01-10 22:23:22 -02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2016-12-27 23:18:37 -08:00
|
|
|
});
|
|
|
|
} else {
|
2017-05-22 14:34:49 -04:00
|
|
|
nodes.push(schema.nodes.paragraph.create({}, e.dataTransfer.getData('text/plain')));
|
2016-12-27 23:18:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
nodes.forEach((node) => {
|
2017-06-19 17:15:59 -04:00
|
|
|
//this.view.props.onAction(this.view.state.tr.replaceSelectionWith(node).action());
|
2016-12-27 23:18:37 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-11-01 16:55:21 -07:00
|
|
|
handleToggle = () => {
|
2017-05-22 14:04:24 -04:00
|
|
|
this.props.onMode('raw');
|
2016-10-03 14:25:27 +02:00
|
|
|
};
|
2016-08-11 11:27:09 -03:00
|
|
|
|
2017-06-19 17:15:59 -04:00
|
|
|
getButtonProps = (type, isBlock) => {
|
|
|
|
const handler = isBlock ? this.handleBlockClick: this.handleMarkClick;
|
|
|
|
const isActive = isBlock ? this.hasBlock : this.hasMark;
|
|
|
|
return { onAction: e => handler(e, type), active: isActive(type) };
|
|
|
|
};
|
|
|
|
|
2016-08-11 11:27:09 -03:00
|
|
|
render() {
|
2017-01-10 22:23:22 -02:00
|
|
|
const { onAddAsset, onRemoveAsset, getAsset } = this.props;
|
2017-03-16 20:45:46 -04:00
|
|
|
const { plugins, selectionPosition, dragging } = this.state;
|
2016-12-27 23:18:37 -08:00
|
|
|
const classNames = [styles.editor];
|
|
|
|
if (dragging) {
|
|
|
|
classNames.push(styles.dragging);
|
|
|
|
}
|
2016-11-01 16:55:21 -07:00
|
|
|
|
2017-05-22 14:34:49 -04:00
|
|
|
return (<div
|
|
|
|
className={classNames.join(' ')}
|
|
|
|
onDragEnter={this.handleDragEnter}
|
|
|
|
onDragLeave={this.handleDragLeave}
|
|
|
|
onDragOver={this.handleDragOver}
|
|
|
|
onDrop={this.handleDrop}
|
|
|
|
>
|
|
|
|
<Sticky
|
|
|
|
className={styles.editorControlBar}
|
|
|
|
classNameActive={styles.editorControlBarSticky}
|
|
|
|
fillContainerWidth
|
2017-05-04 15:19:43 -04:00
|
|
|
>
|
2017-05-22 14:34:49 -04:00
|
|
|
<Toolbar
|
|
|
|
selectionPosition={selectionPosition}
|
2017-06-19 17:15:59 -04:00
|
|
|
buttons={{
|
|
|
|
h1: this.getButtonProps('heading-one', true),
|
|
|
|
h2: this.getButtonProps('heading-two', true),
|
|
|
|
bold: this.getButtonProps('bold'),
|
|
|
|
italic: this.getButtonProps('italic'),
|
|
|
|
link: this.getButtonProps('link'),
|
|
|
|
}}
|
2017-05-22 14:34:49 -04:00
|
|
|
onToggleMode={this.handleToggle}
|
|
|
|
plugins={plugins}
|
|
|
|
onSubmit={this.handlePluginSubmit}
|
|
|
|
onAddAsset={onAddAsset}
|
|
|
|
onRemoveAsset={onRemoveAsset}
|
|
|
|
getAsset={getAsset}
|
|
|
|
/>
|
|
|
|
</Sticky>
|
2017-06-19 17:15:59 -04:00
|
|
|
<SlateEditor
|
|
|
|
className={styles.slateEditor}
|
|
|
|
state={this.state.editorState}
|
|
|
|
schema={this.state.schema}
|
|
|
|
onChange={editorState => this.setState({ editorState })}
|
|
|
|
onDocumentChange={this.handleDocumentChange}
|
|
|
|
onKeyDown={this.onKeyDown}
|
2017-06-23 17:04:17 -04:00
|
|
|
onPaste={this.handlePaste}
|
2017-06-19 17:15:59 -04:00
|
|
|
ref={ref => this.ref = ref}
|
|
|
|
spellCheck
|
|
|
|
/>
|
2017-05-22 14:34:49 -04:00
|
|
|
<div className={styles.shim} />
|
|
|
|
</div>);
|
2016-08-11 11:27:09 -03:00
|
|
|
}
|
|
|
|
}
|
2016-11-04 11:04:54 -07:00
|
|
|
|
|
|
|
Editor.propTypes = {
|
2017-01-10 22:23:22 -02:00
|
|
|
onAddAsset: PropTypes.func.isRequired,
|
|
|
|
onRemoveAsset: PropTypes.func.isRequired,
|
|
|
|
getAsset: PropTypes.func.isRequired,
|
2016-11-04 11:04:54 -07:00
|
|
|
onChange: PropTypes.func.isRequired,
|
|
|
|
onMode: PropTypes.func.isRequired,
|
|
|
|
value: PropTypes.node,
|
|
|
|
};
|