diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js index 0c213884..21c6421e 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/components.js @@ -35,7 +35,6 @@ export const NODE_COMPONENTS = { 'numbered-list': props =>
    {props.children}
, 'link': props => { - // Need to wrap this in mark components for any marks found in data. const data = props.node.get('data'); const marks = data.get('marks'); const url = data.get('url'); @@ -47,6 +46,19 @@ export const NODE_COMPONENTS = { }, link); return result; }, + '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; + }, 'shortcode': props => { const { attributes, node, state: editorState } = props; const isSelected = editorState.selection.hasFocusIn(node); diff --git a/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js b/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js index ae8383e7..768220e8 100644 --- a/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js +++ b/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js @@ -28,4 +28,8 @@ describe('slate', () => { expect(process('**a**b**c**')).toEqual('**a**b**c**\n'); expect(process('**a _b_ c**')).toEqual('**a _b_ c**\n'); }); + + it('should parse inline images as images', () => { + expect(process('a ![b](c)')).toEqual('a ![b](c)\n'); + }); }); diff --git a/src/components/Widgets/Markdown/serializers/remarkImagesToText.js b/src/components/Widgets/Markdown/serializers/remarkImagesToText.js index f63d3820..568e8a06 100644 --- a/src/components/Widgets/Markdown/serializers/remarkImagesToText.js +++ b/src/components/Widgets/Markdown/serializers/remarkImagesToText.js @@ -1,18 +1,26 @@ /** * Images must be parsed as shortcodes for asset proxying. This plugin converts - * MDAST image nodes back to text to allow shortcode pattern matching. + * MDAST image nodes back to text to allow shortcode pattern matching. Note that + * this transformation only occurs for images that are the sole child of a top + * level paragraph - any other image is left alone and treated as an inline + * image. */ export default function remarkImagesToText() { return transform; function transform(node) { - const children = node.children ? node.children.map(transform) : node.children; - if (node.type === 'image') { - const alt = node.alt || ''; - const url = node.url || ''; - const title = node.title ? ` "${node.title}"` : ''; - return { type: 'text', value: `![${alt}](${url}${title})` }; - } + const children = node.children.map(child => { + if ( + child.type === 'paragraph' + && child.children.length === 1 + && child.children[0].type === 'image' + ) { + const { alt = '', url = '', title = '' } = child.children[0]; + const value = `![${alt}](${url}${title ? ' title' : ''})`; + child.children = [{ type: 'text', value }]; + } + return child; + }); return { ...node, children }; } } diff --git a/src/components/Widgets/Markdown/serializers/remarkSlate.js b/src/components/Widgets/Markdown/serializers/remarkSlate.js index 00c6942e..ef28f570 100644 --- a/src/components/Widgets/Markdown/serializers/remarkSlate.js +++ b/src/components/Widgets/Markdown/serializers/remarkSlate.js @@ -74,7 +74,7 @@ function createBlock(type, nodes, props = {}) { /** * Create a Slate Block node. */ -function createInline(type, nodes, props = {}) { +function createInline(type, props = {}, nodes) { return { kind: 'inline', type, nodes, ...props }; } @@ -312,9 +312,23 @@ function convertNode(node, nodes) { case 'link': { const { title, url, data } = node; const newData = { ...data, title, url }; - return createInline(typeMap[type], nodes, { data: newData }); + return createInline(typeMap[type], { data: newData }, nodes); } + /** + * Images + * + * Identical to link nodes except for the lack of child nodes and addition + * of alt attribute data MDAST stores the link attributes directly on the + * node, while our Slate schema references them in the data object. + */ + case 'image': { + const { title, url, alt, data } = node; + const newData = { ...data, title, alt, url }; + return createInline(typeMap[type], { isVoid: true, data: newData }); + } + + /** * Tables * diff --git a/src/components/Widgets/Markdown/serializers/slateRemark.js b/src/components/Widgets/Markdown/serializers/slateRemark.js index aca2d42c..522a9b7a 100644 --- a/src/components/Widgets/Markdown/serializers/slateRemark.js +++ b/src/components/Widgets/Markdown/serializers/slateRemark.js @@ -474,6 +474,21 @@ function convertNode(node, children, shortcodePlugins) { return u(typeMap[node.type], { url, title, data }, children); } + /** + * Images + * + * This transformation is almost identical to that of links, except for the + * lack of child nodes and addition of `alt` attribute data. Currently the + * CMS handles block images by shortcode, so this case will only apply to + * inline images, which currently can only occur through raw markdown + * insertion. + */ + case 'image': { + const { url, title, alt, ...data } = get(node, 'data', {}); + return u(typeMap[node.type], { url, title, alt, data }); + } + + /** * No default case is supplied because an unhandled case should never * occur. In the event that it does, let the error throw (for now).