Merge pull request #638 from netlify/slate-0.25

Migrate to Slate 0.25
This commit is contained in:
Shawn Erquhart 2017-10-05 12:32:55 -04:00 committed by GitHub
commit fd606938e2
10 changed files with 571 additions and 211 deletions

View File

@ -41,9 +41,15 @@
"^.+\\.s?css$": "<rootDir>/__mocks__/styleLoaderMock.js"
},
"mapCoverage": true,
"coverageReporters": ["lcov"],
"collectCoverageFrom": [ "src/**/*.js" ],
"coveragePathIgnorePatterns": [ "/__tests__/" ]
"coverageReporters": [
"lcov"
],
"collectCoverageFrom": [
"src/**/*.js"
],
"coveragePathIgnorePatterns": [
"/__tests__/"
]
},
"keywords": [
"netlify",
@ -151,10 +157,13 @@
"remark-stringify": "^3.0.1",
"sanitize-filename": "^1.6.1",
"semaphore": "^1.0.5",
"slate": "^0.21.0",
"slate-edit-list": "^0.7.1",
"slate-edit-table": "^0.10.1",
"slate-soft-break": "^0.3.0",
"slate": "^0.25.0",
"slate-edit-list": "^0.8.0",
"slate-edit-table": "^0.11.0",
"slate-plain-serializer": "^0.1.10",
"slate-react": "^0.1.10",
"slate-soft-break": "^0.4.0",
"slug": "^0.9.1",
"toml-j0.4": "^1.1.1",
"unified": "^6.1.4",
"unist-builder": "^1.0.2",

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Editor as Slate, Plain } from 'slate';
import { Editor as Slate } from 'slate-react';
import Plain from 'slate-plain-serializer';
import { debounce } from 'lodash';
import Toolbar from '../Toolbar/Toolbar';
import { Sticky } from '../../../../UI/Sticky/Sticky';
@ -18,16 +19,19 @@ export default class RawEditor extends React.Component {
return !this.state.editorState.equals(nextState.editorState);
}
handleChange = editorState => {
this.setState({ editorState });
}
handleChange = change => {
if (!this.state.editorState.document.equals(change.state.document)) {
this.handleDocumentChange(change);
}
this.setState({ editorState: change.state });
};
/**
* When the document value changes, serialize from Slate's AST back to plain
* text (which is Markdown) and pass that up as the new value.
*/
handleDocumentChange = debounce((doc, editorState) => {
const value = Plain.serialize(editorState);
handleDocumentChange = debounce(change => {
const value = Plain.serialize(change.state);
this.props.onChange(value);
}, 150);
@ -36,10 +40,10 @@ export default class RawEditor extends React.Component {
* to the document. Selection logic (where to insert, whether to replace) is
* handled by Slate.
*/
handlePaste = (e, data, state) => {
handlePaste = (e, data, change) => {
if (data.text) {
const fragment = Plain.deserialize(data.text).document;
return state.transform().insertFragment(fragment).apply();
return change.insertFragment(fragment);
}
};
@ -61,7 +65,6 @@ export default class RawEditor extends React.Component {
className={styles.rawEditor}
state={this.state.editorState}
onChange={this.handleChange}
onDocumentChange={this.handleDocumentChange}
onPaste={this.handlePaste}
/>
</div>

View File

@ -10,7 +10,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -30,7 +34,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "yo",
"ranges": Array [
Object {
"text": "yo",
},
],
},
],
"type": "paragraph",
@ -47,7 +55,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "bro",
"ranges": Array [
Object {
"text": "bro",
},
],
},
],
"type": "paragraph",
@ -64,7 +76,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "fro",
"ranges": Array [
Object {
"text": "fro",
},
],
},
],
"type": "paragraph",
@ -90,7 +106,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -110,7 +130,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "yo",
"ranges": Array [
Object {
"text": "yo",
},
],
},
],
"type": "paragraph",
@ -127,7 +151,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "bro",
"ranges": Array [
Object {
"text": "bro",
},
],
},
],
"type": "paragraph",
@ -144,7 +172,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "fro",
"ranges": Array [
Object {
"text": "fro",
},
],
},
],
"type": "paragraph",
@ -173,7 +205,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "var a = 1;",
"ranges": Array [
Object {
"text": "var a = 1;",
},
],
},
],
"type": "code",
@ -193,7 +229,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -210,7 +250,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "blue moon",
"ranges": Array [
Object {
"text": "blue moon",
},
],
},
],
"type": "paragraph",
@ -230,7 +274,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -247,7 +295,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "blue moon",
"ranges": Array [
Object {
"text": "blue moon",
},
],
},
],
"type": "paragraph",
@ -267,7 +319,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "![super](duper.jpg)",
"ranges": Array [
Object {
"text": "![super](duper.jpg)",
},
],
},
],
"type": "paragraph",
@ -287,7 +343,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "Word",
"ranges": Array [
Object {
"text": "Word",
},
],
},
],
"type": "heading-one",
@ -298,7 +358,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "This is some sweet ",
"ranges": Array [
Object {
"text": "This is some sweet ",
},
],
},
Object {
"data": undefined,
@ -317,7 +381,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": " yo!",
"ranges": Array [
Object {
"text": " yo!",
},
],
},
],
"type": "paragraph",
@ -337,7 +405,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "An exhibit of Markdown",
"ranges": Array [
Object {
"text": "An exhibit of Markdown",
},
],
},
],
"type": "heading-one",
@ -348,7 +420,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "This note demonstrates some of what Markdown is capable of doing.",
"ranges": Array [
Object {
"text": "This note demonstrates some of what Markdown is capable of doing.",
},
],
},
],
"type": "paragraph",
@ -380,7 +456,11 @@ automatically save itself.",
Object {
"data": undefined,
"kind": "text",
"text": "Basic formatting",
"ranges": Array [
Object {
"text": "Basic formatting",
},
],
},
],
"type": "heading-two",
@ -391,9 +471,13 @@ automatically save itself.",
Object {
"data": undefined,
"kind": "text",
"text": "Paragraphs can be written like so. A paragraph is the basic block of Markdown.
"ranges": Array [
Object {
"text": "Paragraphs can be written like so. A paragraph is the basic block of Markdown.
A paragraph is what text will turn into when there is no reason it should
become anything else.",
},
],
},
],
"type": "paragraph",
@ -404,7 +488,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Paragraphs must be separated by a blank line. Basic formatting of ",
"ranges": Array [
Object {
"text": "Paragraphs must be separated by a blank line. Basic formatting of ",
},
],
},
Object {
"data": undefined,
@ -423,8 +511,12 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": " and
"ranges": Array [
Object {
"text": " and
",
},
],
},
Object {
"data": undefined,
@ -443,7 +535,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": " is supported. This ",
"ranges": Array [
Object {
"text": " is supported. This ",
},
],
},
Object {
"data": undefined,
@ -481,7 +577,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": " so.",
"ranges": Array [
Object {
"text": " so.",
},
],
},
],
"type": "paragraph",
@ -492,7 +592,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Lists",
"ranges": Array [
Object {
"text": "Lists",
},
],
},
],
"type": "heading-two",
@ -503,7 +607,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Ordered list",
"ranges": Array [
Object {
"text": "Ordered list",
},
],
},
],
"type": "heading-three",
@ -523,7 +631,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Item 1 2. A second item 3. Number 3 4. Ⅳ",
"ranges": Array [
Object {
"text": "Item 1 2. A second item 3. Number 3 4. Ⅳ",
},
],
},
],
"type": "paragraph",
@ -560,7 +672,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Unordered list",
"ranges": Array [
Object {
"text": "Unordered list",
},
],
},
],
"type": "heading-three",
@ -580,7 +696,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "An item Another item Yet another item And there's more...",
"ranges": Array [
Object {
"text": "An item Another item Yet another item And there's more...",
},
],
},
],
"type": "paragraph",
@ -597,7 +717,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Paragraph modifiers",
"ranges": Array [
Object {
"text": "Paragraph modifiers",
},
],
},
],
"type": "heading-two",
@ -608,7 +732,11 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Code block",
"ranges": Array [
Object {
"text": "Code block",
},
],
},
],
"type": "heading-three",
@ -622,9 +750,13 @@ become anything else.",
Object {
"data": undefined,
"kind": "text",
"text": "Code blocks are very useful for developers and other people who look at
"ranges": Array [
Object {
"text": "Code blocks are very useful for developers and other people who look at
code or other things that are written in plain text. As you can see, it
uses a fixed-width font.",
},
],
},
],
"type": "code",
@ -635,7 +767,11 @@ uses a fixed-width font.",
Object {
"data": undefined,
"kind": "text",
"text": "You can also make ",
"ranges": Array [
Object {
"text": "You can also make ",
},
],
},
Object {
"data": undefined,
@ -654,7 +790,11 @@ uses a fixed-width font.",
Object {
"data": undefined,
"kind": "text",
"text": " to add code into other things.",
"ranges": Array [
Object {
"text": " to add code into other things.",
},
],
},
],
"type": "paragraph",
@ -665,7 +805,11 @@ uses a fixed-width font.",
Object {
"data": undefined,
"kind": "text",
"text": "Quote",
"ranges": Array [
Object {
"text": "Quote",
},
],
},
],
"type": "heading-three",
@ -679,8 +823,12 @@ uses a fixed-width font.",
Object {
"data": undefined,
"kind": "text",
"text": "Here is a quote. What this is should be self explanatory. Quotes are
"ranges": Array [
Object {
"text": "Here is a quote. What this is should be self explanatory. Quotes are
automatically indented when they are used.",
},
],
},
],
"type": "paragraph",
@ -694,7 +842,11 @@ automatically indented when they are used.",
Object {
"data": undefined,
"kind": "text",
"text": "Headings",
"ranges": Array [
Object {
"text": "Headings",
},
],
},
],
"type": "heading-two",
@ -705,9 +857,13 @@ automatically indented when they are used.",
Object {
"data": undefined,
"kind": "text",
"text": "There are six levels of headings. They correspond with the six levels of HTML
"ranges": Array [
Object {
"text": "There are six levels of headings. They correspond with the six levels of HTML
headings. You've probably noticed them already in the page. Each level down
uses one more hash character.",
},
],
},
],
"type": "paragraph",
@ -718,7 +874,11 @@ uses one more hash character.",
Object {
"data": undefined,
"kind": "text",
"text": "Headings ",
"ranges": Array [
Object {
"text": "Headings ",
},
],
},
Object {
"data": undefined,
@ -737,7 +897,11 @@ uses one more hash character.",
Object {
"data": undefined,
"kind": "text",
"text": " also contain ",
"ranges": Array [
Object {
"text": " also contain ",
},
],
},
Object {
"data": undefined,
@ -762,7 +926,11 @@ uses one more hash character.",
Object {
"data": undefined,
"kind": "text",
"text": "They can even contain ",
"ranges": Array [
Object {
"text": "They can even contain ",
},
],
},
Object {
"data": undefined,
@ -787,8 +955,12 @@ uses one more hash character.",
Object {
"data": undefined,
"kind": "text",
"text": "Of course, demonstrating what headings look like messes up the structure of the
"ranges": Array [
Object {
"text": "Of course, demonstrating what headings look like messes up the structure of the
page.",
},
],
},
],
"type": "paragraph",
@ -799,10 +971,14 @@ page.",
Object {
"data": undefined,
"kind": "text",
"text": "I don't recommend using more than three or four levels of headings here,
"ranges": Array [
Object {
"text": "I don't recommend using more than three or four levels of headings here,
because, when you're smallest heading isn't too small, and you're largest
heading isn't too big, and you want each size up to look noticeably larger and
more important, there there are only so many sizes that you can use.",
},
],
},
],
"type": "paragraph",
@ -813,7 +989,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "URLs",
"ranges": Array [
Object {
"text": "URLs",
},
],
},
],
"type": "heading-two",
@ -824,7 +1004,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "URLs can be made in a handful of ways:",
"ranges": Array [
Object {
"text": "URLs can be made in a handful of ways:",
},
],
},
],
"type": "paragraph",
@ -844,7 +1028,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "A named link to MarkItDown. The easiest way to do these is to select what you",
"ranges": Array [
Object {
"text": "A named link to MarkItDown. The easiest way to do these is to select what you",
},
],
},
],
"type": "paragraph",
@ -861,7 +1049,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "want to make a link and hit ",
"ranges": Array [
Object {
"text": "want to make a link and hit ",
},
],
},
Object {
"data": undefined,
@ -880,7 +1072,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": ". Another named link to",
"ranges": Array [
Object {
"text": ". Another named link to",
},
],
},
],
"type": "paragraph",
@ -904,7 +1100,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "MarkItDown",
"ranges": Array [
Object {
"text": "MarkItDown",
},
],
},
],
"type": "link",
@ -912,7 +1112,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": " Sometimes you just want a URL like",
"ranges": Array [
Object {
"text": " Sometimes you just want a URL like",
},
],
},
],
"type": "paragraph",
@ -936,7 +1140,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "http://www.markitdown.net/",
"ranges": Array [
Object {
"text": "http://www.markitdown.net/",
},
],
},
],
"type": "link",
@ -944,7 +1152,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": ".",
"ranges": Array [
Object {
"text": ".",
},
],
},
],
"type": "paragraph",
@ -961,7 +1173,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "Horizontal rule",
"ranges": Array [
Object {
"text": "Horizontal rule",
},
],
},
],
"type": "heading-two",
@ -972,7 +1188,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "A horizontal rule is a line that goes across the middle of the page.",
"ranges": Array [
Object {
"text": "A horizontal rule is a line that goes across the middle of the page.",
},
],
},
],
"type": "paragraph",
@ -989,7 +1209,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "It's sometimes handy for breaking things up.",
"ranges": Array [
Object {
"text": "It's sometimes handy for breaking things up.",
},
],
},
],
"type": "paragraph",
@ -1000,7 +1224,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "Images",
"ranges": Array [
Object {
"text": "Images",
},
],
},
],
"type": "heading-two",
@ -1011,7 +1239,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "Markdown can also contain images. I'll need to add something here sometime.",
"ranges": Array [
Object {
"text": "Markdown can also contain images. I'll need to add something here sometime.",
},
],
},
],
"type": "paragraph",
@ -1022,7 +1254,11 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "Finally",
"ranges": Array [
Object {
"text": "Finally",
},
],
},
],
"type": "heading-two",
@ -1033,10 +1269,14 @@ more important, there there are only so many sizes that you can use.",
Object {
"data": undefined,
"kind": "text",
"text": "There's actually a lot more to Markdown than this. See the official
"ranges": Array [
Object {
"text": "There's actually a lot more to Markdown than this. See the official
introduction and syntax for more information. However, be aware that this is
not using the official implementation, and this might work subtly differently
in some of the little things.",
},
],
},
],
"type": "paragraph",
@ -1056,7 +1296,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "Word",
"ranges": Array [
Object {
"text": "Word",
},
],
},
],
"type": "heading-one",
@ -1067,7 +1311,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "How far is it to ",
"ranges": Array [
Object {
"text": "How far is it to ",
},
],
},
Object {
"data": Object {
@ -1079,7 +1327,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "Google",
"ranges": Array [
Object {
"text": "Google",
},
],
},
],
"type": "link",
@ -1087,7 +1339,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": " land?",
"ranges": Array [
Object {
"text": " land?",
},
],
},
],
"type": "paragraph",
@ -1107,7 +1363,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -1118,7 +1378,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H2",
"ranges": Array [
Object {
"text": "H2",
},
],
},
],
"type": "heading-two",
@ -1129,7 +1393,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H3",
"ranges": Array [
Object {
"text": "H3",
},
],
},
],
"type": "heading-three",
@ -1149,7 +1417,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "Word",
"ranges": Array [
Object {
"text": "Word",
},
],
},
],
"type": "heading-one",
@ -1160,7 +1432,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "This is ",
"ranges": Array [
Object {
"text": "This is ",
},
],
},
Object {
"data": undefined,
@ -1204,7 +1480,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "perhaps ",
"ranges": Array [
Object {
"text": "perhaps ",
},
],
},
Object {
"data": undefined,
@ -1223,7 +1503,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": " even",
"ranges": Array [
Object {
"text": " even",
},
],
},
],
"type": "paragraph",
@ -1243,7 +1527,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "![test](test.png)",
"ranges": Array [
Object {
"text": "![test](test.png)",
},
],
},
],
"type": "paragraph",
@ -1254,7 +1542,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "{{< test >}}",
"ranges": Array [
Object {
"text": "{{< test >}}",
},
],
},
],
"type": "paragraph",
@ -1274,7 +1566,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "H1",
"ranges": Array [
Object {
"text": "H1",
},
],
},
],
"type": "heading-one",
@ -1285,7 +1581,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "sweet body",
"ranges": Array [
Object {
"text": "sweet body",
},
],
},
],
"type": "paragraph",
@ -1305,7 +1605,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "blue moon",
"ranges": Array [
Object {
"text": "blue moon",
},
],
},
Object {
"kind": "inline",
@ -1313,8 +1617,12 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "
"ranges": Array [
Object {
"text": "
",
},
],
},
],
"type": "break",
@ -1322,7 +1630,11 @@ Object {
Object {
"data": undefined,
"kind": "text",
"text": "footballs",
"ranges": Array [
Object {
"text": "footballs",
},
],
},
],
"type": "paragraph",

View File

@ -1,7 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { get, isEmpty, debounce } from 'lodash';
import { Editor as Slate, Raw, Block, Text } from 'slate';
import { State, Document, Block, Text } from 'slate';
import { Editor as Slate } from 'slate-react';
import { slateToMarkdown, markdownToSlate, htmlToSlate } from '../../serializers';
import registry from '../../../../../lib/registry';
import Toolbar from '../Toolbar/Toolbar';
@ -15,11 +16,13 @@ import styles from './index.css';
export default class Editor extends Component {
constructor(props) {
super(props);
const emptyBlock = Block.create({ kind: 'block', type: 'paragraph'});
const emptyText = Text.create('');
const emptyBlock = Block.create({ kind: 'block', type: 'paragraph', nodes: [ emptyText ] });
const emptyRawDoc = { nodes: [emptyBlock] };
const rawDoc = this.props.value && markdownToSlate(this.props.value);
const rawDocHasNodes = !isEmpty(get(rawDoc, 'nodes'))
const editorState = Raw.deserialize(rawDocHasNodes ? rawDoc : emptyRawDoc, { terse: true });
const document = Document.fromJSON(rawDocHasNodes ? rawDoc : emptyRawDoc);
const editorState = State.create({ document });
this.state = {
editorState,
schema: {
@ -35,42 +38,36 @@ export default class Editor extends Component {
return !this.state.editorState.equals(nextState.editorState);
}
handlePaste = (e, data, state) => {
handlePaste = (e, data, change) => {
if (data.type !== 'html' || data.isShift) {
return;
}
const ast = htmlToSlate(data.html);
const { document: doc } = Raw.deserialize(ast, { terse: true });
return state.transform().insertFragment(doc).apply();
const doc = Document.fromJSON(ast);
return change.insertFragment(doc);
}
handleDocumentChange = debounce((doc, editorState) => {
const raw = Raw.serialize(editorState, { terse: true });
const plugins = this.state.shortcodePlugins;
const markdown = slateToMarkdown(raw, plugins);
this.props.onChange(markdown);
}, 150);
hasMark = type => this.state.editorState.marks.some(mark => mark.type === type);
hasBlock = type => this.state.editorState.blocks.some(node => node.type === type);
handleMarkClick = (event, type) => {
event.preventDefault();
const resolvedState = this.state.editorState.transform().focus().toggleMark(type).apply();
this.ref.onChange(resolvedState);
this.setState({ editorState: resolvedState });
const resolvedChange = this.state.editorState.change().focus().toggleMark(type);
this.ref.onChange(resolvedChange);
this.setState({ editorState: resolvedChange.state });
};
handleBlockClick = (event, type) => {
event.preventDefault();
let { editorState } = this.state;
const { document: doc, selection } = editorState;
const transform = editorState.transform();
const { unwrapList, wrapInList } = EditListConfigured.changes;
let change = editorState.change();
// Handle everything except list buttons.
if (!['bulleted-list', 'numbered-list'].includes(type)) {
const isActive = this.hasBlock(type);
const transformed = transform.setBlock(isActive ? 'paragraph' : type);
change = change.setBlock(isActive ? 'paragraph' : type);
}
// Handle the extra wrapping required for list buttons.
@ -81,19 +78,18 @@ export default class Editor extends Component {
const isInList = EditListConfigured.utils.isSelectionInList(editorState);
if (isInList && isSameListType) {
EditListConfigured.transforms.unwrapList(transform, type);
change = change.call(unwrapList, type);
} else if (isInList) {
const currentListType = type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list';
EditListConfigured.transforms.unwrapList(transform, currentListType);
EditListConfigured.transforms.wrapInList(transform, type);
change = change.call(unwrapList, currentListType).call(wrapInList, type);
} else {
EditListConfigured.transforms.wrapInList(transform, type);
change = change.call(wrapInList, type);
}
}
const resolvedState = transform.focus().apply();
this.ref.onChange(resolvedState);
this.setState({ editorState: resolvedState });
const resolvedChange = change.focus();
this.ref.onChange(resolvedChange);
this.setState({ editorState: resolvedChange.state });
};
hasLinks = () => {
@ -101,12 +97,12 @@ export default class Editor extends Component {
};
handleLink = () => {
let { editorState } = this.state;
let change = this.state.editorState.change();
// If the current selection contains links, clicking the "link" button
// should simply unlink them.
if (this.hasLinks()) {
editorState = editorState.transform().unwrapInline('link').apply();
change = change.unwrapInline('link');
}
else {
@ -115,23 +111,20 @@ export default class Editor extends Component {
// If nothing is entered in the URL prompt, do nothing.
if (!url) return;
let transform = editorState.transform();
// If no text is selected, use the entered URL as text.
if (editorState.isCollapsed) {
transform = transform
if (change.state.isCollapsed) {
change = change
.insertText(url)
.extend(0 - url.length);
}
editorState = transform
change = change
.wrapInline({ type: 'link', data: { url } })
.collapseToEnd()
.apply();
.collapseToEnd();
}
this.ref.onChange(editorState);
this.setState({ editorState });
this.ref.onChange(change);
this.setState({ editorState: change.state });
};
handlePluginSubmit = (plugin, shortcodeData) => {
@ -140,11 +133,21 @@ export default class Editor extends Component {
shortcode: plugin.id,
shortcodeData,
};
const nodes = [Text.createFromString('')];
const nodes = [Text.create('')];
const block = { kind: 'block', type: 'shortcode', data, isVoid: true, nodes };
const resolvedState = editorState.transform().insertBlock(block).focus().apply();
this.ref.onChange(resolvedState);
this.setState({ editorState: resolvedState });
let change = editorState.change();
const { focusBlock } = change.state;
if (focusBlock.text === '') {
change = change.setNodeByKey(focusBlock.key, block);
} else {
change = change.insertBlock(block);
}
change = change.focus();
this.ref.onChange(change);
this.setState({ editorState: change.state });
};
handleToggle = () => {
@ -158,6 +161,20 @@ export default class Editor extends Component {
return { onAction: e => handler(e, type), active: isActive(type) };
};
handleDocumentChange = debounce(change => {
const raw = change.state.document.toJSON();
const plugins = this.state.shortcodePlugins;
const markdown = slateToMarkdown(raw, plugins);
this.props.onChange(markdown);
}, 150);
handleChange = change => {
if (!this.state.editorState.document.equals(change.state.document)) {
this.handleDocumentChange(change);
}
this.setState({ editorState: change.state });
};
render() {
const { onAddAsset, onRemoveAsset, getAsset } = this.props;
@ -194,8 +211,7 @@ export default class Editor extends Component {
state={this.state.editorState}
schema={this.state.schema}
plugins={plugins}
onChange={editorState => this.setState({ editorState })}
onDocumentChange={this.handleDocumentChange}
onChange={this.handleChange}
onKeyDown={onKeyDown}
onPaste={this.handlePaste}
ref={ref => this.ref = ref}

View File

@ -2,11 +2,11 @@ import { Block, Text } from 'slate';
export default onKeyDown;
function onKeyDown(e, data, state) {
function onKeyDown(e, data, change) {
const createDefaultBlock = () => {
return Block.create({
type: 'paragraph',
nodes: [Text.createFromString('')]
nodes: [Text.create('')]
});
};
if (data.key === 'enter') {
@ -18,7 +18,7 @@ function onKeyDown(e, data, state) {
* If the selected block is the first block in the document, create the
* new block above it. If not, create the new block below it.
*/
const { document: doc, selection, anchorBlock, focusBlock } = state;
const { document: doc, selection, anchorBlock, focusBlock } = change.state;
const singleBlockSelected = anchorBlock === focusBlock;
if (!singleBlockSelected || !focusBlock.isVoid) return;
@ -31,24 +31,12 @@ function onKeyDown(e, data, state) {
const newBlock = createDefaultBlock();
const newBlockIndex = focusBlockIsFirstChild ? 0 : focusBlockIndex + 1;
return state.transform()
return change
.insertNodeByKey(focusBlockParent.key, newBlockIndex, newBlock)
.collapseToStartOf(newBlock)
.apply();
.collapseToStartOf(newBlock);
}
if (data.isMod) {
if (data.key === 'y') {
e.preventDefault();
return state.transform().redo().focus().apply({ save: false });
}
if (data.key === 'z') {
e.preventDefault();
return state.transform()[data.isShift ? 'redo' : 'undo']().focus().apply({ save: false });
}
const marks = {
b: 'bold',
i: 'italic',
@ -61,7 +49,7 @@ function onKeyDown(e, data, state) {
if (mark) {
e.preventDefault();
return state.transform().toggleMark(mark).apply();
return change.toggleMark(mark);
}
}
};

View File

@ -4,37 +4,32 @@ import EditList from 'slate-edit-list';
import EditTable from 'slate-edit-table';
const SoftBreak = (options = {}) => ({
onKeyDown(e, data, state) {
onKeyDown(e, data, change) {
if (data.key != 'enter') return;
if (options.shift && e.shiftKey == false) return;
const { onlyIn, ignoreIn, closeAfter, unwrapBlocks, defaultBlock = 'paragraph' } = options;
const { type, nodes } = state.startBlock;
const { onlyIn, ignoreIn, defaultBlock = 'paragraph' } = options;
const { type, nodes } = change.state.startBlock;
if (onlyIn && !onlyIn.includes(type)) return;
if (ignoreIn && ignoreIn.includes(type)) return;
const shouldClose = nodes.last().characters.takeLast(closeAfter).every(c => c.text === '\n');
if (closeAfter && shouldClose) {
const trimmed = state.transform().deleteBackward(closeAfter);
const unwrapped = unwrapBlocks
? unwrapBlocks.reduce((acc, blockType) => acc.unwrapBlock(blockType), trimmed)
: trimmed;
return unwrapped.insertBlock(defaultBlock).apply();
const shouldClose = nodes.last().characters.last() === '\n';
if (shouldClose) {
const trimmed = change.deleteBackward(1);
return trimmed.insertBlock(defaultBlock);
}
const textNode = Text.createFromString('\n');
const textNode = Text.create('\n');
const breakNode = Inline.create({ type: 'break', nodes: [ textNode ] });
return state.transform()
return change
.insertInline(breakNode)
.insertText('')
.collapseToStartOfNextText()
.apply();
.collapseToStartOfNextText();
}
});
const SoftBreakOpts = {
onlyIn: ['quote', 'code'],
closeAfter: 1
};
export const SoftBreakConfigured = SoftBreak(SoftBreakOpts);
@ -42,10 +37,11 @@ export const SoftBreakConfigured = SoftBreak(SoftBreakOpts);
export const ParagraphSoftBreakConfigured = SoftBreak({ onlyIn: ['paragraph'], shift: true });
const BreakToDefaultBlock = ({ onlyIn = [], defaultBlock = 'paragraph' }) => ({
onKeyDown(e, data, state) {
onKeyDown(e, data, change) {
const { state } = change;
if (data.key != 'enter' || e.shiftKey == true || state.isExpanded) return;
if (onlyIn.includes(state.startBlock.type)) {
return state.transform().insertBlock(defaultBlock).apply();
return change.insertBlock(defaultBlock);
}
}
});
@ -57,21 +53,18 @@ const BreakToDefaultBlockOpts = {
export const BreakToDefaultBlockConfigured = BreakToDefaultBlock(BreakToDefaultBlockOpts);
const BackspaceCloseBlock = (options = {}) => ({
onKeyDown(e, data, state) {
onKeyDown(e, data, change) {
if (data.key != 'backspace') return;
const { defaultBlock = 'paragraph', ignoreIn, onlyIn } = options;
const { startBlock } = state;
const { startBlock } = change.state;
const { type } = startBlock;
if (onlyIn && !onlyIn.includes(type)) return;
if (ignoreIn && ignoreIn.includes(type)) return;
const characters = startBlock.getFirstText().characters;
const isEmpty = !characters || characters.isEmpty();
if (isEmpty) {
return state.transform().insertBlock(defaultBlock).focus().apply();
if (startBlock.text === '') {
return change.setBlock(defaultBlock).focus();
}
}
});

View File

@ -15,13 +15,13 @@ const enforceNeverEmpty = {
const hasBlocks = !doc.getBlocks().isEmpty();
return hasBlocks ? null : {};
},
normalize: transform => {
normalize: change => {
const block = Block.create({
type: 'paragraph',
nodes: [Text.createFromString('')],
nodes: [Text.create('')],
});
const { key } = transform.state.document;
return transform.insertNodeByKey(key, 0, block).focus();
const { key } = change.state.document;
return change.insertNodeByKey(key, 0, block).focus();
},
};
@ -35,8 +35,8 @@ const shortcodesAtRoot = {
return node.type === 'shortcode' && doc.getParent(node.key).key !== doc.key;
});
},
normalize: (transform, doc, node) => {
return transform.unwrapNodeByKey(node.key);
normalize: (change, doc, node) => {
return change.unwrapNodeByKey(node.key);
},
};

View File

@ -84,10 +84,8 @@ function createInline(type, props = {}, nodes) {
*/
function createText(value, data) {
const node = { kind: 'text', data };
if (isArray(value)) {
return { ...node, ranges: value };
}
return {...node, text: value };
const ranges = isArray(value) ? value : [{ text: value }];
return { ...node, ranges };
}
function processMarkNode(node, parentMarks = []) {

View File

@ -435,7 +435,9 @@ function convertNode(node, children, shortcodePlugins) {
* value and the "lang" data property to the new MDAST node.
*/
case 'code': {
const value = get(node.nodes, [0, 'text']);
const value = flatMap(node.nodes, child => {
return flatMap(child.ranges, 'text');
}).join('');
const { lang, ...data } = get(node, 'data', {});
return u(typeMap[node.type], { lang, data }, value);
}

View File

@ -3830,7 +3830,7 @@ immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
immutable@^3.7.6, immutable@^3.8.1:
immutable@^3.7.6:
version "3.8.1"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
@ -4164,6 +4164,12 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
dependencies:
isobject "^3.0.1"
is-posix-bracket@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
@ -4292,6 +4298,10 @@ isobject@^2.0.0:
dependencies:
isarray "1.0.0"
isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
@ -7783,38 +7793,67 @@ slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
slate-edit-list@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.7.1.tgz#84ee960d2d5b5a20ce267ad9df894395a91b93d5"
slate-base64-serializer@^0.1.9:
version "0.1.10"
resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.1.10.tgz#5fb7fe6de52ef56d2619dc34f8960fe81a866bfb"
slate-edit-table@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/slate-edit-table/-/slate-edit-table-0.10.1.tgz#4f01bf26bac2de26e8d25bfbe7116a2f643e5934"
slate-dev-logger@^0.1.10, slate-dev-logger@^0.1.11:
version "0.1.11"
resolved "https://registry.yarnpkg.com/slate-dev-logger/-/slate-dev-logger-0.1.11.tgz#740fd2e23b1dda1f4629419fc914eea91272b6f0"
slate-edit-list@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.8.0.tgz#3f8904fc9cb308d3c614efa5586953a6de6a4608"
slate-edit-table@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/slate-edit-table/-/slate-edit-table-0.11.0.tgz#4ab175a02146ec1c45a4a2826eaa66b5db0d2266"
slate-plain-serializer@^0.1.10:
version "0.1.11"
resolved "https://registry.yarnpkg.com/slate-plain-serializer/-/slate-plain-serializer-0.1.11.tgz#0b70cd870a935b294d6418785084be33ae23b96e"
dependencies:
immutable "^3.8.1"
slate-dev-logger "^0.1.11"
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-prop-types@^0.1.9:
version "0.1.10"
resolved "https://registry.yarnpkg.com/slate-prop-types/-/slate-prop-types-0.1.10.tgz#a0c21d880ae72cb7f38a0d0fb025b7db2fa5d5b1"
dependencies:
slate-dev-logger "^0.1.11"
slate@^0.21.0:
version "0.21.4"
resolved "https://registry.yarnpkg.com/slate/-/slate-0.21.4.tgz#ae6113379cd838b7ec68ecd94834ce9741bc36f3"
slate-react@^0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.1.10.tgz#1d5886fb89bc9f03cb5cf15b74c3e8b9600d52e7"
dependencies:
debug "^2.3.2"
get-window "^1.1.1"
is-in-browser "^1.1.3"
is-window "^1.0.2"
keycode "^2.1.2"
prop-types "^15.5.8"
react-portal "^3.1.0"
selection-is-backward "^1.0.0"
slate-base64-serializer "^0.1.9"
slate-dev-logger "^0.1.10"
slate-plain-serializer "^0.1.10"
slate-prop-types "^0.1.9"
slate-soft-break@^0.4.0:
version "0.4.3"
resolved "https://registry.yarnpkg.com/slate-soft-break/-/slate-soft-break-0.4.3.tgz#e3a9279a9b92ca173915467f5fbd359f739a7e96"
slate@^0.25.0:
version "0.25.3"
resolved "https://registry.yarnpkg.com/slate/-/slate-0.25.3.tgz#d232ee168135269f10fbd3534307560e8ebf38b6"
dependencies:
debug "^2.3.2"
direction "^0.1.5"
es6-map "^0.1.4"
esrever "^0.2.0"
get-window "^1.1.1"
immutable "^3.8.1"
is-empty "^1.0.0"
is-in-browser "^1.1.3"
is-window "^1.0.2"
keycode "^2.1.2"
is-plain-object "^2.0.4"
lodash "^4.17.4"
prop-types "^15.5.8"
react-portal "^3.1.0"
selection-is-backward "^1.0.0"
slate-dev-logger "^0.1.11"
type-of "^2.0.1"
slice-ansi@0.0.4: