diff --git a/package.json b/package.json index 9d025019..baa4efd7 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "slate": "^0.21.0", "slate-edit-list": "^0.7.1", "slate-edit-table": "^0.10.1", + "slate-soft-break": "^0.3.0", "slug": "^0.9.1", "unified": "^6.1.4", "unist-builder": "^1.0.2", diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap index 7d772fb1..341b42b3 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/__snapshots__/parser.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Compile markdown to Prosemirror document structure should compile a markdown ordered list 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile a markdown ordered list 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -80,7 +80,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile bulleted lists 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile bulleted lists 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -160,7 +160,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile code blocks 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile code blocks 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -183,28 +183,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile hard breaks (double space) 1`] = ` -Object { - "kind": "block", - "nodes": Array [ - Object { - "kind": "block", - "nodes": Array [ - Object { - "data": undefined, - "kind": "text", - "text": "blue moon -footballs", - }, - ], - "type": "paragraph", - }, - ], - "type": "root", -} -`; - -exports[`Compile markdown to Prosemirror document structure should compile horizontal rules 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile horizontal rules 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -241,7 +220,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile horizontal rules 2`] = ` +exports[`Compile markdown to Slate Raw AST should compile horizontal rules 2`] = ` Object { "kind": "block", "nodes": Array [ @@ -278,7 +257,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile images 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile images 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -298,7 +277,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile inline code 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile inline code 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -348,7 +327,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile kitchen sink example 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile kitchen sink example 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1048,7 +1027,7 @@ not using the official implementation, and this might work subtly differently } `; -exports[`Compile markdown to Prosemirror document structure should compile links 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile links 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1099,7 +1078,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile multiple header levels 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile multiple header levels 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1141,7 +1120,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile nested inline markup 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile nested inline markup 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1235,7 +1214,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile plugins 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile plugins 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1266,7 +1245,7 @@ Object { } `; -exports[`Compile markdown to Prosemirror document structure should compile simple markdown 1`] = ` +exports[`Compile markdown to Slate Raw AST should compile simple markdown 1`] = ` Object { "kind": "block", "nodes": Array [ @@ -1296,3 +1275,34 @@ Object { "type": "root", } `; + +exports[`Compile markdown to Slate Raw AST should compile soft breaks (double space) 1`] = ` +Object { + "kind": "block", + "nodes": Array [ + Object { + "kind": "block", + "nodes": Array [ + Object { + "data": undefined, + "kind": "text", + "text": "blue moon", + }, + Object { + "data": undefined, + "kind": "text", + "text": " +", + }, + Object { + "data": undefined, + "kind": "text", + "text": "footballs", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", +} +`; diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/parser.spec.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/parser.spec.js index 9006c9ff..6700eacf 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/parser.spec.js +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/__tests__/parser.spec.js @@ -46,7 +46,7 @@ const testPlugins = fromJS([ const parser = markdown => remarkToSlate(markdownToRemark(markdown)); -describe("Compile markdown to Prosemirror document structure", () => { +describe("Compile markdown to Slate Raw AST", () => { it("should compile simple markdown", () => { const value = ` # H1 @@ -111,7 +111,7 @@ blue moon expect(parser(value)).toMatchSnapshot(); }); - it("should compile hard breaks (double space)", () => { + it("should compile soft breaks (double space)", () => { const value = ` blue moon footballs diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/plugins.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/plugins.js index 308a403f..569c06ca 100644 --- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/plugins.js +++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/plugins.js @@ -1,3 +1,4 @@ +import SlateSoftBreak from 'slate-soft-break'; import EditList from 'slate-edit-list'; import EditTable from 'slate-edit-table'; @@ -31,6 +32,8 @@ const SoftBreakOpts = { export const SoftBreakConfigured = SoftBreak(SoftBreakOpts); +export const ParagraphSoftBreakConfigured = SlateSoftBreak({ onlyIn: ['paragraph'], shift: true }); + const BackspaceCloseBlock = (options = {}) => ({ onKeyDown(e, data, state) { if (data.key != 'backspace') return; @@ -82,6 +85,7 @@ export const EditTableConfigured = EditTable(EditTableOpts); const plugins = [ SoftBreakConfigured, + ParagraphSoftBreakConfigured, BackspaceCloseBlockConfigured, EditListConfigured, EditTableConfigured, diff --git a/src/components/Widgets/Markdown/serializers/index.js b/src/components/Widgets/Markdown/serializers/index.js index 9fff5db6..f0d4dbdb 100644 --- a/src/components/Widgets/Markdown/serializers/index.js +++ b/src/components/Widgets/Markdown/serializers/index.js @@ -110,26 +110,11 @@ import registry from '../../../../lib/registry'; * Deserialize a Markdown string to an MDAST. */ export const markdownToRemark = markdown => { - - /** - * Disabling tokenizers allows us to turn off features within the Remark - * parser. - */ - function disableTokenizers() { - - /** - * Turn off soft breaks until we can properly support them across both - * editors. - */ - pull(this.Parser.prototype.inlineMethods, 'break'); - } - /** * Parse the Markdown string input to an MDAST. */ const parsed = unified() .use(markdownToRemarkPlugin, { fences: true, pedantic: true, commonmark: true }) - .use(disableTokenizers) .parse(markdown); /** diff --git a/src/components/Widgets/Markdown/serializers/remarkSlate.js b/src/components/Widgets/Markdown/serializers/remarkSlate.js index 321f0e89..c7673aa7 100644 --- a/src/components/Widgets/Markdown/serializers/remarkSlate.js +++ b/src/components/Widgets/Markdown/serializers/remarkSlate.js @@ -231,6 +231,16 @@ function convertNode(node, nodes) { return createBlock(slateType, nodes, { data }); } + /** + * Breaks + * + * MDAST soft break nodes represent a trailing double space or trailing + * slash from a Markdown document. In Slate, these are simply transformed to + * line breaks within a text node. + */ + case 'break': { + return createText('\n'); + } /** * Thematic Breaks diff --git a/src/components/Widgets/Markdown/serializers/slateRemark.js b/src/components/Widgets/Markdown/serializers/slateRemark.js index 21853abc..b586cc81 100644 --- a/src/components/Widgets/Markdown/serializers/slateRemark.js +++ b/src/components/Widgets/Markdown/serializers/slateRemark.js @@ -1,4 +1,4 @@ -import { get, isEmpty, concat, without, flatten } from 'lodash'; +import { get, isEmpty, concat, without, flatten, flatMap, initial } from 'lodash'; import u from 'unist-builder'; /** @@ -56,6 +56,24 @@ function processCodeMark(markTypes) { } +/** + * Returns an array of one or more MDAST text nodes of the given type, derived + * from the text received. Certain transformations, such as line breaks, cause + * multiple nodes to be returned. + */ +function createTextNodes(text, type = 'html') { + /** + * Split the text string at line breaks, then map each substring to an array + * pair consisting of an MDAST text node followed by a break node. This will + * result in nested arrays, so we use `flatMap` to produce a flattened array, + * and `initial` to leave off the superfluous trailing break. + */ + const brokenText = text.split('\n'); + const toPair = str => [u(type, str), u('break')]; + return initial(flatMap(brokenText, toPair)); +} + + /** * Wraps a text node in one or more mark nodes by placing the text node in an * array and using that as the `children` value of a mark node. The resulting @@ -130,7 +148,7 @@ function convertTextNode(node) { * MDAST node. */ if (!node.ranges) { - return u('html', node.text); + return createTextNodes(node.text); } /** @@ -154,13 +172,13 @@ function convertTextNode(node) { /** * Create the base text node. */ - const textNode = u(textNodeType, text); + const textNodes = createTextNodes(text, textNodeType); /** * Recursively wrap the base text node in the individual mark nodes, if * any exist. */ - return wrapTextWithMarks(textNode, filteredMarkTypes); + return textNodes.map(textNode => wrapTextWithMarks(textNode, filteredMarkTypes)); }); /** diff --git a/yarn.lock b/yarn.lock index 4aa24746..f2869818 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7844,6 +7844,10 @@ slate-edit-table@^0.10.1: dependencies: immutable "^3.8.1" +slate-soft-break@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/slate-soft-break/-/slate-soft-break-0.3.0.tgz#3d28dea9e0aa4783ddcea785ff5db7277214d65f" + slate@^0.21.0: version "0.21.4" resolved "https://registry.yarnpkg.com/slate/-/slate-0.21.4.tgz#ae6113379cd838b7ec68ecd94834ce9741bc36f3"