1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-19 05:31:56 +02:00

remove leaves (#2715)

* first stab at removing leaves with tests passing

* add deprecation warning for creating texts with leaves

* fixes

* update examples
This commit is contained in:
Ian Storm Taylor
2019-04-30 12:15:22 -07:00
committed by GitHub
parent a431576e73
commit a220cd5ae1
184 changed files with 2281 additions and 2599 deletions

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,46 +9,30 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "With Slate you can build complex block types that have their own embedded content and behaviors, like rendering checkboxes inside check list items!"
"text":
"With Slate you can build complex block types that have their own embedded content and behaviors, like rendering checkboxes inside check list items!"
}
]
} }
] ]
}, },
{ {
"object": "block", "object": "block",
"type": "check-list-item", "type": "check-list-item",
"data": { "data": { "checked": true },
"checked": true
},
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Slide to the left."
{
"text": "Slide to the left."
}
]
} }
] ]
}, },
{ {
"object": "block", "object": "block",
"type": "check-list-item", "type": "check-list-item",
"data": { "data": { "checked": true },
"checked": true
},
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Slide to the right."
{
"text": "Slide to the right."
}
]
} }
] ]
}, },
@@ -59,28 +45,18 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Criss-cross."
{
"text": "Criss-cross."
}
]
} }
] ]
}, },
{ {
"object": "block", "object": "block",
"type": "check-list-item", "type": "check-list-item",
"data": { "data": { "checked": true },
"checked": true
},
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Criss-cross!"
{
"text": "Criss-cross!"
}
]
} }
] ]
}, },
@@ -93,11 +69,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Cha cha real smooth…"
{
"text": "Cha cha real smooth…"
}
]
} }
] ]
}, },
@@ -110,11 +82,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Let's go to work!"
{
"text": "Let's go to work!"
}
]
} }
] ]
}, },
@@ -124,11 +92,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself!"
{
"text": "Try it out for yourself!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "There are certain behaviors that require rendering dynamic marks on string of text, like rendering code highlighting. For example:"
"text":
"There are certain behaviors that require rendering dynamic marks on string of text, like rendering code highlighting. For example:"
}
]
} }
] ]
}, },
@@ -29,11 +27,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "// A simple FizzBuzz implementation."
{
"text": "// A simple FizzBuzz implementation."
}
]
} }
] ]
}, },
@@ -43,11 +37,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "for (var i = 1; i <= 100; i++) {"
{
"text": "for (var i = 1; i <= 100; i++) {"
}
]
} }
] ]
}, },
@@ -57,11 +47,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " if (i % 15 == 0) {"
{
"text": " if (i % 15 == 0) {"
}
]
} }
] ]
}, },
@@ -71,11 +57,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " console.log('Fizz Buzz');"
{
"text": " console.log('Fizz Buzz');"
}
]
} }
] ]
}, },
@@ -85,11 +67,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " } else if (i % 5 == 0) {"
{
"text": " } else if (i % 5 == 0) {"
}
]
} }
] ]
}, },
@@ -99,11 +77,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " console.log('Buzz');"
{
"text": " console.log('Buzz');"
}
]
} }
] ]
}, },
@@ -113,11 +87,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " } else if (i % 3 == 0) {"
{
"text": " } else if (i % 3 == 0) {"
}
]
} }
] ]
}, },
@@ -127,11 +97,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " console.log('Fizz');"
{
"text": " console.log('Fizz');"
}
]
} }
] ]
}, },
@@ -141,11 +107,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " } else {"
{
"text": " } else {"
}
]
} }
] ]
}, },
@@ -155,11 +117,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " console.log(i);"
{
"text": " console.log(i);"
}
]
} }
] ]
}, },
@@ -169,11 +127,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": " }"
{
"text": " }"
}
]
} }
] ]
}, },
@@ -183,11 +137,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "}"
{
"text": "}"
}
]
} }
] ]
} }
@@ -199,11 +149,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself!"
{
"text": "Try it out for yourself!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,14 +9,14 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Insert Text: ",
{ "text": "Insert Text: ", "marks": [{ "type": "bold" }] }, "marks": [{ "type": "bold" }]
{ },
"text": {
"Type 'cat' before every word 'before' and after every word 'after' and in the middle of the word 'pion' so that it says 'pi cat on' using the virtual keyboard", "object": "text",
"marks": [{ "type": "italic" }] "text":
} "Type 'cat' before every word 'before' and after every word 'after' and in the middle of the word 'pion' so that it says 'pi cat on' using the virtual keyboard",
] "marks": [{ "type": "italic" }]
} }
] ]
}, },
@@ -24,11 +26,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Before there before is pion at after for after"
{
"text": "Before there before is pion at after for after"
}
]
} }
] ]
}, },
@@ -38,14 +36,14 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Handle Enter: ",
{ "text": "Handle Enter: ", "marks": [{ "type": "bold" }] }, "marks": [{ "type": "bold" }]
{ },
"text": {
"Hit Enter twice before every word 'before' and after every word 'after' and in the middle of the word 'split'", "object": "text",
"marks": [{ "type": "italic" }] "text":
} "Hit Enter twice before every word 'before' and after every word 'after' and in the middle of the word 'split'",
] "marks": [{ "type": "italic" }]
} }
] ]
}, },
@@ -55,11 +53,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Before there before is split at after for after"
{
"text": "Before there before is split at after for after"
}
]
} }
] ]
}, },
@@ -69,20 +63,18 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Since it's rich text, you can do things like turn a selection of text "
"text": },
"Since it's rich text, you can do things like turn a selection of text " {
}, "object": "text",
{ "text": "bold",
"text": "bold", "marks": [{ "type": "bold" }]
"marks": [{ "type": "bold" }] },
}, {
{ "object": "text",
"text": "text":
", or add a semantically rendered block quote in the middle of the page, like this:" ", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
} }
] ]
}, },
@@ -92,11 +84,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "A wise quote."
{
"text": "A wise quote."
}
]
} }
] ]
}, },
@@ -106,11 +94,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself!"
{
"text": "Try it out for yourself!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "In addition to simple image nodes, you can actually create complex embedded nodes. For example, this one contains an input element that lets you change the video being rendered!"
"text":
"In addition to simple image nodes, you can actually create complex embedded nodes. For example, this one contains an input element that lets you change the video being rendered!"
}
]
} }
] ]
}, },
@@ -29,12 +27,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Try it out! If you want another good video URL to try, go with: https://www.youtube.com/embed/6Ejga4kJUts"
"text":
"Try it out! If you want another good video URL to try, go with: https://www.youtube.com/embed/6Ejga4kJUts"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,27 +9,17 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "In addition to block nodes, you can create inline void nodes, like "
"text":
"In addition to block nodes, you can create inline void nodes, like "
}
]
}, },
{ {
"object": "inline", "object": "inline",
"type": "emoji", "type": "emoji",
"data": { "data": { "code": "😃" }
"code": "😃"
}
}, },
{ {
"object": "text", "object": "text",
"leaves": [ "text": "!"
{
"text": "!"
}
]
} }
] ]
}, },
@@ -38,9 +30,7 @@
{ {
"object": "inline", "object": "inline",
"type": "emoji", "type": "emoji",
"data": { "data": { "code": "🍔" }
"code": "🍔"
}
} }
] ]
}, },
@@ -50,11 +40,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "This example shows emojis in action."
{
"text": "This example shows emojis in action."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,11 +9,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Enforce Your Layout!"
{
"text": "Enforce Your Layout!"
}
]
} }
] ]
}, },
@@ -21,12 +19,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This example shows how to enforce your layout with schema-specific rules. This document will always have a title block at the top and at least one paragraph in the body. Try deleting them and see what happens!"
"text":
"This example shows how to enforce your layout with schema-specific rules. This document will always have a title block at the top and at least one paragraph in the body. Try deleting them and see what happens!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Slate editors save all changes to an internal \"history\" automatically, so you don't need to implement undo/redo yourself. And the editor automatically binds to the browser's default undo/redo keyboard shortcuts."
"text":
"Slate editors save all changes to an internal \"history\" automatically, so you don't need to implement undo/redo yourself. And the editor automatically binds to the browser's default undo/redo keyboard shortcuts."
}
]
} }
] ]
}, },
@@ -22,12 +20,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Try it out for yourself! Make any changes you'd like then press \"cmd+z\"."
"text":
"Try it out for yourself! Make any changes you'd like then press \"cmd+z\"."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,34 +9,25 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This example shows how you can make a hovering menu appear above your content, which you can use to make text "
"text": },
"This example shows how you can make a hovering menu appear above your content, which you can use to make text " {
}, "object": "text",
{ "text": "bold",
"text": "bold", "marks": [{ "type": "bold" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
} "text": ", "
] },
}, {
{ "object": "text",
"text": ", " "text": "italic",
}, "marks": [{ "type": "italic" }]
{ },
"text": "italic", {
"marks": [ "text": ", or anything else you might want to do!"
{
"type": "italic"
}
]
},
{
"text": ", or anything else you might want to do!"
}
]
} }
] ]
}, },
@@ -44,25 +37,16 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out yourself! Just "
{ },
"text": "Try it out yourself! Just " {
}, "object": "text",
{ "text": "select any piece of text and the menu will appear",
"text": "select any piece of text and the menu will appear", "marks": [{ "type": "bold" }, { "type": "italic" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
}, "text": "."
{
"type": "italic"
}
]
},
{
"text": "."
}
]
} }
] ]
} }

View File

@@ -23,14 +23,14 @@ for (let h = 0; h < HEADINGS; h++) {
nodes.push({ nodes.push({
object: 'block', object: 'block',
type: 'heading', type: 'heading',
nodes: [{ object: 'text', leaves: [{ text: faker.lorem.sentence() }] }], nodes: [{ object: 'text', text: faker.lorem.sentence() }],
}) })
for (let p = 0; p < PARAGRAPHS; p++) { for (let p = 0; p < PARAGRAPHS; p++) {
nodes.push({ nodes.push({
object: 'block', object: 'block',
type: 'paragraph', type: 'paragraph',
nodes: [{ object: 'text', leaves: [{ text: faker.lorem.paragraph() }] }], nodes: [{ object: 'text', text: faker.lorem.paragraph() }],
}) })
} }
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "In addition to nodes that contain editable text, you can also create other types of nodes, like images or videos."
"text":
"In addition to nodes that contain editable text, you can also create other types of nodes, like images or videos."
}
]
} }
] ]
}, },
@@ -30,12 +28,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This example shows images in action. It features two ways to add images. You can either add an image via the toolbar icon above, or if you want in on a little secret, copy an image URL to your keyboard and paste it anywhere in the editor!"
"text":
"This example shows images in action. It features two ways to add images. You can either add an image via the toolbar icon above, or if you want in on a little secret, copy an image URL to your keyboard and paste it anywhere in the editor!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,45 +9,35 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "This Slate editor records all of the "
{ },
"text": "This Slate editor records all of the " {
}, "object": "text",
{ "text": "keyboard",
"text": "keyboard", "marks": [{ "type": "bold" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
} "text": ", "
] },
}, {
{ "object": "text",
"text": ", " "text": "input",
}, "marks": [{ "type": "bold" }]
{ },
"text": "input", {
"marks": [ "object": "text",
{ "text": " and "
"type": "bold" },
} {
] "object": "text",
}, "text": "selection",
{ "marks": [{ "type": "bold" }]
"text": " and " },
}, {
{ "object": "text",
"text": "selection", "text":
"marks": [ " event that occur while using it, so you can debug the exact combination of events that is firing for particular editing behaviors."
{
"type": "bold"
}
]
},
{
"text":
" event that occur while using it, so you can debug the exact combination of events that is firing for particular editing behaviors."
}
]
} }
] ]
}, },
@@ -55,12 +47,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "And this is a quote in case you need to try testing across block types."
"text":
"And this is a quote in case you need to try testing across block types."
}
]
} }
] ]
}, },
@@ -70,11 +58,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out!"
{
"text": "Try it out!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "In addition to block nodes, you can create inline nodes, like "
"text":
"In addition to block nodes, you can create inline nodes, like "
}
]
}, },
{ {
"object": "inline", "object": "inline",
@@ -23,21 +21,13 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "hyperlinks"
{
"text": "hyperlinks"
}
]
} }
] ]
}, },
{ {
"object": "text", "object": "text",
"leaves": [ "text": "!"
{
"text": "!"
}
]
} }
] ]
}, },
@@ -47,12 +37,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This example shows hyperlinks in action. It features two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected."
"text":
"This example shows hyperlinks in action. It features two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "The editor gives you full control over the logic you can add. For example, it's fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with \"> \" you get a blockquote that looks like this:"
"text":
"The editor gives you full control over the logic you can add. For example, it's fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with \"> \" you get a blockquote that looks like this:"
}
]
} }
] ]
}, },
@@ -22,11 +20,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "A wise quote."
{
"text": "A wise quote."
}
]
} }
] ]
}, },
@@ -36,12 +30,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Order when you start a line with \"## \" you get a level-two heading, like this:"
"text":
"Order when you start a line with \"## \" you get a level-two heading, like this:"
}
]
} }
] ]
}, },
@@ -51,11 +41,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out!"
{
"text": "Try it out!"
}
]
} }
] ]
}, },
@@ -65,12 +51,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
"text":
"Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,11 +9,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try mentioning some users, like Luke or Leia."
{
"text": "Try mentioning some users, like Luke or Leia."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "By default, pasting content into a Slate editor will use the content's plain text representation. This is fine for some use cases, but sometimes you want to actually be able to paste in content and have it parsed into blocks and links and things. To do this, you need to add a parser that triggers on paste. This is an example of doing exactly that!"
"text":
"By default, pasting content into a Slate editor will use the content's plain text representation. This is fine for some use cases, but sometimes you want to actually be able to paste in content and have it parsed into blocks and links and things. To do this, you need to add a parser that triggers on paste. This is an example of doing exactly that!"
}
]
} }
] ]
}, },
@@ -22,12 +20,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Try it out for yourself! Copy and paste some rendered HTML content (not the source code) from another site into this editor."
"text":
"Try it out for yourself! Copy and paste some rendered HTML content (not the source code) from another site into this editor."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,7 +9,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [] "text": ""
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"objcet": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,44 +9,34 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "This is editable "
{ },
"text": "This is editable " {
}, "object": "text",
{ "text": "rich",
"text": "rich", "marks": [{ "type": "bold" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
} "text": " text, "
] },
}, {
{ "object": "text",
"text": " text, " "text": "much",
}, "marks": [{ "type": "italic" }]
{ },
"text": "much", {
"marks": [ "object": "text",
{ "text": " better than a "
"type": "italic" },
} {
] "object": "text",
}, "text": "<textarea>",
{ "marks": [{ "type": "code" }]
"text": " better than a " },
}, {
{ "object": "text",
"text": "<textarea>", "text": "!"
"marks": [
{
"type": "code"
}
]
},
{
"text": "!"
}
]
} }
] ]
}, },
@@ -54,24 +46,18 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Since it's rich text, you can do things like turn a selection of text "
"text": },
"Since it's rich text, you can do things like turn a selection of text " {
}, "object": "text",
{ "text": "bold",
"text": "bold", "marks": [{ "type": "bold" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
} "text":
] ", or add a semantically rendered block quote in the middle of the page, like this:"
},
{
"text":
", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
} }
] ]
}, },
@@ -81,11 +67,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "A wise quote."
{
"text": "A wise quote."
}
]
} }
] ]
}, },
@@ -95,11 +77,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself!"
{
"text": "Try it out for yourself!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Slate supports both left-to-right text editing (English, French, etc.) and right-to-left text editing (Arabic, Hebrew, etc.) which it automatically detects. Here's an example featuring excerpts from Khalil Gibran:"
"text":
"Slate supports both left-to-right text editing (English, French, etc.) and right-to-left text editing (Arabic, Hebrew, etc.) which it automatically detects. Here's an example featuring excerpts from Khalil Gibran:"
}
]
} }
] ]
}, },
@@ -22,12 +20,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Et un jeune dit : parle-nous de l'amitié.\nEt il répondit, disant :\nVotre ami est votre besoin qui a trouvé une réponse.\nIl est le champ que vous semez avec amour et moissonnez avec reconnaissance.\nIl est votre table et votre foyer."
"text":
"Et un jeune dit : parle-nous de l'amitié.\nEt il répondit, disant :\nVotre ami est votre besoin qui a trouvé une réponse.\nIl est le champ que vous semez avec amour et moissonnez avec reconnaissance.\nIl est votre table et votre foyer."
}
]
} }
] ]
}, },
@@ -37,12 +31,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "ثم قال له شاب: هات حدثناعن الصداقة.\nفأجاب و قال:\nإن صديقك هو كفاية حاجاتك.\nهو حقك الذي تزرعه بالمحبة و تحصده بالشكر.\nهو مائدتك و موقدك."
"text":
"ثم قال له شاب: هات حدثناعن الصداقة.\nفأجاب و قال:\nإن صديقك هو كفاية حاجاتك.\nهو حقك الذي تزرعه بالمحبة و تحصده بالشكر.\nهو مائدتك و موقدك."
}
]
} }
] ]
}, },
@@ -52,12 +42,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "And a youth said, \"Speak to us of Friendship.\"\nYour friend is your needs answered.\nHe is your field which you sow with love and reap with thanksgiving.\nAnd he is your board and your fireside."
"text":
"And a youth said, \"Speak to us of Friendship.\"\nYour friend is your needs answered.\nHe is your field which you sow with love and reap with thanksgiving.\nAnd he is your board and your fireside."
}
]
} }
] ]
}, },
@@ -67,11 +53,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself!"
{
"text": "Try it out for yourself!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This is editable text that you can search. As you search, it looks for matching strings of text, and adds \"decoration\" marks to them in realtime."
"text":
"This is editable text that you can search. As you search, it looks for matching strings of text, and adds \"decoration\" marks to them in realtime."
}
]
} }
] ]
}, },
@@ -22,12 +20,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Try it out for yourself by typing in the search box above!"
{
"text":
"Try it out for yourself by typing in the search box above!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,22 +9,16 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "These two editors are kept "
{ },
"text": "These two editors are kept " {
}, "object": "text",
{ "text": "in sync",
"text": "in sync", "marks": [{ "type": "bold" }]
"marks": [ },
{ {
"type": "bold" "object": "text",
} "text": " with one another as you type!"
]
},
{
"text": " with one another as you type!"
}
]
} }
] ]
}, },
@@ -32,23 +28,17 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "They achieve this by sending any document-altering operations to each other whenever a change occurs, and then applying them locally with "
"text": },
"They achieve this by sending any document-altering operations to each other whenever a change occurs, and then applying them locally with " {
}, "object": "text",
{ "text": "editor.applyOperation()",
"text": "editor.applyOperation()", "marks": [{ "type": "code" }]
"marks": [ },
{ {
"type": "code" "object": "text",
} "text": "."
]
},
{
"text": "."
}
]
} }
] ]
}, },
@@ -58,20 +48,13 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Note: ",
{ "marks": [{ "type": "italic" }]
"text": "Note: ", },
"marks": [ {
{ "object": "text",
"type": "italic" "text":
} "this example doesn't showcase operational transforms or network communication, which are required for realtime editing with multiple people at once."
]
},
{
"text":
"this example doesn't showcase operational transforms or network communication, which are required for realtime editing with multiple people at once."
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "Since the editor is based on a recursive tree model, similar to an HTML document, you can create complex nested structures, like tables:"
"text":
"Since the editor is based on a recursive tree model, similar to an HTML document, you can create complex nested structures, like tables:"
}
]
} }
] ]
}, },
@@ -30,11 +28,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": ""
{
"text": ""
}
]
} }
] ]
}, },
@@ -44,16 +38,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Human",
{ "marks": [{ "type": "bold" }]
"text": "Human",
"marks": [
{
"type": "bold"
}
]
}
]
} }
] ]
}, },
@@ -63,16 +49,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Dog",
{ "marks": [{ "type": "bold" }]
"text": "Dog",
"marks": [
{
"type": "bold"
}
]
}
]
} }
] ]
}, },
@@ -82,16 +60,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "Cat",
{ "marks": [{ "type": "bold" }]
"text": "Cat",
"marks": [
{
"type": "bold"
}
]
}
]
} }
] ]
} }
@@ -107,16 +77,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "# of Feet",
{ "marks": [{ "type": "bold" }]
"text": "# of Feet",
"marks": [
{
"type": "bold"
}
]
}
]
} }
] ]
}, },
@@ -126,11 +88,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "2"
{
"text": "2"
}
]
} }
] ]
}, },
@@ -140,11 +98,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "4"
{
"text": "4"
}
]
} }
] ]
}, },
@@ -154,11 +108,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "4"
{
"text": "4"
}
]
} }
] ]
} }
@@ -174,16 +124,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "# of Lives",
{ "marks": [{ "type": "bold" }]
"text": "# of Lives",
"marks": [
{
"type": "bold"
}
]
}
]
} }
] ]
}, },
@@ -193,11 +135,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "1"
{
"text": "1"
}
]
} }
] ]
}, },
@@ -207,11 +145,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "1"
{
"text": "1"
}
]
} }
] ]
}, },
@@ -221,11 +155,7 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text": "9"
{
"text": "9"
}
]
} }
] ]
} }
@@ -239,12 +169,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This table is just a basic example of rendering a table, and it doesn't have fancy functionality. But you could augment it to add support for navigating with arrow keys, displaying table headers, adding column and rows, or even formulas if you wanted to get really crazy!"
"text":
"This table is just a basic example of rendering a table, and it doesn't have fancy functionality. But you could augment it to add support for navigating with arrow keys, displaying table headers, adding column and rows, or even formulas if you wanted to get really crazy!"
}
]
} }
] ]
} }

View File

@@ -1,5 +1,7 @@
{ {
"object": "value",
"document": { "document": {
"object": "document",
"nodes": [ "nodes": [
{ {
"object": "block", "object": "block",
@@ -7,12 +9,8 @@
"nodes": [ "nodes": [
{ {
"object": "text", "object": "text",
"leaves": [ "text":
{ "This example shows how you might implement a version history, where you can save a new version after applying some changes, and then rollback to a previous version at any time."
"text":
"This example shows how you might implement a version history, where you can save a new version after applying some changes, and then rollback to a previous version at any time."
}
]
} }
] ]
} }

View File

@@ -27,12 +27,8 @@ const TEXT_RULE = {
if (el.tagName && el.tagName.toLowerCase() === 'br') { if (el.tagName && el.tagName.toLowerCase() === 'br') {
return { return {
object: 'text', object: 'text',
leaves: [ text: '\n',
{ marks: [],
object: 'leaf',
text: '\n',
},
],
} }
} }
@@ -41,12 +37,8 @@ const TEXT_RULE = {
return { return {
object: 'text', object: 'text',
leaves: [ text: el.nodeValue,
{ marks: [],
object: 'leaf',
text: el.nodeValue,
},
],
} }
} }
}, },
@@ -162,13 +154,8 @@ class Html {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
], ],
}, },
@@ -274,6 +261,14 @@ class Html {
node = ret node = ret
} }
if (node.object === 'block' || node.object === 'inline') {
node.data = node.data || {}
node.nodes = node.nodes || []
} else if (node.object === 'text') {
node.marks = node.marks || []
node.text = node.text || ''
}
break break
} }
@@ -292,13 +287,11 @@ class Html {
const applyMark = node => { const applyMark = node => {
if (node.object === 'mark') { if (node.object === 'mark') {
return this.deserializeMark(node) const ret = this.deserializeMark(node)
return ret
} else if (node.object === 'text') { } else if (node.object === 'text') {
node.leaves = node.leaves.map(leaf => { node.marks = node.marks || []
leaf.marks = leaf.marks || [] node.marks.push({ type, data })
leaf.marks.push({ type, data })
return leaf
})
} else if (node.nodes) { } else if (node.nodes) {
node.nodes = node.nodes.map(applyMark) node.nodes = node.nodes.map(applyMark)
} }

View File

@@ -43,12 +43,8 @@ export const output = (
<document> <document>
<paragraph> <paragraph>
<text>o</text> <text>o</text>
<text> <b>n</b>
<b>n</b> <b>e</b>
</text>
<text>
<b>e</b>
</text>
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -29,15 +29,12 @@ export const output = {
{ {
object: 'block', object: 'block',
type: 'paragraph', type: 'paragraph',
data: {},
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
},
],
}, },
], ],
}, },

View File

@@ -1,7 +1,6 @@
import { import {
Decoration, Decoration,
Document, Document,
Leaf,
Mark, Mark,
Node, Node,
Point, Point,
@@ -42,7 +41,7 @@ export function createAnchor(tagName, attributes, children) {
export function createBlock(tagName, attributes, children) { export function createBlock(tagName, attributes, children) {
const attrs = { ...attributes, object: 'block' } const attrs = { ...attributes, object: 'block' }
const block = createNode('node', attrs, children) const block = createNode(null, attrs, children)
return block return block
} }
@@ -77,15 +76,15 @@ export function createDecoration(tagName, attributes, children) {
return new DecorationPoint({ id: key, type, data }) return new DecorationPoint({ id: key, type, data })
} }
const leaves = createLeaves('leaves', {}, children) const texts = createChildren(children)
const first = leaves.first() const first = texts.first()
const last = leaves.last() const last = texts.last()
const id = `__decoration_${uid++}__` const id = `__decoration_${uid++}__`
const start = new DecorationPoint({ id, type, data }) const start = new DecorationPoint({ id, type, data })
const end = new DecorationPoint({ id, type, data }) const end = new DecorationPoint({ id, type, data })
setPoint(first, start, 0) setPoint(first, start, 0)
setPoint(last, end, last.text.length) setPoint(last, end, last.text.length)
return leaves return texts
} }
/** /**
@@ -99,7 +98,7 @@ export function createDecoration(tagName, attributes, children) {
export function createDocument(tagName, attributes, children) { export function createDocument(tagName, attributes, children) {
const attrs = { ...attributes, object: 'document' } const attrs = { ...attributes, object: 'document' }
const document = createNode('node', attrs, children) const document = createNode(null, attrs, children)
return document return document
} }
@@ -127,65 +126,10 @@ export function createFocus(tagName, attributes, children) {
export function createInline(tagName, attributes, children) { export function createInline(tagName, attributes, children) {
const attrs = { ...attributes, object: 'inline' } const attrs = { ...attributes, object: 'inline' }
const inline = createNode('node', attrs, children) const inline = createNode(null, attrs, children)
return inline return inline
} }
/**
* Create a list of leaves.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {List<Leaf>}
*/
export function createLeaves(tagName, attributes, children) {
const { marks = Mark.createSet() } = attributes
let length = 0
let leaves = Leaf.createList([])
let leaf
children.forEach(child => {
if (Leaf.isLeafList(child)) {
if (leaf) {
leaves = leaves.push(leaf)
leaf = null
}
child.forEach(l => {
l = preservePoint(l, obj => obj.addMarks(marks))
leaves = leaves.push(l)
})
} else {
if (!leaf) {
leaf = Leaf.create({ marks, text: '' })
length = 0
}
if (typeof child === 'string') {
const offset = leaf.text.length
leaf = preservePoint(leaf, obj => obj.insertText(offset, child))
length += child.length
}
if (isPoint(child)) {
setPoint(leaf, child, length)
}
}
})
if (!leaves.size && !leaf) {
leaf = Leaf.create({ marks, text: '' })
}
if (leaf) {
leaves = leaves.push(leaf)
}
return leaves
}
/** /**
* Create a list of leaves from a mark. * Create a list of leaves from a mark.
* *
@@ -196,9 +140,28 @@ export function createLeaves(tagName, attributes, children) {
*/ */
export function createMark(tagName, attributes, children) { export function createMark(tagName, attributes, children) {
const marks = Mark.createSet([attributes]) const { key, ...mark } = attributes
const leaves = createLeaves('leaves', { marks }, children) const marks = Mark.createSet([mark])
return leaves const list = createChildren(children)
let node
if (list.size > 1) {
throw new Error(
`The <mark> hyperscript tag must only contain a single node's worth of children.`
)
} else if (list.size === 0) {
node = Text.create({ key, marks })
} else {
node = list.first()
node = preservePoints(node, n => {
if (key) n = n.set('key', key)
if (marks) n = n.set('marks', n.marks.union(marks))
return n
})
}
return node
} }
/** /**
@@ -214,31 +177,11 @@ export function createNode(tagName, attributes, children) {
const { object } = attributes const { object } = attributes
if (object === 'text') { if (object === 'text') {
return createText('text', {}, children) const text = createText(null, attributes, children)
} return text
const nodes = []
let others = []
children.forEach(child => {
if (Node.isNode(child)) {
if (others.length) {
const text = createText('text', {}, others)
nodes.push(text)
}
nodes.push(child)
others = []
} else {
others.push(child)
}
})
if (others.length) {
const text = createText('text', {}, others)
nodes.push(text)
} }
const nodes = createChildren(children)
const node = Node.create({ ...attributes, nodes }) const node = Node.create({ ...attributes, nodes })
return node return node
} }
@@ -284,18 +227,27 @@ export function createSelection(tagName, attributes, children) {
*/ */
export function createText(tagName, attributes, children) { export function createText(tagName, attributes, children) {
const { key } = attributes const { key, marks } = attributes
const leaves = createLeaves('leaves', {}, children) const list = createChildren(children)
const text = Text.create({ key, leaves }) let node
let length = 0
leaves.forEach(leaf => { if (list.size > 1) {
incrementPoint(leaf, length) throw new Error(
preservePoint(leaf, () => text) `The <text> hyperscript tag must only contain a single node's worth of children.`
length += leaf.text.length )
}) } else if (list.size === 0) {
node = Text.create({ key })
} else {
node = list.first()
return text node = preservePoints(node, n => {
if (key) n = n.set('key', key)
if (marks) n = n.set('marks', Mark.createSet(marks))
return n
})
}
return node
} }
/** /**
@@ -414,6 +366,74 @@ export function createValue(tagName, attributes, children) {
return value return value
} }
/**
* Create a list of text nodes.
*
* @param {Array} children
* @return {List<Leaf>}
*/
export function createChildren(children) {
let nodes = Node.createList()
const push = node => {
const last = nodes.last()
const isString = typeof node === 'string'
if (last && last.__string && (isString || node.__string)) {
const text = isString ? node : node.text
const { length } = last.text
const next = preservePoints(last, l => l.insertText(length, text))
incrementPoints(node, length)
copyPoints(node, next)
next.__string = true
nodes = nodes.pop().push(next)
} else if (isString) {
node = Text.create({ text: node })
node.__string = true
nodes = nodes.push(node)
} else {
nodes = nodes.push(node)
}
}
children.forEach(child => {
if (Node.isNodeList(child)) {
child.forEach(c => push(c))
}
if (Node.isNode(child)) {
push(child)
}
if (typeof child === 'string') {
push(child)
}
if (isPoint(child)) {
if (!nodes.size) {
push('')
}
let last = nodes.last()
if (last.object !== 'text') {
push('')
last = nodes.last()
}
if (!last || !last.__string) {
push('')
last = nodes.last()
}
setPoint(last, child, last.text.length)
}
})
return nodes
}
/** /**
* Point classes that can be created at different points in the document and * Point classes that can be created at different points in the document and
* then searched for afterwards, for creating ranges. * then searched for afterwards, for creating ranges.
@@ -481,7 +501,7 @@ class DecorationPoint {
* @param {Number} n * @param {Number} n
*/ */
function incrementPoint(object, n) { function incrementPoints(object, n) {
const { __anchor, __focus, __decorations } = object const { __anchor, __focus, __decorations } = object
if (__anchor != null) { if (__anchor != null) {
@@ -521,15 +541,19 @@ function isPoint(object) {
* @return {Any} * @return {Any}
*/ */
function preservePoint(object, updator) { function preservePoints(object, updator) {
const { __anchor, __focus, __decorations } = object
const next = updator(object) const next = updator(object)
if (__anchor != null) next.__anchor = __anchor copyPoints(object, next)
if (__focus != null) next.__focus = __focus
if (__decorations != null) next.__decorations = __decorations
return next return next
} }
function copyPoints(object, other) {
const { __anchor, __focus, __decorations } = object
if (__anchor != null) other.__anchor = __anchor
if (__focus != null) other.__focus = __focus
if (__decorations != null) other.__decorations = __decorations
}
/** /**
* Set a `point` on an `object`. * Set a `point` on an `object`.
* *

View File

@@ -20,13 +20,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -15,17 +15,12 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: '',
marks: [
{ {
object: 'leaf', object: 'mark',
text: '', type: 'bold',
marks: [ data: {},
{
object: 'mark',
type: 'bold',
data: {},
},
],
}, },
], ],
}, },

View File

@@ -15,17 +15,12 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'word',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'word', type: 'bold',
marks: [ data: {},
{
object: 'mark',
type: 'bold',
data: {},
},
],
}, },
], ],
}, },

View File

@@ -0,0 +1,44 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<mark type="bold">one</mark>two<mark type="italic">three</mark>
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
text: 'one',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
text: 'two',
marks: [],
},
{
object: 'text',
text: 'three',
marks: [
{
object: 'mark',
type: 'italic',
data: {},
},
],
},
],
}

View File

@@ -4,9 +4,11 @@ import h from 'slate-hyperscript'
export const input = ( export const input = (
<block type="paragraph"> <block type="paragraph">
<mark type="bold">w</mark>
<mark type="bold"> <mark type="bold">
w<mark type="italic">or</mark>d <mark type="italic">or</mark>
</mark> </mark>
<mark type="bold">d</mark>
</block> </block>
) )
@@ -17,44 +19,39 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'w',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'w', type: 'bold',
marks: [ data: {},
{ },
object: 'mark', ],
type: 'bold', },
data: {}, {
}, object: 'text',
], text: 'or',
marks: [
{
object: 'mark',
type: 'italic',
data: {},
}, },
{ {
object: 'leaf', object: 'mark',
text: 'or', type: 'bold',
marks: [ data: {},
{
object: 'mark',
type: 'italic',
data: {},
},
{
object: 'mark',
type: 'bold',
data: {},
},
],
}, },
],
},
{
object: 'text',
text: 'd',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'd', type: 'bold',
marks: [ data: {},
{
object: 'mark',
type: 'bold',
data: {},
},
],
}, },
], ],
}, },

View File

@@ -11,13 +11,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
}, },
], ],
} }

View File

@@ -15,13 +15,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
], ],
} }

View File

@@ -15,13 +15,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
}, },
], ],
} }

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -44,13 +44,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
{ {
object: 'inline', object: 'inline',
@@ -61,26 +56,16 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '1', key: '1',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
key: '3', key: '3',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
], ],
}, },
@@ -93,13 +78,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '5', key: '5',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
{ {
object: 'inline', object: 'inline',
@@ -110,26 +90,16 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '6', key: '6',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
key: '8', key: '8',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -36,13 +36,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -55,13 +50,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -36,13 +36,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -55,13 +50,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -36,13 +36,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -55,13 +50,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -0,0 +1,81 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">
<text>
<anchor />
</text>
</block>
<block type="paragraph">
<text>
<focus />
</text>
</block>
</document>
</value>
)
export const options = {
preserveSelection: true,
preserveKeys: true,
}
export const output = {
object: 'value',
document: {
object: 'document',
key: '4',
data: {},
nodes: [
{
object: 'block',
key: '1',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '0',
text: '',
marks: [],
},
],
},
{
object: 'block',
key: '3',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '2',
text: '',
marks: [],
},
],
},
],
},
selection: {
object: 'selection',
anchor: {
object: 'point',
key: '0',
path: [0, 0],
offset: 0,
},
focus: {
object: 'point',
key: '2',
path: [1, 0],
offset: 0,
},
isFocused: true,
marks: null,
},
}

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -56,13 +51,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
@@ -75,13 +65,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '4', key: '4',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -56,13 +51,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
@@ -75,13 +65,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '4', key: '4',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -56,13 +51,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
@@ -75,13 +65,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '4', key: '4',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -39,13 +39,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -0,0 +1,62 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">
<text key="a">
<cursor />
</text>
</block>
</document>
</value>
)
export const options = {
preserveSelection: true,
preserveKeys: true,
}
export const output = {
object: 'value',
document: {
object: 'document',
key: '2',
data: {},
nodes: [
{
object: 'block',
key: '1',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: 'a',
text: '',
marks: [],
},
],
},
],
},
selection: {
object: 'selection',
anchor: {
object: 'point',
key: 'a',
path: [0, 0],
offset: 0,
},
focus: {
object: 'point',
key: 'a',
path: [0, 0],
offset: 0,
},
isFocused: true,
marks: null,
},
}

View File

@@ -0,0 +1,62 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">
<text>
<cursor />
</text>
</block>
</document>
</value>
)
export const options = {
preserveSelection: true,
preserveKeys: true,
}
export const output = {
object: 'value',
document: {
object: 'document',
key: '2',
data: {},
nodes: [
{
object: 'block',
key: '1',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '0',
text: '',
marks: [],
},
],
},
],
},
selection: {
object: 'selection',
anchor: {
object: 'point',
key: '0',
path: [0, 0],
offset: 0,
},
focus: {
object: 'point',
key: '0',
path: [0, 0],
offset: 0,
},
isFocused: true,
marks: null,
},
}

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
{ {
object: 'inline', object: 'inline',
@@ -54,26 +49,16 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
key: '3', key: '3',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
{ {
object: 'inline', object: 'inline',
@@ -54,26 +49,16 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
key: '3', key: '3',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -37,13 +37,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
{ {
object: 'inline', object: 'inline',
@@ -54,26 +49,16 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
key: '3', key: '3',
leaves: [ text: 'three',
{ marks: [],
object: 'leaf',
text: 'three',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -0,0 +1,73 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">
<mark type="bold">one</mark>
<cursor />two
</block>
</document>
</value>
)
export const options = {
preserveSelection: true,
preserveKeys: true,
}
export const output = {
object: 'value',
document: {
object: 'document',
data: {},
key: '3',
nodes: [
{
object: 'block',
key: '2',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
text: 'one',
key: '0',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
key: '1',
text: 'two',
marks: [],
},
],
},
],
},
selection: {
object: 'selection',
anchor: {
object: 'point',
key: '1',
path: [0, 1],
offset: 0,
},
focus: {
object: 'point',
key: '1',
path: [0, 1],
offset: 0,
},
isFocused: true,
marks: null,
},
}

View File

@@ -26,41 +26,38 @@ export const output = {
document: { document: {
object: 'document', object: 'document',
data: {}, data: {},
key: '2', key: '4',
nodes: [ nodes: [
{ {
object: 'block', object: 'block',
key: '1', key: '3',
type: 'paragraph', type: 'paragraph',
data: {}, data: {},
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
key: '1',
text: 'one',
marks: [],
},
{
object: 'text',
text: 'two',
key: '0', key: '0',
leaves: [ marks: [
{ {
object: 'leaf', object: 'mark',
text: 'one', type: 'bold',
marks: [], data: {},
},
{
object: 'leaf',
text: 'two',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'three',
marks: [],
}, },
], ],
}, },
{
object: 'text',
key: '2',
text: 'three',
marks: [],
},
], ],
}, },
], ],
@@ -70,14 +67,14 @@ export const output = {
anchor: { anchor: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 6, offset: 3,
}, },
focus: { focus: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 6, offset: 3,
}, },
isFocused: true, isFocused: true,
marks: null, marks: null,

View File

@@ -26,41 +26,38 @@ export const output = {
document: { document: {
object: 'document', object: 'document',
data: {}, data: {},
key: '2', key: '4',
nodes: [ nodes: [
{ {
object: 'block', object: 'block',
key: '1', key: '3',
type: 'paragraph', type: 'paragraph',
data: {}, data: {},
nodes: [ nodes: [
{
object: 'text',
key: '1',
text: 'one',
marks: [],
},
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'two',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'one', type: 'bold',
marks: [], data: {},
},
{
object: 'leaf',
text: 'two',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'three',
marks: [],
}, },
], ],
}, },
{
object: 'text',
key: '2',
text: 'three',
marks: [],
},
], ],
}, },
], ],
@@ -70,14 +67,14 @@ export const output = {
anchor: { anchor: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 3, offset: 0,
}, },
focus: { focus: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 3, offset: 0,
}, },
isFocused: true, isFocused: true,
marks: null, marks: null,

View File

@@ -26,41 +26,38 @@ export const output = {
document: { document: {
object: 'document', object: 'document',
data: {}, data: {},
key: '2', key: '4',
nodes: [ nodes: [
{ {
object: 'block', object: 'block',
key: '1', key: '3',
type: 'paragraph', type: 'paragraph',
data: {}, data: {},
nodes: [ nodes: [
{
object: 'text',
key: '1',
text: 'one',
marks: [],
},
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'two',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'one', type: 'bold',
marks: [], data: {},
},
{
object: 'leaf',
text: 'two',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'three',
marks: [],
}, },
], ],
}, },
{
object: 'text',
key: '2',
text: 'three',
marks: [],
},
], ],
}, },
], ],
@@ -70,14 +67,14 @@ export const output = {
anchor: { anchor: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 4, offset: 1,
}, },
focus: { focus: {
object: 'point', object: 'point',
key: '0', key: '0',
path: [0, 0], path: [0, 1],
offset: 4, offset: 1,
}, },
isFocused: true, isFocused: true,
marks: null, marks: null,

View File

@@ -33,13 +33,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -0,0 +1,68 @@
/** @jsx h */
import { createHyperscript } from 'slate-hyperscript'
const h = createHyperscript({
blocks: {
paragraph: 'paragraph',
},
marks: {
b: 'bold',
},
})
export const input = (
<value>
<document>
<paragraph>
<text>one</text>
<b>two</b>
<b>three</b>
</paragraph>
</document>
</value>
)
export const output = {
object: 'value',
document: {
object: 'document',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
text: 'one',
marks: [],
},
{
object: 'text',
text: 'two',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
text: 'three',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
],
},
],
},
}

View File

@@ -40,30 +40,25 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'A string of ',
marks: [],
},
{
object: 'text',
text: 'bold',
marks: [
{ {
object: 'leaf', object: 'mark',
text: 'A string of ', type: 'bold',
marks: [], data: {},
},
{
object: 'leaf',
text: 'bold',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: ' in a ',
marks: [],
}, },
], ],
}, },
{
object: 'text',
text: ' in a ',
marks: [],
},
{ {
object: 'inline', object: 'inline',
type: 'link', type: 'link',
@@ -73,25 +68,15 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'Slate',
{ marks: [],
object: 'leaf',
text: 'Slate',
marks: [],
},
],
}, },
], ],
}, },
{ {
object: 'text', object: 'text',
leaves: [ text: ' editor!',
{ marks: [],
object: 'leaf',
text: ' editor!',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -30,25 +30,20 @@ export const output = {
object: 'value', object: 'value',
document: { document: {
object: 'document', object: 'document',
key: '2', key: '3',
data: {}, data: {},
nodes: [ nodes: [
{ {
object: 'block', object: 'block',
key: '1', key: '2',
type: 'paragraph', type: 'paragraph',
data: {}, data: {},
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
key: '0', key: '1',
leaves: [ text: 'onetwothree',
{ marks: [],
object: 'leaf',
text: 'onetwothree',
marks: [],
},
],
}, },
], ],
}, },
@@ -59,13 +54,13 @@ export const output = {
object: 'decoration', object: 'decoration',
anchor: { anchor: {
object: 'point', object: 'point',
key: '0', key: '1',
path: [0, 0], path: [0, 0],
offset: 3, offset: 3,
}, },
focus: { focus: {
object: 'point', object: 'point',
key: '0', key: '1',
path: [0, 0], path: [0, 0],
offset: 6, offset: 6,
}, },

View File

@@ -45,13 +45,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '0', key: '0',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },
@@ -64,13 +59,8 @@ export const output = {
{ {
object: 'text', object: 'text',
key: '2', key: '2',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -11,13 +11,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
}, },
], ],
} }

View File

@@ -25,25 +25,20 @@ export const output = {
object: 'value', object: 'value',
document: { document: {
object: 'document', object: 'document',
key: '1', key: '2',
data: {}, data: {},
nodes: [ nodes: [
{ {
object: 'block', object: 'block',
key: '0', key: '1',
type: 'paragraph', type: 'paragraph',
data: {}, data: {},
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
key: 'a', key: 'a',
leaves: [ text: 'two',
{ marks: [],
object: 'leaf',
text: 'two',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -6,11 +6,6 @@ export const input = <text />
export const output = { export const output = {
object: 'text', object: 'text',
leaves: [ text: '',
{ marks: [],
object: 'leaf',
text: '',
marks: [],
},
],
} }

View File

@@ -6,11 +6,6 @@ export const input = <text>word</text>
export const output = { export const output = {
object: 'text', object: 'text',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
} }

View File

@@ -0,0 +1,15 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<text>
<text>word</text>
</text>
)
export const output = {
object: 'text',
text: 'word',
marks: [],
}

View File

@@ -11,11 +11,6 @@ export const options = {
export const output = { export const output = {
object: 'text', object: 'text',
key: 'a', key: 'a',
leaves: [ text: 'word',
{ marks: [],
object: 'leaf',
text: 'word',
marks: [],
},
],
} }

View File

@@ -40,13 +40,8 @@ function deserialize(string, options = {}) {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: line,
{ marks: defaultMarks,
object: 'leaf',
text: line,
marks: defaultMarks,
},
],
}, },
], ],
} }

View File

@@ -15,13 +15,8 @@ export const output = {
nodes: [ nodes: [
{ {
object: 'text', object: 'text',
leaves: [ text: 'one',
{ marks: [],
object: 'leaf',
text: 'one',
marks: [],
},
],
}, },
], ],
}, },

View File

@@ -36,7 +36,7 @@ export const value = (
<document> <document>
<paragraph> <paragraph>
<text key="a"> <text key="a">
<anchor /> <anchor isFocused={false} />
</text> </text>
</paragraph> </paragraph>
<image src="https://example.com/image.png"> <image src="https://example.com/image.png">
@@ -44,17 +44,13 @@ export const value = (
</image> </image>
<paragraph> <paragraph>
<text key="b"> <text key="b">
<focus /> <focus isFocused={false} />
</text> </text>
</paragraph> </paragraph>
<image src="https://example.com/image2.png"> <image src="https://example.com/image2.png">
<text /> <text />
</image> </image>
</document> </document>
<selection isFocused={false}>
<anchor key="a" offset={0} />
<focus key="b" offset={0} />
</selection>
</value> </value>
) )

View File

@@ -37,11 +37,15 @@ export const output = `
<span data-slate-leaf="true"> <span data-slate-leaf="true">
<span data-slate-content="true">one</span> <span data-slate-content="true">one</span>
</span> </span>
</span>
<span>
<span data-slate-leaf="true"> <span data-slate-leaf="true">
<strong data-slate-mark="true"> <strong data-slate-mark="true">
<span data-slate-content="true">two</span> <span data-slate-content="true">two</span>
</strong> </strong>
</span> </span>
</span>
<span>
<span data-slate-leaf="true"> <span data-slate-leaf="true">
<span data-slate-content="true">three</span> <span data-slate-content="true">three</span>
</span> </span>

View File

@@ -865,7 +865,9 @@ Commands.insertInlineAtRange = (editor, range, inline) => {
const startText = document.assertDescendant(start.key) const startText = document.assertDescendant(start.key)
const index = parent.nodes.indexOf(startText) const index = parent.nodes.indexOf(startText)
if (editor.isVoid(parent)) return if (editor.isVoid(parent)) {
return
}
editor.splitNodeByKey(start.key, start.offset) editor.splitNodeByKey(start.key, start.offset)
editor.insertNodeByKey(parent.key, index + 1, inline) editor.insertNodeByKey(parent.key, index + 1, inline)
@@ -1329,28 +1331,23 @@ Commands.wrapInlineAtRange = (editor, range, inline) => {
const endIndex = endBlock.nodes.indexOf(endChild) const endIndex = endBlock.nodes.indexOf(endChild)
if (startInline && startInline === endInline) { if (startInline && startInline === endInline) {
const text = startBlock const texts = startBlock.getTextsAtRange(range).map(text => {
.getTextsAtRange(range) if (start.key === text.key && end.key === text.key) {
.get(0) return text
.splitText(start.offset)[1] .splitText(start.offset)[1]
.splitText(end.offset - start.offset)[0] .splitText(end.offset - start.offset)[0]
.regenerateKey()
} else if (start.key === text.key) {
return text.splitText(start.offset)[1].regenerateKey()
} else if (end.key === text.key) {
return text.splitText(end.offset)[0].regenerateKey()
} else {
return text.regenerateKey()
}
})
inline = inline.set('nodes', List([text])) inline = inline.set('nodes', texts)
editor.insertInlineAtRange(range, inline) editor.insertInlineAtRange(range, inline)
const inlinekey = inline.getFirstText().key
const rng = {
anchor: {
key: inlinekey,
offset: 0,
},
focus: {
key: inlinekey,
offset: end.offset - start.offset,
},
isFocused: true,
}
editor.select(rng)
} else if (startBlock === endBlock) { } else if (startBlock === endBlock) {
document = editor.value.document document = editor.value.document
startBlock = document.getClosestBlock(start.key) startBlock = document.getClosestBlock(start.key)
@@ -1422,8 +1419,8 @@ Commands.wrapTextAtRange = (editor, range, prefix, suffix = prefix) => {
} }
editor.withoutNormalizing(() => { editor.withoutNormalizing(() => {
editor.insertTextAtRange(startRange, prefix, []) editor.insertTextAtRange(startRange, prefix)
editor.insertTextAtRange(endRange, suffix, []) editor.insertTextAtRange(endRange, suffix)
}) })
} }

View File

@@ -25,42 +25,43 @@ const Commands = {}
Commands.addMarkByPath = (editor, path, offset, length, mark) => { Commands.addMarkByPath = (editor, path, offset, length, mark) => {
mark = Mark.create(mark) mark = Mark.create(mark)
editor.addMarksByPath(path, offset, length, [mark])
}
Commands.addMarksByPath = (editor, path, offset, length, marks) => {
marks = Mark.createSet(marks)
if (!marks.size) {
return
}
const { value } = editor const { value } = editor
const { document } = value const { document } = value
const node = document.assertNode(path) const node = document.assertNode(path)
const leaves = node.getLeaves()
const operations = [] editor.withoutNormalizing(() => {
const bx = offset // If it ends before the end of the node, we'll need to split to create a new
const by = offset + length // text with different marks.
let o = 0 if (offset + length < node.text.length) {
editor.splitNodeByPath(path, offset + length)
}
leaves.forEach(leaf => { // Same thing if it starts after the start. But in that case, we need to
const ax = o // update our path and offset to point to the new start.
const ay = ax + leaf.text.length if (offset > 0) {
editor.splitNodeByPath(path, offset)
path = PathUtils.increment(path)
offset = 0
}
o += leaf.text.length marks.forEach(mark => {
editor.applyOperation({
// If the leaf doesn't overlap with the operation, continue on. type: 'add_mark',
if (ay < bx || by < ax) return path,
mark: Mark.create(mark),
// If the leaf already has the mark, continue on. })
if (leaf.marks.has(mark)) return
// Otherwise, determine which offset and characters overlap.
const start = Math.max(ax, bx)
const end = Math.min(ay, by)
operations.push({
type: 'add_mark',
path,
offset: start,
length: end - start,
mark,
}) })
}) })
operations.forEach(op => editor.applyOperation(op))
} }
/** /**
@@ -106,13 +107,12 @@ Commands.insertNodeByPath = (editor, path, index, node) => {
*/ */
Commands.insertTextByPath = (editor, path, offset, text, marks) => { Commands.insertTextByPath = (editor, path, offset, text, marks) => {
marks = Mark.createSet(marks)
const { value } = editor const { value } = editor
const { decorations, document } = value const { decorations, document } = value
const node = document.assertNode(path) const node = document.assertNode(path)
marks = marks || node.getMarksAtIndex(offset)
let updated = false
const { key } = node const { key } = node
let updated = false
const decs = decorations.filter(dec => { const decs = decorations.filter(dec => {
const { start, end, mark } = dec const { start, end, mark } = dec
@@ -128,16 +128,21 @@ Commands.insertTextByPath = (editor, path, offset, text, marks) => {
return true return true
}) })
if (updated) { editor.withoutNormalizing(() => {
editor.setDecorations(decs) if (updated) {
} editor.setDecorations(decs)
}
editor.applyOperation({ editor.applyOperation({
type: 'insert_text', type: 'insert_text',
path, path,
offset, offset,
text, text,
marks, })
if (marks.size) {
editor.addMarksByPath(path, offset, text.length, marks)
}
}) })
} }
@@ -218,42 +223,45 @@ Commands.moveNodeByPath = (editor, path, newParentPath, newIndex) => {
Commands.removeMarkByPath = (editor, path, offset, length, mark) => { Commands.removeMarkByPath = (editor, path, offset, length, mark) => {
mark = Mark.create(mark) mark = Mark.create(mark)
editor.removeMarksByPath(path, offset, length, [mark])
}
Commands.removeMarksByPath = (editor, path, offset, length, marks) => {
marks = Mark.createSet(marks)
if (!marks.size) {
return
}
const { value } = editor const { value } = editor
const { document } = value const { document } = value
const node = document.assertNode(path) const node = document.assertNode(path)
const leaves = node.getLeaves()
const operations = [] editor.withoutNormalizing(() => {
const bx = offset // If it ends before the end of the node, we'll need to split to create a new
const by = offset + length // text with different marks.
let o = 0 if (offset + length < node.text.length) {
editor.splitNodeByPath(path, offset + length)
}
leaves.forEach(leaf => { // Same thing if it starts after the start. But in that case, we need to
const ax = o // update our path and offset to point to the new start.
const ay = ax + leaf.text.length if (offset > 0) {
editor.splitNodeByPath(path, offset)
path = PathUtils.increment(path)
offset = 0
}
o += leaf.text.length marks.forEach(mark => {
editor.applyOperation({
// If the leaf doesn't overlap with the operation, continue on. type: 'remove_mark',
if (ay < bx || by < ax) return path,
offset,
// If the leaf already has the mark, continue on. length,
if (!leaf.marks.has(mark)) return mark,
})
// Otherwise, determine which offset and characters overlap.
const start = Math.max(ax, bx)
const end = Math.min(ay, by)
operations.push({
type: 'remove_mark',
path,
offset: start,
length: end - start,
mark,
}) })
}) })
operations.forEach(op => editor.applyOperation(op))
} }
/** /**
@@ -270,7 +278,7 @@ Commands.removeAllMarksByPath = (editor, path) => {
const texts = node.object === 'text' ? [node] : node.getTextsAsArray() const texts = node.object === 'text' ? [node] : node.getTextsAsArray()
texts.forEach(text => { texts.forEach(text => {
text.getMarksAsArray().forEach(mark => { text.marks.forEach(mark => {
editor.removeMarkByKey(text.key, 0, text.text.length, mark) editor.removeMarkByKey(text.key, 0, text.text.length, mark)
}) })
}) })
@@ -306,69 +314,47 @@ Commands.removeNodeByPath = (editor, path) => {
Commands.removeTextByPath = (editor, path, offset, length) => { Commands.removeTextByPath = (editor, path, offset, length) => {
const { value } = editor const { value } = editor
const { decorations, document } = value const { document, decorations } = value
const node = document.assertNode(path) const node = document.assertNode(path)
const leaves = node.getLeaves()
const { text } = node
let updated = false const { text } = node
const string = text.slice(offset, offset + length)
const { key } = node const { key } = node
const from = offset let updated = false
const to = offset + length
const decs = decorations.filter(dec => { const decs = decorations.filter(dec => {
const { start, end, mark } = dec const { start, end, mark } = dec
const isAtomic = editor.isAtomic(mark) const isAtomic = editor.isAtomic(mark)
if (!isAtomic) return true
if (start.key !== key) return true
if (start.offset < from && (end.key !== key || end.offset > from)) { if (!isAtomic) {
updated = true return true
return false
} }
if (start.offset < to && (end.key !== key || end.offset > to)) { if (start.key !== key) {
return true
}
if (start.offset < offset && (end.key !== key || end.offset > offset)) {
updated = true updated = true
return null return false
} }
return true return true
}) })
if (updated) { editor.withoutNormalizing(() => {
editor.setDecorations(decs) if (updated) {
} editor.setDecorations(decs)
}
const removals = [] editor.applyOperation({
const bx = offset
const by = offset + length
let o = 0
leaves.forEach(leaf => {
const ax = o
const ay = ax + leaf.text.length
o += leaf.text.length
// If the leaf doesn't overlap with the removal, continue on.
if (ay < bx || by < ax) return
// Otherwise, determine which offset and characters overlap.
const start = Math.max(ax, bx)
const end = Math.min(ay, by)
const string = text.slice(start, end)
removals.push({
type: 'remove_text', type: 'remove_text',
path, path,
offset: start, offset,
text: string, text: string,
marks: leaf.marks,
}) })
}) })
// Apply in reverse order, so subsequent removals don't impact previous ones.
removals.reverse().forEach(op => editor.applyOperation(op))
} }
/** /**
@@ -391,7 +377,8 @@ Commands.replaceNodeByPath = (editor, path, newNode) => {
} }
/** /**
* Replace A Length of Text with another string or text * Replace a `length` of text at `offset` with new `text` and optional `marks`.
*
* @param {Editor} editor * @param {Editor} editor
* @param {String} key * @param {String} key
* @param {Number} offset * @param {Number} offset
@@ -401,36 +388,8 @@ Commands.replaceNodeByPath = (editor, path, newNode) => {
*/ */
Commands.replaceTextByPath = (editor, path, offset, length, text, marks) => { Commands.replaceTextByPath = (editor, path, offset, length, text, marks) => {
const { document } = editor.value
const node = document.assertNode(path)
if (length + offset > node.text.length) {
length = node.text.length - offset
}
const range = document.createRange({
anchor: { path, offset },
focus: { path, offset: offset + length },
})
let activeMarks = document.getActiveMarksAtRange(range)
editor.withoutNormalizing(() => { editor.withoutNormalizing(() => {
editor.removeTextByPath(path, offset, length) editor.removeTextByPath(path, offset, length)
if (!marks) {
// Do not use mark at index when marks and activeMarks are both empty
marks = activeMarks ? activeMarks : []
} else if (activeMarks) {
// Do not use `has` because we may want to reset marks like font-size with
// an updated data;
activeMarks = activeMarks.filter(
activeMark => !marks.find(m => activeMark.type === m.type)
)
marks = activeMarks.merge(marks)
}
editor.insertTextByPath(path, offset, text, marks) editor.insertTextByPath(path, offset, text, marks)
}) })
} }
@@ -454,17 +413,34 @@ Commands.setMarkByPath = (
properties, properties,
newProperties newProperties
) => { ) => {
// we call Mark.create() here because we need the complete previous mark instance
properties = Mark.create(properties) properties = Mark.create(properties)
newProperties = Mark.createProperties(newProperties) newProperties = Mark.createProperties(newProperties)
editor.applyOperation({ const { value } = editor
type: 'set_mark', const { document } = value
path, const node = document.assertNode(path)
offset,
length, editor.withoutNormalizing(() => {
properties, // If it ends before the end of the node, we'll need to split to create a new
newProperties, // text with different marks.
if (offset + length < node.text.length) {
editor.splitNodeByPath(path, offset + length)
}
// Same thing if it starts after the start. But in that case, we need to
// update our path and offset to point to the new start.
if (offset > 0) {
editor.splitNodeByPath(path, offset)
path = PathUtils.increment(path)
offset = 0
}
editor.applyOperation({
type: 'set_mark',
path,
properties,
newProperties,
})
}) })
} }

View File

@@ -316,13 +316,16 @@ Commands.insertText = (editor, text, marks) => {
const { value } = editor const { value } = editor
const { document, selection } = value const { document, selection } = value
marks = marks || selection.marks || document.getInsertMarksAtRange(selection) marks = marks || selection.marks || document.getInsertMarksAtRange(selection)
editor.insertTextAtRange(selection, text, marks)
// If the text was successfully inserted, and the selection had marks on it, editor.withoutNormalizing(() => {
// unset the selection's marks. editor.insertTextAtRange(selection, text, marks)
if (selection.marks && document !== editor.value.document) {
editor.select({ marks: null }) // If the text was successfully inserted, and the selection had marks on it,
} // unset the selection's marks.
if (selection.marks && document !== editor.value.document) {
editor.select({ marks: null })
}
})
} }
/** /**

View File

@@ -25,19 +25,27 @@ import Operation from '../models/operation'
class ElementInterface { class ElementInterface {
/** /**
* Add mark to text at `offset` and `length` in node by `path`. * Get the concatenated text of the node.
*
* @return {String}
*/
get text() {
return this.getText()
}
/**
* Add `mark` to text at `path`.
* *
* @param {List|String} path * @param {List|String} path
* @param {Number} offset
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Node} * @return {Node}
*/ */
addMark(path, offset, length, mark) { addMark(path, mark) {
let node = this.assertDescendant(path)
path = this.resolvePath(path) path = this.resolvePath(path)
node = node.addMark(offset, length, mark) let node = this.assertDescendant(path)
node = node.addMark(mark)
const ret = this.replaceNode(path, node) const ret = this.replaceNode(path, node)
return ret return ret
} }
@@ -268,15 +276,17 @@ class ElementInterface {
} }
if (PathUtils.isEqual(startPath, endPath)) { if (PathUtils.isEqual(startPath, endPath)) {
return startText.getActiveMarksBetweenOffsets(startOffset, endOffset) return startText.marks
} }
const startMarks = startText.getActiveMarksBetweenOffsets( const startMarks = startText.marks
startOffset,
startText.text.length // PERF: if start marks is empty we can return early.
) if (startMarks.size === 0) {
if (startMarks.size === 0) return Set() return Set()
const endMarks = endText.getActiveMarksBetweenOffsets(0, endOffset) }
const endMarks = endText.marks
let marks = startMarks.intersect(endMarks) let marks = startMarks.intersect(endMarks)
// If marks is already empty, the active marks is empty // If marks is already empty, the active marks is empty
@@ -288,12 +298,16 @@ class ElementInterface {
while (!PathUtils.isEqual(startPath, endPath)) { while (!PathUtils.isEqual(startPath, endPath)) {
if (startText.text.length !== 0) { if (startText.text.length !== 0) {
marks = marks.intersect(startText.getActiveMarks()) marks = marks.intersect(startText.marks)
if (marks.size === 0) return Set()
if (marks.size === 0) {
return Set()
}
} }
;[startText, startPath] = this.getNextTextAndPath(startPath) ;[startText, startPath] = this.getNextTextAndPath(startPath)
} }
return marks return marks
} }
@@ -804,7 +818,7 @@ class ElementInterface {
} }
const text = this.getDescendant(start.path) const text = this.getDescendant(start.path)
const marks = text.getMarksAtIndex(start.offset + 1) const { marks } = text
return marks return marks
} }
@@ -939,7 +953,9 @@ class ElementInterface {
const result = [] const result = []
this.nodes.forEach(node => { this.nodes.forEach(node => {
result.push(node.getMarksAsArray()) result.push(
node.object === 'text' ? node.marks.toArray() : node.getMarksAsArray()
)
}) })
// PERF: use only one concat rather than multiple for speed. // PERF: use only one concat rather than multiple for speed.
@@ -958,22 +974,29 @@ class ElementInterface {
getMarksAtPosition(path, offset) { getMarksAtPosition(path, offset) {
path = this.resolvePath(path) path = this.resolvePath(path)
const text = this.getDescendant(path) const text = this.getDescendant(path)
const currentMarks = text.getMarksAtIndex(offset) const currentMarks = text.marks
if (offset !== 0) return currentMarks
if (offset !== 0) {
return currentMarks
}
const closestBlock = this.getClosestBlock(path) const closestBlock = this.getClosestBlock(path)
// insert mark for empty block; the empty block are often created by split node or add marks in a range including empty blocks
if (closestBlock.text === '') { if (closestBlock.text === '') {
// insert mark for empty block; the empty block are often created by split node or add marks in a range including empty blocks
return currentMarks return currentMarks
} }
const previous = this.getPreviousTextAndPath(path) const previous = this.getPreviousTextAndPath(path)
if (!previous) return Set()
if (!previous) {
return Set()
}
const [previousText, previousPath] = previous const [previousText, previousPath] = previous
if (closestBlock.hasDescendant(previousPath)) { if (closestBlock.hasDescendant(previousPath)) {
return previousText.getMarksAtIndex(previousText.text.length) return previousText.marks
} }
return currentMarks return currentMarks
@@ -1346,28 +1369,18 @@ class ElementInterface {
getOrderedMarksBetweenPositions(startPath, startOffset, endPath, endOffset) { getOrderedMarksBetweenPositions(startPath, startOffset, endPath, endOffset) {
startPath = this.resolvePath(startPath) startPath = this.resolvePath(startPath)
endPath = this.resolvePath(endPath) endPath = this.resolvePath(endPath)
const startText = this.getDescendant(startPath) const startText = this.getDescendant(startPath)
// PERF: if the paths are equal, we can just use the start.
if (PathUtils.isEqual(startPath, endPath)) { if (PathUtils.isEqual(startPath, endPath)) {
return startText.getMarksBetweenOffsets(startOffset, endOffset) return startText.marks
} }
const endText = this.getDescendant(endPath)
const texts = this.getTextsBetweenPathPositionsAsArray(startPath, endPath) const texts = this.getTextsBetweenPathPositionsAsArray(startPath, endPath)
return OrderedSet().withMutations(result => { return OrderedSet().withMutations(result => {
texts.forEach(text => { texts.forEach(text => {
if (text.key === startText.key) { result.union(text.marks)
result.union(
text.getMarksBetweenOffsets(startOffset, text.text.length)
)
} else if (text.key === endText.key) {
result.union(text.getMarksBetweenOffsets(0, endOffset))
} else {
result.union(text.getMarks())
}
}) })
}) })
} }
@@ -1902,14 +1915,13 @@ class ElementInterface {
* @param {List|String} path * @param {List|String} path
* @param {Number} offset * @param {Number} offset
* @param {String} text * @param {String} text
* @param {Set} marks
* @return {Node} * @return {Node}
*/ */
insertText(path, offset, text, marks) { insertText(path, offset, text) {
let node = this.assertDescendant(path)
path = this.resolvePath(path) path = this.resolvePath(path)
node = node.insertText(offset, text, marks) let node = this.assertDescendant(path)
node = node.insertText(offset, text)
const ret = this.replaceNode(path, node) const ret = this.replaceNode(path, node)
return ret return ret
} }
@@ -2086,19 +2098,17 @@ class ElementInterface {
} }
/** /**
* Remove mark from text at `offset` and `length` in node. * Remove `mark` from text at `path`.
* *
* @param {List} path * @param {List} path
* @param {Number} offset
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Node} * @return {Node}
*/ */
removeMark(path, offset, length, mark) { removeMark(path, mark) {
let node = this.assertDescendant(path)
path = this.resolvePath(path) path = this.resolvePath(path)
node = node.removeMark(offset, length, mark) let node = this.assertDescendant(path)
node = node.removeMark(mark)
const ret = this.replaceNode(path, node) const ret = this.replaceNode(path, node)
return ret return ret
} }
@@ -2240,9 +2250,10 @@ class ElementInterface {
* @return {Node} * @return {Node}
*/ */
setMark(path, offset, length, properties, newProperties) { setMark(path, properties, newProperties) {
let node = this.assertNode(path) path = this.resolvePath(path)
node = node.updateMark(offset, length, properties, newProperties) let node = this.assertDescendant(path)
node = node.setMark(properties, newProperties)
const ret = this.replaceNode(path, node) const ret = this.replaceNode(path, node)
return ret return ret
} }

View File

@@ -18,16 +18,6 @@ import Text from '../models/text'
*/ */
class NodeInterface { class NodeInterface {
/**
* Get the concatenated text of the node.
*
* @return {String}
*/
get text() {
return this.getText()
}
/** /**
* Get the first text node of a node, or the node itself. * Get the first text node of a node, or the node itself.
* *
@@ -141,8 +131,11 @@ class NodeInterface {
*/ */
getText() { getText() {
const children = this.object === 'text' ? this.leaves : this.nodes if (this.object === 'text') {
const text = children.reduce((memo, c) => memo + c.text, '') return this.text
}
const text = this.nodes.reduce((memo, c) => memo + c.text, '')
return text return text
} }

View File

@@ -69,7 +69,30 @@ class Node {
static createList(elements = []) { static createList(elements = []) {
if (List.isList(elements) || Array.isArray(elements)) { if (List.isList(elements) || Array.isArray(elements)) {
const list = List(elements.map(Node.create)) let array = []
elements.forEach(el => {
if (
el &&
el.object === 'text' &&
el.leaves &&
Array.isArray(el.leaves)
) {
warning(
false,
'As of slate@0.46, the `leaves` property of Text nodes has been removed. Instead, each text node contains a string of text and a unique set of marks and leaves are unnecessary.'
)
const texts = Text.createList(el.leaves).toArray()
array = array.concat(texts)
return
}
const node = Node.create(el)
array.push(node)
})
const list = List(array)
return list return list
} }

View File

@@ -16,15 +16,15 @@ import invert from '../operations/invert'
*/ */
const OPERATION_ATTRIBUTES = { const OPERATION_ATTRIBUTES = {
add_mark: ['path', 'offset', 'length', 'mark', 'data'], add_mark: ['path', 'mark', 'data'],
insert_node: ['path', 'node', 'data'], insert_node: ['path', 'node', 'data'],
insert_text: ['path', 'offset', 'text', 'marks', 'data'], insert_text: ['path', 'offset', 'text', 'data'],
merge_node: ['path', 'position', 'properties', 'target', 'data'], merge_node: ['path', 'position', 'properties', 'target', 'data'],
move_node: ['path', 'newPath', 'data'], move_node: ['path', 'newPath', 'data'],
remove_mark: ['path', 'offset', 'length', 'mark', 'data'], remove_mark: ['path', 'mark', 'data'],
remove_node: ['path', 'node', 'data'], remove_node: ['path', 'node', 'data'],
remove_text: ['path', 'offset', 'text', 'marks', 'data'], remove_text: ['path', 'offset', 'text', 'data'],
set_mark: ['path', 'offset', 'length', 'properties', 'newProperties', 'data'], set_mark: ['path', 'properties', 'newProperties', 'data'],
set_node: ['path', 'properties', 'newProperties', 'data'], set_node: ['path', 'properties', 'newProperties', 'data'],
set_selection: ['properties', 'newProperties', 'data'], set_selection: ['properties', 'newProperties', 'data'],
set_value: ['properties', 'newProperties', 'data'], set_value: ['properties', 'newProperties', 'data'],

View File

@@ -408,12 +408,28 @@ class Point extends Record(DEFAULTS) {
// TODO: if we look up by path above and it differs by key, do we want to reset it to looking up by key? // TODO: if we look up by path above and it differs by key, do we want to reset it to looking up by key?
} }
const point = this.merge({ let point = this.merge({
key: target.key, key: target.key,
path: path == null ? node.getPath(target.key) : path, path: path == null ? node.getPath(target.key) : path,
offset: offset == null ? 0 : Math.min(offset, target.text.length), offset: offset == null ? 0 : Math.min(offset, target.text.length),
}) })
// COMPAT: There is an ambiguity, since a point can exist at the end of a
// text node, or at the start of the following one. To eliminate it we
// enforce that if there is a following text node, we always move it there.
if (point.offset === target.text.length) {
const block = node.getClosestBlock(point.path)
const next = block.getNextText()
if (next) {
point = point.merge({
key: next.key,
path: node.getPath(next.key),
offset: 0,
})
}
}
return point return point
} }

View File

@@ -1,11 +1,10 @@
import isPlainObject from 'is-plain-object' import isPlainObject from 'is-plain-object'
import warning from 'tiny-warning' import invariant from 'tiny-invariant'
import { List, OrderedSet, Record, Set } from 'immutable' import { List, Record } from 'immutable'
import Mark from './mark'
import Leaf from './leaf' import Leaf from './leaf'
import Mark from './mark'
import KeyUtils from '../utils/key-utils' import KeyUtils from '../utils/key-utils'
import memoize from '../utils/memoize'
/** /**
* Default properties. * Default properties.
@@ -14,8 +13,9 @@ import memoize from '../utils/memoize'
*/ */
const DEFAULTS = { const DEFAULTS = {
leaves: undefined,
key: undefined, key: undefined,
marks: undefined,
text: undefined,
} }
/** /**
@@ -38,15 +38,10 @@ class Text extends Record(DEFAULTS) {
} }
if (typeof attrs === 'string') { if (typeof attrs === 'string') {
attrs = { leaves: [{ text: attrs }] } attrs = { text: attrs }
} }
if (isPlainObject(attrs)) { if (isPlainObject(attrs)) {
if (attrs.text) {
const { text, marks, key } = attrs
attrs = { key, leaves: [{ text, marks }] }
}
return Text.fromJSON(attrs) return Text.fromJSON(attrs)
} }
@@ -85,37 +80,16 @@ class Text extends Record(DEFAULTS) {
return object return object
} }
const { key = KeyUtils.create() } = object invariant(
let { leaves } = object object.leaves == null,
'As of slate@0.46, the `leaves` property of text nodes has been removed! Each individual leaf should be created as a text node instead.'
if (!leaves) { )
if (object.ranges) {
warning(
false,
'As of slate@0.27.0, the `ranges` property of Slate objects has been renamed to `leaves`.'
)
leaves = object.ranges
} else {
leaves = List()
}
}
if (Array.isArray(leaves)) {
leaves = List(leaves.map(x => Leaf.create(x)))
} else if (List.isList(leaves)) {
leaves = leaves.map(x => Leaf.create(x))
} else {
throw new Error('leaves must be either Array or Immutable.List')
}
if (leaves.size === 0) {
leaves = leaves.push(Leaf.create())
}
const { text = '', marks = [], key = KeyUtils.create() } = object
const node = new Text({ const node = new Text({
leaves: Leaf.createLeaves(leaves),
key, key,
text,
marks: Mark.createSet(marks),
}) })
return node return node
@@ -133,122 +107,58 @@ class Text extends Record(DEFAULTS) {
} }
/** /**
* Find the 'first' leaf at offset; By 'first' the alorighthm prefers `endOffset === offset` than `startOffset === offset` * Add a `mark`.
* Corner Cases:
* 1. if offset is negative, return the first leaf;
* 2. if offset is larger than text length, the leaf is null, startOffset, endOffset and index is of the last leaf
* *
* @param {number}
* @returns {Object}
* @property {number} startOffset
* @property {number} endOffset
* @property {number} index
* @property {Leaf} leaf
*/
searchLeafAtOffset(offset) {
let endOffset = 0
let startOffset = 0
let index = -1
const leaf = this.leaves.find(l => {
index++
startOffset = endOffset
endOffset = startOffset + l.text.length
return endOffset >= offset
})
return {
leaf,
endOffset,
index,
startOffset,
}
}
/**
* Add a `mark` at `index` and `length`.
*
* @param {Number} index
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Text} * @return {Text}
*/ */
addMark(index, length, mark) { addMark(mark) {
const marks = Set.of(mark) mark = Mark.create(mark)
return this.addMarks(index, length, marks) const { marks } = this
const next = marks.add(mark)
const node = this.set('marks', next)
return node
} }
/** /**
* Add a `set` of marks at `index` and `length`. * Add a set of `marks`.
* Corner Cases:
* 1. If empty text, and if length === 0 and index === 0, will make sure the text contain an empty leaf with the given mark.
* *
* @param {Number} index * @param {Set<Mark>} marks
* @param {Number} length
* @param {Set<Mark>} set
* @return {Text} * @return {Text}
*/ */
addMarks(index, length, set) { addMarks(marks) {
if (this.text === '' && length === 0 && index === 0) { marks = Mark.createSet(marks)
const { leaves } = this const node = this.set('marks', this.marks.union(marks))
const first = leaves.first() return node
if (!first) {
return this.set(
'leaves',
List.of(Leaf.fromJSON({ text: '', marks: set }))
)
}
const newFirst = first.addMarks(set)
if (newFirst === first) return this
return this.set('leaves', List.of(newFirst))
}
if (this.text === '') return this
if (length === 0) return this
if (index >= this.text.length) return this
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
const [middle, after] = Leaf.splitLeaves(bundle, length)
const leaves = before.concat(middle.map(x => x.addMarks(set)), after)
return this.setLeaves(leaves)
} }
/** /**
* Derive the leaves for a list of `decorations`. * Get the leaves for the text node, with `decorations`.
* *
* @param {List} decorations (optional) * @param {List<Decoration>} decorations
* @return {List<Leaf>} * @return {List<Leaf>}
*/ */
getLeaves(decorations) { getLeaves(decorations) {
let { leaves } = this const { key, text, marks } = this
const leaf = Leaf.create({ text, marks })
let leaves = Leaf.createList([leaf])
// PERF: We can exit early without decorations. // PERF: We can exit early without decorations.
if (!decorations || decorations.size === 0) return leaves if (!decorations || decorations.size === 0) {
return leaves
// HACK: We shouldn't need this, because text nodes should never be in a
// position of not having any leaves...
if (leaves.size === 0) {
const marks = decorations.map(d => d.mark)
const leaf = Leaf.create({ marks })
return List([leaf])
} }
// HACK: this shouldn't be necessary, because the loop below should handle // HACK: this shouldn't be necessary, because the loop below should handle
// the `0` case without failures. It may already even, not sure. // the `0` case without failures. It may already even, not sure.
if (this.text.length === 0) { if (text === '') {
const marks = decorations.map(d => d.mark) const decMarks = decorations.map(d => d.mark)
const leaf = Leaf.create({ marks }) const l = Leaf.create({ marks: decMarks })
return List([leaf]) return List([l])
} }
const { key, text } = this
decorations.forEach(dec => { decorations.forEach(dec => {
const { start, end, mark } = dec const { start, end, mark } = dec
const hasStart = start.key === key const hasStart = start.key === key
@@ -276,274 +186,49 @@ class Text extends Record(DEFAULTS) {
return Leaf.createLeaves(leaves) return Leaf.createLeaves(leaves)
} }
/**
* Get all of the active marks on between two offsets
* Corner Cases:
* 1. if startOffset is equal or bigger than endOffset, then return Set();
* 2. If no text is selected between start and end, then return Set()
*
* @return {Set<Mark>}
*/
getActiveMarksBetweenOffsets(startOffset, endOffset) {
if (startOffset <= 0 && endOffset >= this.text.length) {
return this.getActiveMarks()
}
if (startOffset >= endOffset) return Set()
// For empty text in a paragraph, use getActiveMarks;
if (this.text === '') return this.getActiveMarks()
let result = null
let leafEnd = 0
this.leaves.forEach(leaf => {
const leafStart = leafEnd
leafEnd = leafStart + leaf.text.length
if (leafEnd <= startOffset) return
if (leafStart >= endOffset) return false
if (!result) {
result = leaf.marks
return
}
result = result.intersect(leaf.marks)
if (result && result.size === 0) return false
return false
})
return result || Set()
}
/**
* Get all of the active marks on the text
*
* @return {Set<Mark>}
*/
getActiveMarks() {
if (this.leaves.size === 0) return Set()
const result = this.leaves.first().marks
if (result.size === 0) return result
return result.toOrderedSet().withMutations(x => {
this.leaves.forEach(c => {
x.intersect(c.marks)
if (x.size === 0) return false
})
})
}
/**
* Get all of the marks on between two offsets
* Corner Cases:
* 1. if startOffset is equal or bigger than endOffset, then return Set();
* 2. If no text is selected between start and end, then return Set()
*
* @return {OrderedSet<Mark>}
*/
getMarksBetweenOffsets(startOffset, endOffset) {
if (startOffset <= 0 && endOffset >= this.text.length) {
return this.getMarks()
}
if (startOffset >= endOffset) return Set()
// For empty text in a paragraph, use getActiveMarks;
if (this.text === '') return this.getActiveMarks()
let result = null
let leafEnd = 0
this.leaves.forEach(leaf => {
const leafStart = leafEnd
leafEnd = leafStart + leaf.text.length
if (leafEnd <= startOffset) return
if (leafStart >= endOffset) return false
if (!result) {
result = leaf.marks
return
}
result = result.union(leaf.marks)
})
return result || Set()
}
/**
* Get all of the marks on the text.
*
* @return {OrderedSet<Mark>}
*/
getMarks() {
const array = this.getMarksAsArray()
return new OrderedSet(array)
}
/**
* Get all of the marks on the text as an array
*
* @return {Array}
*/
getMarksAsArray() {
if (this.leaves.size === 0) return []
const first = this.leaves.first().marks
if (this.leaves.size === 1) return first.toArray()
const result = []
this.leaves.forEach(leaf => {
result.push(leaf.marks.toArray())
})
return Array.prototype.concat.apply(first.toArray(), result)
}
/**
* Get the marks on the text at `index`.
* Corner Cases:
* 1. if no text is before the index, and index !== 0, then return Set()
* 2. (for insert after split node or mark at range) if index === 0, and text === '', then return the leaf.marks
* 3. if index === 0, text !== '', return Set()
*
*
* @param {Number} index
* @return {Set<Mark>}
*/
getMarksAtIndex(index) {
const { leaf } = this.searchLeafAtOffset(index)
if (!leaf) return Set()
return leaf.marks
}
/** /**
* Insert `text` at `index`. * Insert `text` at `index`.
* *
* @param {Numbder} offset * @param {Number} index
* @param {String} text * @param {String} string
* @param {Set} marks (optional)
* @return {Text} * @return {Text}
*/ */
insertText(offset, text, marks) { insertText(index, string) {
if (this.text === '') { const { text } = this
return this.set('leaves', List.of(Leaf.create({ text, marks }))) const next = text.slice(0, index) + string + text.slice(index)
} const node = this.set('text', next)
return node
if (text.length === 0) return this
if (!marks) marks = Set()
const { startOffset, leaf, index } = this.searchLeafAtOffset(offset)
const delta = offset - startOffset
const beforeText = leaf.text.slice(0, delta)
const afterText = leaf.text.slice(delta)
const { leaves } = this
if (leaf.marks.equals(marks)) {
return this.set(
'leaves',
leaves.set(index, leaf.set('text', beforeText + text + afterText))
)
}
const nextLeaves = leaves.splice(
index,
1,
leaf.set('text', beforeText),
Leaf.create({ text, marks }),
leaf.set('text', afterText)
)
return this.setLeaves(nextLeaves)
} }
/** /**
* Remove a `mark` at `index` and `length`. * Remove a `mark`.
* *
* @param {Number} index
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Text} * @return {Text}
*/ */
removeMark(index, length, mark) { removeMark(mark) {
if (this.text === '' && index === 0 && length === 0) { mark = Mark.create(mark)
const first = this.leaves.first() const { marks } = this
if (!first) return this const next = marks.remove(mark)
const newFirst = first.removeMark(mark) const node = this.set('marks', next)
if (newFirst === first) return this return node
return this.set('leaves', List.of(newFirst))
}
if (length <= 0) return this
if (index >= this.text.length) return this
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
const [middle, after] = Leaf.splitLeaves(bundle, length)
const leaves = before.concat(middle.map(x => x.removeMark(mark)), after)
return this.setLeaves(leaves)
} }
/** /**
* Remove text from the text node at `start` for `length`. * Remove text from the text node at `index` for `length`.
* *
* @param {Number} start * @param {Number} index
* @param {Number} length * @param {Number} length
* @return {Text} * @return {Text}
*/ */
removeText(start, length) { removeText(index, length) {
if (length <= 0) return this const { text } = this
if (start >= this.text.length) return this const next = text.slice(0, index) + text.slice(index + length)
const node = this.set('text', next)
// PERF: For simple backspace, we can operate directly on the leaf return node
if (length === 1) {
const { leaf, index, startOffset } = this.searchLeafAtOffset(start + 1)
const offset = start - startOffset
if (leaf) {
if (leaf.text.length === 1) {
const leaves = this.leaves.remove(index)
return this.setLeaves(leaves)
}
const beforeText = leaf.text.slice(0, offset)
const afterText = leaf.text.slice(offset + length)
const text = beforeText + afterText
if (text.length > 0) {
return this.set(
'leaves',
this.leaves.set(index, leaf.set('text', text))
)
}
}
}
const [before, bundle] = Leaf.splitLeaves(this.leaves, start)
const after = Leaf.splitLeaves(bundle, length)[1]
const leaves = Leaf.createLeaves(before.concat(after))
if (leaves.size === 1) {
const first = leaves.first()
if (first.text === '') {
return this.set(
'leaves',
List.of(first.set('marks', this.getActiveMarks()))
)
}
}
return this.set('leaves', leaves)
} }
/** /**
@@ -556,9 +241,8 @@ class Text extends Record(DEFAULTS) {
toJSON(options = {}) { toJSON(options = {}) {
const object = { const object = {
object: this.object, object: this.object,
leaves: this.getLeaves() text: this.text,
.toArray() marks: this.marks.toArray().map(m => m.toJSON()),
.map(r => r.toJSON()),
} }
if (options.preserveKeys) { if (options.preserveKeys) {
@@ -569,100 +253,50 @@ class Text extends Record(DEFAULTS) {
} }
/** /**
* Update a `mark` at `index` and `length` with `properties`. * Set a `newProperties` on an existing `mark`.
* *
* @param {Number} index * @param {Object} mark
* @param {Number} length
* @param {Object} properties
* @param {Object} newProperties * @param {Object} newProperties
* @return {Text} * @return {Text}
*/ */
updateMark(index, length, properties, newProperties) { setMark(properties, newProperties) {
const { marks } = this
const mark = Mark.create(properties) const mark = Mark.create(properties)
const newMark = mark.merge(newProperties) const newMark = mark.merge(newProperties)
const next = marks.remove(mark).add(newMark)
if (this.text === '' && length === 0 && index === 0) { const node = this.set('marks', next)
const { leaves } = this return node
const first = leaves.first()
if (!first) return this
const newFirst = first.updateMark(mark, newMark)
if (newFirst === first) return this
return this.set('leaves', List.of(newFirst))
}
if (length <= 0) return this
if (index >= this.text.length) return this
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
const [middle, after] = Leaf.splitLeaves(bundle, length)
const leaves = before.concat(
middle.map(x => x.updateMark(mark, newMark)),
after
)
return this.setLeaves(leaves)
} }
/** /**
* Split this text and return two different texts * Split the node into two at `index`.
* @param {Number} position *
* @param {Number} index
* @returns {Array<Text>} * @returns {Array<Text>}
*/ */
splitText(offset) { splitText(index) {
const splitted = Leaf.splitLeaves(this.leaves, offset) const { text } = this
const one = this.set('leaves', splitted[0]) const one = this.set('text', text.slice(0, index))
const two = this.set('leaves', splitted[1]).regenerateKey() const two = this.set('text', text.slice(index)).regenerateKey()
return [one, two] return [one, two]
} }
/** /**
* merge this text and another text at the end * Merge the node with an `other` text node.
* @param {Text} text
* @returns {Text}
*/
mergeText(text) {
const leaves = this.leaves.concat(text.leaves)
return this.setLeaves(leaves)
}
/**
* Set leaves with normalized `leaves`
* *
* @param {List} leaves * @param {Text} other
* @returns {Text} * @returns {Text}
*/ */
setLeaves(leaves) { mergeText(other) {
leaves = Leaf.createLeaves(leaves) const next = this.text + other.text
const node = this.set('text', next)
if (leaves.size === 1) { return node
const first = leaves.first()
if (!first.marks || first.marks.size === 0) {
if (first.text === '') {
return this.set('leaves', List([Leaf.create()]))
}
}
}
if (leaves.size === 0) {
leaves = leaves.push(Leaf.create())
}
return this.set('leaves', leaves)
} }
} }
/**
* Memoize read methods.
*/
memoize(Text.prototype, ['getActiveMarks', 'getMarks', 'getMarksAsArray'])
/** /**
* Export. * Export.
* *

View File

@@ -2,6 +2,7 @@ import isPlainObject from 'is-plain-object'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { Record, Set, List } from 'immutable' import { Record, Set, List } from 'immutable'
import Mark from './mark'
import PathUtils from '../utils/path-utils' import PathUtils from '../utils/path-utils'
import Data from './data' import Data from './data'
import Decoration from './decoration' import Decoration from './decoration'
@@ -418,20 +419,19 @@ class Value extends Record(DEFAULTS) {
} }
/** /**
* Add mark to text at `offset` and `length` in node by `path`. * Add `mark` to text at `path`.
* *
* @param {List|String} path * @param {List|String} path
* @param {Number} offset
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Value} * @return {Value}
*/ */
addMark(path, offset, length, mark) { addMark(path, mark) {
mark = Mark.create(mark)
let value = this let value = this
let { document } = value let { document } = value
document = document.addMark(path, offset, length, mark) document = document.addMark(path, mark)
value = this.set('document', document) value = value.set('document', document)
return value return value
} }
@@ -462,23 +462,23 @@ class Value extends Record(DEFAULTS) {
* @param {List|String} path * @param {List|String} path
* @param {Number} offset * @param {Number} offset
* @param {String} text * @param {String} text
* @param {Set} marks
* @return {Value} * @return {Value}
*/ */
insertText(path, offset, text, marks) { insertText(path, offset, text) {
let value = this let value = this
let { document } = value let { document } = value
const node = document.assertNode(path) let node = document.assertNode(path)
document = document.insertText(path, offset, text, marks) document = document.insertText(path, offset, text)
node = document.assertNode(path)
value = value.set('document', document) value = value.set('document', document)
value = value.mapRanges(range => { value = value.mapPoints(point => {
return range.updatePoints(point => { if (point.key === node.key && point.offset >= offset) {
return point.key === node.key && point.offset >= offset return point.setOffset(point.offset + text.length)
? point.setOffset(point.offset + text.length) } else {
: point return point
}) }
}) })
return value return value
@@ -537,31 +537,31 @@ class Value extends Record(DEFAULTS) {
moveNode(path, newPath, newIndex = 0) { moveNode(path, newPath, newIndex = 0) {
let value = this let value = this
let { document } = value let { document } = value
if (PathUtils.isEqual(path, newPath)) {
return value
}
document = document.moveNode(path, newPath, newIndex) document = document.moveNode(path, newPath, newIndex)
value = value.set('document', document) value = value.set('document', document)
value = value.mapPoints(point => point.setPath(null))
value = value.mapRanges(range =>
range.updatePoints(point => point.setPath(null))
)
return value return value
} }
/** /**
* Remove mark from text at `offset` and `length` in node. * Remove `mark` at `path`.
* *
* @param {List|String} path * @param {List|String} path
* @param {Number} offset
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Value} * @return {Value}
*/ */
removeMark(path, offset, length, mark) { removeMark(path, mark) {
mark = Mark.create(mark)
let value = this let value = this
let { document } = value let { document } = value
document = document.removeMark(path, offset, length, mark) document = document.removeMark(path, mark)
value = this.set('document', document) value = value.set('document', document)
return value return value
} }
@@ -627,22 +627,20 @@ class Value extends Record(DEFAULTS) {
const start = offset const start = offset
const end = offset + length const end = offset + length
value = value.mapRanges(range => { value = value.mapPoints(point => {
return range.updatePoints(point => { if (point.key !== node.key) {
if (point.key !== node.key) {
return point
}
if (point.offset >= end) {
return point.setOffset(point.offset - length)
}
if (point.offset > start) {
return point.setOffset(start)
}
return point return point
}) }
if (point.offset >= end) {
return point.setOffset(point.offset - length)
}
if (point.offset > start) {
return point.setOffset(start)
}
return point
}) })
return value return value
@@ -668,17 +666,15 @@ class Value extends Record(DEFAULTS) {
* Set `properties` on `mark` on text at `offset` and `length` in node. * Set `properties` on `mark` on text at `offset` and `length` in node.
* *
* @param {List|String} path * @param {List|String} path
* @param {Number} offset
* @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @param {Object} properties * @param {Object} properties
* @return {Value} * @return {Value}
*/ */
setMark(path, offset, length, mark, properties) { setMark(path, mark, properties) {
let value = this let value = this
let { document } = value let { document } = value
document = document.setMark(path, offset, length, mark, properties) document = document.setMark(path, mark, properties)
value = value.set('document', document) value = value.set('document', document)
return value return value
} }
@@ -793,6 +789,10 @@ class Value extends Record(DEFAULTS) {
return value return value
} }
mapPoints(iterator) {
return this.mapRanges(range => range.updatePoints(iterator))
}
/** /**
* Return a JSON representation of the value. * Return a JSON representation of the value.
* *

View File

@@ -1,7 +1,6 @@
import Debug from 'debug' import Debug from 'debug'
import Operation from '../models/operation' import Operation from '../models/operation'
import PathUtils from '../utils/path-utils'
/** /**
* Debug. * Debug.
@@ -26,8 +25,8 @@ function applyOperation(value, op) {
switch (type) { switch (type) {
case 'add_mark': { case 'add_mark': {
const { path, offset, length, mark } = op const { path, mark } = op
const next = value.addMark(path, offset, length, mark) const next = value.addMark(path, mark)
return next return next
} }
@@ -51,18 +50,13 @@ function applyOperation(value, op) {
case 'move_node': { case 'move_node': {
const { path, newPath } = op const { path, newPath } = op
if (PathUtils.isEqual(path, newPath)) {
return value
}
const next = value.moveNode(path, newPath) const next = value.moveNode(path, newPath)
return next return next
} }
case 'remove_mark': { case 'remove_mark': {
const { path, offset, length, mark } = op const { path, mark } = op
const next = value.removeMark(path, offset, length, mark) const next = value.removeMark(path, mark)
return next return next
} }
@@ -79,14 +73,8 @@ function applyOperation(value, op) {
} }
case 'set_mark': { case 'set_mark': {
const { path, offset, length, properties, newProperties } = op const { path, properties, newProperties } = op
const next = value.setMark( const next = value.setMark(path, properties, newProperties)
path,
offset,
length,
properties,
newProperties
)
return next return next
} }

View File

@@ -154,18 +154,40 @@ function CorePlugin(options = {}) {
}, },
}, },
// Merge adjacent text nodes. // Merge adjacent text nodes with the same marks.
{ {
match: { object: 'text' }, match: { object: 'text' },
next: [{ object: 'block' }, { object: 'inline' }], next: (next, match) => {
return next.object !== 'text' || !match.marks.equals(next.marks)
},
normalize: (editor, error) => { normalize: (editor, error) => {
const { code, next } = error const { code, next } = error
if (code === 'next_sibling_object_invalid') { if (code === 'next_sibling_invalid') {
editor.mergeNodeByKey(next.key) editor.mergeNodeByKey(next.key)
} }
}, },
}, },
// Remove extra adjacent empty text nodes.
{
match: { object: 'text' },
previous: prev => {
return prev.object !== 'text' || prev.text !== ''
},
next: next => {
return next.object !== 'text' || next.text !== ''
},
normalize: (editor, error) => {
const { code, next, previous } = error
if (code === 'next_sibling_invalid') {
editor.removeNodeByKey(next.key)
} else if (code === 'previous_sibling_invalid') {
editor.removeNodeByKey(previous.key)
}
},
},
], ],
}) })

View File

@@ -243,7 +243,12 @@ function testRules(object, rules) {
*/ */
function validateRules(object, rule, rules, options = {}) { function validateRules(object, rule, rules, options = {}) {
const { every = false } = options const { every = false, match = null } = options
if (typeof rule === 'function') {
const valid = rule(object, match)
return valid ? null : fail('node_invalid', { rule, node: object })
}
if (Array.isArray(rule)) { if (Array.isArray(rule)) {
const array = rule.length ? rule : [{}] const array = rule.length ? rule : [{}]
@@ -306,7 +311,9 @@ function validateData(node, rule) {
function validateMarks(node, rule) { function validateMarks(node, rule) {
if (rule.marks == null) return if (rule.marks == null) return
const marks = node.getMarks().toArray()
const marks =
node.object === 'text' ? node.marks.toArray() : node.getMarks().toArray()
for (const mark of marks) { for (const mark of marks) {
const valid = rule.marks.some( const valid = rule.marks.some(
@@ -569,7 +576,7 @@ function validateNext(node, child, next, index, rules) {
if (rule.next == null) continue if (rule.next == null) continue
if (!testRules(child, rule.match)) continue if (!testRules(child, rule.match)) continue
const error = validateRules(next, rule.next) const error = validateRules(next, rule.next, [], { match: child })
if (!error) continue if (!error) continue
error.rule = rule error.rule = rule

View File

@@ -23,8 +23,10 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
wo<anchor /> wo
<b>rd</b> <b>
<anchor />rd
</b>
</paragraph> </paragraph>
<paragraph> <paragraph>
<b>an</b> <b>an</b>

View File

@@ -10,14 +10,18 @@ export const input = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<text />
<link> <link>
wo<anchor />rd wo<anchor />rd
</link> </link>
<text />
</paragraph> </paragraph>
<paragraph> <paragraph>
<text />
<link> <link>
an<focus />other an<focus />other
</link> </link>
<text />
</paragraph> </paragraph>
</document> </document>
</value> </value>
@@ -27,9 +31,12 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<text />
<link> <link>
wo<anchor /> wo
<b>rd</b> <b>
<anchor />rd
</b>
</link> </link>
<b /> <b />
</paragraph> </paragraph>
@@ -39,6 +46,7 @@ export const output = (
<b>an</b> <b>an</b>
<focus />other <focus />other
</link> </link>
<text />
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -3,15 +3,14 @@
import h from '../../../helpers/h' import h from '../../../helpers/h'
export default function(editor) { export default function(editor) {
editor.replaceMark('italic', 'bold').insertText('a') editor.addMark('bold').insertText('a')
} }
export const input = ( export const input = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<i /> word<cursor />
<cursor />word
</paragraph> </paragraph>
</document> </document>
</value> </value>
@@ -21,8 +20,9 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<b>a</b> word<b>
<cursor />word a<cursor />
</b>
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -0,0 +1,28 @@
/** @jsx h */
import h from '../../../helpers/h'
export default function(editor) {
editor.addMark('bold').insertText('a')
}
export const input = (
<value>
<document>
<paragraph>
wo<cursor />rd
</paragraph>
</document>
</value>
)
export const output = (
<value>
<document>
<paragraph>
wo<b>a</b>
<cursor />rd
</paragraph>
</document>
</value>
)

View File

@@ -0,0 +1,44 @@
/** @jsx h */
import h from '../../../helpers/h'
export default function(editor) {
editor.addMark('bold')
}
export const input = (
<value>
<document>
<paragraph>
wo<i>
<anchor />rd
</i>
</paragraph>
<paragraph>
<i>an</i>
<focus />other
</paragraph>
</document>
</value>
)
export const output = (
<value>
<document>
<paragraph>
wo
<b>
<i>
<anchor />rd
</i>
</b>
</paragraph>
<paragraph>
<b>
<i>an</i>
</b>
<focus />other
</paragraph>
</document>
</value>
)

View File

@@ -0,0 +1,47 @@
/** @jsx h */
import h from '../../../helpers/h'
export default function(editor) {
editor.addMark('bold')
}
export const input = (
<value>
<document>
<paragraph>
<i>
wo<anchor />rd
</i>
</paragraph>
<paragraph>
<i>
an<focus />other
</i>
</paragraph>
</document>
</value>
)
export const output = (
<value>
<document>
<paragraph>
<i>wo</i>
<b>
<i>
<anchor />rd
</i>
</b>
</paragraph>
<paragraph>
<b>
<i>an</i>
</b>
<i>
<focus />other
</i>
</paragraph>
</document>
</value>
)

View File

@@ -10,9 +10,8 @@ export const input = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor />
<i> <i>
wo<focus />rd <anchor />wo<focus />rd
</i> </i>
</paragraph> </paragraph>
</document> </document>
@@ -23,12 +22,14 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor />
<b> <b>
<i>wo</i> <i>
<anchor />wo
</i>
</b> </b>
<focus /> <i>
<i>rd</i> <focus />rd
</i>
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -20,8 +20,9 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor /> <b>
<b>w</b> <anchor />w
</b>
<focus />ord <focus />ord
</paragraph> </paragraph>
</document> </document>

View File

@@ -20,9 +20,10 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
wor<anchor /> wor
<b>d</b> <b>
<focus /> <anchor />d<focus />
</b>
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -20,8 +20,10 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
w<anchor /> w
<b>o</b> <b>
<anchor />o
</b>
<focus />rd <focus />rd
</paragraph> </paragraph>
</document> </document>

View File

@@ -20,9 +20,9 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor /> <b>
<b>word</b> <anchor />word<focus />
<focus /> </b>
</paragraph> </paragraph>
</document> </document>
</value> </value>

View File

@@ -27,8 +27,9 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor /> <b thing="value">
<b thing="value">w</b> <anchor />w
</b>
<focus />ord <focus />ord
</paragraph> </paragraph>
</document> </document>

View File

@@ -23,8 +23,9 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
<anchor /> <b thing="value">
<b thing="value">w</b> <anchor />w
</b>
<focus />ord <focus />ord
</paragraph> </paragraph>
</document> </document>

View File

@@ -23,9 +23,11 @@ export const output = (
<value> <value>
<document> <document>
<paragraph> <paragraph>
wo<anchor /> wo
<i> <i>
<b>rd</b> <b>
<anchor />rd
</b>
</i> </i>
</paragraph> </paragraph>
<paragraph> <paragraph>

Some files were not shown because too many files have changed in this diff Show More