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-row': props => <tr {...props.attributes}>{props.children}</tr>,
|
||||||
'table-cell': props => <td {...props.attributes}>{props.children}</td>,
|
'table-cell': props => <td {...props.attributes}>{props.children}</td>,
|
||||||
'thematic-break': props => <hr {...props.attributes}/>,
|
'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 => {
|
shortcode: props => {
|
||||||
const { attributes, node, state: editorState } = props;
|
const { attributes, node, state: editorState } = props;
|
||||||
const isSelected = editorState.selection.hasFocusIn(node);
|
const isSelected = editorState.selection.hasFocusIn(node);
|
||||||
const className = cn(styles.shortcode, { [styles.shortcodeSelected]: isSelected });
|
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 {
|
.shortcode {
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
margin: 2px 0;
|
margin: 16px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export default class Editor extends Component {
|
|||||||
marks: MARK_COMPONENTS,
|
marks: MARK_COMPONENTS,
|
||||||
rules: RULES,
|
rules: RULES,
|
||||||
},
|
},
|
||||||
shortcodes: registry.getEditorComponents(),
|
shortcodePlugins: registry.getEditorComponents(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,8 @@ export default class Editor extends Component {
|
|||||||
|
|
||||||
handleDocumentChange = (doc, editorState) => {
|
handleDocumentChange = (doc, editorState) => {
|
||||||
const raw = Raw.serialize(editorState, { terse: true });
|
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);
|
this.props.onChange(mdast);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -136,12 +137,11 @@ export default class Editor extends Component {
|
|||||||
const { editorState } = this.state;
|
const { editorState } = this.state;
|
||||||
const data = {
|
const data = {
|
||||||
shortcode: plugin.id,
|
shortcode: plugin.id,
|
||||||
shortcodeValue: plugin.toBlock(shortcodeData.toJS()),
|
|
||||||
shortcodeData,
|
shortcodeData,
|
||||||
};
|
};
|
||||||
const nodes = [Text.createFromString('')];
|
const nodes = [Text.createFromString('')];
|
||||||
const block = { kind: 'block', type: 'shortcode', data, isVoid: true, nodes };
|
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.ref.onChange(resolvedState);
|
||||||
this.setState({ editorState: resolvedState });
|
this.setState({ editorState: resolvedState });
|
||||||
};
|
};
|
||||||
@ -180,7 +180,7 @@ export default class Editor extends Component {
|
|||||||
codeBlock: this.getButtonProps('code', { isBlock: true }),
|
codeBlock: this.getButtonProps('code', { isBlock: true }),
|
||||||
}}
|
}}
|
||||||
onToggleMode={this.handleToggle}
|
onToggleMode={this.handleToggle}
|
||||||
plugins={this.state.shortcodes}
|
plugins={this.state.shortcodePlugins}
|
||||||
onSubmit={this.handlePluginSubmit}
|
onSubmit={this.handlePluginSubmit}
|
||||||
onAddAsset={onAddAsset}
|
onAddAsset={onAddAsset}
|
||||||
onRemoveAsset={onRemoveAsset}
|
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;
|
export default rules;
|
||||||
|
@ -143,17 +143,18 @@ const remarkShortcodes = ({ plugins }) => {
|
|||||||
if (!nodeMayContainShortcode(node)) return node;
|
if (!nodeMayContainShortcode(node)) return node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine the text values of all children to a single string, then
|
* Combine the text values of all children to a single string, check the
|
||||||
* check that string for a shortcode pattern match.
|
* 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 { plugin, match } = matchTextToPlugin(text);
|
||||||
|
const matchIsValid = validateMatch(text, match);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a matching shortcode plugin is found, return a new node with shortcode
|
* If a valid match is found, return a new node with shortcode data
|
||||||
* data included. Otherwise, return the original node.
|
* 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 };
|
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
|
* 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
|
* 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 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;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const slateToRemark = raw => {
|
export const slateToRemark = (raw, shortcodePlugins) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'paragraph': 'paragraph',
|
'paragraph': 'paragraph',
|
||||||
'heading-one': 'heading',
|
'heading-one': 'heading',
|
||||||
@ -532,7 +542,9 @@ export const slateToRemark = raw => {
|
|||||||
|
|
||||||
if (node.type === 'shortcode') {
|
if (node.type === 'shortcode') {
|
||||||
const { data } = node;
|
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 ]);
|
return u('paragraph', { data }, [ textNode ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user