1
0
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:
Ian Storm Taylor
2016-07-28 11:42:52 -07:00
parent 386b671baa
commit 45433198b6
2 changed files with 136 additions and 76 deletions

View File

@@ -7,6 +7,7 @@ import Inline from './inline'
import Mark from './mark'
import Selection from './selection'
import Text from './text'
import typeOf from 'type-of'
import uid from '../utils/uid'
import { List, Map, Set } from 'immutable'
@@ -22,7 +23,7 @@ const Transforms = {
* Add a new `mark` to the characters at `range`.
*
* @param {Selection} range
* @param {Mark or String} mark
* @param {Mark or String or Object} mark
* @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 {Node} node
* @param {Block or String or Object} block
* @return {Node} node
*/
insertBlockAtRange(range, node) {
let doc = this
insertBlockAtRange(range, block) {
block = normalizeBlock(block)
let node = this
// If expanded, delete the range first.
if (range.isExpanded) {
doc = doc.deleteAtRange(range)
node = node.deleteAtRange(range)
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
let startBlock = doc.getClosestBlock(startKey)
let parent = doc.getParent(startBlock)
let nodes = Block.createList([node])
const isParent = parent == doc
let startBlock = node.getClosestBlock(startKey)
let parent = node.getParent(startBlock)
let nodes = Block.createList([block])
const isParent = parent == node
// If the start block is void, insert after it.
if (startBlock.isVoid) {
@@ -268,21 +264,21 @@ const Transforms = {
// Otherwise, split the block and insert between.
else {
doc = doc.splitBlockAtRange(range)
parent = doc.getParent(startBlock)
startBlock = doc.getClosestBlock(startKey)
node = node.splitBlockAtRange(range)
parent = node.getParent(startBlock)
startBlock = node.getClosestBlock(startKey)
nodes = parent.nodes.takeUntil(n => n == startBlock)
.push(startBlock)
.push(node)
.push(block)
.concat(parent.nodes.skipUntil(n => n == startBlock).rest())
parent = parent.merge({ nodes })
}
doc = isParent
node = isParent
? 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 {Node} node
* @param {Inline or String or Object} inline
* @return {Node} node
*/
insertInlineAtRange(range, node) {
let doc = this
insertInlineAtRange(range, inline) {
inline = normalizeInline(inline)
let node = this
// If expanded, delete the range first.
if (range.isExpanded) {
doc = doc.deleteAtRange(range)
node = node.deleteAtRange(range)
range = range.collapseToStart()
}
const { startKey, endKey, startOffset, endOffset } = range
// If the range is inside a void, abort.
const block = doc.getClosestBlock(startKey)
if (block && block.isVoid) return doc
const startBlock = node.getClosestBlock(startKey)
if (startBlock && startBlock.isVoid) return node
const inline = doc.getClosestInline(startKey)
if (inline && inline.isVoid) return doc
// 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)
const startInline = node.getClosestInline(startKey)
if (startInline && startInline.isVoid) return node
// Split the text nodes at the cursor.
doc = doc.splitTextAtRange(range)
node = node.splitTextAtRange(range)
// Insert the node between the split text nodes.
const startText = doc.getDescendant(startKey)
let parent = doc.getParent(startKey)
// Insert the inline between the split text nodes.
const startText = node.getDescendant(startKey)
let parent = node.getParent(startKey)
const nodes = parent.nodes.takeUntil(n => n == startText)
.push(startText)
.push(node)
.push(inline)
.concat(parent.nodes.skipUntil(n => n == startText).rest())
parent = parent.merge({ nodes })
doc = doc.updateDescendant(parent)
return doc.normalize()
node = node.updateDescendant(parent)
return node.normalize()
},
/**
@@ -521,20 +512,10 @@ const Transforms = {
*/
setBlockAtRange(range, properties = {}) {
properties = normalizeProperties(properties)
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)
blocks.forEach((block) => {
block = block.merge(properties)
node = node.updateDescendant(block)
@@ -552,17 +533,11 @@ const Transforms = {
*/
setInlineAtRange(range, properties = {}) {
properties = normalizeProperties(properties)
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)
inlines.forEach((inline) => {
if (properties.data) properties.data = Data.create(properties.data)
inline = inline.merge(properties)
node = node.updateDescendant(inline)
})
@@ -579,15 +554,9 @@ const Transforms = {
*/
setNodeByKey(key, properties) {
properties = normalizeProperties(properties)
let node = this
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)
node = node.updateDescendant(descendant)
return node
@@ -1093,13 +1062,103 @@ function isInRange(index, text, range) {
*/
function normalizeMark(mark) {
if (typeof mark == 'string') {
return Mark.create({ type: mark })
} else {
return Mark.create(mark)
if (mark instanceof Mark) return mark
const type = typeOf(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
}
/**
* Export.
*/

View File

@@ -14,6 +14,7 @@
"keycode": "^2.1.2",
"lodash": "^4.13.1",
"react-portal": "^2.2.0",
"type-of": "^2.0.1",
"ua-parser-js": "^0.7.10",
"uid": "0.0.2"
},