diff --git a/package.json b/package.json index 4b84871d..30d972c6 100644 --- a/package.json +++ b/package.json @@ -74,10 +74,10 @@ "json-loader": "^0.5.4", "localforage": "^1.4.2", "lodash": "^4.13.1", + "markup-it": "git+https://github.com/cassiozen/markup-it.git", "pluralize": "^3.0.0", "react-portal": "^2.2.1", "selection-position": "^1.0.0", - "slate": "^0.11.2", - "slate-markdown-serializer": "^0.1.5" + "slate": "^0.12.2" } } diff --git a/src/components/Widgets/MarkdownControl.js b/src/components/Widgets/MarkdownControl.js index 26c576c4..dee24487 100644 --- a/src/components/Widgets/MarkdownControl.js +++ b/src/components/Widgets/MarkdownControl.js @@ -1,8 +1,9 @@ import React, { PropTypes } from 'react'; import _ from 'lodash'; -import { Editor, Plain } from 'slate'; +import { Editor, Raw } from 'slate'; import position from 'selection-position'; -import Markdown from 'slate-markdown-serializer'; +import MarkupIt, { SlateUtils } from 'markup-it'; +import getSyntax from './MarkdownControlElements/syntax'; import { DEFAULT_NODE, NODES, MARKS } from './MarkdownControlElements/localRenderers'; import StylesMenu from './MarkdownControlElements/StylesMenu'; import BlockTypesMenu from './MarkdownControlElements/BlockTypesMenu'; @@ -15,11 +16,12 @@ class MarkdownControl extends React.Component { constructor(props) { super(props); - this.customMarkdownSerialize = this.customMarkdownSerialize.bind(this); - this.markdown = new Markdown({ rules: [{ serialize: this.customMarkdownSerialize }] }); + this.getMedia = this.getMedia.bind(this); + const MarkdownSyntax = getSyntax(this.getMedia); + this.markdown = new MarkupIt(MarkdownSyntax); this.customImageNodeRenderer = this.customImageNodeRenderer.bind(this); - NODES['image'] = this.customImageNodeRenderer; + NODES['mediaproxy'] = this.customImageNodeRenderer; this.blockEdit = false; this.menuPositions = { @@ -37,8 +39,29 @@ class MarkdownControl extends React.Component { } }; + let rawJson; + if (props.value !== undefined) { + // Parse the markdown + const content = this.markdown.toContent(props.value); + // Convert the content to JSON + rawJson = SlateUtils.encode(content); + } else { + rawJson = { + nodes: [ + { kind: 'block', + type: 'paragraph', + nodes: [{ + kind: 'text', + ranges: [{ + text: '' + }] + }] + } + ] + }; + } this.state = { - state: props.value ? this.markdown.deserialize(props.value) : Plain.deserialize('') + state: Raw.deserialize(rawJson, { terse: true }) }; this.handleChange = this.handleChange.bind(this); @@ -57,18 +80,13 @@ class MarkdownControl extends React.Component { this.renderMark = this.renderMark.bind(this); } + getMedia(src) { + return this.props.getMedia(src); + } /** - * The two custom methods customMarkdownSerialize and customImageNodeRenderer make sure that - * both Markdown serializer and Node renderers have access to getMedia with the latest state. + * Custom local renderer for image proxy. */ - customMarkdownSerialize(obj, children) { - if (obj.kind === 'block' && obj.type === 'image') { - const src = this.props.getMedia(obj.getIn(['data', 'src'])); - const alt = obj.getIn(['data', 'alt']) || ''; - return `![${alt}](${src})`; - } - } customImageNodeRenderer(editorProps) { const { node, state } = editorProps; const isFocused = state.selection.hasEdgeIn(node); @@ -95,7 +113,9 @@ class MarkdownControl extends React.Component { } handleDocumentChange(document, state) { - this.props.onChange(this.markdown.serialize(state)); + const rawJson = Raw.serialize(state, { terse: true }); + const content = SlateUtils.decode(rawJson); + this.props.onChange(this.markdown.toText(content)); } calculateHoverMenuPosition() { @@ -144,13 +164,13 @@ class MarkdownControl extends React.Component { const { document } = state; // Handle everything but list buttons. - if (type != 'bulleted-list' && type != 'numbered-list') { + if (type != 'unordered_list' && type != 'ordered_list') { if (isList) { transform = transform .setBlock(isActive ? DEFAULT_NODE : type) - .unwrapBlock('bulleted-list') - .unwrapBlock('numbered-list'); + .unwrapBlock('unordered_list') + .unwrapBlock('ordered_list'); } else { @@ -168,14 +188,14 @@ class MarkdownControl extends React.Component { if (isList && isType) { transform = transform .setBlock(DEFAULT_NODE) - .unwrapBlock('bulleted-list'); + .unwrapBlock('unordered_list'); } else if (isList) { transform = transform - .unwrapBlock(type == 'bulleted-list') + .unwrapBlock(type == 'unordered_list') .wrapBlock(type); } else { transform = transform - .setBlock('list-item') + .setBlock('list_item') .wrapBlock(type); } } @@ -237,16 +257,20 @@ class MarkdownControl extends React.Component { handleImageClick(mediaProxy) { let { state } = this.state; this.props.onAddMedia(mediaProxy); - state = state - .transform() - .insertBlock({ - type: 'image', - isVoid: true, - data: { src: mediaProxy.path } - }) - .apply(); - this.setState({ state }, this.focusAndAddParagraph); + state = state + .transform() + .insertInline({ + type: 'mediaproxy', + isVoid: true, + data: { src: mediaProxy.path } + }) + .collapseToEnd() + .insertBlock(DEFAULT_NODE) + .focus() + .apply(); + + this.setState({ state }); } focusAndAddParagraph() { diff --git a/src/components/Widgets/MarkdownControlElements/BlockTypesMenu.js b/src/components/Widgets/MarkdownControlElements/BlockTypesMenu.js index a05a155e..5337668f 100644 --- a/src/components/Widgets/MarkdownControlElements/BlockTypesMenu.js +++ b/src/components/Widgets/MarkdownControlElements/BlockTypesMenu.js @@ -96,7 +96,7 @@ export default class BlockTypesMenu extends Component { if (this.state.expanded) { return (