mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 18:39:51 +02:00
distinguish between block and inline nodes
This commit is contained in:
@@ -186,7 +186,8 @@ class App extends React.Component {
|
||||
onBackspace(e, state) {
|
||||
if (state.isCurrentlyExpanded) return
|
||||
if (state.currentStartOffset != 0) return
|
||||
const node = state.currentWrappingNodes.first()
|
||||
const node = state.currentBlockNodes.first()
|
||||
if (!node) debugger
|
||||
if (node.type == 'paragraph') return
|
||||
|
||||
e.preventDefault()
|
||||
@@ -207,7 +208,8 @@ class App extends React.Component {
|
||||
|
||||
onEnter(e, state) {
|
||||
if (state.isCurrentlyExpanded) return
|
||||
const node = state.currentWrappingNodes.first()
|
||||
const node = state.currentBlockNodes.first()
|
||||
if (!node) debugger
|
||||
if (state.currentStartOffset == 0 && node.length == 0) return this.onBackspace(e, state)
|
||||
if (state.currentEndOffset != node.length) return
|
||||
|
||||
|
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"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:"
|
||||
@@ -14,10 +15,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "block-quote",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "A wise quote."
|
||||
@@ -27,10 +29,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Order when you start a line with \"## \" you get a level-two heading, like this:"
|
||||
@@ -40,10 +43,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "heading-two",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Try it out!"
|
||||
@@ -53,10 +57,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
|
||||
|
@@ -1,5 +1,5 @@
|
||||
|
||||
import Editor, { Character, Document, Element, State, Text } from '../..'
|
||||
import Editor, { Character, Document, Block, State, Text } from '../..'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import state from './state.json'
|
||||
@@ -19,13 +19,13 @@ function deserialize(string) {
|
||||
}, Character.createList())
|
||||
|
||||
const text = Text.create({ characters })
|
||||
const texts = Element.createMap([text])
|
||||
const node = Element.create({
|
||||
const texts = Block.createMap([text])
|
||||
const node = Block.create({
|
||||
type: 'paragraph',
|
||||
nodes: texts,
|
||||
})
|
||||
|
||||
const nodes = Element.createMap([node])
|
||||
const nodes = Block.createMap([node])
|
||||
const document = Document.create({ nodes })
|
||||
const state = State.create({ document })
|
||||
return state
|
||||
|
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "This is editable "
|
||||
@@ -47,10 +48,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Since it's rich text, you can do things like turn a selection of text ",
|
||||
@@ -70,10 +72,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "block-quote",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "A wise quote."
|
||||
@@ -83,10 +86,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
|
@@ -10,9 +10,10 @@ export default Editor
|
||||
* Models.
|
||||
*/
|
||||
|
||||
export { default as Block } from './models/block'
|
||||
export { default as Character } from './models/character'
|
||||
export { default as Element } from './models/element'
|
||||
export { default as Document } from './models/document'
|
||||
export { default as Inline } from './models/inline'
|
||||
export { default as Mark } from './models/mark'
|
||||
export { default as Selection } from './models/selection'
|
||||
export { default as State } from './models/state'
|
||||
|
97
lib/models/block.js
Normal file
97
lib/models/block.js
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
import Node from './node'
|
||||
import uid from 'uid'
|
||||
import { OrderedMap, Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
data: new Map(),
|
||||
key: null,
|
||||
nodes: new OrderedMap(),
|
||||
type: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Block.
|
||||
*/
|
||||
|
||||
class Block extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `Block` with `properties`.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Block} element
|
||||
*/
|
||||
|
||||
static create(properties = {}) {
|
||||
if (!properties.type) throw new Error('You must pass a block `type`.')
|
||||
properties.key = uid(4)
|
||||
let block = new Block(properties)
|
||||
return block.normalize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ordered map of `Blocks` from an array of `Blocks`.
|
||||
*
|
||||
* @param {Array} elements
|
||||
* @return {OrderedMap} map
|
||||
*/
|
||||
|
||||
static createMap(elements = []) {
|
||||
return elements.reduce((map, element) => {
|
||||
return map.set(element.key, element)
|
||||
}, new OrderedMap())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
*
|
||||
* @return {String} kind
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
* @return {Number} length
|
||||
*/
|
||||
|
||||
get length() {
|
||||
return this.text.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concatenated text `string` of all child nodes.
|
||||
*
|
||||
* @return {String} text
|
||||
*/
|
||||
|
||||
get text() {
|
||||
return this.nodes
|
||||
.map(node => node.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix in `Node` methods.
|
||||
*/
|
||||
|
||||
for (const method in Node) {
|
||||
Block.prototype[method] = Node[method]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default Block
|
@@ -7,7 +7,8 @@ import { OrderedMap, Record } from 'immutable'
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
nodes: new OrderedMap()
|
||||
nodes: new OrderedMap(),
|
||||
parent: null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +25,18 @@ class Document extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
static create(properties = {}) {
|
||||
return new Document(properties)
|
||||
let document = new Document(properties)
|
||||
return document.normalize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
*
|
||||
* @return {String} kind
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'document'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,16 +61,6 @@ class Document extends Record(DEFAULTS) {
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Type.
|
||||
*
|
||||
* @return {String} type
|
||||
*/
|
||||
|
||||
get type() {
|
||||
return 'document'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,26 +15,27 @@ const DEFAULTS = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Element.
|
||||
* Inline.
|
||||
*/
|
||||
|
||||
class Element extends Record(DEFAULTS) {
|
||||
class Inline extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `Element` with `properties`.
|
||||
* Create a new `Inline` with `properties`.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Element} element
|
||||
* @return {Inline} element
|
||||
*/
|
||||
|
||||
static create(properties = {}) {
|
||||
if (!properties.type) throw new Error('You must pass an element `type`.')
|
||||
if (!properties.type) throw new Error('You must pass an inline `type`.')
|
||||
properties.key = uid(4)
|
||||
return new Element(properties)
|
||||
let inline = new Inline(properties)
|
||||
return inline.normalize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ordered map of `Elements` from an array of `Elements`.
|
||||
* Create an ordered map of `Inlines` from an array of `Inlines`.
|
||||
*
|
||||
* @param {Array} elements
|
||||
* @return {OrderedMap} map
|
||||
@@ -46,6 +47,16 @@ class Element extends Record(DEFAULTS) {
|
||||
}, new OrderedMap())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
*
|
||||
* @return {String} kind
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'inline'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
@@ -75,7 +86,7 @@ class Element extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
for (const method in Node) {
|
||||
Element.prototype[method] = Node[method]
|
||||
Inline.prototype[method] = Node[method]
|
||||
}
|
||||
|
||||
|
||||
@@ -83,4 +94,4 @@ for (const method in Node) {
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default Element
|
||||
export default Inline
|
@@ -1,6 +1,6 @@
|
||||
|
||||
import Block from './block'
|
||||
import Character from './character'
|
||||
import Element from './element'
|
||||
import Mark from './mark'
|
||||
import Selection from './selection'
|
||||
import Text from './text'
|
||||
@@ -9,8 +9,8 @@ import { List, OrderedMap, OrderedSet, Set } from 'immutable'
|
||||
/**
|
||||
* Node.
|
||||
*
|
||||
* And interface that `Document` and `Element` both implement, to make working
|
||||
* recursively easier with the tree easier.
|
||||
* And interface that `Document`, `Block` and `Inline` all implement, to make
|
||||
* working with the recursive node tree easier.
|
||||
*/
|
||||
|
||||
const Node = {
|
||||
@@ -202,7 +202,7 @@ const Node = {
|
||||
if (shallow != null) return shallow
|
||||
|
||||
return this.nodes
|
||||
.map(node => node.type == 'text' ? null : node.findNode(iterator))
|
||||
.map(node => node.kind == 'text' ? null : node.findNode(iterator))
|
||||
.filter(node => node)
|
||||
.first()
|
||||
},
|
||||
@@ -217,7 +217,7 @@ const Node = {
|
||||
filterNodes(iterator) {
|
||||
const shallow = this.nodes.filter(iterator)
|
||||
const deep = this.nodes
|
||||
.map(node => node.type == 'text' ? null : node.filterNodes(iterator))
|
||||
.map(node => node.kind == 'text' ? null : node.filterNodes(iterator))
|
||||
.filter(node => node)
|
||||
.reduce((all, map) => {
|
||||
return all.concat(map)
|
||||
@@ -254,7 +254,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
getFirstTextNode() {
|
||||
return this.findNode(node => node.type == 'text') || null
|
||||
return this.findNode(node => node.kind == 'text') || null
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -264,7 +264,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
getLastTextNode() {
|
||||
const texts = this.filterNodes(node => node.type == 'text')
|
||||
const texts = this.filterNodes(node => node.kind == 'text')
|
||||
return texts.last() || null
|
||||
},
|
||||
|
||||
@@ -335,7 +335,7 @@ const Node = {
|
||||
// Get all of the nodes that come before the matching child.
|
||||
const child = this.nodes.find((node) => {
|
||||
if (node == match) return true
|
||||
return node.type == 'text'
|
||||
return node.kind == 'text'
|
||||
? false
|
||||
: node.hasNode(match)
|
||||
})
|
||||
@@ -372,7 +372,7 @@ const Node = {
|
||||
if (shallow != null) return shallow
|
||||
|
||||
return this.nodes
|
||||
.map(node => node.type == 'text' ? null : node.getNextNode(key))
|
||||
.map(node => node.kind == 'text' ? null : node.getNextNode(key))
|
||||
.filter(node => node)
|
||||
.first()
|
||||
},
|
||||
@@ -396,7 +396,7 @@ const Node = {
|
||||
}
|
||||
|
||||
return this.nodes
|
||||
.map(node => node.type == 'text' ? null : node.getPreviousNode(key))
|
||||
.map(node => node.kind == 'text' ? null : node.getPreviousNode(key))
|
||||
.filter(node => node)
|
||||
.first()
|
||||
},
|
||||
@@ -412,7 +412,7 @@ const Node = {
|
||||
key = normalizeKey(key)
|
||||
|
||||
// Create a new selection starting at the first text node.
|
||||
const first = this.findNode(node => node.type == 'text')
|
||||
const first = this.findNode(node => node.kind == 'text')
|
||||
const range = Selection.create({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
@@ -439,7 +439,7 @@ const Node = {
|
||||
let node = null
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
if (child.type == 'text') return
|
||||
if (child.kind == 'text') return
|
||||
const match = child.getParentNode(key)
|
||||
if (match) node = match
|
||||
})
|
||||
@@ -455,13 +455,11 @@ const Node = {
|
||||
*/
|
||||
|
||||
getTextNodeAtOffset(offset) {
|
||||
let match = null
|
||||
let i
|
||||
|
||||
this.nodes.forEach((node) => {
|
||||
if (!node.length > offset + i) return
|
||||
match = node.type == 'text' ? node : node.getNodeAtOffset(offset - i)
|
||||
i += node.length
|
||||
let length = 0
|
||||
let texts = this.filterNodes(node => node.kind == 'text')
|
||||
let match = texts.find((node) => {
|
||||
length += node.length
|
||||
return length >= offset
|
||||
})
|
||||
|
||||
return match
|
||||
@@ -477,14 +475,17 @@ const Node = {
|
||||
getTextNodesAtRange(range) {
|
||||
range = range.normalize(this)
|
||||
const { startKey, endKey } = range
|
||||
|
||||
// If the selection isn't formed, return an empty map.
|
||||
if (startKey == null || endKey == null) return new OrderedMap()
|
||||
|
||||
// Assert that the nodes exist before searching.
|
||||
this.assertHasNode(startKey)
|
||||
this.assertHasNode(endKey)
|
||||
|
||||
// Return the text nodes after the start offset and before the end offset.
|
||||
const endNode = this.getNode(endKey)
|
||||
const texts = this.filterNodes(node => node.type == 'text')
|
||||
const texts = this.filterNodes(node => node.kind == 'text')
|
||||
const afterStart = texts.skipUntil(node => node.key == startKey)
|
||||
const upToEnd = afterStart.takeUntil(node => node.key == endKey)
|
||||
let matches = upToEnd.set(endNode.key, endNode)
|
||||
@@ -492,22 +493,51 @@ const Node = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the wrapping nodes in a `range`.
|
||||
* Get the closets block nodes for each text node in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @return {OrderedMap} nodes
|
||||
*/
|
||||
|
||||
getWrappingNodesAtRange(range) {
|
||||
getBlockNodesAtRange(range) {
|
||||
const node = this
|
||||
range = range.normalize(node)
|
||||
|
||||
const texts = node.getTextNodesAtRange(range)
|
||||
const parents = texts.map((text) => {
|
||||
return node.nodes.includes(text) ? node : node.getParentNode(text)
|
||||
})
|
||||
const blocks = texts.map(text => node.getClosestBlockNode(text))
|
||||
return blocks
|
||||
},
|
||||
|
||||
return parents
|
||||
/**
|
||||
* Get the node's closest block parent node.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
getClosestBlockNode(node) {
|
||||
let parent = this.getParentNode(node)
|
||||
|
||||
while (parent && parent.kind != 'block') {
|
||||
parent = this.getParentNode(parent)
|
||||
}
|
||||
|
||||
return parent
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the node's closest inline parent node.
|
||||
*
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
getClosestInlineNode() {
|
||||
let parent = this.getParentNode(node)
|
||||
|
||||
while (parent && parent.kind != 'inline') {
|
||||
parent = this.getParentNode(parent)
|
||||
}
|
||||
|
||||
return parent
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -524,7 +554,7 @@ const Node = {
|
||||
if (shallow) return true
|
||||
|
||||
const deep = this.nodes
|
||||
.map(node => node.type == 'text' ? false : node.hasNode(key))
|
||||
.map(node => node.kind == 'text' ? false : node.hasNode(key))
|
||||
.some(has => has)
|
||||
if (deep) return true
|
||||
|
||||
@@ -625,29 +655,31 @@ const Node = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize the node, joining any two adjacent text child nodes.
|
||||
* Normalize the node by joining any two adjacent text child nodes.
|
||||
*
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
normalize() {
|
||||
let node = this
|
||||
let first = node.findNode((child) => {
|
||||
if (child.type != 'text') return
|
||||
|
||||
// See if there are any adjacent text nodes.
|
||||
let firstAdjacent = node.findNode((child) => {
|
||||
if (child.kind != 'text') return
|
||||
const parent = node.getParentNode(child)
|
||||
const next = parent.getNextNode(child)
|
||||
return next && next.type == 'text'
|
||||
return next && next.kind == 'text'
|
||||
})
|
||||
|
||||
// If no text node was followed by another, do nothing.
|
||||
if (!first) return node
|
||||
// If no text nodes are adjacent, abort.
|
||||
if (!firstAdjacent) return node
|
||||
|
||||
// Otherwise, add the text of the second node to the first...
|
||||
let parent = node.getParentNode(first)
|
||||
const second = parent.getNextNode(first)
|
||||
const characters = first.characters.concat(second.characters)
|
||||
first = first.merge({ characters })
|
||||
parent = parent.updateNode(first)
|
||||
// Fix an adjacent text node if one exists.
|
||||
let parent = node.getParentNode(firstAdjacent)
|
||||
const second = parent.getNextNode(firstAdjacent)
|
||||
const characters = firstAdjacent.characters.concat(second.characters)
|
||||
firstAdjacent = firstAdjacent.merge({ characters })
|
||||
parent = parent.updateNode(firstAdjacent)
|
||||
|
||||
// Then remove the second node.
|
||||
parent = parent.removeNode(second)
|
||||
@@ -659,7 +691,7 @@ const Node = {
|
||||
node = parent
|
||||
}
|
||||
|
||||
// Finally, recurse by normalizing again.
|
||||
// Recurse by normalizing again.
|
||||
return node.normalize()
|
||||
},
|
||||
|
||||
@@ -760,7 +792,7 @@ const Node = {
|
||||
|
||||
// Create a brand new second element with the second set of characters.
|
||||
let secondText = Text.create({})
|
||||
let secondElement = Element.create({
|
||||
let secondElement = Block.create({
|
||||
type: firstElement.type,
|
||||
data: firstElement.data
|
||||
})
|
||||
@@ -852,7 +884,7 @@ const Node = {
|
||||
}
|
||||
|
||||
const nodes = this.nodes.map((child) => {
|
||||
return child.type == 'text' ? child : child.updateNode(key, node)
|
||||
return child.kind == 'text' ? child : child.updateNode(key, node)
|
||||
})
|
||||
|
||||
return this.merge({ nodes })
|
||||
@@ -872,7 +904,7 @@ const Node = {
|
||||
|
||||
// Allow for the parent to by just a type.
|
||||
if (typeof parent == 'string') {
|
||||
parent = Element.create({ type: parent })
|
||||
parent = Block.create({ type: parent })
|
||||
}
|
||||
|
||||
// Add the child to the parent's nodes.
|
||||
|
@@ -109,7 +109,7 @@ class Selection extends SelectionRecord {
|
||||
|
||||
isAtStartOf(node) {
|
||||
const { startKey, startOffset } = this
|
||||
const first = node.type == 'text' ? node : node.getFirstTextNode()
|
||||
const first = node.kind == 'text' ? node : node.getFirstTextNode()
|
||||
return startKey == first.key && startOffset == 0
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class Selection extends SelectionRecord {
|
||||
|
||||
isAtEndOf(node) {
|
||||
const { endKey, endOffset } = this
|
||||
const last = node.type == 'text' ? node : node.getLastTextNode()
|
||||
const last = node.kind == 'text' ? node : node.getLastTextNode()
|
||||
return endKey == last.key && endOffset == last.length
|
||||
}
|
||||
|
||||
@@ -148,8 +148,8 @@ class Selection extends SelectionRecord {
|
||||
let focusNode = node.getNode(focusKey)
|
||||
|
||||
// If the anchor node isn't a text node, match it to one.
|
||||
if (anchorNode.type != 'text') {
|
||||
anchorNode = node.getNodeAtOffset(anchorOffset)
|
||||
if (anchorNode.kind != 'text') {
|
||||
anchorNode = node.getTextNodeAtOffset(anchorOffset)
|
||||
let parent = node.getParentNode(anchorNode)
|
||||
let offset = parent.getNodeOffset(anchorNode)
|
||||
anchorOffset = anchorOffset - offset
|
||||
@@ -157,8 +157,8 @@ class Selection extends SelectionRecord {
|
||||
}
|
||||
|
||||
// If the focus node isn't a text node, match it to one.
|
||||
if (focusNode.type != 'text') {
|
||||
focusNode = node.getNodeAtOffset(focusOffset)
|
||||
if (focusNode.kind != 'text') {
|
||||
focusNode = node.getTextNodeAtOffset(focusOffset)
|
||||
let parent = node.getParentNode(focusNode)
|
||||
let offset = parent.getNodeOffset(focusNode)
|
||||
focusOffset = focusOffset - offset
|
||||
|
@@ -134,13 +134,13 @@ class State extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapping nodes in the current selection.
|
||||
* Get the block nodes in the current selection.
|
||||
*
|
||||
* @return {OrderedMap} nodes
|
||||
*/
|
||||
|
||||
get currentWrappingNodes() {
|
||||
return this.document.getWrappingNodesAtRange(this.selection)
|
||||
get currentBlockNodes() {
|
||||
return this.document.getBlockNodesAtRange(this.selection)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,19 +3,20 @@ import uid from 'uid'
|
||||
import { List, Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* Record.
|
||||
* Default properties.
|
||||
*/
|
||||
|
||||
const TextRecord = new Record({
|
||||
const DEFAULTS = {
|
||||
characters: new List(),
|
||||
key: null
|
||||
})
|
||||
key: null,
|
||||
parent: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Text.
|
||||
*/
|
||||
|
||||
class Text extends TextRecord {
|
||||
class Text extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `Text` with `properties`.
|
||||
@@ -29,6 +30,16 @@ class Text extends TextRecord {
|
||||
return new Text(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
*
|
||||
* @return {String} kind
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'text'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
@@ -51,16 +62,6 @@ class Text extends TextRecord {
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable type to match other nodes.
|
||||
*
|
||||
* @return {String} type
|
||||
*/
|
||||
|
||||
get type() {
|
||||
return 'text'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,10 +1,11 @@
|
||||
|
||||
import Block from '../models/block'
|
||||
import Character from '../models/character'
|
||||
import Document from '../models/document'
|
||||
import Inline from '../models/inline'
|
||||
import Mark from '../models/mark'
|
||||
import Element from '../models/element'
|
||||
import Text from '../models/text'
|
||||
import State from '../models/state'
|
||||
import Text from '../models/text'
|
||||
import groupByMarks from '../utils/group-by-marks'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
@@ -29,7 +30,7 @@ function serialize(state) {
|
||||
*/
|
||||
|
||||
function serializeNode(node) {
|
||||
switch (node.type) {
|
||||
switch (node.kind) {
|
||||
case 'document': {
|
||||
return {
|
||||
nodes: node.nodes.toArray().map(node => serializeNode(node))
|
||||
@@ -37,15 +38,17 @@ function serializeNode(node) {
|
||||
}
|
||||
case 'text': {
|
||||
return {
|
||||
type: 'text',
|
||||
kind: node.kind,
|
||||
ranges: serializeCharacters(node.characters)
|
||||
}
|
||||
}
|
||||
default: {
|
||||
case 'block':
|
||||
case 'inline': {
|
||||
return {
|
||||
type: node.type,
|
||||
data: node.data.toJSON(),
|
||||
nodes: node.nodes.toArray().map(node => serializeNode(node))
|
||||
kind: node.kind,
|
||||
nodes: node.nodes.toArray().map(node => serializeNode(node)),
|
||||
type: node.type
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +96,7 @@ function serializeMark(mark) {
|
||||
function deserialize(object) {
|
||||
return State.create({
|
||||
document: Document.create({
|
||||
nodes: Element.createMap(object.nodes.map(deserializeNode))
|
||||
nodes: Block.createMap(object.nodes.map(deserializeNode))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -106,19 +109,26 @@ function deserialize(object) {
|
||||
*/
|
||||
|
||||
function deserializeNode(object) {
|
||||
switch (object.type) {
|
||||
switch (object.kind) {
|
||||
case 'block': {
|
||||
return Block.create({
|
||||
type: object.type,
|
||||
data: new Map(object.data),
|
||||
nodes: Block.createMap(object.nodes.map(deserializeNode))
|
||||
})
|
||||
}
|
||||
case 'inline': {
|
||||
return Inline.create({
|
||||
type: object.type,
|
||||
data: new Map(object.data),
|
||||
nodes: Inline.createMap(object.nodes.map(deserializeNode))
|
||||
})
|
||||
}
|
||||
case 'text': {
|
||||
return Text.create({
|
||||
characters: deserializeRanges(object.ranges)
|
||||
})
|
||||
}
|
||||
default: {
|
||||
return Element.create({
|
||||
type: object.type,
|
||||
data: new Map(object.data),
|
||||
nodes: Element.createMap(object.nodes.map(deserializeNode))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user