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 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
} }
/** /**

View File

@@ -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"
}, },