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

refactor core schema to remove last for void nodes

This commit is contained in:
Ian Storm Taylor
2016-11-22 12:57:02 -08:00
parent da85c5ac87
commit 6346ef3aa2
8 changed files with 130 additions and 67 deletions

View File

@@ -1105,6 +1105,17 @@ const Node = {
return !!this.getDescendant(key)
},
/**
* Recursively check if a node exists by `key`.
*
* @param {String} key
* @return {Boolean}
*/
hasNode(key) {
return !!this.getNode(key)
},
/**
* Check if a node has a void parent by `key`.
*
@@ -1480,6 +1491,7 @@ memoize(Node, [
'getNextBlock',
'getNextSibling',
'getNextText',
'getNode',
'getOffset',
'getOffsetAtRange',
'getParent',

View File

@@ -105,6 +105,16 @@ class Selection extends new Record(DEFAULTS) {
return this.isBackward == null ? null : !this.isBackward
}
/**
* Check whether the selection's keys are set.
*
* @return {Boolean}
*/
get isSet() {
return this.anchorKey != null && this.focusKey != null
}
/**
* Check whether the selection's keys are not set.
*
@@ -112,7 +122,7 @@ class Selection extends new Record(DEFAULTS) {
*/
get isUnset() {
return this.anchorKey == null || this.focusKey == null
return !this.isSet
}
/**

View File

@@ -57,6 +57,7 @@ class State extends new Record(DEFAULTS) {
}
const state = new State({ document, selection })
return state.transform({ normalized: false })
.normalize(SCHEMA)
.apply({ save: false })

View File

@@ -190,6 +190,19 @@ class Text extends new Record(DEFAULTS) {
return char.marks
}
/**
* Get a node by `key`, to parallel other nodes.
*
* @param {String} key
* @return {Node|Null}
*/
getNode(key) {
return this.key == key
? this
: null
}
/**
* Derive the ranges for a list of `characters`.
*
@@ -240,6 +253,17 @@ class Text extends new Record(DEFAULTS) {
return ranges
}
/**
* Check if the node has a node by `key`, to parallel other nodes.
*
* @param {String} key
* @return {Boolean}
*/
hasNode(key) {
return !!this.getNode(key)
}
/**
* Insert `text` at `index`.
*

View File

@@ -3,6 +3,14 @@ import Schema from '../models/schema'
import Text from '../models/text'
import { List } from 'immutable'
/**
* Options object with normalize set to `false`.
*
* @type {Object}
*/
const OPTS = { normalize: false }
/**
* Only allow block nodes in documents.
*
@@ -19,7 +27,7 @@ const DOCUMENT_CHILDREN_RULE = {
return invalids.size ? invalids : null
},
normalize: (transform, document, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, { normalize: false }), transform)
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, OPTS), transform)
}
}
@@ -39,7 +47,7 @@ const BLOCK_CHILDREN_RULE = {
return invalids.size ? invalids : null
},
normalize: (transform, block, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, { normalize: false }), transform)
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, OPTS), transform)
}
}
@@ -59,7 +67,7 @@ const MIN_TEXT_RULE = {
},
normalize: (transform, node) => {
const text = Text.create()
return transform.insertNodeByKey(node.key, 0, text, { normalize: false })
return transform.insertNodeByKey(node.key, 0, text, OPTS)
}
}
@@ -79,7 +87,7 @@ const INLINE_CHILDREN_RULE = {
return invalids.size ? invalids : null
},
normalize: (transform, inline, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, { normalize: false }), transform)
return invalids.reduce((t, n) => t.removeNodeByKey(n.key, OPTS), transform)
}
}
@@ -107,8 +115,8 @@ const INLINE_NO_EMPTY = {
return block.nodes.reduce((tr, child, index) => {
if (child.kind == 'inline' && child.text == '') {
return transform
.removeNodeByKey(child.key, { normalize: false })
.insertNodeByKey(block.key, index, Text.createFromString(''), { normalize: false })
.removeNodeByKey(child.key, OPTS)
.insertNodeByKey(block.key, index, Text.createFromString(''), OPTS)
} else {
return tr
}
@@ -117,24 +125,35 @@ const INLINE_NO_EMPTY = {
}
/**
* Ensure that void nodes contain a single space of content.
* Ensure that void nodes contain a text node with a single space of text.
*
* @type {Object}
*/
const VOID_TEXT_RULE = {
match: (object) => {
return (object.kind == 'inline' || object.kind == 'block') && object.isVoid
return (
(object.kind == 'inline' || object.kind == 'block') &&
(object.isVoid)
)
},
validate: (node) => {
return node.text !== ' ' || node.nodes.size !== 1
return (
node.text !== ' ' ||
node.nodes.size !== 1
)
},
normalize: (transform, node, result) => {
node.nodes.reduce((t, child) => {
return t.removeNodeByKey(child.key, { normalize: false })
}, transform)
const text = Text.createFromString(' ')
const index = node.nodes.size
return transform.insertNodeByKey(node.key, 0, Text.createFromString(' '), { normalize: false })
transform.insertNodeByKey(node.key, index, text, OPTS)
node.nodes.forEach((child) => {
transform.removeNodeByKey(child.key, OPTS)
})
return transform
}
}
@@ -175,11 +194,11 @@ const INLINE_VOID_TEXTS_AROUND_RULE = {
return invalids.reduce((t, { index, next, prev }) => {
if (prev) {
t = t.insertNodeByKey(block.key, shift + index, Text.create(), { normalize: false })
t = t.insertNodeByKey(block.key, shift + index, Text.create(), OPTS)
shift = shift + 1
}
if (next) {
t = t.insertNodeByKey(block.key, shift + index + 1, Text.create(), { normalize: false })
t = t.insertNodeByKey(block.key, shift + index + 1, Text.create(), OPTS)
shift = shift + 1
}
@@ -219,7 +238,7 @@ const NO_ADJACENT_TEXT_RULE = {
.reverse()
.reduce((t, pair) => {
const [ first, second ] = pair
return t.joinNodeByKey(second.key, first.key, { normalize: false })
return t.joinNodeByKey(second.key, first.key, OPTS)
}, transform)
}
}
@@ -273,7 +292,7 @@ const NO_EMPTY_TEXT_RULE = {
},
normalize: (transform, node, invalids) => {
return invalids.reduce((t, text) => {
return t.removeNodeByKey(text.key, { normalize: false })
return t.removeNodeByKey(text.key, OPTS)
}, transform)
}
}

View File

@@ -231,54 +231,50 @@ function removeNode(state, operation) {
const { path } = operation
let { document, selection } = state
const { startKey, endKey } = selection
// Preserve previous document
const prevDocument = document
// Update the document
const node = document.assertPath(path)
// If the selection is set, check to see if it needs to be updated.
if (selection.isSet) {
const hasStartNode = node.hasNode(startKey)
const hasEndNode = node.hasNode(endKey)
// If one of the selection's nodes is being removed, we need to update it.
if (hasStartNode) {
const prev = document.getPreviousText(startKey)
const next = document.getNextText(startKey)
if (prev) {
selection = selection.moveStartTo(prev.key, prev.length)
} else if (next) {
selection = selection.moveStartTo(next.key, 0)
} else {
selection = selection.unset()
}
}
if (hasEndNode) {
const prev = document.getPreviousText(endKey)
const next = document.getNextText(endKey)
if (prev) {
selection = selection.moveEndTo(prev.key, prev.length)
} else if (next) {
selection = selection.moveEndTo(next.key, 0)
} else {
selection = selection.unset()
}
}
}
// Remove the node from the document.
let parent = document.getParent(node.key)
const index = parent.nodes.indexOf(node)
const isParent = document == parent
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
function getRemoved(key) {
if (key === node.key) return node
if (node.kind == 'text') return null
return node.getDescendant(key)
}
// Update the selection, if one of the anchor/focus has been removed
const startDesc = startKey ? getRemoved(startKey) : null
const endDesc = endKey ? getRemoved(endKey) : null
if (startDesc) {
const prevText = prevDocument.getTexts()
.takeUntil(text => text.key == startKey)
.filter(text => !getRemoved(text.key))
.last()
selection = !prevText
? selection.unset()
: selection.moveStartTo(prevText.key, prevText.length)
}
if (endDesc) {
// The whole selection is inside the node, we collapse to the previous text node
if (startKey == endKey) {
selection = selection.collapseToStart()
} else {
const nextText = prevDocument.getTexts()
.skipUntil(text => text.key == startKey)
.slice(1)
.filter(text => !getRemoved(text.key))
.first()
selection = !nextText
? selection.unset()
: selection.moveEndTo(nextText.key, 0)
}
}
// Update the document and selection.
state = state.merge({ document, selection })
return state
}

View File

@@ -12,9 +12,9 @@ import warn from '../utils/warn'
*/
export function normalize(transform, schema) {
transform.normalizeDocument(schema)
transform.normalizeSelection(schema)
return transform
.normalizeDocument(schema)
.normalizeSelection(schema)
}
/**
@@ -28,7 +28,8 @@ export function normalize(transform, schema) {
export function normalizeDocument(transform, schema) {
const { state } = transform
const { document } = state
return transform.normalizeNodeByKey(document.key, schema)
transform.normalizeNodeByKey(document.key, schema)
return transform
}
/**

View File

@@ -4,7 +4,7 @@ import assert from 'assert'
export default function (state) {
const { document, selection } = state
const texts = document.getTexts()
let first = texts.first()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 0,
@@ -21,14 +21,14 @@ export default function (state) {
})
.apply()
// Selection is reset, in theory it should me on the emoji
first = next.document.getTexts().first()
const updated = next.document.getTexts().get(1)
assert.deepEqual(
next.selection.toJS(),
range.merge({
anchorKey: first.key,
anchorKey: updated.key,
anchorOffset: 0,
focusKey: first.key,
focusKey: updated.key,
focusOffset: 0
}).toJS()
)