mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 12:41:44 +02:00
fixing lots of marks logic
This commit is contained in:
@@ -21,7 +21,7 @@ p {
|
||||
|
||||
.menu {
|
||||
margin: 0 -10px;
|
||||
padding: 1px 0 9px 7px;
|
||||
padding: 1px 0 9px 8px;
|
||||
border-bottom: 2px solid #eee;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
|
||||
import Editor from '../..'
|
||||
import Editor, { Mark, Raw } from '../..'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Raw } from '../..'
|
||||
|
||||
/**
|
||||
* State.
|
||||
@@ -72,13 +71,7 @@ class App extends React.Component {
|
||||
isMarkActive(type) {
|
||||
const { state } = this.state
|
||||
const { document, selection } = state
|
||||
const { startKey, startOffset } = selection
|
||||
const startNode = document.getNode(startKey)
|
||||
if (!startNode) return false
|
||||
|
||||
const { characters } = startNode
|
||||
const character = characters.get(startOffset)
|
||||
const { marks } = character
|
||||
const marks = document.getMarksAtRange(selection)
|
||||
return marks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
@@ -88,13 +81,14 @@ class App extends React.Component {
|
||||
let { state } = this.state
|
||||
const { marks } = state
|
||||
const isActive = this.isMarkActive(type)
|
||||
const mark = Mark.create({ type })
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
[isActive ? 'unmark' : 'mark']()
|
||||
[isActive ? 'unmark' : 'mark'](mark)
|
||||
.apply()
|
||||
|
||||
this.onChange(state)
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@@ -13,6 +13,7 @@ export default Editor
|
||||
export { default as Character } from './models/character'
|
||||
export { default as Element } from './models/element'
|
||||
export { default as Document } from './models/document'
|
||||
export { default as Mark } from './models/mark'
|
||||
export { default as Selection } from './models/selection'
|
||||
export { default as State } from './models/state'
|
||||
export { default as Text } from './models/text'
|
||||
|
@@ -1,12 +1,12 @@
|
||||
|
||||
import { List, Record } from 'immutable'
|
||||
import { List, Record, Set } from 'immutable'
|
||||
|
||||
/**
|
||||
* Record.
|
||||
*/
|
||||
|
||||
const CharacterRecord = new Record({
|
||||
marks: new List(),
|
||||
marks: new Set(),
|
||||
text: ''
|
||||
})
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
|
||||
import { List, Map, Record } from 'immutable'
|
||||
import { Map, Record, Set } from 'immutable'
|
||||
|
||||
/**
|
||||
* Record.
|
||||
@@ -29,14 +29,14 @@ class Mark extends MarkRecord {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a marks list from an array of marks.
|
||||
* Create a marks set from an array of marks.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @return {List} marks
|
||||
* @return {Set} marks
|
||||
*/
|
||||
|
||||
static createList(array = []) {
|
||||
return new List(array)
|
||||
static createSet(array = []) {
|
||||
return new Set(array)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import Character from './character'
|
||||
import Element from './element'
|
||||
import Selection from './selection'
|
||||
import Text from './text'
|
||||
import { OrderedMap } from 'immutable'
|
||||
import { List, OrderedMap, Set } from 'immutable'
|
||||
|
||||
/**
|
||||
* Node.
|
||||
@@ -14,6 +14,16 @@ import { OrderedMap } from 'immutable'
|
||||
|
||||
const Node = {
|
||||
|
||||
/**
|
||||
* Assert that the node has a child by `key`.
|
||||
*
|
||||
* @param {String or Node} key
|
||||
*/
|
||||
|
||||
assertHasNode(key) {
|
||||
if (!this.hasNode(key)) throw new Error('Could not find that child node.')
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete everything in a `range`.
|
||||
*
|
||||
@@ -29,8 +39,8 @@ const Node = {
|
||||
|
||||
// Make sure the children exist.
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
if (!node.hasNode(startKey)) throw new Error('Could not find that start node.')
|
||||
if (!node.hasNode(endKey)) throw new Error('Could not find that end node.')
|
||||
node.assertHasNode(startKey)
|
||||
node.assertHasNode(endKey)
|
||||
|
||||
let startNode = node.getNode(startKey)
|
||||
|
||||
@@ -212,6 +222,66 @@ const Node = {
|
||||
return deep
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of the characters in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @return {List} characters
|
||||
*/
|
||||
|
||||
getCharactersAtRange(range) {
|
||||
const texts = this.getTextNodesAtRange(range)
|
||||
let list = new List()
|
||||
|
||||
texts.forEach((text) => {
|
||||
let { characters } = text
|
||||
characters = characters.filter((char, i) => isInRange(i, text, range))
|
||||
list = list.concat(characters)
|
||||
})
|
||||
|
||||
return list
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a set of the marks in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @return {Set} marks
|
||||
*/
|
||||
|
||||
getMarksAtRange(range) {
|
||||
const { startKey, startOffset, endKey } = range
|
||||
|
||||
// If the selection isn't set, return nothing.
|
||||
if (startKey == null || endKey == null) return new Set()
|
||||
|
||||
// If the range is collapsed, and at the start of the node, check the
|
||||
// previous text node.
|
||||
if (range.isCollapsed && startOffset == 0) {
|
||||
const previous = this.getPreviousTextNode(startKey)
|
||||
if (!previous) return new Set()
|
||||
const char = text.characters.get(previous.length - 1)
|
||||
return char.marks
|
||||
}
|
||||
|
||||
// If the range is collapsed, check the character before the start.
|
||||
if (range.isCollapsed) {
|
||||
const text = this.getNode(startKey)
|
||||
const char = text.characters.get(range.startOffset - 1)
|
||||
return char.marks
|
||||
}
|
||||
|
||||
// Otherwise, get a set of the marks for each character in the range.
|
||||
const characters = this.getCharactersAtRange(range)
|
||||
let set = new Set()
|
||||
|
||||
characters.forEach((char) => {
|
||||
set = set.union(char.marks)
|
||||
})
|
||||
|
||||
return set
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a child node by `key`.
|
||||
*
|
||||
@@ -220,9 +290,41 @@ const Node = {
|
||||
*/
|
||||
|
||||
getNode(key) {
|
||||
key = normalizeKey(key)
|
||||
return this.findNode(node => node.key == key) || null
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the child text node at an `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeOffset(key) {
|
||||
this.assertHasNode(key)
|
||||
const match = this.getNode(key)
|
||||
|
||||
// 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'
|
||||
? false
|
||||
: node.hasNode(match)
|
||||
})
|
||||
|
||||
const befores = this.nodes.takeUntil(node => node.key == child.key)
|
||||
|
||||
// Calculate the offset of the nodes before the matching child.
|
||||
const offset = befores.map(child => child.length)
|
||||
|
||||
// If the child's parent is this node, return the offset of all of the nodes
|
||||
// before it, otherwise recurse.
|
||||
return this.nodes.has(match.key)
|
||||
? offset
|
||||
: offset + child.getNodeOffset(key)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the child node after the one by `key`.
|
||||
*
|
||||
@@ -231,9 +333,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
getNextNode(key) {
|
||||
if (typeof key != 'string') {
|
||||
key = key.key
|
||||
}
|
||||
key = normalizeKey(key)
|
||||
|
||||
const shallow = this.nodes
|
||||
.skipUntil(node => node.key == key)
|
||||
@@ -256,9 +356,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
getPreviousNode(key) {
|
||||
if (typeof key != 'string') {
|
||||
key = key.key
|
||||
}
|
||||
key = normalizeKey(key)
|
||||
|
||||
const matches = this.nodes.get(key)
|
||||
|
||||
@@ -274,6 +372,30 @@ const Node = {
|
||||
.first()
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the previous text node by `key`.
|
||||
*
|
||||
* @param {String or Node} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getPreviousTextNode(key) {
|
||||
key = normalizeKey(key)
|
||||
|
||||
// Create a new selection starting at the first text node.
|
||||
const first = this.findNode(node => node.type == 'text')
|
||||
const range = Selection.create({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const texts = this.getTextNodesAtRange()
|
||||
const previous = texts.get(text.size - 2)
|
||||
return previous
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the parent of a child node by `key`.
|
||||
*
|
||||
@@ -282,9 +404,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
getParentNode(key) {
|
||||
if (typeof key != 'string') {
|
||||
key = key.key
|
||||
}
|
||||
key = normalizeKey(key)
|
||||
|
||||
if (this.nodes.get(key)) return this
|
||||
let node = null
|
||||
@@ -299,13 +419,13 @@ const Node = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the child text node at `offset`.
|
||||
* Get the child text node at an `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeAtOffset(offset) {
|
||||
getTextNodeAtOffset(offset) {
|
||||
let match = null
|
||||
let i
|
||||
|
||||
@@ -318,6 +438,81 @@ const Node = {
|
||||
return match
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the child text nodes after an `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {OrderedMap} matches
|
||||
*/
|
||||
|
||||
getTextNodesAfterOffset(offset) {
|
||||
let matches = new OrderedMap()
|
||||
let i
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
if (child.length <= offset + i) return
|
||||
|
||||
matches = child.type == 'text'
|
||||
? matches.set(child.key, child)
|
||||
: matches.concat(child.getTextNodesAfterOffset(offset - i))
|
||||
|
||||
i += child.length
|
||||
})
|
||||
|
||||
return matches
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the child text nodes before an `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {OrderedMap} matches
|
||||
*/
|
||||
|
||||
getTextNodesBeforeOffset(offset) {
|
||||
let matches = new OrderedMap()
|
||||
let i
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
if (child.length > offset + i) return
|
||||
|
||||
matches = child.type == 'text'
|
||||
? matches.set(child.key, child)
|
||||
: matches.concat(child.getTextNodesBeforeOffset(offset - i))
|
||||
|
||||
i += child.length
|
||||
})
|
||||
|
||||
return matches
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the text nodes in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @return {OrderedMap} nodes
|
||||
*/
|
||||
|
||||
getTextNodesAtRange(range) {
|
||||
const { startKey, endKey } = range
|
||||
if (startKey == null || endKey == null) return new OrderedMap()
|
||||
|
||||
this.assertHasNode(startKey)
|
||||
this.assertHasNode(endKey)
|
||||
|
||||
// Convert the start and end nodes to offsets.
|
||||
const startNode = this.getNode(startKey)
|
||||
const endNode = this.getNode(endKey)
|
||||
const startOffset = this.getNodeOffset(startNode)
|
||||
const endOffset = this.getNodeOffset(endNode)
|
||||
|
||||
// Return the text nodes after the start offset and before the end offset.
|
||||
const afterStart = this.getTextNodesAfterOffset(startOffset)
|
||||
const beforeEnd = this.getTextNodesBeforeOffset(endOffset)
|
||||
const between = afterStart.filter(node => beforeEnd.includes(node))
|
||||
return between
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively check if a child node exists by `key`.
|
||||
*
|
||||
@@ -326,9 +521,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
hasNode(key) {
|
||||
if (typeof key != 'string') {
|
||||
key = key.key
|
||||
}
|
||||
key = normalizeKey(key)
|
||||
|
||||
const shallow = this.nodes.has(key)
|
||||
if (shallow) return true
|
||||
@@ -346,7 +539,7 @@ const Node = {
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {String} text
|
||||
* @return {Document} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
insertTextAtRange(range, text) {
|
||||
@@ -362,9 +555,11 @@ const Node = {
|
||||
let startNode = node.getNode(startKey)
|
||||
let { characters } = startNode
|
||||
|
||||
// Create a list of the new characters, with the right marks.
|
||||
const marks = characters.has(startOffset)
|
||||
? characters.get(startOffset).marks
|
||||
// Create a list of the new characters, with the marks from the previous
|
||||
// character if one exists.
|
||||
const prevOffset = startOffset - 1
|
||||
const marks = characters.has(prevOffset)
|
||||
? characters.get(prevOffset).marks
|
||||
: null
|
||||
|
||||
const newCharacters = text.split('').reduce((list, char) => {
|
||||
@@ -387,6 +582,44 @@ const Node = {
|
||||
return node.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark} mark
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
markAtRange(range, mark) {
|
||||
let node = this
|
||||
|
||||
// When the range is collapsed, do nothing.
|
||||
if (range.isCollapsed) return node
|
||||
|
||||
// Otherwise, find each of the text nodes within the range.
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
let texts = node.getTextNodesAtRange(range)
|
||||
|
||||
// Apply the mark to each of the text nodes's matching characters.
|
||||
texts = texts.map((text) => {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = marks.add(mark)
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
return text.merge({ characters })
|
||||
})
|
||||
|
||||
// Update each of the text nodes.
|
||||
texts.forEach((text) => {
|
||||
node = node.updateNode(text)
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize the node, joining any two adjacent text child nodes.
|
||||
*
|
||||
@@ -430,14 +663,14 @@ const Node = {
|
||||
* Push a new `node` onto the map of nodes.
|
||||
*
|
||||
* @param {String or Node} key
|
||||
* @param {Node} node
|
||||
* @param {Node} node (optional)
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
pushNode(key, node) {
|
||||
if (typeof key != 'string') {
|
||||
if (arguments.length == 1) {
|
||||
node = key
|
||||
key = node.key
|
||||
key = normalizeKey(key)
|
||||
}
|
||||
|
||||
let nodes = this.nodes.set(key, node)
|
||||
@@ -452,9 +685,7 @@ const Node = {
|
||||
*/
|
||||
|
||||
removeNode(key) {
|
||||
if (typeof key != 'string') {
|
||||
key = key.key
|
||||
}
|
||||
key = normalizeKey(key)
|
||||
|
||||
let nodes = this.nodes.remove(key)
|
||||
return this.merge({ nodes })
|
||||
@@ -520,18 +751,55 @@ const Node = {
|
||||
return node.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an existing `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark} mark
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
unmarkAtRange(range, mark) {
|
||||
let node = this
|
||||
|
||||
// When the range is collapsed, do nothing.
|
||||
if (range.isCollapsed) return node
|
||||
|
||||
// Otherwise, find each of the text nodes within the range.
|
||||
let texts = node.getTextNodesAtRange(range)
|
||||
|
||||
// Apply the mark to each of the text nodes's matching characters.
|
||||
texts = texts.map((text) => {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = marks.remove(mark)
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
return text.merge({ characters })
|
||||
})
|
||||
|
||||
// Update each of the text nodes.
|
||||
texts.forEach((text) => {
|
||||
node = node.updateNode(text)
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a new value for a child node by `key`.
|
||||
*
|
||||
* @param {String or Node} key
|
||||
* @param {Node} node
|
||||
* @param {Node} node (optional)
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
updateNode(key, node) {
|
||||
if (typeof key != 'string') {
|
||||
if (arguments.length == 1) {
|
||||
node = key
|
||||
key = node.key
|
||||
key = normalizeKey(key)
|
||||
}
|
||||
|
||||
if (this.nodes.get(key)) {
|
||||
@@ -548,6 +816,42 @@ const Node = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `key`, from a key string or a node.
|
||||
*
|
||||
* @param {String or Node} key
|
||||
* @return {String} key
|
||||
*/
|
||||
|
||||
function normalizeKey(key) {
|
||||
if (typeof key == 'string') return key
|
||||
return key.key
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an `index` of a `text` node is in a `range`.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Text} text
|
||||
* @param {Selection} range
|
||||
* @return {Set} characters
|
||||
*/
|
||||
|
||||
function isInRange(index, text, range) {
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
let matcher
|
||||
|
||||
if (text.key == startKey && text.key == endKey) {
|
||||
return startOffset <= index && index < endOffset
|
||||
} else if (text.key == startKey) {
|
||||
return startOffset <= index
|
||||
} else if (text.key == endKey) {
|
||||
return index < endOffset
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
@@ -17,7 +17,7 @@ const History = new Record({
|
||||
* Default properties.
|
||||
*/
|
||||
|
||||
const DEFAULT_PROPERTIES = {
|
||||
const DEFAULTS = {
|
||||
document: new Document(),
|
||||
selection: new Selection(),
|
||||
history: new History(),
|
||||
@@ -25,10 +25,10 @@ const DEFAULT_PROPERTIES = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Document-like methods, that should be mixed into the `State` prototype.
|
||||
* Node-like methods that should be mixed into the `State` prototype.
|
||||
*/
|
||||
|
||||
const DOCUMENT_LIKE_METHODS = [
|
||||
const NODE_LIKE_METHODS = [
|
||||
'deleteAtRange',
|
||||
'deleteBackwardAtRange',
|
||||
'deleteForwardAtRange',
|
||||
@@ -40,12 +40,12 @@ const DOCUMENT_LIKE_METHODS = [
|
||||
* State.
|
||||
*/
|
||||
|
||||
class State extends Record(DEFAULT_PROPERTIES) {
|
||||
class State extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `State` with `properties`.
|
||||
*
|
||||
* @param {Objetc} properties
|
||||
* @param {Object} properties
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,39 @@ class State extends Record(DEFAULT_PROPERTIES) {
|
||||
return new State(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the characters in the current selection.
|
||||
*
|
||||
* @return {List} characters
|
||||
*/
|
||||
|
||||
get characters() {
|
||||
const { document, selection } = this
|
||||
return document.getCharactersAtRange(selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marks of the current selection.
|
||||
*
|
||||
* @return {Set} marks
|
||||
*/
|
||||
|
||||
get marks() {
|
||||
const { document, selection } = this
|
||||
return document.getMarksAtRange(selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text nodes in the current selection.
|
||||
*
|
||||
* @return {OrderedMap} nodes
|
||||
*/
|
||||
|
||||
get textNodes() {
|
||||
const { document, selection } = this
|
||||
return document.getTextNodesAtRange(selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new `Transform` with the current state as a starting point.
|
||||
*
|
||||
@@ -60,11 +93,12 @@ class State extends Record(DEFAULT_PROPERTIES) {
|
||||
*/
|
||||
|
||||
transform() {
|
||||
return new Transform({ state: this })
|
||||
const state = this
|
||||
return new Transform({ state })
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single character.
|
||||
* Delete at the current selection.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
@@ -145,7 +179,7 @@ class State extends Record(DEFAULT_PROPERTIES) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `text` string at the current cursor position.
|
||||
* Insert a `text` string at the current selection.
|
||||
*
|
||||
* @param {String} text
|
||||
* @return {State} state
|
||||
@@ -163,7 +197,22 @@ class State extends Record(DEFAULT_PROPERTIES) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Split at a the current cursor position.
|
||||
* Add a `mark` to the characters in the current selection.
|
||||
*
|
||||
* @param {Mark} mark
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
mark(mark) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.markAtRange(selection, mark)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the node at the current selection.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
@@ -188,13 +237,28 @@ class State extends Record(DEFAULT_PROPERTIES) {
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a `mark` to the characters in the current selection.
|
||||
*
|
||||
* @param {Mark} mark
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
unmark(mark) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.unmarkAtRange(selection, mark)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix in node-like methods.
|
||||
*/
|
||||
|
||||
DOCUMENT_LIKE_METHODS.forEach((method) => {
|
||||
NODE_LIKE_METHODS.forEach((method) => {
|
||||
State.prototype[method] = function (...args) {
|
||||
let { document } = this
|
||||
document = document[method](...args)
|
||||
|
@@ -44,8 +44,12 @@ const TRANSFORM_TYPES = [
|
||||
'deleteForwardAtRange',
|
||||
'insertText',
|
||||
'insertTextAtRange',
|
||||
'mark',
|
||||
'markAtRange',
|
||||
'split',
|
||||
'splitAtRange'
|
||||
'splitAtRange',
|
||||
'unmark',
|
||||
'unmarkAtRange'
|
||||
]
|
||||
|
||||
/**
|
||||
|
@@ -137,7 +137,7 @@ function deserializeRanges(array) {
|
||||
.map(char => {
|
||||
return Character.create({
|
||||
text: char,
|
||||
marks: Mark.createList(marks.map(deserializeMark))
|
||||
marks: Mark.createSet(marks.map(deserializeMark))
|
||||
})
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user