fix slate migration bugs
This commit is contained in:
parent
1bbaebf6d5
commit
9342c9c064
@ -10,8 +10,7 @@
|
||||
|
||||
& input,
|
||||
& textarea,
|
||||
& select,
|
||||
& div[contenteditable=true] {
|
||||
& select {
|
||||
@apply --input;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
}
|
||||
|
||||
.nc-rawEditor-rawEditor {
|
||||
@apply(--input);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
|
@ -14,6 +14,7 @@
|
||||
}
|
||||
|
||||
.nc-visualEditor-editor {
|
||||
@apply(--input);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
|
@ -8,65 +8,96 @@ import cn from 'classnames';
|
||||
* by us when we manually deserialize from Remark's MDAST to Slate's AST.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mark Components
|
||||
*/
|
||||
const Bold = props => <strong>{props.children}</strong>;
|
||||
const Italic = props => <em>{props.children}</em>;
|
||||
const Strikethrough = props => <s>{props.children}</s>;
|
||||
const Code = props => <code>{props.children}</code>;
|
||||
|
||||
/**
|
||||
* Node Components
|
||||
*/
|
||||
const Paragraph = props => <p {...props.attributes}>{props.children}</p>;
|
||||
const ListItem = props => <li {...props.attributes}>{props.children}</li>;
|
||||
const Quote = props => <blockquote {...props.attributes}>{props.children}</blockquote>;
|
||||
const CodeBlock = props => <pre><code {...props.attributes}>{props.children}</code></pre>;
|
||||
const HeadingOne = props => <h1 {...props.attributes}>{props.children}</h1>;
|
||||
const HeadingTwo = props => <h2 {...props.attributes}>{props.children}</h2>;
|
||||
const HeadingThree = props => <h3 {...props.attributes}>{props.children}</h3>;
|
||||
const HeadingFour = props => <h4 {...props.attributes}>{props.children}</h4>;
|
||||
const HeadingFive = props => <h5 {...props.attributes}>{props.children}</h5>;
|
||||
const HeadingSix = props => <h6 {...props.attributes}>{props.children}</h6>;
|
||||
const Table = props => <table><tbody {...props.attributes}>{props.children}</tbody></table>;
|
||||
const TableRow = props => <tr {...props.attributes}>{props.children}</tr>;
|
||||
const TableCell = props => <td {...props.attributes}>{props.children}</td>;
|
||||
const ThematicBreak = props => <hr {...props.attributes}/>;
|
||||
const BulletedList = props => <ul {...props.attributes}>{props.children}</ul>;
|
||||
const NumberedList = props => (
|
||||
<ol {...props.attributes} start={props.node.data.get('start') || 1}>{props.children}</ol>
|
||||
);
|
||||
const 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 = <a href={url} title={title} {...props.attributes}>{props.children}</a>;
|
||||
const result = !marks ? link : marks.reduce((acc, mark) => {
|
||||
const MarkComponent = MARK_COMPONENTS[mark.type];
|
||||
return <MarkComponent>{acc}</MarkComponent>;
|
||||
}, link);
|
||||
return result;
|
||||
};
|
||||
const 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 = <img src={url} title={title} alt={alt} {...props.attributes}/>;
|
||||
const result = !marks ? image : marks.reduce((acc, mark) => {
|
||||
const MarkComponent = MARK_COMPONENTS[mark.type];
|
||||
return <MarkComponent>{acc}</MarkComponent>;
|
||||
}, image);
|
||||
return result;
|
||||
};
|
||||
const Shortcode = props => {
|
||||
const { attributes, node, editor } = props;
|
||||
const isSelected = editor.value.selection.hasFocusIn(node);
|
||||
const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected });
|
||||
return <div {...attributes} className={className} draggable >{node.data.get('shortcode')}</div>;
|
||||
};
|
||||
|
||||
export const renderMark = props => {
|
||||
switch (props.mark.type) {
|
||||
case bold: return props => <strong>{props.children}</strong>;
|
||||
case italic: return props => <em>{props.children}</em>;
|
||||
case strikethrough: return props => <s>{props.children}</s>;
|
||||
case code: return props => <code>{props.children}</code>;
|
||||
case 'bold': return <Bold {...props}/>;
|
||||
case 'italic': return <Italic {...props}/>;
|
||||
case 'strikethrough': return <Strikethrough {...props}/>;
|
||||
case 'code': return <Code {...props}/>;
|
||||
}
|
||||
};
|
||||
|
||||
export const renderNode = props => {
|
||||
switch (props.node.type) {
|
||||
case 'paragraph': return props => <p {...props.attributes}>{props.children}</p>;
|
||||
case 'list-item': return props => <li {...props.attributes}>{props.children}</li>;
|
||||
case 'quote': return props => <blockquote {...props.attributes}>{props.children}</blockquote>;
|
||||
case 'code': return props => <pre><code {...props.attributes}>{props.children}</code></pre>;
|
||||
case 'heading-one': return props => <h1 {...props.attributes}>{props.children}</h1>;
|
||||
case 'heading-two': return props => <h2 {...props.attributes}>{props.children}</h2>;
|
||||
case 'heading-three': return props => <h3 {...props.attributes}>{props.children}</h3>;
|
||||
case 'heading-four': return props => <h4 {...props.attributes}>{props.children}</h4>;
|
||||
case 'heading-five': return props => <h5 {...props.attributes}>{props.children}</h5>;
|
||||
case 'heading-six': return props => <h6 {...props.attributes}>{props.children}</h6>;
|
||||
case 'table': return props => <table><tbody {...props.attributes}>{props.children}</tbody></table>;
|
||||
case 'table-row': return props => <tr {...props.attributes}>{props.children}</tr>;
|
||||
case 'table-cell': return props => <td {...props.attributes}>{props.children}</td>;
|
||||
case 'thematic-break': return props => <hr {...props.attributes}/>;
|
||||
case 'bulleted-list': return props => <ul {...props.attributes}>{props.children}</ul>;
|
||||
case 'numbered-list': return props => (
|
||||
<ol {...props.attributes} start={props.node.data.get('start') || 1}>{props.children}</ol>
|
||||
);
|
||||
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 = <a href={url} title={title} {...props.attributes}>{props.children}</a>;
|
||||
const result = !marks ? link : marks.reduce((acc, mark) => {
|
||||
const MarkComponent = MARK_COMPONENTS[mark.type];
|
||||
return <MarkComponent>{acc}</MarkComponent>;
|
||||
}, 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 = <img src={url} title={title} alt={alt} {...props.attributes}/>;
|
||||
const result = !marks ? image : marks.reduce((acc, mark) => {
|
||||
const MarkComponent = MARK_COMPONENTS[mark.type];
|
||||
return <MarkComponent>{acc}</MarkComponent>;
|
||||
}, image);
|
||||
return result;
|
||||
};
|
||||
case 'shortcode': props => {
|
||||
const { attributes, node, editor } = props;
|
||||
const isSelected = editor.value.selection.hasFocusIn(node);
|
||||
const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected });
|
||||
return <div {...attributes} className={className} draggable >{node.data.get('shortcode')}</div>;
|
||||
};
|
||||
case 'paragraph': return <Paragraph {...props}/>;
|
||||
case 'list-item': return <ListItem {...props}/>;
|
||||
case 'quote': return <Quote {...props}/>;
|
||||
case 'code': return <CodeBlock {...props}/>;
|
||||
case 'heading-one': return <HeadingOne {...props}/>;
|
||||
case 'heading-two': return <HeadingTwo {...props}/>;
|
||||
case 'heading-three': return <HeadingThree {...props}/>;
|
||||
case 'heading-four': return <HeadingFour {...props}/>;
|
||||
case 'heading-five': return <HeadingFive {...props}/>;
|
||||
case 'heading-six': return <HeadingSix {...props}/>;
|
||||
case 'table': return <Table {...props}/>;
|
||||
case 'table-row': return <TableRow {...props}/>;
|
||||
case 'table-cell': return <TableCell {...props}/>;
|
||||
case 'thematic-break': return <ThematicBreak {...props}/>;
|
||||
case 'bulleted-list': return <BulletedList {...props}/>;
|
||||
case 'numbered-list': return <NumberedList {...props}/>;
|
||||
case 'link': return <Link {...props}/>;
|
||||
case 'image': return <Image {...props}/>;
|
||||
case 'shortcode': return <Shortcode {...props}/>;
|
||||
}
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ export function validateNode(node) {
|
||||
* Validation of the document itself.
|
||||
*/
|
||||
if (node.kind === 'document') {
|
||||
const doc = node;
|
||||
/**
|
||||
* If the editor is ever in an empty state, insert an empty
|
||||
* paragraph block.
|
||||
@ -28,26 +29,45 @@ export function validateNode(node) {
|
||||
/**
|
||||
* Ensure that shortcodes are children of the root node.
|
||||
*/
|
||||
const nestedShortcode = node.findDescendant(descendant => {
|
||||
const nestedShortcode = doc.findDescendant(descendant => {
|
||||
const { type, key } = descendant;
|
||||
return type === 'shortcode' && node.getParent(key).key !== node.key;
|
||||
return type === 'shortcode' && doc.getParent(key).key !== doc.key;
|
||||
});
|
||||
if (nestedShortcode) {
|
||||
return change => change.unwrapNodeByKey(node.key);
|
||||
const unwrapShortcode = change => {
|
||||
const key = nestedShortcode.key;
|
||||
const newDoc = change.value.document;
|
||||
const newParent = newDoc.getParent(key);
|
||||
const docIsParent = newParent.key === newDoc.key;
|
||||
const newParentParent = newDoc.getParent(newParent.key);
|
||||
const docIsParentParent = newParentParent && newParentParent.key === newDoc.key;
|
||||
if (docIsParent) {
|
||||
return change;
|
||||
}
|
||||
/**
|
||||
* Normalization happens by default, and causes all validation to
|
||||
* restart with the result of a change upon execution. This unwrap loop
|
||||
* could temporarily place a shortcode node in conflict with an outside
|
||||
* plugin's schema, resulting in an infinite loop. To ensure against
|
||||
* this, we turn off normalization until the last change.
|
||||
*/
|
||||
change.unwrapNodeByKey(nestedShortcode.key, { normalize: docIsParentParent });
|
||||
};
|
||||
return unwrapShortcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that trailing shortcodes are followed by an empty paragraph.
|
||||
*/
|
||||
const trailingShortcode = node.findDescendant(descendant => {
|
||||
const trailingShortcode = doc.findDescendant(descendant => {
|
||||
const { type, key } = descendant;
|
||||
return type === 'shortcode' && node.getBlocks().last().key === key;
|
||||
return type === 'shortcode' && doc.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);
|
||||
return change.insertNodeByKey(doc.key, doc.get('nodes').size, block);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ function convertTextNode(node) {
|
||||
/**
|
||||
* Process Slate node leaves in preparation for MDAST transformation.
|
||||
*/
|
||||
function processLeaves(leaves) {
|
||||
function processLeaves(leaf) {
|
||||
/**
|
||||
* Get an array of the mark types, converted to their MDAST equivalent
|
||||
* types.
|
||||
|
Loading…
x
Reference in New Issue
Block a user