diff --git a/package.json b/package.json
index fa53cd9d..64b5b4c9 100644
--- a/package.json
+++ b/package.json
@@ -176,12 +176,12 @@
"remark-stringify": "^3.0.1",
"sanitize-filename": "^1.6.1",
"semaphore": "^1.0.5",
- "slate": "^0.27.0",
+ "slate": "^0.28.0",
"slate-edit-list": "^0.9.0",
"slate-edit-table": "^0.12.0",
"slate-plain-serializer": "^0.2.0",
- "slate-react": "^0.4.0",
- "slate-soft-break": "^0.4.0",
+ "slate-react": "^0.8.0",
+ "slate-soft-break": "^0.5.0",
"slug": "^0.9.1",
"toml-j0.4": "^1.1.1",
"tomlify-j0.4": "^3.0.0-alpha.0",
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js
deleted file mode 100644
index cdae5ee2..00000000
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react';
-import { List } from 'immutable';
-import cn from 'classnames';
-
-/**
- * Slate uses React components to render each type of node that it receives.
- * This is the closest thing Slate has to a schema definition. The types are set
- * by us when we manually deserialize from Remark's MDAST to Slate's AST.
- */
-
-export const MARK_COMPONENTS = {
- bold: props => {props.children},
- italic: props => {props.children},
- strikethrough: props => {props.children},
- code: props => {props.children}
,
-};
-
-export const NODE_COMPONENTS = {
- 'paragraph': props =>
{props.children}
,
- 'list-item': props => {props.children},
- 'quote': props => {props.children}
,
- 'code': props => {props.children}
,
- 'heading-one': props => {props.children}
,
- 'heading-two': props => {props.children}
,
- 'heading-three': props => {props.children}
,
- 'heading-four': props => {props.children}
,
- 'heading-five': props => {props.children}
,
- 'heading-six': props => {props.children}
,
- 'table': props => ,
- 'table-row': props => {props.children}
,
- 'table-cell': props => {props.children} | ,
- 'thematic-break': props =>
,
- 'bulleted-list': props => ,
- 'numbered-list': props =>
- {props.children}
,
- 'link': props => {
- const data = props.node.get('data');
- const marks = data.get('marks');
- const url = data.get('url');
- const title = data.get('title');
- const link = {props.children};
- const result = !marks ? link : marks.reduce((acc, mark) => {
- const MarkComponent = MARK_COMPONENTS[mark.type];
- return {acc};
- }, link);
- return result;
- },
- 'image': props => {
- const data = props.node.get('data');
- const marks = data.get('marks');
- const url = data.get('url');
- const title = data.get('title');
- const alt = data.get('alt');
- const image = ;
- const result = !marks ? image : marks.reduce((acc, mark) => {
- const MarkComponent = MARK_COMPONENTS[mark.type];
- return {acc};
- }, image);
- return result;
- },
- 'shortcode': props => {
- const { attributes, node, state: editorState } = props;
- const isSelected = editorState.selection.hasFocusIn(node);
- const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected });
- return {node.data.get('shortcode')}
;
- },
-};
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
index 22177e1c..9a8cfe6d 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
@@ -7,8 +7,8 @@ import { slateToMarkdown, markdownToSlate, htmlToSlate } from '../../serializers
import registry from '../../../../../lib/registry';
import Toolbar from '../Toolbar/Toolbar';
import { Sticky } from '../../../../UI/Sticky/Sticky';
-import { MARK_COMPONENTS, NODE_COMPONENTS } from './components';
-import RULES from './rules';
+import { renderNode, renderMark } from './renderers';
+import { validateNode } from './validators';
import plugins, { EditListConfigured } from './plugins';
import onKeyDown from './keys';
@@ -24,11 +24,6 @@ export default class Editor extends Component {
const editorState = State.create({ document });
this.state = {
editorState,
- schema: {
- nodes: NODE_COMPONENTS,
- marks: MARK_COMPONENTS,
- rules: RULES,
- },
shortcodePlugins: registry.getEditorComponents(),
};
}
@@ -208,7 +203,9 @@ export default class Editor extends Component {
{
+ switch (props.mark.type) {
+ case bold: return props => {props.children};
+ case italic: return props => {props.children};
+ case strikethrough: return props => {props.children};
+ case code: return props => {props.children}
;
+ }
+};
+
+export const renderNode = props => {
+ switch (props.node.type) {
+ case 'paragraph': return props => {props.children}
;
+ case 'list-item': return props => {props.children};
+ case 'quote': return props => {props.children}
;
+ case 'code': return props => {props.children}
;
+ case 'heading-one': return props => {props.children}
;
+ case 'heading-two': return props => {props.children}
;
+ case 'heading-three': return props => {props.children}
;
+ case 'heading-four': return props => {props.children}
;
+ case 'heading-five': return props => {props.children}
;
+ case 'heading-six': return props => {props.children}
;
+ case 'table': return props => ;
+ case 'table-row': return props => {props.children}
;
+ case 'table-cell': return props => {props.children} | ;
+ case 'thematic-break': return props =>
;
+ case 'bulleted-list': return props => ;
+ case 'numbered-list': return props => (
+ {props.children}
+ );
+ case 'link': return props => {
+ const data = props.node.get('data');
+ const marks = data.get('marks');
+ const url = data.get('url');
+ const title = data.get('title');
+ const link = {props.children};
+ const result = !marks ? link : marks.reduce((acc, mark) => {
+ const MarkComponent = MARK_COMPONENTS[mark.type];
+ return {acc};
+ }, link);
+ return result;
+ };
+ case 'image': props => {
+ const data = props.node.get('data');
+ const marks = data.get('marks');
+ const url = data.get('url');
+ const title = data.get('title');
+ const alt = data.get('alt');
+ const image = ;
+ const result = !marks ? image : marks.reduce((acc, mark) => {
+ const MarkComponent = MARK_COMPONENTS[mark.type];
+ return {acc};
+ }, image);
+ return result;
+ };
+ case 'shortcode': props => {
+ const { attributes, node, state: editorState } = props;
+ const isSelected = editorState.selection.hasFocusIn(node);
+ const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected });
+ return {node.data.get('shortcode')}
;
+ };
+ }
+};
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js
deleted file mode 100644
index c898aff1..00000000
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Block, Text } from 'slate';
-
-/**
- * Rules are used to validate the editor state each time it changes, to ensure
- * it is never rendered in an undesirable state.
- */
-
-/**
- * If the editor is ever in an empty state, insert an empty
- * paragraph block.
- */
-const enforceNeverEmpty = {
- match: object => object.kind === 'document',
- validate: doc => {
- const hasBlocks = !doc.getBlocks().isEmpty();
- return hasBlocks ? null : {};
- },
- normalize: change => {
- const block = Block.create({
- type: 'paragraph',
- nodes: [Text.create('')],
- });
- const { key } = change.state.document;
- return change.insertNodeByKey(key, 0, block).focus();
- },
-};
-
-/**
- * Ensure that shortcodes are children of the root node.
- */
-const shortcodesAtRoot = {
- match: object => object.kind === 'document',
- validate: doc => {
- return doc.findDescendant(node => {
- return node.type === 'shortcode' && doc.getParent(node.key).key !== doc.key;
- });
- },
- normalize: (change, doc, node) => {
- return change.unwrapNodeByKey(node.key);
- },
-};
-
-/**
- * Ensure that trailing shortcodes are followed by an empty paragraph.
- */
-const noTrailingShortcodes = {
- match: object => object.kind === 'document',
- validate: doc => {
- return doc.findDescendant(node => {
- return node.type === 'shortcode' && doc.getBlocks().last().key === node.key;
- });
- },
- normalize: (change, doc, node) => {
- const text = Text.create('');
- const block = Block.create({ type: 'paragraph', nodes: [ text ] });
- return change.insertNodeByKey(doc.key, doc.get('nodes').size, block);
- },
-};
-
-/**
- * Ensure that code blocks contain no marks.
- */
-const codeBlocksContainPlainText = {
- match: node => node.type === 'code',
- validate: node => {
- const invalidChild = node.getTexts().find(text => !text.getMarks().isEmpty());
- return invalidChild || null;
- },
- normalize: (change, node, invalidChild) => {
- invalidChild.getMarks().forEach(mark => {
- change.removeMarkByKey(invalidChild.key, 0, invalidChild.get('characters').size, mark);
- });
- },
-};
-
-const rules = [ enforceNeverEmpty, shortcodesAtRoot, noTrailingShortcodes, codeBlocksContainPlainText ];
-
-export default rules;
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js
new file mode 100644
index 00000000..4c7be246
--- /dev/null
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js
@@ -0,0 +1,69 @@
+import { Block, Text } from 'slate';
+
+/**
+ * Validation functions are used to validate the editor state each time it
+ * changes, to ensure it is never rendered in an undesirable state.
+ */
+export function validateNode(node) {
+ /**
+ * Validation of the document itself.
+ */
+ if (node.kind === 'document') {
+ /**
+ * If the editor is ever in an empty state, insert an empty
+ * paragraph block.
+ */
+ const hasBlocks = !doc.getBlocks().isEmpty();
+ if (!hasBlocks) {
+ return change => {
+ const block = Block.create({
+ type: 'paragraph',
+ nodes: [Text.create('')],
+ });
+ const { key } = change.state.document;
+ return change.insertNodeByKey(key, 0, block).focus();
+ };
+ }
+
+ /**
+ * Ensure that shortcodes are children of the root node.
+ */
+ const nestedShortcode = node.findDescendant(descendant => {
+ const { type, key } = descendant;
+ return type === 'shortcode' && node.getParent(key).key !== node.key;
+ });
+ if (nestedShortcode) {
+ return change => change.unwrapNodeByKey(node.key);
+ }
+
+ /**
+ * Ensure that trailing shortcodes are followed by an empty paragraph.
+ */
+ const trailingShortcode = node.findDescendant(descendant => {
+ const { type, key } = descendant;
+ return type === 'shortcode' && node.getBlocks().last().key === key;
+ });
+ if (trailingShortcode) {
+ return change => {
+ const text = Text.create('');
+ const block = Block.create({ type: 'paragraph', nodes: [ text ] });
+ return change.insertNodeByKey(node.key, node.get('nodes').size, block);
+ };
+ }
+ }
+
+
+ /**
+ * Ensure that code blocks contain no marks.
+ */
+ if (node.type === 'code') {
+ const invalidChild = node.getTexts().find(text => !text.getMarks().isEmpty());
+ if (invalidChild) {
+ return change => (
+ invalidChild.getMarks().forEach(mark => (
+ change.removeMarkByKey(invalidChild.key, 0, invalidChild.get('characters').size, mark)
+ ))
+ );
+ }
+ }
+};
diff --git a/yarn.lock b/yarn.lock
index 89fa8656..ae58dca3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2827,7 +2827,7 @@ es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
es5-ext "^0.10.14"
es6-symbol "^3.1"
-es6-map@^0.1.3, es6-map@^0.1.4:
+es6-map@^0.1.3:
version "0.1.5"
resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
dependencies:
@@ -4472,6 +4472,10 @@ is-hexadecimal@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69"
+is-hotkey@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.1.1.tgz#b279a2fd108391be9aa93c6cb317f50357da549a"
+
is-in-browser@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
@@ -8521,13 +8525,13 @@ slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-slate-base64-serializer@^0.1.14:
+slate-base64-serializer@^0.1.22:
version "0.1.22"
resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.1.22.tgz#548e589178c75653004168004aad152f1976dd35"
dependencies:
isomorphic-base64 "^1.0.2"
-slate-dev-logger@^0.1.15, slate-dev-logger@^0.1.20, slate-dev-logger@^0.1.23:
+slate-dev-logger@^0.1.23:
version "0.1.33"
resolved "https://registry.yarnpkg.com/slate-dev-logger/-/slate-dev-logger-0.1.33.tgz#b4a4272255c2d598e5f26db5d85c58435357755f"
@@ -8539,24 +8543,25 @@ slate-edit-table@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/slate-edit-table/-/slate-edit-table-0.12.0.tgz#9163e67b8025c3c09d6037eb76cb5e652b65dd47"
-slate-plain-serializer@^0.2.0:
+slate-plain-serializer@^0.2.0, slate-plain-serializer@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/slate-plain-serializer/-/slate-plain-serializer-0.2.8.tgz#9bff5fafa09ab2ad47d961820f09d7d2abcb20a9"
dependencies:
slate-dev-logger "^0.1.23"
-slate-prop-types@^0.2.0:
+slate-prop-types@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/slate-prop-types/-/slate-prop-types-0.2.8.tgz#2d0e1df0a372c635068c6f74a52b567b996f51c2"
dependencies:
slate-dev-logger "^0.1.23"
-slate-react@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.4.0.tgz#e15c9034df5ea58fcb8a0c49c1cd159702296e0c"
+slate-react@^0.8.0:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.8.2.tgz#035452c7aa90d7ec37f097e2430b2ce4198cfb78"
dependencies:
debug "^2.3.2"
get-window "^1.1.1"
+ is-hotkey "^0.1.1"
is-in-browser "^1.1.3"
is-window "^1.0.2"
keycode "^2.1.2"
@@ -8564,27 +8569,26 @@ slate-react@^0.4.0:
react-immutable-proptypes "^2.1.0"
react-portal "^3.1.0"
selection-is-backward "^1.0.0"
- slate-base64-serializer "^0.1.14"
- slate-dev-logger "^0.1.15"
- slate-plain-serializer "^0.2.0"
- slate-prop-types "^0.2.0"
+ slate-base64-serializer "^0.1.22"
+ slate-dev-logger "^0.1.23"
+ slate-plain-serializer "^0.2.8"
+ slate-prop-types "^0.2.8"
-slate-soft-break@^0.4.0:
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/slate-soft-break/-/slate-soft-break-0.4.3.tgz#e3a9279a9b92ca173915467f5fbd359f739a7e96"
+slate-soft-break@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/slate-soft-break/-/slate-soft-break-0.5.1.tgz#817348c6c38c5c4983f58de3bc497234b27378eb"
-slate@^0.27.0:
- version "0.27.5"
- resolved "https://registry.yarnpkg.com/slate/-/slate-0.27.5.tgz#ab9d9f35e03f1910c59016ad66ee685255b5a645"
+slate@^0.28.0:
+ version "0.28.2"
+ resolved "https://registry.yarnpkg.com/slate/-/slate-0.28.2.tgz#e740976ae494c9a2952e925b00f2694416b5e84d"
dependencies:
debug "^2.3.2"
direction "^0.1.5"
- es6-map "^0.1.4"
esrever "^0.2.0"
is-empty "^1.0.0"
is-plain-object "^2.0.4"
lodash "^4.17.4"
- slate-dev-logger "^0.1.20"
+ slate-dev-logger "^0.1.23"
type-of "^2.0.1"
slice-ansi@0.0.4: