fix slate migration bugs

This commit is contained in:
Shawn Erquhart 2017-11-16 16:37:02 -05:00
parent 1bbaebf6d5
commit 9342c9c064
6 changed files with 114 additions and 62 deletions

View File

@ -10,8 +10,7 @@
& input, & input,
& textarea, & textarea,
& select, & select {
& div[contenteditable=true] {
@apply --input; @apply --input;
} }
} }

View File

@ -3,6 +3,7 @@
} }
.nc-rawEditor-rawEditor { .nc-rawEditor-rawEditor {
@apply(--input);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-x: auto; overflow-x: auto;

View File

@ -14,6 +14,7 @@
} }
.nc-visualEditor-editor { .nc-visualEditor-editor {
@apply(--input);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-x: auto; overflow-x: auto;

View File

@ -8,36 +8,36 @@ import cn from 'classnames';
* by us when we manually deserialize from Remark's MDAST to Slate's AST. * by us when we manually deserialize from Remark's MDAST to Slate's AST.
*/ */
export const renderMark = props => { /**
switch (props.mark.type) { * Mark Components
case bold: return props => <strong>{props.children}</strong>; */
case italic: return props => <em>{props.children}</em>; const Bold = props => <strong>{props.children}</strong>;
case strikethrough: return props => <s>{props.children}</s>; const Italic = props => <em>{props.children}</em>;
case code: return props => <code>{props.children}</code>; const Strikethrough = props => <s>{props.children}</s>;
} const Code = props => <code>{props.children}</code>;
};
export const renderNode = props => { /**
switch (props.node.type) { * Node Components
case 'paragraph': return props => <p {...props.attributes}>{props.children}</p>; */
case 'list-item': return props => <li {...props.attributes}>{props.children}</li>; const Paragraph = props => <p {...props.attributes}>{props.children}</p>;
case 'quote': return props => <blockquote {...props.attributes}>{props.children}</blockquote>; const ListItem = props => <li {...props.attributes}>{props.children}</li>;
case 'code': return props => <pre><code {...props.attributes}>{props.children}</code></pre>; const Quote = props => <blockquote {...props.attributes}>{props.children}</blockquote>;
case 'heading-one': return props => <h1 {...props.attributes}>{props.children}</h1>; const CodeBlock = props => <pre><code {...props.attributes}>{props.children}</code></pre>;
case 'heading-two': return props => <h2 {...props.attributes}>{props.children}</h2>; const HeadingOne = props => <h1 {...props.attributes}>{props.children}</h1>;
case 'heading-three': return props => <h3 {...props.attributes}>{props.children}</h3>; const HeadingTwo = props => <h2 {...props.attributes}>{props.children}</h2>;
case 'heading-four': return props => <h4 {...props.attributes}>{props.children}</h4>; const HeadingThree = props => <h3 {...props.attributes}>{props.children}</h3>;
case 'heading-five': return props => <h5 {...props.attributes}>{props.children}</h5>; const HeadingFour = props => <h4 {...props.attributes}>{props.children}</h4>;
case 'heading-six': return props => <h6 {...props.attributes}>{props.children}</h6>; const HeadingFive = props => <h5 {...props.attributes}>{props.children}</h5>;
case 'table': return props => <table><tbody {...props.attributes}>{props.children}</tbody></table>; const HeadingSix = props => <h6 {...props.attributes}>{props.children}</h6>;
case 'table-row': return props => <tr {...props.attributes}>{props.children}</tr>; const Table = props => <table><tbody {...props.attributes}>{props.children}</tbody></table>;
case 'table-cell': return props => <td {...props.attributes}>{props.children}</td>; const TableRow = props => <tr {...props.attributes}>{props.children}</tr>;
case 'thematic-break': return props => <hr {...props.attributes}/>; const TableCell = props => <td {...props.attributes}>{props.children}</td>;
case 'bulleted-list': return props => <ul {...props.attributes}>{props.children}</ul>; const ThematicBreak = props => <hr {...props.attributes}/>;
case 'numbered-list': return props => ( 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> <ol {...props.attributes} start={props.node.data.get('start') || 1}>{props.children}</ol>
); );
case 'link': return props => { const Link = props => {
const data = props.node.get('data'); const data = props.node.get('data');
const marks = data.get('marks'); const marks = data.get('marks');
const url = data.get('url'); const url = data.get('url');
@ -49,7 +49,7 @@ export const renderNode = props => {
}, link); }, link);
return result; return result;
}; };
case 'image': props => { const Image = props => {
const data = props.node.get('data'); const data = props.node.get('data');
const marks = data.get('marks'); const marks = data.get('marks');
const url = data.get('url'); const url = data.get('url');
@ -62,11 +62,42 @@ export const renderNode = props => {
}, image); }, image);
return result; return result;
}; };
case 'shortcode': props => { const Shortcode = props => {
const { attributes, node, editor } = props; const { attributes, node, editor } = props;
const isSelected = editor.value.selection.hasFocusIn(node); const isSelected = editor.value.selection.hasFocusIn(node);
const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected }); const className = cn('nc-visualEditor-shortcode', { ['nc-visualEditor-shortcodeSelected']: isSelected });
return <div {...attributes} className={className} draggable >{node.data.get('shortcode')}</div>; return <div {...attributes} className={className} draggable >{node.data.get('shortcode')}</div>;
}; };
export const renderMark = props => {
switch (props.mark.type) {
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 <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}/>;
} }
}; };

View File

@ -9,6 +9,7 @@ export function validateNode(node) {
* Validation of the document itself. * Validation of the document itself.
*/ */
if (node.kind === 'document') { if (node.kind === 'document') {
const doc = node;
/** /**
* If the editor is ever in an empty state, insert an empty * If the editor is ever in an empty state, insert an empty
* paragraph block. * paragraph block.
@ -28,26 +29,45 @@ export function validateNode(node) {
/** /**
* Ensure that shortcodes are children of the root node. * Ensure that shortcodes are children of the root node.
*/ */
const nestedShortcode = node.findDescendant(descendant => { const nestedShortcode = doc.findDescendant(descendant => {
const { type, key } = 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) { 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. * Ensure that trailing shortcodes are followed by an empty paragraph.
*/ */
const trailingShortcode = node.findDescendant(descendant => { const trailingShortcode = doc.findDescendant(descendant => {
const { type, key } = descendant; const { type, key } = descendant;
return type === 'shortcode' && node.getBlocks().last().key === key; return type === 'shortcode' && doc.getBlocks().last().key === key;
}); });
if (trailingShortcode) { if (trailingShortcode) {
return change => { return change => {
const text = Text.create(''); const text = Text.create('');
const block = Block.create({ type: 'paragraph', nodes: [ text ] }); 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);
}; };
} }
} }

View File

@ -249,7 +249,7 @@ function convertTextNode(node) {
/** /**
* Process Slate node leaves in preparation for MDAST transformation. * 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 * Get an array of the mark types, converted to their MDAST equivalent
* types. * types.