mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-22 15:02:51 +02:00
refactor normalization of arguments with better error handling
This commit is contained in:
@@ -7,6 +7,7 @@ import Inline from './inline'
|
|||||||
import Mark from './mark'
|
import Mark from './mark'
|
||||||
import Selection from './selection'
|
import Selection from './selection'
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
|
import typeOf from 'type-of'
|
||||||
import uid from '../utils/uid'
|
import uid from '../utils/uid'
|
||||||
import { List, Map, Set } from 'immutable'
|
import { List, Map, Set } from 'immutable'
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ const Transforms = {
|
|||||||
* Add a new `mark` to the characters at `range`.
|
* Add a new `mark` to the characters at `range`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {Mark or String} mark
|
* @param {Mark or String or Object} mark
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -216,33 +217,28 @@ const Transforms = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a block `node` at `range`.
|
* Insert a `block` node at `range`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {Node} node
|
* @param {Block or String or Object} block
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
insertBlockAtRange(range, node) {
|
insertBlockAtRange(range, block) {
|
||||||
let doc = this
|
block = normalizeBlock(block)
|
||||||
|
let node = this
|
||||||
|
|
||||||
// If expanded, delete the range first.
|
// If expanded, delete the range first.
|
||||||
if (range.isExpanded) {
|
if (range.isExpanded) {
|
||||||
doc = doc.deleteAtRange(range)
|
node = node.deleteAtRange(range)
|
||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow for passing just a type string.
|
|
||||||
if (typeof node == 'string') node = { type: node }
|
|
||||||
|
|
||||||
// Allow for passing a plain object of properties.
|
|
||||||
node = Block.create(node)
|
|
||||||
|
|
||||||
const { startKey, startOffset } = range
|
const { startKey, startOffset } = range
|
||||||
let startBlock = doc.getClosestBlock(startKey)
|
let startBlock = node.getClosestBlock(startKey)
|
||||||
let parent = doc.getParent(startBlock)
|
let parent = node.getParent(startBlock)
|
||||||
let nodes = Block.createList([node])
|
let nodes = Block.createList([block])
|
||||||
const isParent = parent == doc
|
const isParent = parent == node
|
||||||
|
|
||||||
// If the start block is void, insert after it.
|
// If the start block is void, insert after it.
|
||||||
if (startBlock.isVoid) {
|
if (startBlock.isVoid) {
|
||||||
@@ -268,21 +264,21 @@ const Transforms = {
|
|||||||
|
|
||||||
// Otherwise, split the block and insert between.
|
// Otherwise, split the block and insert between.
|
||||||
else {
|
else {
|
||||||
doc = doc.splitBlockAtRange(range)
|
node = node.splitBlockAtRange(range)
|
||||||
parent = doc.getParent(startBlock)
|
parent = node.getParent(startBlock)
|
||||||
startBlock = doc.getClosestBlock(startKey)
|
startBlock = node.getClosestBlock(startKey)
|
||||||
nodes = parent.nodes.takeUntil(n => n == startBlock)
|
nodes = parent.nodes.takeUntil(n => n == startBlock)
|
||||||
.push(startBlock)
|
.push(startBlock)
|
||||||
.push(node)
|
.push(block)
|
||||||
.concat(parent.nodes.skipUntil(n => n == startBlock).rest())
|
.concat(parent.nodes.skipUntil(n => n == startBlock).rest())
|
||||||
parent = parent.merge({ nodes })
|
parent = parent.merge({ nodes })
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = isParent
|
node = isParent
|
||||||
? parent
|
? parent
|
||||||
: doc.updateDescendant(parent)
|
: node.updateDescendant(parent)
|
||||||
|
|
||||||
return doc.normalize()
|
return node.normalize()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,51 +381,46 @@ const Transforms = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert an inline `node` at `range`.
|
* Insert an `inline` node at `range`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {Node} node
|
* @param {Inline or String or Object} inline
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
insertInlineAtRange(range, node) {
|
insertInlineAtRange(range, inline) {
|
||||||
let doc = this
|
inline = normalizeInline(inline)
|
||||||
|
let node = this
|
||||||
|
|
||||||
// If expanded, delete the range first.
|
// If expanded, delete the range first.
|
||||||
if (range.isExpanded) {
|
if (range.isExpanded) {
|
||||||
doc = doc.deleteAtRange(range)
|
node = node.deleteAtRange(range)
|
||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { startKey, endKey, startOffset, endOffset } = range
|
const { startKey, endKey, startOffset, endOffset } = range
|
||||||
|
|
||||||
// If the range is inside a void, abort.
|
// If the range is inside a void, abort.
|
||||||
const block = doc.getClosestBlock(startKey)
|
const startBlock = node.getClosestBlock(startKey)
|
||||||
if (block && block.isVoid) return doc
|
if (startBlock && startBlock.isVoid) return node
|
||||||
|
|
||||||
const inline = doc.getClosestInline(startKey)
|
const startInline = node.getClosestInline(startKey)
|
||||||
if (inline && inline.isVoid) return doc
|
if (startInline && startInline.isVoid) return node
|
||||||
|
|
||||||
// Allow for passing a type string.
|
|
||||||
if (typeof node == 'string') node = { type: node }
|
|
||||||
|
|
||||||
// Allow for passing a plain object of properties.
|
|
||||||
node = Inline.create(node)
|
|
||||||
|
|
||||||
// Split the text nodes at the cursor.
|
// Split the text nodes at the cursor.
|
||||||
doc = doc.splitTextAtRange(range)
|
node = node.splitTextAtRange(range)
|
||||||
|
|
||||||
// Insert the node between the split text nodes.
|
// Insert the inline between the split text nodes.
|
||||||
const startText = doc.getDescendant(startKey)
|
const startText = node.getDescendant(startKey)
|
||||||
let parent = doc.getParent(startKey)
|
let parent = node.getParent(startKey)
|
||||||
const nodes = parent.nodes.takeUntil(n => n == startText)
|
const nodes = parent.nodes.takeUntil(n => n == startText)
|
||||||
.push(startText)
|
.push(startText)
|
||||||
.push(node)
|
.push(inline)
|
||||||
.concat(parent.nodes.skipUntil(n => n == startText).rest())
|
.concat(parent.nodes.skipUntil(n => n == startText).rest())
|
||||||
|
|
||||||
parent = parent.merge({ nodes })
|
parent = parent.merge({ nodes })
|
||||||
doc = doc.updateDescendant(parent)
|
node = node.updateDescendant(parent)
|
||||||
return doc.normalize()
|
return node.normalize()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -521,20 +512,10 @@ const Transforms = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
setBlockAtRange(range, properties = {}) {
|
setBlockAtRange(range, properties = {}) {
|
||||||
|
properties = normalizeProperties(properties)
|
||||||
let node = this
|
let node = this
|
||||||
|
|
||||||
// Allow for properties to be a string `type` for convenience.
|
|
||||||
if (typeof properties == 'string') {
|
|
||||||
properties = { type: properties }
|
|
||||||
}
|
|
||||||
if (properties.data) {
|
|
||||||
properties.data = Data.create(properties.data)
|
|
||||||
} else {
|
|
||||||
delete properties.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update each of the blocks.
|
|
||||||
const blocks = node.getBlocksAtRange(range)
|
const blocks = node.getBlocksAtRange(range)
|
||||||
|
|
||||||
blocks.forEach((block) => {
|
blocks.forEach((block) => {
|
||||||
block = block.merge(properties)
|
block = block.merge(properties)
|
||||||
node = node.updateDescendant(block)
|
node = node.updateDescendant(block)
|
||||||
@@ -552,17 +533,11 @@ const Transforms = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
setInlineAtRange(range, properties = {}) {
|
setInlineAtRange(range, properties = {}) {
|
||||||
|
properties = normalizeProperties(properties)
|
||||||
let node = this
|
let node = this
|
||||||
|
|
||||||
// Allow for properties to be a string `type` for convenience.
|
|
||||||
if (typeof properties == 'string') {
|
|
||||||
properties = { type: properties }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update each of the inlines.
|
|
||||||
const inlines = node.getInlinesAtRange(range)
|
const inlines = node.getInlinesAtRange(range)
|
||||||
|
|
||||||
inlines.forEach((inline) => {
|
inlines.forEach((inline) => {
|
||||||
if (properties.data) properties.data = Data.create(properties.data)
|
|
||||||
inline = inline.merge(properties)
|
inline = inline.merge(properties)
|
||||||
node = node.updateDescendant(inline)
|
node = node.updateDescendant(inline)
|
||||||
})
|
})
|
||||||
@@ -579,15 +554,9 @@ const Transforms = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
setNodeByKey(key, properties) {
|
setNodeByKey(key, properties) {
|
||||||
|
properties = normalizeProperties(properties)
|
||||||
let node = this
|
let node = this
|
||||||
let descendant = node.assertDescendant(key)
|
let descendant = node.assertDescendant(key)
|
||||||
|
|
||||||
// Allow for properties to be a string `type` for convenience.
|
|
||||||
if (typeof properties == 'string') properties = { type: properties }
|
|
||||||
|
|
||||||
// Ensure that `data` is immutable.
|
|
||||||
if (properties.data) properties.data = Data.create(properties.data)
|
|
||||||
|
|
||||||
descendant = descendant.merge(properties)
|
descendant = descendant.merge(properties)
|
||||||
node = node.updateDescendant(descendant)
|
node = node.updateDescendant(descendant)
|
||||||
return node
|
return node
|
||||||
@@ -1093,11 +1062,101 @@ function isInRange(index, text, range) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function normalizeMark(mark) {
|
function normalizeMark(mark) {
|
||||||
if (typeof mark == 'string') {
|
if (mark instanceof Mark) return mark
|
||||||
return Mark.create({ type: mark })
|
|
||||||
} else {
|
const type = typeOf(mark)
|
||||||
return Mark.create(mark)
|
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case 'object': {
|
||||||
|
return Mark.create(normalizeProperties(mark))
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`A \`mark\` argument must be a mark, an object or a string, but you passed: "${type}".`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a `block` argument, which can be a string or plain object too.
|
||||||
|
*
|
||||||
|
* @param {Block or String or Object} block
|
||||||
|
* @return {Block}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizeBlock(block) {
|
||||||
|
if (block instanceof Block) return block
|
||||||
|
|
||||||
|
const type = typeOf(block)
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case 'object': {
|
||||||
|
return Block.create(normalizeProperties(block))
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`A \`block\` argument must be a block, an object or a string, but you passed: "${type}".`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an `inline` argument, which can be a string or plain object too.
|
||||||
|
*
|
||||||
|
* @param {Inline or String or Object} inline
|
||||||
|
* @return {Inline}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizeInline(inline) {
|
||||||
|
if (inline instanceof Inline) return inline
|
||||||
|
|
||||||
|
const type = typeOf(inline)
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case 'object': {
|
||||||
|
return Inline.create(normalizeProperties(inline))
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`An \`inline\` argument must be an inline, an object or a string, but you passed: "${type}".`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the `properties` of a node or mark, which can be either a type
|
||||||
|
* string or a dictionary of properties. If it's a dictionary, `data` is
|
||||||
|
* optional and shouldn't be set if null or undefined.
|
||||||
|
*
|
||||||
|
* @param {String or Object} properties
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizeProperties(properties = {}) {
|
||||||
|
const ret = {}
|
||||||
|
const type = typeOf(properties)
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'string': {
|
||||||
|
ret.type = properties
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'object': {
|
||||||
|
for (const key in properties) {
|
||||||
|
if (key == 'data') {
|
||||||
|
if (properties[key] != null) ret[key] = Data.create(properties[key])
|
||||||
|
} else {
|
||||||
|
ret[key] = properties[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`A \`properties\` argument must be an object or a string, but you passed: "${type}".`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
"keycode": "^2.1.2",
|
"keycode": "^2.1.2",
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"react-portal": "^2.2.0",
|
"react-portal": "^2.2.0",
|
||||||
|
"type-of": "^2.0.1",
|
||||||
"ua-parser-js": "^0.7.10",
|
"ua-parser-js": "^0.7.10",
|
||||||
"uid": "0.0.2"
|
"uid": "0.0.2"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user