improve shortcode handling in visual editor
This commit is contained in:
parent
ca60a6b8c9
commit
1d654662d2
@ -33,17 +33,10 @@ export const NODE_COMPONENTS = {
|
||||
'table-row': props => <tr {...props.attributes}>{props.children}</tr>,
|
||||
'table-cell': props => <td {...props.attributes}>{props.children}</td>,
|
||||
'thematic-break': props => <hr {...props.attributes}/>,
|
||||
'shortcode-wrapper': props => <div {...props.attributes}>{props.children}</div>,
|
||||
link: props => {
|
||||
const data = props.node.get('data');
|
||||
const url = data.get('url');
|
||||
const title = data.get('title');
|
||||
return <a href={href} title={title} {...props.attributes}>{props.children}</a>;
|
||||
},
|
||||
shortcode: props => {
|
||||
const { attributes, node, state: editorState } = props;
|
||||
const isSelected = editorState.selection.hasFocusIn(node);
|
||||
const className = cn(styles.shortcode, { [styles.shortcodeSelected]: isSelected });
|
||||
return <span {...attributes} className={className} draggable >{node.data.get('shortcode')}</span>;
|
||||
return <div {...attributes} className={className} draggable >{node.data.get('shortcode')}</div>;
|
||||
},
|
||||
};
|
||||
|
@ -96,7 +96,7 @@
|
||||
.shortcode {
|
||||
border: 2px solid black;
|
||||
padding: 8px;
|
||||
margin: 2px 0;
|
||||
margin: 16px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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 ]);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user