1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-18 05:01:17 +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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,46 +9,30 @@
"nodes": [
{
"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",
"type": "check-list-item",
"data": {
"checked": true
},
"data": { "checked": true },
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Slide to the left."
}
]
"text": "Slide to the left."
}
]
},
{
"object": "block",
"type": "check-list-item",
"data": {
"checked": true
},
"data": { "checked": true },
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Slide to the right."
}
]
"text": "Slide to the right."
}
]
},
@@ -59,28 +45,18 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Criss-cross."
}
]
"text": "Criss-cross."
}
]
},
{
"object": "block",
"type": "check-list-item",
"data": {
"checked": true
},
"data": { "checked": true },
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Criss-cross!"
}
]
"text": "Criss-cross!"
}
]
},
@@ -93,11 +69,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Cha cha real smooth…"
}
]
"text": "Cha cha real smooth…"
}
]
},
@@ -110,11 +82,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Let's go to work!"
}
]
"text": "Let's go to work!"
}
]
},
@@ -124,11 +92,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Try it out for yourself!"
}
]
"text": "Try it out for yourself!"
}
]
}

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": "// A simple FizzBuzz implementation."
}
]
"text": "// A simple FizzBuzz implementation."
}
]
},
@@ -43,11 +37,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "for (var i = 1; i <= 100; i++) {"
}
]
"text": "for (var i = 1; i <= 100; i++) {"
}
]
},
@@ -57,11 +47,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " if (i % 15 == 0) {"
}
]
"text": " if (i % 15 == 0) {"
}
]
},
@@ -71,11 +57,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " console.log('Fizz Buzz');"
}
]
"text": " console.log('Fizz Buzz');"
}
]
},
@@ -85,11 +67,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " } else if (i % 5 == 0) {"
}
]
"text": " } else if (i % 5 == 0) {"
}
]
},
@@ -99,11 +77,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " console.log('Buzz');"
}
]
"text": " console.log('Buzz');"
}
]
},
@@ -113,11 +87,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " } else if (i % 3 == 0) {"
}
]
"text": " } else if (i % 3 == 0) {"
}
]
},
@@ -127,11 +97,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " console.log('Fizz');"
}
]
"text": " console.log('Fizz');"
}
]
},
@@ -141,11 +107,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " } else {"
}
]
"text": " } else {"
}
]
},
@@ -155,11 +117,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " console.log(i);"
}
]
"text": " console.log(i);"
}
]
},
@@ -169,11 +127,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": " }"
}
]
"text": " }"
}
]
},
@@ -183,11 +137,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "}"
}
]
"text": "}"
}
]
}
@@ -199,11 +149,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Try it out for yourself!"
}
]
"text": "Try it out for yourself!"
}
]
}

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,14 +9,14 @@
"nodes": [
{
"object": "text",
"leaves": [
{ "text": "Insert Text: ", "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",
"marks": [{ "type": "italic" }]
}
]
"text": "Insert Text: ",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"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": [
{
"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": [
{
"object": "text",
"leaves": [
{ "text": "Handle Enter: ", "marks": [{ "type": "bold" }] },
{
"text":
"Hit Enter twice before every word 'before' and after every word 'after' and in the middle of the word 'split'",
"marks": [{ "type": "italic" }]
}
]
"text": "Handle Enter: ",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"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": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text":
"Since it's rich text, you can do things like turn a selection of text "
},
{
"text": "bold",
"marks": [{ "type": "bold" }]
},
{
"text":
", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
"text":
"Since it's rich text, you can do things like turn a selection of text "
},
{
"object": "text",
"text": "bold",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"text":
", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
},
@@ -92,11 +84,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "A wise quote."
}
]
"text": "A wise quote."
}
]
},
@@ -106,11 +94,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Try it out for yourself!"
}
]
"text": "Try it out for yourself!"
}
]
}

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,27 +9,17 @@
"nodes": [
{
"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",
"type": "emoji",
"data": {
"code": "😃"
}
"data": { "code": "😃" }
},
{
"object": "text",
"leaves": [
{
"text": "!"
}
]
"text": "!"
}
]
},
@@ -38,9 +30,7 @@
{
"object": "inline",
"type": "emoji",
"data": {
"code": "🍔"
}
"data": { "code": "🍔" }
}
]
},
@@ -50,11 +40,7 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,11 +9,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Enforce Your Layout!"
}
]
"text": "Enforce Your Layout!"
}
]
},
@@ -21,12 +19,8 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,34 +9,25 @@
"nodes": [
{
"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": "bold",
"marks": [
{
"type": "bold"
}
]
},
{
"text": ", "
},
{
"text": "italic",
"marks": [
{
"type": "italic"
}
]
},
{
"text": ", or anything else you might want to do!"
}
]
"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",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"text": ", "
},
{
"object": "text",
"text": "italic",
"marks": [{ "type": "italic" }]
},
{
"text": ", or anything else you might want to do!"
}
]
},
@@ -44,25 +37,16 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Try it out yourself! Just "
},
{
"text": "select any piece of text and the menu will appear",
"marks": [
{
"type": "bold"
},
{
"type": "italic"
}
]
},
{
"text": "."
}
]
"text": "Try it out yourself! Just "
},
{
"object": "text",
"text": "select any piece of text and the menu will appear",
"marks": [{ "type": "bold" }, { "type": "italic" }]
},
{
"object": "text",
"text": "."
}
]
}

View File

@@ -23,14 +23,14 @@ for (let h = 0; h < HEADINGS; h++) {
nodes.push({
object: 'block',
type: 'heading',
nodes: [{ object: 'text', leaves: [{ text: faker.lorem.sentence() }] }],
nodes: [{ object: 'text', text: faker.lorem.sentence() }],
})
for (let p = 0; p < PARAGRAPHS; p++) {
nodes.push({
object: 'block',
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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,45 +9,35 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "This Slate editor records all of the "
},
{
"text": "keyboard",
"marks": [
{
"type": "bold"
}
]
},
{
"text": ", "
},
{
"text": "input",
"marks": [
{
"type": "bold"
}
]
},
{
"text": " and "
},
{
"text": "selection",
"marks": [
{
"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."
}
]
"text": "This Slate editor records all of the "
},
{
"object": "text",
"text": "keyboard",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"text": ", "
},
{
"object": "text",
"text": "input",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"text": " and "
},
{
"object": "text",
"text": "selection",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"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": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": "Try it out!"
}
]
"text": "Try it out!"
}
]
}

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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",
@@ -23,21 +21,13 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "hyperlinks"
}
]
"text": "hyperlinks"
}
]
},
{
"object": "text",
"leaves": [
{
"text": "!"
}
]
"text": "!"
}
]
},
@@ -47,12 +37,8 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": "A wise quote."
}
]
"text": "A wise quote."
}
]
},
@@ -36,12 +30,8 @@
"nodes": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": "Try it out!"
}
]
"text": "Try it out!"
}
]
},
@@ -65,12 +51,8 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,11 +9,7 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,7 +9,7 @@
"nodes": [
{
"object": "text",
"leaves": []
"text": ""
}
]
}

View File

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

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text":
"ثم قال له شاب: هات حدثناعن الصداقة.\nفأجاب و قال:\nإن صديقك هو كفاية حاجاتك.\nهو حقك الذي تزرعه بالمحبة و تحصده بالشكر.\nهو مائدتك و موقدك."
}
]
"text":
"ثم قال له شاب: هات حدثناعن الصداقة.\nفأجاب و قال:\nإن صديقك هو كفاية حاجاتك.\nهو حقك الذي تزرعه بالمحبة و تحصده بالشكر.\nهو مائدتك و موقدك."
}
]
},
@@ -52,12 +42,8 @@
"nodes": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": "Try it out for yourself!"
}
]
"text": "Try it out for yourself!"
}
]
}

View File

@@ -1,5 +1,7 @@
{
"object": "value",
"document": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,22 +9,16 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "These two editors are kept "
},
{
"text": "in sync",
"marks": [
{
"type": "bold"
}
]
},
{
"text": " with one another as you type!"
}
]
"text": "These two editors are kept "
},
{
"object": "text",
"text": "in sync",
"marks": [{ "type": "bold" }]
},
{
"object": "text",
"text": " with one another as you type!"
}
]
},
@@ -32,23 +28,17 @@
"nodes": [
{
"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": "editor.applyOperation()",
"marks": [
{
"type": "code"
}
]
},
{
"text": "."
}
]
"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()",
"marks": [{ "type": "code" }]
},
{
"object": "text",
"text": "."
}
]
},
@@ -58,20 +48,13 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Note: ",
"marks": [
{
"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": "Note: ",
"marks": [{ "type": "italic" }]
},
{
"object": "text",
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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": [
{
"object": "text",
"leaves": [
{
"text": ""
}
]
"text": ""
}
]
},
@@ -44,16 +38,8 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Human",
"marks": [
{
"type": "bold"
}
]
}
]
"text": "Human",
"marks": [{ "type": "bold" }]
}
]
},
@@ -63,16 +49,8 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Dog",
"marks": [
{
"type": "bold"
}
]
}
]
"text": "Dog",
"marks": [{ "type": "bold" }]
}
]
},
@@ -82,16 +60,8 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "Cat",
"marks": [
{
"type": "bold"
}
]
}
]
"text": "Cat",
"marks": [{ "type": "bold" }]
}
]
}
@@ -107,16 +77,8 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "# of Feet",
"marks": [
{
"type": "bold"
}
]
}
]
"text": "# of Feet",
"marks": [{ "type": "bold" }]
}
]
},
@@ -126,11 +88,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "2"
}
]
"text": "2"
}
]
},
@@ -140,11 +98,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "4"
}
]
"text": "4"
}
]
},
@@ -154,11 +108,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "4"
}
]
"text": "4"
}
]
}
@@ -174,16 +124,8 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "# of Lives",
"marks": [
{
"type": "bold"
}
]
}
]
"text": "# of Lives",
"marks": [{ "type": "bold" }]
}
]
},
@@ -193,11 +135,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "1"
}
]
"text": "1"
}
]
},
@@ -207,11 +145,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "1"
}
]
"text": "1"
}
]
},
@@ -221,11 +155,7 @@
"nodes": [
{
"object": "text",
"leaves": [
{
"text": "9"
}
]
"text": "9"
}
]
}
@@ -239,12 +169,8 @@
"nodes": [
{
"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": {
"object": "document",
"nodes": [
{
"object": "block",
@@ -7,12 +9,8 @@
"nodes": [
{
"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') {
return {
object: 'text',
leaves: [
{
object: 'leaf',
text: '\n',
},
],
text: '\n',
marks: [],
}
}
@@ -41,12 +37,8 @@ const TEXT_RULE = {
return {
object: 'text',
leaves: [
{
object: 'leaf',
text: el.nodeValue,
},
],
text: el.nodeValue,
marks: [],
}
}
},
@@ -162,13 +154,8 @@ class Html {
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [],
},
],
text: '',
marks: [],
},
],
},
@@ -274,6 +261,14 @@ class Html {
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
}
@@ -292,13 +287,11 @@ class Html {
const applyMark = node => {
if (node.object === 'mark') {
return this.deserializeMark(node)
const ret = this.deserializeMark(node)
return ret
} else if (node.object === 'text') {
node.leaves = node.leaves.map(leaf => {
leaf.marks = leaf.marks || []
leaf.marks.push({ type, data })
return leaf
})
node.marks = node.marks || []
node.marks.push({ type, data })
} else if (node.nodes) {
node.nodes = node.nodes.map(applyMark)
}

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import {
Decoration,
Document,
Leaf,
Mark,
Node,
Point,
@@ -42,7 +41,7 @@ export function createAnchor(tagName, attributes, children) {
export function createBlock(tagName, attributes, children) {
const attrs = { ...attributes, object: 'block' }
const block = createNode('node', attrs, children)
const block = createNode(null, attrs, children)
return block
}
@@ -77,15 +76,15 @@ export function createDecoration(tagName, attributes, children) {
return new DecorationPoint({ id: key, type, data })
}
const leaves = createLeaves('leaves', {}, children)
const first = leaves.first()
const last = leaves.last()
const texts = createChildren(children)
const first = texts.first()
const last = texts.last()
const id = `__decoration_${uid++}__`
const start = new DecorationPoint({ id, type, data })
const end = new DecorationPoint({ id, type, data })
setPoint(first, start, 0)
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) {
const attrs = { ...attributes, object: 'document' }
const document = createNode('node', attrs, children)
const document = createNode(null, attrs, children)
return document
}
@@ -127,65 +126,10 @@ export function createFocus(tagName, attributes, children) {
export function createInline(tagName, attributes, children) {
const attrs = { ...attributes, object: 'inline' }
const inline = createNode('node', attrs, children)
const inline = createNode(null, attrs, children)
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.
*
@@ -196,9 +140,28 @@ export function createLeaves(tagName, attributes, children) {
*/
export function createMark(tagName, attributes, children) {
const marks = Mark.createSet([attributes])
const leaves = createLeaves('leaves', { marks }, children)
return leaves
const { key, ...mark } = attributes
const marks = Mark.createSet([mark])
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
if (object === 'text') {
return createText('text', {}, children)
}
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 text = createText(null, attributes, children)
return text
}
const nodes = createChildren(children)
const node = Node.create({ ...attributes, nodes })
return node
}
@@ -284,18 +227,27 @@ export function createSelection(tagName, attributes, children) {
*/
export function createText(tagName, attributes, children) {
const { key } = attributes
const leaves = createLeaves('leaves', {}, children)
const text = Text.create({ key, leaves })
let length = 0
const { key, marks } = attributes
const list = createChildren(children)
let node
leaves.forEach(leaf => {
incrementPoint(leaf, length)
preservePoint(leaf, () => text)
length += leaf.text.length
})
if (list.size > 1) {
throw new Error(
`The <text> hyperscript tag must only contain a single node's worth of children.`
)
} 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
}
/**
* 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
* then searched for afterwards, for creating ranges.
@@ -481,7 +501,7 @@ class DecorationPoint {
* @param {Number} n
*/
function incrementPoint(object, n) {
function incrementPoints(object, n) {
const { __anchor, __focus, __decorations } = object
if (__anchor != null) {
@@ -521,15 +541,19 @@ function isPoint(object) {
* @return {Any}
*/
function preservePoint(object, updator) {
const { __anchor, __focus, __decorations } = object
function preservePoints(object, updator) {
const next = updator(object)
if (__anchor != null) next.__anchor = __anchor
if (__focus != null) next.__focus = __focus
if (__decorations != null) next.__decorations = __decorations
copyPoints(object, 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`.
*

View File

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

View File

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

View File

@@ -15,17 +15,12 @@ export const output = {
nodes: [
{
object: 'text',
leaves: [
text: 'word',
marks: [
{
object: 'leaf',
text: 'word',
marks: [
{
object: 'mark',
type: 'bold',
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 = (
<block type="paragraph">
<mark type="bold">w</mark>
<mark type="bold">
w<mark type="italic">or</mark>d
<mark type="italic">or</mark>
</mark>
<mark type="bold">d</mark>
</block>
)
@@ -17,44 +19,39 @@ export const output = {
nodes: [
{
object: 'text',
leaves: [
text: 'w',
marks: [
{
object: 'leaf',
text: 'w',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
text: 'or',
marks: [
{
object: 'mark',
type: 'italic',
data: {},
},
{
object: 'leaf',
text: 'or',
marks: [
{
object: 'mark',
type: 'italic',
data: {},
},
{
object: 'mark',
type: 'bold',
data: {},
},
],
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
text: 'd',
marks: [
{
object: 'leaf',
text: 'd',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
object: 'mark',
type: 'bold',
data: {},
},
],
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,13 +36,8 @@ export const output = {
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
text: 'one',
marks: [],
},
],
},
@@ -55,13 +50,8 @@ export const output = {
{
object: 'text',
key: '2',
leaves: [
{
object: 'leaf',
text: 'two',
marks: [],
},
],
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',
key: '0',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
text: 'one',
marks: [],
},
],
},
@@ -56,13 +51,8 @@ export const output = {
{
object: 'text',
key: '2',
leaves: [
{
object: 'leaf',
text: 'two',
marks: [],
},
],
text: 'two',
marks: [],
},
],
},
@@ -75,13 +65,8 @@ export const output = {
{
object: 'text',
key: '4',
leaves: [
{
object: 'leaf',
text: 'three',
marks: [],
},
],
text: 'three',
marks: [],
},
],
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,13 +39,8 @@ export const output = {
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
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',
key: '2',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
text: 'one',
marks: [],
},
{
object: 'inline',
@@ -54,26 +49,16 @@ export const output = {
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'two',
marks: [],
},
],
text: 'two',
marks: [],
},
],
},
{
object: 'text',
key: '3',
leaves: [
{
object: 'leaf',
text: 'three',
marks: [],
},
],
text: 'three',
marks: [],
},
],
},

View File

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

View File

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

View File

@@ -33,13 +33,8 @@ export const output = {
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
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: {
object: 'document',
data: {},
key: '2',
key: '4',
nodes: [
{
object: 'block',
key: '1',
key: '3',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '1',
text: 'one',
marks: [],
},
{
object: 'text',
text: 'two',
key: '0',
leaves: [
marks: [
{
object: 'leaf',
text: 'one',
marks: [],
},
{
object: 'leaf',
text: 'two',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'three',
marks: [],
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
key: '2',
text: 'three',
marks: [],
},
],
},
],
@@ -70,14 +67,14 @@ export const output = {
anchor: {
object: 'point',
key: '0',
path: [0, 0],
offset: 6,
path: [0, 1],
offset: 3,
},
focus: {
object: 'point',
key: '0',
path: [0, 0],
offset: 6,
path: [0, 1],
offset: 3,
},
isFocused: true,
marks: null,

View File

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

View File

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

View File

@@ -33,13 +33,8 @@ export const output = {
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'one',
marks: [],
},
],
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: [
{
object: 'text',
leaves: [
text: 'A string of ',
marks: [],
},
{
object: 'text',
text: 'bold',
marks: [
{
object: 'leaf',
text: 'A string of ',
marks: [],
},
{
object: 'leaf',
text: 'bold',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: ' in a ',
marks: [],
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'text',
text: ' in a ',
marks: [],
},
{
object: 'inline',
type: 'link',
@@ -73,25 +68,15 @@ export const output = {
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'Slate',
marks: [],
},
],
text: 'Slate',
marks: [],
},
],
},
{
object: 'text',
leaves: [
{
object: 'leaf',
text: ' editor!',
marks: [],
},
],
text: ' editor!',
marks: [],
},
],
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,11 +6,6 @@ export const input = <text>word</text>
export const output = {
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
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 = {
object: 'text',
key: 'a',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
text: 'word',
marks: [],
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -865,7 +865,9 @@ Commands.insertInlineAtRange = (editor, range, inline) => {
const startText = document.assertDescendant(start.key)
const index = parent.nodes.indexOf(startText)
if (editor.isVoid(parent)) return
if (editor.isVoid(parent)) {
return
}
editor.splitNodeByKey(start.key, start.offset)
editor.insertNodeByKey(parent.key, index + 1, inline)
@@ -1329,28 +1331,23 @@ Commands.wrapInlineAtRange = (editor, range, inline) => {
const endIndex = endBlock.nodes.indexOf(endChild)
if (startInline && startInline === endInline) {
const text = startBlock
.getTextsAtRange(range)
.get(0)
.splitText(start.offset)[1]
.splitText(end.offset - start.offset)[0]
const texts = startBlock.getTextsAtRange(range).map(text => {
if (start.key === text.key && end.key === text.key) {
return text
.splitText(start.offset)[1]
.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)
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) {
document = editor.value.document
startBlock = document.getClosestBlock(start.key)
@@ -1422,8 +1419,8 @@ Commands.wrapTextAtRange = (editor, range, prefix, suffix = prefix) => {
}
editor.withoutNormalizing(() => {
editor.insertTextAtRange(startRange, prefix, [])
editor.insertTextAtRange(endRange, suffix, [])
editor.insertTextAtRange(startRange, prefix)
editor.insertTextAtRange(endRange, suffix)
})
}

View File

@@ -25,42 +25,43 @@ const Commands = {}
Commands.addMarkByPath = (editor, path, offset, length, 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 { document } = value
const node = document.assertNode(path)
const leaves = node.getLeaves()
const operations = []
const bx = offset
const by = offset + length
let o = 0
editor.withoutNormalizing(() => {
// If it ends before the end of the node, we'll need to split to create a new
// text with different marks.
if (offset + length < node.text.length) {
editor.splitNodeByPath(path, offset + length)
}
leaves.forEach(leaf => {
const ax = o
const ay = ax + leaf.text.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
}
o += leaf.text.length
// If the leaf doesn't overlap with the operation, continue on.
if (ay < bx || by < ax) return
// 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,
marks.forEach(mark => {
editor.applyOperation({
type: 'add_mark',
path,
mark: Mark.create(mark),
})
})
})
operations.forEach(op => editor.applyOperation(op))
}
/**
@@ -106,13 +107,12 @@ Commands.insertNodeByPath = (editor, path, index, node) => {
*/
Commands.insertTextByPath = (editor, path, offset, text, marks) => {
marks = Mark.createSet(marks)
const { value } = editor
const { decorations, document } = value
const node = document.assertNode(path)
marks = marks || node.getMarksAtIndex(offset)
let updated = false
const { key } = node
let updated = false
const decs = decorations.filter(dec => {
const { start, end, mark } = dec
@@ -128,16 +128,21 @@ Commands.insertTextByPath = (editor, path, offset, text, marks) => {
return true
})
if (updated) {
editor.setDecorations(decs)
}
editor.withoutNormalizing(() => {
if (updated) {
editor.setDecorations(decs)
}
editor.applyOperation({
type: 'insert_text',
path,
offset,
text,
marks,
editor.applyOperation({
type: 'insert_text',
path,
offset,
text,
})
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) => {
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 { document } = value
const node = document.assertNode(path)
const leaves = node.getLeaves()
const operations = []
const bx = offset
const by = offset + length
let o = 0
editor.withoutNormalizing(() => {
// If it ends before the end of the node, we'll need to split to create a new
// text with different marks.
if (offset + length < node.text.length) {
editor.splitNodeByPath(path, offset + length)
}
leaves.forEach(leaf => {
const ax = o
const ay = ax + leaf.text.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
}
o += leaf.text.length
// If the leaf doesn't overlap with the operation, continue on.
if (ay < bx || by < ax) return
// 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: 'remove_mark',
path,
offset: start,
length: end - start,
mark,
marks.forEach(mark => {
editor.applyOperation({
type: 'remove_mark',
path,
offset,
length,
mark,
})
})
})
operations.forEach(op => editor.applyOperation(op))
}
/**
@@ -270,7 +278,7 @@ Commands.removeAllMarksByPath = (editor, path) => {
const texts = node.object === 'text' ? [node] : node.getTextsAsArray()
texts.forEach(text => {
text.getMarksAsArray().forEach(mark => {
text.marks.forEach(mark => {
editor.removeMarkByKey(text.key, 0, text.text.length, mark)
})
})
@@ -306,69 +314,47 @@ Commands.removeNodeByPath = (editor, path) => {
Commands.removeTextByPath = (editor, path, offset, length) => {
const { value } = editor
const { decorations, document } = value
const { document, decorations } = value
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 from = offset
const to = offset + length
let updated = false
const decs = decorations.filter(dec => {
const { start, end, mark } = dec
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)) {
updated = true
return false
if (!isAtomic) {
return true
}
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
return null
return false
}
return true
})
if (updated) {
editor.setDecorations(decs)
}
editor.withoutNormalizing(() => {
if (updated) {
editor.setDecorations(decs)
}
const removals = []
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({
editor.applyOperation({
type: 'remove_text',
path,
offset: start,
offset,
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 {String} key
* @param {Number} offset
@@ -401,36 +388,8 @@ Commands.replaceNodeByPath = (editor, path, newNode) => {
*/
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.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)
})
}
@@ -454,17 +413,34 @@ Commands.setMarkByPath = (
properties,
newProperties
) => {
// we call Mark.create() here because we need the complete previous mark instance
properties = Mark.create(properties)
newProperties = Mark.createProperties(newProperties)
editor.applyOperation({
type: 'set_mark',
path,
offset,
length,
properties,
newProperties,
const { value } = editor
const { document } = value
const node = document.assertNode(path)
editor.withoutNormalizing(() => {
// If it ends before the end of the node, we'll need to split to create a new
// 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 { document, selection } = value
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,
// unset the selection's marks.
if (selection.marks && document !== editor.value.document) {
editor.select({ marks: null })
}
editor.withoutNormalizing(() => {
editor.insertTextAtRange(selection, text, marks)
// 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 {
/**
* 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 {Number} offset
* @param {Number} length
* @param {Mark} mark
* @return {Node}
*/
addMark(path, offset, length, mark) {
let node = this.assertDescendant(path)
addMark(path, mark) {
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)
return ret
}
@@ -268,15 +276,17 @@ class ElementInterface {
}
if (PathUtils.isEqual(startPath, endPath)) {
return startText.getActiveMarksBetweenOffsets(startOffset, endOffset)
return startText.marks
}
const startMarks = startText.getActiveMarksBetweenOffsets(
startOffset,
startText.text.length
)
if (startMarks.size === 0) return Set()
const endMarks = endText.getActiveMarksBetweenOffsets(0, endOffset)
const startMarks = startText.marks
// PERF: if start marks is empty we can return early.
if (startMarks.size === 0) {
return Set()
}
const endMarks = endText.marks
let marks = startMarks.intersect(endMarks)
// If marks is already empty, the active marks is empty
@@ -288,12 +298,16 @@ class ElementInterface {
while (!PathUtils.isEqual(startPath, endPath)) {
if (startText.text.length !== 0) {
marks = marks.intersect(startText.getActiveMarks())
if (marks.size === 0) return Set()
marks = marks.intersect(startText.marks)
if (marks.size === 0) {
return Set()
}
}
;[startText, startPath] = this.getNextTextAndPath(startPath)
}
return marks
}
@@ -804,7 +818,7 @@ class ElementInterface {
}
const text = this.getDescendant(start.path)
const marks = text.getMarksAtIndex(start.offset + 1)
const { marks } = text
return marks
}
@@ -939,7 +953,9 @@ class ElementInterface {
const result = []
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.
@@ -958,22 +974,29 @@ class ElementInterface {
getMarksAtPosition(path, offset) {
path = this.resolvePath(path)
const text = this.getDescendant(path)
const currentMarks = text.getMarksAtIndex(offset)
if (offset !== 0) return currentMarks
const currentMarks = text.marks
if (offset !== 0) {
return currentMarks
}
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 === '') {
// 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
}
const previous = this.getPreviousTextAndPath(path)
if (!previous) return Set()
if (!previous) {
return Set()
}
const [previousText, previousPath] = previous
if (closestBlock.hasDescendant(previousPath)) {
return previousText.getMarksAtIndex(previousText.text.length)
return previousText.marks
}
return currentMarks
@@ -1346,28 +1369,18 @@ class ElementInterface {
getOrderedMarksBetweenPositions(startPath, startOffset, endPath, endOffset) {
startPath = this.resolvePath(startPath)
endPath = this.resolvePath(endPath)
const startText = this.getDescendant(startPath)
// PERF: if the paths are equal, we can just use the start.
if (PathUtils.isEqual(startPath, endPath)) {
return startText.getMarksBetweenOffsets(startOffset, endOffset)
return startText.marks
}
const endText = this.getDescendant(endPath)
const texts = this.getTextsBetweenPathPositionsAsArray(startPath, endPath)
return OrderedSet().withMutations(result => {
texts.forEach(text => {
if (text.key === startText.key) {
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())
}
result.union(text.marks)
})
})
}
@@ -1902,14 +1915,13 @@ class ElementInterface {
* @param {List|String} path
* @param {Number} offset
* @param {String} text
* @param {Set} marks
* @return {Node}
*/
insertText(path, offset, text, marks) {
let node = this.assertDescendant(path)
insertText(path, offset, text) {
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)
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 {Number} offset
* @param {Number} length
* @param {Mark} mark
* @return {Node}
*/
removeMark(path, offset, length, mark) {
let node = this.assertDescendant(path)
removeMark(path, mark) {
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)
return ret
}
@@ -2240,9 +2250,10 @@ class ElementInterface {
* @return {Node}
*/
setMark(path, offset, length, properties, newProperties) {
let node = this.assertNode(path)
node = node.updateMark(offset, length, properties, newProperties)
setMark(path, properties, newProperties) {
path = this.resolvePath(path)
let node = this.assertDescendant(path)
node = node.setMark(properties, newProperties)
const ret = this.replaceNode(path, node)
return ret
}

View File

@@ -18,16 +18,6 @@ import Text from '../models/text'
*/
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.
*
@@ -141,8 +131,11 @@ class NodeInterface {
*/
getText() {
const children = this.object === 'text' ? this.leaves : this.nodes
const text = children.reduce((memo, c) => memo + c.text, '')
if (this.object === 'text') {
return this.text
}
const text = this.nodes.reduce((memo, c) => memo + c.text, '')
return text
}

View File

@@ -69,7 +69,30 @@ class Node {
static createList(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
}

View File

@@ -16,15 +16,15 @@ import invert from '../operations/invert'
*/
const OPERATION_ATTRIBUTES = {
add_mark: ['path', 'offset', 'length', 'mark', 'data'],
add_mark: ['path', 'mark', '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'],
move_node: ['path', 'newPath', 'data'],
remove_mark: ['path', 'offset', 'length', 'mark', 'data'],
remove_mark: ['path', 'mark', 'data'],
remove_node: ['path', 'node', 'data'],
remove_text: ['path', 'offset', 'text', 'marks', 'data'],
set_mark: ['path', 'offset', 'length', 'properties', 'newProperties', 'data'],
remove_text: ['path', 'offset', 'text', 'data'],
set_mark: ['path', 'properties', 'newProperties', 'data'],
set_node: ['path', 'properties', 'newProperties', 'data'],
set_selection: ['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?
}
const point = this.merge({
let point = this.merge({
key: target.key,
path: path == null ? node.getPath(target.key) : path,
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
}

View File

@@ -1,11 +1,10 @@
import isPlainObject from 'is-plain-object'
import warning from 'tiny-warning'
import { List, OrderedSet, Record, Set } from 'immutable'
import invariant from 'tiny-invariant'
import { List, Record } from 'immutable'
import Mark from './mark'
import Leaf from './leaf'
import Mark from './mark'
import KeyUtils from '../utils/key-utils'
import memoize from '../utils/memoize'
/**
* Default properties.
@@ -14,8 +13,9 @@ import memoize from '../utils/memoize'
*/
const DEFAULTS = {
leaves: undefined,
key: undefined,
marks: undefined,
text: undefined,
}
/**
@@ -38,15 +38,10 @@ class Text extends Record(DEFAULTS) {
}
if (typeof attrs === 'string') {
attrs = { leaves: [{ text: attrs }] }
attrs = { text: attrs }
}
if (isPlainObject(attrs)) {
if (attrs.text) {
const { text, marks, key } = attrs
attrs = { key, leaves: [{ text, marks }] }
}
return Text.fromJSON(attrs)
}
@@ -85,37 +80,16 @@ class Text extends Record(DEFAULTS) {
return object
}
const { key = KeyUtils.create() } = object
let { leaves } = object
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())
}
invariant(
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.'
)
const { text = '', marks = [], key = KeyUtils.create() } = object
const node = new Text({
leaves: Leaf.createLeaves(leaves),
key,
text,
marks: Mark.createSet(marks),
})
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`
* 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
* Add a `mark`.
*
* @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
* @return {Text}
*/
addMark(index, length, mark) {
const marks = Set.of(mark)
return this.addMarks(index, length, marks)
addMark(mark) {
mark = Mark.create(mark)
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`.
* 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.
* Add a set of `marks`.
*
* @param {Number} index
* @param {Number} length
* @param {Set<Mark>} set
* @param {Set<Mark>} marks
* @return {Text}
*/
addMarks(index, length, set) {
if (this.text === '' && length === 0 && index === 0) {
const { leaves } = this
const first = leaves.first()
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)
addMarks(marks) {
marks = Mark.createSet(marks)
const node = this.set('marks', this.marks.union(marks))
return node
}
/**
* 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>}
*/
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.
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])
if (!decorations || decorations.size === 0) {
return leaves
}
// HACK: this shouldn't be necessary, because the loop below should handle
// the `0` case without failures. It may already even, not sure.
if (this.text.length === 0) {
const marks = decorations.map(d => d.mark)
const leaf = Leaf.create({ marks })
return List([leaf])
if (text === '') {
const decMarks = decorations.map(d => d.mark)
const l = Leaf.create({ marks: decMarks })
return List([l])
}
const { key, text } = this
decorations.forEach(dec => {
const { start, end, mark } = dec
const hasStart = start.key === key
@@ -276,274 +186,49 @@ class Text extends Record(DEFAULTS) {
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`.
*
* @param {Numbder} offset
* @param {String} text
* @param {Set} marks (optional)
* @param {Number} index
* @param {String} string
* @return {Text}
*/
insertText(offset, text, marks) {
if (this.text === '') {
return this.set('leaves', List.of(Leaf.create({ text, marks })))
}
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)
insertText(index, string) {
const { text } = this
const next = text.slice(0, index) + string + text.slice(index)
const node = this.set('text', next)
return node
}
/**
* Remove a `mark` at `index` and `length`.
* Remove a `mark`.
*
* @param {Number} index
* @param {Number} length
* @param {Mark} mark
* @return {Text}
*/
removeMark(index, length, mark) {
if (this.text === '' && index === 0 && length === 0) {
const first = this.leaves.first()
if (!first) return this
const newFirst = first.removeMark(mark)
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.removeMark(mark)), after)
return this.setLeaves(leaves)
removeMark(mark) {
mark = Mark.create(mark)
const { marks } = this
const next = marks.remove(mark)
const node = this.set('marks', next)
return node
}
/**
* 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
* @return {Text}
*/
removeText(start, length) {
if (length <= 0) return this
if (start >= this.text.length) return this
// PERF: For simple backspace, we can operate directly on the leaf
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)
removeText(index, length) {
const { text } = this
const next = text.slice(0, index) + text.slice(index + length)
const node = this.set('text', next)
return node
}
/**
@@ -556,9 +241,8 @@ class Text extends Record(DEFAULTS) {
toJSON(options = {}) {
const object = {
object: this.object,
leaves: this.getLeaves()
.toArray()
.map(r => r.toJSON()),
text: this.text,
marks: this.marks.toArray().map(m => m.toJSON()),
}
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 {Number} length
* @param {Object} properties
* @param {Object} mark
* @param {Object} newProperties
* @return {Text}
*/
updateMark(index, length, properties, newProperties) {
setMark(properties, newProperties) {
const { marks } = this
const mark = Mark.create(properties)
const newMark = mark.merge(newProperties)
if (this.text === '' && length === 0 && index === 0) {
const { leaves } = this
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)
const next = marks.remove(mark).add(newMark)
const node = this.set('marks', next)
return node
}
/**
* Split this text and return two different texts
* @param {Number} position
* Split the node into two at `index`.
*
* @param {Number} index
* @returns {Array<Text>}
*/
splitText(offset) {
const splitted = Leaf.splitLeaves(this.leaves, offset)
const one = this.set('leaves', splitted[0])
const two = this.set('leaves', splitted[1]).regenerateKey()
splitText(index) {
const { text } = this
const one = this.set('text', text.slice(0, index))
const two = this.set('text', text.slice(index)).regenerateKey()
return [one, two]
}
/**
* merge this text and another text at the end
* @param {Text} text
* @returns {Text}
*/
mergeText(text) {
const leaves = this.leaves.concat(text.leaves)
return this.setLeaves(leaves)
}
/**
* Set leaves with normalized `leaves`
* Merge the node with an `other` text node.
*
* @param {List} leaves
* @param {Text} other
* @returns {Text}
*/
setLeaves(leaves) {
leaves = Leaf.createLeaves(leaves)
if (leaves.size === 1) {
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)
mergeText(other) {
const next = this.text + other.text
const node = this.set('text', next)
return node
}
}
/**
* Memoize read methods.
*/
memoize(Text.prototype, ['getActiveMarks', 'getMarks', 'getMarksAsArray'])
/**
* Export.
*

View File

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

View File

@@ -1,7 +1,6 @@
import Debug from 'debug'
import Operation from '../models/operation'
import PathUtils from '../utils/path-utils'
/**
* Debug.
@@ -26,8 +25,8 @@ function applyOperation(value, op) {
switch (type) {
case 'add_mark': {
const { path, offset, length, mark } = op
const next = value.addMark(path, offset, length, mark)
const { path, mark } = op
const next = value.addMark(path, mark)
return next
}
@@ -51,18 +50,13 @@ function applyOperation(value, op) {
case 'move_node': {
const { path, newPath } = op
if (PathUtils.isEqual(path, newPath)) {
return value
}
const next = value.moveNode(path, newPath)
return next
}
case 'remove_mark': {
const { path, offset, length, mark } = op
const next = value.removeMark(path, offset, length, mark)
const { path, mark } = op
const next = value.removeMark(path, mark)
return next
}
@@ -79,14 +73,8 @@ function applyOperation(value, op) {
}
case 'set_mark': {
const { path, offset, length, properties, newProperties } = op
const next = value.setMark(
path,
offset,
length,
properties,
newProperties
)
const { path, properties, newProperties } = op
const next = value.setMark(path, properties, newProperties)
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' },
next: [{ object: 'block' }, { object: 'inline' }],
next: (next, match) => {
return next.object !== 'text' || !match.marks.equals(next.marks)
},
normalize: (editor, error) => {
const { code, next } = error
if (code === 'next_sibling_object_invalid') {
if (code === 'next_sibling_invalid') {
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 = {}) {
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)) {
const array = rule.length ? rule : [{}]
@@ -306,7 +311,9 @@ function validateData(node, rule) {
function validateMarks(node, rule) {
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) {
const valid = rule.marks.some(
@@ -569,7 +576,7 @@ function validateNext(node, child, next, index, rules) {
if (rule.next == null) continue
if (!testRules(child, rule.match)) continue
const error = validateRules(next, rule.next)
const error = validateRules(next, rule.next, [], { match: child })
if (!error) continue
error.rule = rule

View File

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

View File

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

View File

@@ -3,15 +3,14 @@
import h from '../../../helpers/h'
export default function(editor) {
editor.replaceMark('italic', 'bold').insertText('a')
editor.addMark('bold').insertText('a')
}
export const input = (
<value>
<document>
<paragraph>
<i />
<cursor />word
word<cursor />
</paragraph>
</document>
</value>
@@ -21,8 +20,9 @@ export const output = (
<value>
<document>
<paragraph>
<b>a</b>
<cursor />word
word<b>
a<cursor />
</b>
</paragraph>
</document>
</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>
<document>
<paragraph>
<anchor />
<i>
wo<focus />rd
<anchor />wo<focus />rd
</i>
</paragraph>
</document>
@@ -23,12 +22,14 @@ export const output = (
<value>
<document>
<paragraph>
<anchor />
<b>
<i>wo</i>
<i>
<anchor />wo
</i>
</b>
<focus />
<i>rd</i>
<i>
<focus />rd
</i>
</paragraph>
</document>
</value>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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