Merge pull request #572 from netlify/soft-breaks
add markdown editor soft break support
This commit is contained in:
commit
4ffe89a668
@ -156,6 +156,7 @@
|
|||||||
"slate": "^0.21.0",
|
"slate": "^0.21.0",
|
||||||
"slate-edit-list": "^0.7.1",
|
"slate-edit-list": "^0.7.1",
|
||||||
"slate-edit-table": "^0.10.1",
|
"slate-edit-table": "^0.10.1",
|
||||||
|
"slate-soft-break": "^0.3.0",
|
||||||
"slug": "^0.9.1",
|
"slug": "^0.9.1",
|
||||||
"unified": "^6.1.4",
|
"unified": "^6.1.4",
|
||||||
"unist-builder": "^1.0.2",
|
"unist-builder": "^1.0.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"nodes": Array [
|
||||||
@ -183,28 +183,7 @@ Object {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Compile markdown to Prosemirror document structure should compile hard breaks (double space) 1`] = `
|
exports[`Compile markdown to Slate Raw AST should compile horizontal rules 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`] = `
|
|
||||||
Object {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"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 {
|
Object {
|
||||||
"kind": "block",
|
"kind": "block",
|
||||||
"nodes": Array [
|
"nodes": Array [
|
||||||
@ -1296,3 +1275,34 @@ Object {
|
|||||||
"type": "root",
|
"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",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -46,7 +46,7 @@ const testPlugins = fromJS([
|
|||||||
|
|
||||||
const parser = markdown => remarkToSlate(markdownToRemark(markdown));
|
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", () => {
|
it("should compile simple markdown", () => {
|
||||||
const value = `
|
const value = `
|
||||||
# H1
|
# H1
|
||||||
@ -111,7 +111,7 @@ blue moon
|
|||||||
expect(parser(value)).toMatchSnapshot();
|
expect(parser(value)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should compile hard breaks (double space)", () => {
|
it("should compile soft breaks (double space)", () => {
|
||||||
const value = `
|
const value = `
|
||||||
blue moon
|
blue moon
|
||||||
footballs
|
footballs
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import SlateSoftBreak from 'slate-soft-break';
|
||||||
import EditList from 'slate-edit-list';
|
import EditList from 'slate-edit-list';
|
||||||
import EditTable from 'slate-edit-table';
|
import EditTable from 'slate-edit-table';
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ const SoftBreakOpts = {
|
|||||||
|
|
||||||
export const SoftBreakConfigured = SoftBreak(SoftBreakOpts);
|
export const SoftBreakConfigured = SoftBreak(SoftBreakOpts);
|
||||||
|
|
||||||
|
export const ParagraphSoftBreakConfigured = SlateSoftBreak({ onlyIn: ['paragraph'], shift: true });
|
||||||
|
|
||||||
const BackspaceCloseBlock = (options = {}) => ({
|
const BackspaceCloseBlock = (options = {}) => ({
|
||||||
onKeyDown(e, data, state) {
|
onKeyDown(e, data, state) {
|
||||||
if (data.key != 'backspace') return;
|
if (data.key != 'backspace') return;
|
||||||
@ -82,6 +85,7 @@ export const EditTableConfigured = EditTable(EditTableOpts);
|
|||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
SoftBreakConfigured,
|
SoftBreakConfigured,
|
||||||
|
ParagraphSoftBreakConfigured,
|
||||||
BackspaceCloseBlockConfigured,
|
BackspaceCloseBlockConfigured,
|
||||||
EditListConfigured,
|
EditListConfigured,
|
||||||
EditTableConfigured,
|
EditTableConfigured,
|
||||||
|
@ -110,26 +110,11 @@ import registry from '../../../../lib/registry';
|
|||||||
* Deserialize a Markdown string to an MDAST.
|
* Deserialize a Markdown string to an MDAST.
|
||||||
*/
|
*/
|
||||||
export const markdownToRemark = markdown => {
|
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.
|
* Parse the Markdown string input to an MDAST.
|
||||||
*/
|
*/
|
||||||
const parsed = unified()
|
const parsed = unified()
|
||||||
.use(markdownToRemarkPlugin, { fences: true, pedantic: true, commonmark: true })
|
.use(markdownToRemarkPlugin, { fences: true, pedantic: true, commonmark: true })
|
||||||
.use(disableTokenizers)
|
|
||||||
.parse(markdown);
|
.parse(markdown);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -231,6 +231,16 @@ function convertNode(node, nodes) {
|
|||||||
return createBlock(slateType, nodes, { data });
|
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
|
* Thematic Breaks
|
||||||
|
@ -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';
|
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
|
* 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
|
* array and using that as the `children` value of a mark node. The resulting
|
||||||
@ -130,7 +148,7 @@ function convertTextNode(node) {
|
|||||||
* MDAST node.
|
* MDAST node.
|
||||||
*/
|
*/
|
||||||
if (!node.ranges) {
|
if (!node.ranges) {
|
||||||
return u('html', node.text);
|
return createTextNodes(node.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,13 +172,13 @@ function convertTextNode(node) {
|
|||||||
/**
|
/**
|
||||||
* Create the base text 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
|
* Recursively wrap the base text node in the individual mark nodes, if
|
||||||
* any exist.
|
* any exist.
|
||||||
*/
|
*/
|
||||||
return wrapTextWithMarks(textNode, filteredMarkTypes);
|
return textNodes.map(textNode => wrapTextWithMarks(textNode, filteredMarkTypes));
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7844,6 +7844,10 @@ slate-edit-table@^0.10.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
immutable "^3.8.1"
|
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:
|
slate@^0.21.0:
|
||||||
version "0.21.4"
|
version "0.21.4"
|
||||||
resolved "https://registry.yarnpkg.com/slate/-/slate-0.21.4.tgz#ae6113379cd838b7ec68ecd94834ce9741bc36f3"
|
resolved "https://registry.yarnpkg.com/slate/-/slate-0.21.4.tgz#ae6113379cd838b7ec68ecd94834ce9741bc36f3"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user