From 9342c9c06483fd7d61e302c8eee0cce5a195305a Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Thu, 16 Nov 2017 16:37:02 -0500 Subject: [PATCH] fix slate migration bugs --- src/components/ControlPanel/ControlPane.css | 3 +- .../MarkdownControl/RawEditor/index.css | 1 + .../MarkdownControl/VisualEditor/index.css | 1 + .../MarkdownControl/VisualEditor/renderers.js | 137 +++++++++++------- .../VisualEditor/validators.js | 32 +++- .../Markdown/serializers/slateRemark.js | 2 +- 6 files changed, 114 insertions(+), 62 deletions(-) diff --git a/src/components/ControlPanel/ControlPane.css b/src/components/ControlPanel/ControlPane.css index 8251a3ee..7d496776 100644 --- a/src/components/ControlPanel/ControlPane.css +++ b/src/components/ControlPanel/ControlPane.css @@ -10,8 +10,7 @@ & input, & textarea, - & select, - & div[contenteditable=true] { + & select { @apply --input; } } diff --git a/src/components/Widgets/Markdown/MarkdownControl/RawEditor/index.css b/src/components/Widgets/Markdown/MarkdownControl/RawEditor/index.css index 7783ab5b..02877169 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/RawEditor/index.css +++ b/src/components/Widgets/Markdown/MarkdownControl/RawEditor/index.css @@ -3,6 +3,7 @@ } .nc-rawEditor-rawEditor { + @apply(--input); position: relative; overflow: hidden; overflow-x: auto; diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css index ac2ce2ca..384334ab 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.css @@ -14,6 +14,7 @@ } .nc-visualEditor-editor { + @apply(--input); position: relative; overflow: hidden; overflow-x: auto; diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/renderers.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/renderers.js index 8abedb64..52a04427 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/renderers.js +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/renderers.js @@ -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 => {props.children}; +const Italic = props => {props.children}; +const Strikethrough = props => {props.children}; +const Code = props => {props.children}; + +/** + * Node Components + */ +const Paragraph = props =>

{props.children}

; +const ListItem = props =>
  • {props.children}
  • ; +const Quote = props =>
    {props.children}
    ; +const CodeBlock = props =>
    {props.children}
    ; +const HeadingOne = props =>

    {props.children}

    ; +const HeadingTwo = props =>

    {props.children}

    ; +const HeadingThree = props =>

    {props.children}

    ; +const HeadingFour = props =>

    {props.children}

    ; +const HeadingFive = props =>
    {props.children}
    ; +const HeadingSix = props =>
    {props.children}
    ; +const Table = props => {props.children}
    ; +const TableRow = props => {props.children}; +const TableCell = props => {props.children}; +const ThematicBreak = props =>
    ; +const BulletedList = props => ; +const NumberedList = props => ( +
      {props.children}
    +); +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 = {props.children}; + const result = !marks ? link : marks.reduce((acc, mark) => { + const MarkComponent = MARK_COMPONENTS[mark.type]; + return {acc}; + }, 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 = {alt}; + const result = !marks ? image : marks.reduce((acc, mark) => { + const MarkComponent = MARK_COMPONENTS[mark.type]; + return {acc}; + }, 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
    {node.data.get('shortcode')}
    ; +}; + export const renderMark = props => { switch (props.mark.type) { - case bold: return props => {props.children}; - case italic: return props => {props.children}; - case strikethrough: return props => {props.children}; - case code: return props => {props.children}; + case 'bold': return ; + case 'italic': return ; + case 'strikethrough': return ; + case 'code': return ; } }; export const renderNode = props => { switch (props.node.type) { - case 'paragraph': return props =>

    {props.children}

    ; - case 'list-item': return props =>
  • {props.children}
  • ; - case 'quote': return props =>
    {props.children}
    ; - case 'code': return props =>
    {props.children}
    ; - case 'heading-one': return props =>

    {props.children}

    ; - case 'heading-two': return props =>

    {props.children}

    ; - case 'heading-three': return props =>

    {props.children}

    ; - case 'heading-four': return props =>

    {props.children}

    ; - case 'heading-five': return props =>
    {props.children}
    ; - case 'heading-six': return props =>
    {props.children}
    ; - case 'table': return props => {props.children}
    ; - case 'table-row': return props => {props.children}; - case 'table-cell': return props => {props.children}; - case 'thematic-break': return props =>
    ; - case 'bulleted-list': return props =>
      {props.children}
    ; - case 'numbered-list': return props => ( -
      {props.children}
    - ); - 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 = {props.children}; - const result = !marks ? link : marks.reduce((acc, mark) => { - const MarkComponent = MARK_COMPONENTS[mark.type]; - return {acc}; - }, 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 = {alt}; - const result = !marks ? image : marks.reduce((acc, mark) => { - const MarkComponent = MARK_COMPONENTS[mark.type]; - return {acc}; - }, 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
    {node.data.get('shortcode')}
    ; - }; + case 'paragraph': return ; + case 'list-item': return ; + case 'quote': return ; + case 'code': return ; + case 'heading-one': return ; + case 'heading-two': return ; + case 'heading-three': return ; + case 'heading-four': return ; + case 'heading-five': return ; + case 'heading-six': return ; + case 'table': return ; + case 'table-row': return ; + case 'table-cell': return ; + case 'thematic-break': return ; + case 'bulleted-list': return ; + case 'numbered-list': return ; + case 'link': return ; + case 'image': return ; + case 'shortcode': return ; } }; diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js index aa7a3248..d3c4e8d9 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/validators.js @@ -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); }; } } diff --git a/src/components/Widgets/Markdown/serializers/slateRemark.js b/src/components/Widgets/Markdown/serializers/slateRemark.js index a4e6c0de..cd6b83c9 100644 --- a/src/components/Widgets/Markdown/serializers/slateRemark.js +++ b/src/components/Widgets/Markdown/serializers/slateRemark.js @@ -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.