diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js
index 3a06e026..4d891424 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js
@@ -33,17 +33,10 @@ export const NODE_COMPONENTS = {
'table-row': props =>
{props.children}
,
'table-cell': props => {props.children} | ,
'thematic-break': props =>
,
- 'shortcode-wrapper': props => {props.children}
,
- link: props => {
- const data = props.node.get('data');
- const url = data.get('url');
- const title = data.get('title');
- return {props.children};
- },
shortcode: props => {
const { attributes, node, state: editorState } = props;
const isSelected = editorState.selection.hasFocusIn(node);
const className = cn(styles.shortcode, { [styles.shortcodeSelected]: isSelected });
- return {node.data.get('shortcode')};
+ return {node.data.get('shortcode')}
;
},
};
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css
index 6c40e7c4..6a13efba 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css
@@ -96,7 +96,7 @@
.shortcode {
border: 2px solid black;
padding: 8px;
- margin: 2px 0;
+ margin: 16px 0;
cursor: pointer;
}
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
index 70c067e2..ebdaf6b1 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
@@ -26,7 +26,7 @@ export default class Editor extends Component {
marks: MARK_COMPONENTS,
rules: RULES,
},
- shortcodes: registry.getEditorComponents(),
+ shortcodePlugins: registry.getEditorComponents(),
};
}
@@ -45,7 +45,8 @@ export default class Editor extends Component {
handleDocumentChange = (doc, editorState) => {
const raw = Raw.serialize(editorState, { terse: true });
- const mdast = slateToRemark(raw);
+ const plugins = this.state.shortcodePlugins;
+ const mdast = slateToRemark(raw, plugins);
this.props.onChange(mdast);
};
@@ -136,12 +137,11 @@ export default class Editor extends Component {
const { editorState } = this.state;
const data = {
shortcode: plugin.id,
- shortcodeValue: plugin.toBlock(shortcodeData.toJS()),
shortcodeData,
};
const nodes = [Text.createFromString('')];
const block = { kind: 'block', type: 'shortcode', data, isVoid: true, nodes };
- const resolvedState = editorState.transform().insertBlock(block).apply();
+ const resolvedState = editorState.transform().insertBlock(block).focus().apply();
this.ref.onChange(resolvedState);
this.setState({ editorState: resolvedState });
};
@@ -180,7 +180,7 @@ export default class Editor extends Component {
codeBlock: this.getButtonProps('code', { isBlock: true }),
}}
onToggleMode={this.handleToggle}
- plugins={this.state.shortcodes}
+ plugins={this.state.shortcodePlugins}
onSubmit={this.handlePluginSubmit}
onAddAsset={onAddAsset}
onRemoveAsset={onRemoveAsset}
diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js
index 261f9e43..a367b875 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/rules.js
@@ -25,6 +25,21 @@ const enforceNeverEmpty = {
},
};
-const rules = [ enforceNeverEmpty ];
+/**
+ * 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: (transform, doc, node) => {
+ return transform.unwrapNodeByKey(node.key);
+ },
+};
+
+const rules = [ enforceNeverEmpty, shortcodesAtRoot ];
export default rules;
diff --git a/src/components/Widgets/Markdown/unified.js b/src/components/Widgets/Markdown/unified.js
index 2683850b..6a5071ba 100644
--- a/src/components/Widgets/Markdown/unified.js
+++ b/src/components/Widgets/Markdown/unified.js
@@ -143,17 +143,18 @@ const remarkShortcodes = ({ plugins }) => {
if (!nodeMayContainShortcode(node)) return node;
/**
- * Combine the text values of all children to a single string, then
- * check that string for a shortcode pattern match.
+ * Combine the text values of all children to a single string, check the
+ * string for a shortcode pattern match, and validate the match.
*/
- const text = mdastToString(node);
+ const text = mdastToString(node).trim();
const { plugin, match } = matchTextToPlugin(text);
+ const matchIsValid = validateMatch(text, match);
/**
- * If a matching shortcode plugin is found, return a new node with shortcode
- * data included. Otherwise, return the original node.
+ * If a valid match is found, return a new node with shortcode data
+ * included. Otherwise, return the original node.
*/
- return plugin ? createShortcodeNode(text, plugin, match) : node;
+ return matchIsValid ? createShortcodeNode(text, plugin, match) : node;
};
/**
@@ -185,6 +186,13 @@ const remarkShortcodes = ({ plugins }) => {
return { plugin, match };
}
+ /**
+ * A match is only valid if it takes up the entire paragraph.
+ */
+ function validateMatch(text, match) {
+ return match && match[0].length === text.length;
+ }
+
/**
* Create a new node with shortcode data included. Use an 'html' node instead
* of a 'text' node as the child to ensure the node content is not parsed by
@@ -242,7 +250,9 @@ const remarkToRehypeShortcodes = ({ plugins, getAsset }) => {
/**
* Return a new 'html' type node containing the shortcode preview markup.
*/
- return u('html', valueHtml);
+ const textNode = u('html', valueHtml);
+ const children = [ textNode ];
+ return { ...node, children };
}
};
@@ -471,7 +481,7 @@ export const remarkToSlate = mdast => {
return result;
};
-export const slateToRemark = raw => {
+export const slateToRemark = (raw, shortcodePlugins) => {
const typeMap = {
'paragraph': 'paragraph',
'heading-one': 'heading',
@@ -532,7 +542,9 @@ export const slateToRemark = raw => {
if (node.type === 'shortcode') {
const { data } = node;
- const textNode = u('html', data.shortcodeValue);
+ const plugin = shortcodePlugins.get(data.shortcode);
+ const text = plugin.toBlock(data.shortcodeData);
+ const textNode = u('html', text);
return u('paragraph', { data }, [ textNode ]);
}