mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-20 14:11:35 +02:00
refactor more transforms
This commit is contained in:
@@ -12,6 +12,7 @@ import './inline'
|
|||||||
|
|
||||||
import Block from './block'
|
import Block from './block'
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
|
import uid from '../utils/uid'
|
||||||
import { OrderedMap, Record } from 'immutable'
|
import { OrderedMap, Record } from 'immutable'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +20,8 @@ import { OrderedMap, Record } from 'immutable'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
nodes: new OrderedMap()
|
key: null,
|
||||||
|
nodes: new OrderedMap(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +39,10 @@ class Document extends new Record(DEFAULTS) {
|
|||||||
|
|
||||||
static create(properties = {}) {
|
static create(properties = {}) {
|
||||||
if (properties instanceof Document) return properties
|
if (properties instanceof Document) return properties
|
||||||
|
|
||||||
|
properties.key = properties.key || uid(4)
|
||||||
properties.nodes = Block.createList(properties.nodes)
|
properties.nodes = Block.createList(properties.nodes)
|
||||||
|
|
||||||
return new Document(properties).normalize()
|
return new Document(properties).normalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -765,6 +765,11 @@ const Node = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getPath(key) {
|
getPath(key) {
|
||||||
|
debugger
|
||||||
|
key = Normalize.key(key)
|
||||||
|
|
||||||
|
if (key == this.key) return []
|
||||||
|
|
||||||
let child = this.assertDescendant(key)
|
let child = this.assertDescendant(key)
|
||||||
let path = []
|
let path = []
|
||||||
let parent
|
let parent
|
||||||
|
@@ -274,72 +274,44 @@ export function deleteForwardAtRange(transform, range, n = 1) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function insertBlockAtRange(transform, range, block) {
|
export function insertBlockAtRange(transform, range, block) {
|
||||||
let { state } = transform
|
|
||||||
let { document } = state
|
|
||||||
|
|
||||||
// Normalize the block argument.
|
|
||||||
block = Normalize.block(block)
|
block = Normalize.block(block)
|
||||||
|
|
||||||
// If expanded, delete the range first.
|
|
||||||
if (range.isExpanded) {
|
if (range.isExpanded) {
|
||||||
transform = deleteAtRange(transform, range)
|
transform.deleteAtRange(range)
|
||||||
state = transform.state
|
|
||||||
document = state.document
|
|
||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { state } = transform
|
||||||
|
const { document } = state
|
||||||
const { startKey, startOffset } = range
|
const { startKey, startOffset } = range
|
||||||
let startBlock = document.getClosestBlock(startKey)
|
const startText = document.assertDescendant(startKey)
|
||||||
let parent = document.getParent(startBlock)
|
const startBlock = document.getClosestBlock(startKey)
|
||||||
let nodes = Block.createList([block])
|
const parent = document.getParent(startBlock)
|
||||||
const isParent = parent == document
|
const index = parent.nodes.indexOf(startBlock)
|
||||||
|
|
||||||
// If the start block is void, insert after it.
|
|
||||||
if (startBlock.isVoid) {
|
if (startBlock.isVoid) {
|
||||||
parent = parent.insertChildrenAfter(startBlock, nodes)
|
transform.insertNodeByKey(parent.key, index + 1, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the block is empty, replace it.
|
|
||||||
else if (startBlock.isEmpty) {
|
else if (startBlock.isEmpty) {
|
||||||
parent = parent.insertChildrenAfter(startBlock, nodes)
|
transform.removeNodeByKey(startBlock.key)
|
||||||
parent = parent.removeDescendant(startBlock)
|
transform.insertNodeByKey(parent.key, index, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the range is at the start of the block, insert before.
|
|
||||||
else if (range.isAtStartOf(startBlock)) {
|
else if (range.isAtStartOf(startBlock)) {
|
||||||
parent = parent.insertChildrenBefore(startBlock, nodes)
|
transform.insertNodeByKey(parent.key, index, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the range is at the end of the block, insert after.
|
|
||||||
else if (range.isAtEndOf(startBlock)) {
|
else if (range.isAtEndOf(startBlock)) {
|
||||||
parent = parent.insertChildrenAfter(startBlock, nodes)
|
transform.insertNodeByKey(parent.key, index + 1, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, split the block and insert between.
|
|
||||||
else {
|
else {
|
||||||
transform = splitBlockAtRange(transform, range)
|
const offset = startBlock.getOffset(startText) + startOffset
|
||||||
state = transform.state
|
transform.splitNodeByKey(startBlock.key, offset)
|
||||||
document = state.document
|
transform.insertNodeByKey(parent.key, index + 1, block)
|
||||||
parent = document.getParent(startBlock)
|
|
||||||
startBlock = document.getClosestBlock(startKey)
|
|
||||||
nodes = parent.nodes.takeUntil(n => n == startBlock)
|
|
||||||
.push(startBlock)
|
|
||||||
.push(block)
|
|
||||||
.concat(parent.nodes.skipUntil(n => n == startBlock).rest())
|
|
||||||
parent = parent.merge({ nodes })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the document.
|
|
||||||
document = isParent
|
|
||||||
? parent
|
|
||||||
: document.updateDescendant(parent)
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
document = document.normalize()
|
|
||||||
|
|
||||||
// Return the updated state.
|
|
||||||
state = state.merge({ document })
|
|
||||||
transform.state = state
|
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,47 +439,26 @@ export function insertFragmentAtRange(transform, range, fragment) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function insertInlineAtRange(transform, range, inline) {
|
export function insertInlineAtRange(transform, range, inline) {
|
||||||
let { state } = transform
|
|
||||||
let { document } = state
|
|
||||||
|
|
||||||
// Normalize the inline argument.
|
|
||||||
inline = Normalize.inline(inline)
|
inline = Normalize.inline(inline)
|
||||||
|
|
||||||
// If expanded, delete the range first.
|
|
||||||
if (range.isExpanded) {
|
if (range.isExpanded) {
|
||||||
transform = deleteAtRange(transform, range)
|
transform.deleteAtRange(range)
|
||||||
state = transform.state
|
|
||||||
document = state.document
|
|
||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { startKey, endKey, startOffset, endOffset } = range
|
const { state } = transform
|
||||||
|
const { document } = state
|
||||||
|
const { startKey, startOffset } = range
|
||||||
|
const parent = document.getParent(startKey)
|
||||||
|
const startText = document.assertDescendant(startKey)
|
||||||
|
const index = parent.nodes.indexOf(startText)
|
||||||
|
|
||||||
// If the range is inside a void, abort.
|
if (parent.isVoid) {
|
||||||
const startBlock = document.getClosestBlock(startKey)
|
return transform
|
||||||
if (startBlock && startBlock.isVoid) return transform
|
}
|
||||||
|
|
||||||
const startInline = document.getClosestInline(startKey)
|
transform.splitNodeByKey(startKey, startOffset)
|
||||||
if (startInline && startInline.isVoid) return transform
|
transform.insertNodeByKey(parent.key, index + 1, inline)
|
||||||
|
|
||||||
// Split the text nodes at the cursor.
|
|
||||||
transform = splitTextAtRange(transform, range)
|
|
||||||
state = transform.state
|
|
||||||
document = state.document
|
|
||||||
|
|
||||||
// Insert the inline between the split text nodes.
|
|
||||||
const startText = document.getDescendant(startKey)
|
|
||||||
let parent = document.getParent(startKey)
|
|
||||||
const nodes = parent.nodes.takeUntil(n => n == startText)
|
|
||||||
.push(startText)
|
|
||||||
.push(inline)
|
|
||||||
.concat(parent.nodes.skipUntil(n => n == startText).rest())
|
|
||||||
|
|
||||||
parent = parent.merge({ nodes })
|
|
||||||
document = document.updateDescendant(parent)
|
|
||||||
document = document.normalize()
|
|
||||||
state = state.merge({ document })
|
|
||||||
transform.state = state
|
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,9 +476,9 @@ export function insertTextAtRange(transform, range, text, marks) {
|
|||||||
const { state } = transform
|
const { state } = transform
|
||||||
const { document } = state
|
const { document } = state
|
||||||
const { startKey, startOffset } = range
|
const { startKey, startOffset } = range
|
||||||
const isVoid = document.hasVoidParent(startKey)
|
const parent = document.getParent(startKey)
|
||||||
|
|
||||||
if (isVoid) {
|
if (parent.isVoid) {
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,51 +632,6 @@ export function splitInlineAtRange(transform, range, height = Infinity) {
|
|||||||
return transform.splitNodeByKey(node.key, offset)
|
return transform.splitNodeByKey(node.key, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Split the text nodes at a `range`.
|
|
||||||
*
|
|
||||||
* @param {Transform} transform
|
|
||||||
* @param {Selection} range
|
|
||||||
* @return {Transform}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function splitTextAtRange(transform, range) {
|
|
||||||
let { state } = transform
|
|
||||||
let { document } = state
|
|
||||||
|
|
||||||
// If the range is expanded, remove it first.
|
|
||||||
if (range.isExpanded) {
|
|
||||||
transform = deleteAtRange(transform, range)
|
|
||||||
state = transform.state
|
|
||||||
document = state.document
|
|
||||||
range = range.collapseToStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the text node's characters.
|
|
||||||
const { startKey, startOffset } = range
|
|
||||||
const text = document.getDescendant(startKey)
|
|
||||||
const { characters } = text
|
|
||||||
const firstChars = characters.take(startOffset)
|
|
||||||
const secondChars = characters.skip(startOffset)
|
|
||||||
let firstChild = text.merge({ characters: firstChars })
|
|
||||||
let secondChild = Text.create({ characters: secondChars })
|
|
||||||
|
|
||||||
// Split the text nodes.
|
|
||||||
let parent = document.getParent(text)
|
|
||||||
const nodes = parent.nodes
|
|
||||||
.takeUntil(c => c.key == firstChild.key)
|
|
||||||
.push(firstChild)
|
|
||||||
.push(secondChild)
|
|
||||||
.concat(parent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
|
||||||
|
|
||||||
// Update the nodes.
|
|
||||||
parent = parent.merge({ nodes })
|
|
||||||
document = document.updateDescendant(parent)
|
|
||||||
state = state.merge({ document })
|
|
||||||
transform.state = state
|
|
||||||
return transform
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add or remove a `mark` from the characters at `range`, depending on whether
|
* Add or remove a `mark` from the characters at `range`, depending on whether
|
||||||
* it's already there.
|
* it's already there.
|
||||||
@@ -1048,12 +954,11 @@ export function wrapInlineAtRange(transform, range, properties) {
|
|||||||
* @param {Transform} transform
|
* @param {Transform} transform
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {String} prefix
|
* @param {String} prefix
|
||||||
* @param {String} suffix
|
* @param {String} suffix (optional)
|
||||||
* @return {Transform}
|
* @return {Transform}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function wrapTextAtRange(transform, range, prefix, suffix = prefix) {
|
export function wrapTextAtRange(transform, range, prefix, suffix = prefix) {
|
||||||
const { state } = transform
|
|
||||||
const { startKey, endKey } = range
|
const { startKey, endKey } = range
|
||||||
const start = range.collapseToStart()
|
const start = range.collapseToStart()
|
||||||
let end = range.collapseToEnd()
|
let end = range.collapseToEnd()
|
||||||
|
@@ -50,12 +50,13 @@ export function addMarkByKey(transform, key, offset, length, mark) {
|
|||||||
export function insertNodeByKey(transform, key, index, node) {
|
export function insertNodeByKey(transform, key, index, node) {
|
||||||
let { state } = transform
|
let { state } = transform
|
||||||
let { document } = state
|
let { document } = state
|
||||||
let parent = document.assertDescendant(key)
|
let parent = document.key == key ? document : document.assertDescendant(key)
|
||||||
|
const isParent = document == parent
|
||||||
const path = document.getPath(parent)
|
const path = document.getPath(parent)
|
||||||
const nodes = parent.nodes.splice(index + 1, 0, node)
|
const nodes = parent.nodes.splice(index, 0, node)
|
||||||
|
|
||||||
parent = parent.merge({ nodes })
|
parent = parent.merge({ nodes })
|
||||||
document = document.updateDescendant(parent)
|
document = isParent ? parent : document.updateDescendant(parent)
|
||||||
document = document.normalize()
|
document = document.normalize()
|
||||||
state = state.merge({ document })
|
state = state.merge({ document })
|
||||||
|
|
||||||
|
@@ -57,7 +57,7 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
addMarkByKey,
|
addMarkByKey,
|
||||||
insertNodeAfterNodeByKey,
|
insertNodeByKey,
|
||||||
insertTextByKey,
|
insertTextByKey,
|
||||||
moveNodeByKey,
|
moveNodeByKey,
|
||||||
removeMarkByKey,
|
removeMarkByKey,
|
||||||
@@ -170,7 +170,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
addMarkByKey,
|
addMarkByKey,
|
||||||
insertNodeAfterNodeByKey,
|
insertNodeByKey,
|
||||||
insertTextByKey,
|
insertTextByKey,
|
||||||
moveNodeByKey,
|
moveNodeByKey,
|
||||||
removeMarkByKey,
|
removeMarkByKey,
|
||||||
|
32
lib/transforms/normalize.js
Normal file
32
lib/transforms/normalize.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the document.
|
||||||
|
*
|
||||||
|
* @param {Transform} transform
|
||||||
|
* @return {Transform}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function normalizeDocument(transform) {
|
||||||
|
let { state } = transform
|
||||||
|
let { document } = state
|
||||||
|
document = document.normalize()
|
||||||
|
state = state.merge({ document })
|
||||||
|
transform.state = state
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the selection.
|
||||||
|
*
|
||||||
|
* @param {Transform} transform
|
||||||
|
* @return {Transform}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function normalizeSelection(transform) {
|
||||||
|
let { state } = transform
|
||||||
|
let { document, selection } = state
|
||||||
|
selection = selection.normalize(document)
|
||||||
|
state = state.merge({ selection })
|
||||||
|
transform.state = state
|
||||||
|
return transform
|
||||||
|
}
|
@@ -60,12 +60,10 @@ function inline(value) {
|
|||||||
|
|
||||||
function key(value) {
|
function key(value) {
|
||||||
if (value instanceof Block) return value.key
|
if (value instanceof Block) return value.key
|
||||||
|
if (value instanceof Document) return value.key
|
||||||
if (value instanceof Inline) return value.key
|
if (value instanceof Inline) return value.key
|
||||||
if (value instanceof Text) return value.key
|
if (value instanceof Text) return value.key
|
||||||
|
|
||||||
// Special-case document for now.
|
|
||||||
if (value instanceof Document) return
|
|
||||||
|
|
||||||
const type = typeOf(value)
|
const type = typeOf(value)
|
||||||
if (type == 'string') return value
|
if (type == 'string') return value
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user