diff --git a/package.json b/package.json index 970339973..5505c93cf 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "immutable": "^3.8.1", "is-empty": "^1.0.0", "is-in-browser": "^1.1.3", + "is-plain-object": "^2.0.4", "is-window": "^1.0.2", "keycode": "^2.1.2", "lodash": "^4.17.4", diff --git a/src/changes/at-current-range.js b/src/changes/at-current-range.js index a60c3b7e6..94dc736b5 100644 --- a/src/changes/at-current-range.js +++ b/src/changes/at-current-range.js @@ -1,5 +1,7 @@ -import Normalize from '../utils/normalize' +import Block from '../models/block' +import Inline from '../models/inline' +import Mark from '../models/mark' /** * Changes. @@ -49,7 +51,7 @@ PROXY_TRANSFORMS.forEach((method) => { */ Changes.addMark = (change, mark) => { - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { state } = change const { document, selection } = state @@ -95,7 +97,7 @@ Changes.delete = (change) => { */ Changes.insertBlock = (change, block) => { - block = Normalize.block(block) + block = Block.create(block) const { state } = change const { selection } = state change.insertBlockAtRange(selection, block) @@ -155,7 +157,7 @@ Changes.insertFragment = (change, fragment) => { */ Changes.insertInline = (change, inline) => { - inline = Normalize.inline(inline) + inline = Inline.create(inline) const { state } = change const { selection } = state change.insertInlineAtRange(selection, inline) @@ -209,7 +211,7 @@ Changes.splitBlock = (change, depth = 1) => { */ Changes.removeMark = (change, mark) => { - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { state } = change const { document, selection } = state @@ -239,7 +241,7 @@ Changes.removeMark = (change, mark) => { */ Changes.toggleMark = (change, mark) => { - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { state } = change const exists = state.activeMarks.has(mark) diff --git a/src/changes/at-range.js b/src/changes/at-range.js index 73bc14f73..2969b2646 100644 --- a/src/changes/at-range.js +++ b/src/changes/at-range.js @@ -1,5 +1,8 @@ -import Normalize from '../utils/normalize' +import Block from '../models/block' +import Inline from '../models/inline' +import Mark from '../models/mark' +import Node from '../models/node' import String from '../utils/string' import SCHEMA from '../schemas/core' import { List } from 'immutable' @@ -597,7 +600,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => { */ Changes.insertBlockAtRange = (change, range, block, options = {}) => { - block = Normalize.block(block) + block = Block.create(block) const { normalize = true } = options if (range.isExpanded) { @@ -766,7 +769,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => { Changes.insertInlineAtRange = (change, range, inline, options = {}) => { const { normalize = true } = options - inline = Normalize.inline(inline) + inline = Inline.create(inline) if (range.isExpanded) { change.deleteAtRange(range, OPTS) @@ -978,7 +981,7 @@ Changes.splitInlineAtRange = (change, range, height = Infinity, options = {}) => Changes.toggleMarkAtRange = (change, range, mark, options = {}) => { if (range.isCollapsed) return - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { normalize = true } = options const { state } = change @@ -1004,7 +1007,7 @@ Changes.toggleMarkAtRange = (change, range, mark, options = {}) => { */ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => { - properties = Normalize.nodeProperties(properties) + properties = Node.createProperties(properties) const { normalize = true } = options let { state } = change @@ -1097,7 +1100,7 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => { */ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => { - properties = Normalize.nodeProperties(properties) + properties = Node.createProperties(properties) const { normalize = true } = options const { state } = change @@ -1143,7 +1146,7 @@ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => { */ Changes.wrapBlockAtRange = (change, range, block, options = {}) => { - block = Normalize.block(block) + block = Block.create(block) block = block.set('nodes', block.nodes.clear()) const { normalize = true } = options @@ -1229,7 +1232,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => { return change.wrapInlineByKey(inlineParent.key, inline, options) } - inline = Normalize.inline(inline) + inline = Inline.create(inline) inline = inline.set('nodes', inline.nodes.clear()) const blocks = document.getBlocksAtRange(range) diff --git a/src/changes/by-key.js b/src/changes/by-key.js index 5433cda32..cfc71a369 100644 --- a/src/changes/by-key.js +++ b/src/changes/by-key.js @@ -1,5 +1,8 @@ -import Normalize from '../utils/normalize' +import Block from '../models/block' +import Inline from '../models/inline' +import Mark from '../models/mark' +import Node from '../models/node' import SCHEMA from '../schemas/core' /** @@ -23,7 +26,7 @@ const Changes = {} */ Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => { - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { normalize = true } = options const { state } = change const { document } = state @@ -232,7 +235,7 @@ Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => { */ Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => { - mark = Normalize.mark(mark) + mark = Mark.create(mark) const { normalize = true } = options const { state } = change const { document } = state @@ -376,8 +379,8 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => { */ Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = {}) => { - mark = Normalize.mark(mark) - properties = Normalize.markProperties(properties) + mark = Mark.create(mark) + properties = Mark.createProperties(properties) const { normalize = true } = options const { state } = change const { document } = state @@ -409,7 +412,7 @@ Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = */ Changes.setNodeByKey = (change, key, properties, options = {}) => { - properties = Normalize.nodeProperties(properties) + properties = Node.createProperties(properties) const { normalize = true } = options const { state } = change const { document } = state @@ -599,7 +602,7 @@ Changes.unwrapNodeByKey = (change, key, options = {}) => { */ Changes.wrapInlineByKey = (change, key, inline, options) => { - inline = Normalize.inline(inline) + inline = Inline.create(inline) inline = inline.set('nodes', inline.nodes.clear()) const { document } = change.state @@ -622,7 +625,7 @@ Changes.wrapInlineByKey = (change, key, inline, options) => { */ Changes.wrapBlockByKey = (change, key, block, options) => { - block = Normalize.block(block) + block = Block.create(block) block = block.set('nodes', block.nodes.clear()) const { document } = change.state diff --git a/src/changes/normalize.js b/src/changes/normalize.js index 4fde7dee3..097b5a491 100644 --- a/src/changes/normalize.js +++ b/src/changes/normalize.js @@ -1,5 +1,4 @@ -import Normalize from '../utils/normalize' import Schema from '../models/schema' import { Set } from 'immutable' @@ -49,11 +48,9 @@ Changes.normalizeNodeByKey = (change, key, schema) => { // If the schema has no validation rules, there's nothing to normalize. if (!schema.hasValidators) return - key = Normalize.key(key) const { state } = change const { document } = state const node = document.assertNode(key) - normalizeNodeAndChildren(change, node, schema) } diff --git a/src/changes/on-selection.js b/src/changes/on-selection.js index 9ef0723b4..3618bdcfb 100644 --- a/src/changes/on-selection.js +++ b/src/changes/on-selection.js @@ -1,5 +1,5 @@ -import Normalize from '../utils/normalize' +import Selection from '../models/selection' import isEmpty from 'is-empty' import logger from '../utils/logger' import pick from 'lodash/pick' @@ -20,7 +20,7 @@ const Changes = {} */ Changes.select = (change, properties, options = {}) => { - properties = Normalize.selectionProperties(properties) + properties = Selection.createProperties(properties) const { snapshot = false } = options const { state } = change diff --git a/src/models/block.js b/src/models/block.js index d455bb2c1..52f739bdd 100644 --- a/src/models/block.js +++ b/src/models/block.js @@ -11,11 +11,11 @@ import './document' import Data from './data' import Node from './node' -import Inline from './inline' import Text from './text' import MODEL_TYPES from '../constants/model-types' import generateKey from '../utils/generate-key' -import { Map, List, Record } from 'immutable' +import isPlainObject from 'is-plain-object' +import { List, Map, Record } from 'immutable' /** * Default properties. @@ -26,9 +26,9 @@ import { Map, List, Record } from 'immutable' const DEFAULTS = { data: new Map(), isVoid: false, - key: null, + key: undefined, nodes: new List(), - type: null + type: undefined, } /** @@ -37,55 +37,64 @@ const DEFAULTS = { * @type {Block} */ -class Block extends new Record(DEFAULTS) { +class Block extends Record(DEFAULTS) { /** * Create a new `Block` with `attrs`. * - * @param {Object|Block} attrs + * @param {Object|String|Block} attrs * @return {Block} */ static create(attrs = {}) { - if (Block.isBlock(attrs)) return attrs - if (Inline.isInline(attrs)) return attrs - if (Text.isText(attrs)) return attrs - - if (!attrs.type) { - throw new Error('You must pass a block `type`.') + if (Block.isBlock(attrs)) { + return attrs } - const { nodes } = attrs - const empty = !nodes || nodes.size == 0 || nodes.length == 0 - const block = new Block({ - type: attrs.type, - key: attrs.key || generateKey(), - data: Data.create(attrs.data), - isVoid: !!attrs.isVoid, - nodes: Node.createList(empty ? [Text.create()] : nodes), - }) + if (typeof attrs == 'string') { + attrs = { type: attrs } + } - return block + if (isPlainObject(attrs)) { + const { data, isVoid, key, type } = attrs + let { nodes } = attrs + + if (typeof type != 'string') { + throw new Error('`Block.create` requires a block `type` string.') + } + + if (nodes == null || nodes.length == 0) { + nodes = [Text.create()] + } + + const block = new Block({ + data: Data.create(data), + isVoid: !!isVoid, + key: key || generateKey(), + nodes: Node.createList(nodes), + type, + }) + + return block + } + + throw new Error(`\`Block.create\` only accepts objects, strings or blocks, but you passed it: ${attrs}`) } /** * Create a list of `Blocks` from `elements`. * - * @param {Array|List} elements + * @param {Array|List} elements * @return {List} */ static createList(elements = []) { - if (List.isList(elements)) { - return elements - } - - if (Array.isArray(elements)) { + if (List.isList(elements) || Array.isArray(elements)) { const list = new List(elements.map(Block.create)) return list } - throw new Error(`Block.createList() must be passed an \`Array\` or a \`List\`. You passed: ${elements}`) + throw new Error(`\`Block.createList\` only accepts arrays or lists, but you passed it: ${elements}`) } /** @@ -110,7 +119,7 @@ class Block extends new Record(DEFAULTS) { } /** - * Is the node empty? + * Check if the block is empty. * * @return {Boolean} */ @@ -120,7 +129,7 @@ class Block extends new Record(DEFAULTS) { } /** - * Get the concatenated text `string` of all child nodes. + * Get the concatenated text of all the block's children. * * @return {String} */ diff --git a/src/models/character.js b/src/models/character.js index 6f2bc318e..3c81826a4 100644 --- a/src/models/character.js +++ b/src/models/character.js @@ -1,6 +1,8 @@ -import Mark from './mark' import MODEL_TYPES from '../constants/model-types' +import Mark from './mark' +import isPlainObject from 'is-plain-object' +import logger from '../utils/logger' import { List, Record, Set } from 'immutable' /** @@ -11,7 +13,7 @@ import { List, Record, Set } from 'immutable' const DEFAULTS = { marks: new Set(), - text: '' + text: '', } /** @@ -20,58 +22,56 @@ const DEFAULTS = { * @type {Character} */ -class Character extends new Record(DEFAULTS) { +class Character extends Record(DEFAULTS) { /** * Create a `Character` with `attrs`. * - * @param {Object|Character} attrs + * @param {Object|String|Character} attrs * @return {Character} */ static create(attrs = {}) { - if (Character.isCharacter(attrs)) return attrs + if (Character.isCharacter(attrs)) { + return attrs + } - const character = new Character({ - text: attrs.text, - marks: Mark.createSet(attrs.marks), - }) + if (typeof attrs == 'string') { + attrs = { text: attrs } + } - return character + if (isPlainObject(attrs)) { + const { marks, text } = attrs + + const character = new Character({ + text, + marks: Mark.createSet(marks), + }) + + return character + } + + throw new Error(`\`Character.create\` only accepts objects, strings or characters, but you passed it: ${attrs}`) } /** * Create a list of `Characters` from `elements`. * - * @param {Array|List} elements + * @param {String|Array|List} elements * @return {List} */ static createList(elements = []) { - if (List.isList(elements)) { - return elements + if (typeof elements == 'string') { + elements = elements.split('') } - if (Array.isArray(elements)) { + if (List.isList(elements) || Array.isArray(elements)) { const list = new List(elements.map(Character.create)) return list } - throw new Error(`Character.createList() must be passed an \`Array\` or a \`List\`. You passed: ${elements}`) - } - - /** - * Create a characters list from a `string` and optional `marks`. - * - * @param {String} string - * @param {Set} marks (optional) - * @return {List} - */ - - static createListFromText(string, marks) { - const chars = string.split('').map(text => ({ text, marks })) - const list = Character.createList(chars) - return list + throw new Error(`\`Block.createList\` only accepts strings, arrays or lists, but you passed it: ${elements}`) } /** @@ -85,6 +85,15 @@ class Character extends new Record(DEFAULTS) { return !!(value && value[MODEL_TYPES.CHARACTER]) } + /** + * Deprecated. + */ + + static createListFromText(string) { + logger.deprecate('0.22.0', 'The `Character.createListFromText(string)` method is deprecated, use `Character.createList(string)` instead.') + return this.createList(string) + } + /** * Get the kind. * diff --git a/src/models/data.js b/src/models/data.js index b0700f873..315698484 100644 --- a/src/models/data.js +++ b/src/models/data.js @@ -1,4 +1,5 @@ +import isPlainObject from 'is-plain-object' import { Map } from 'immutable' /** @@ -15,14 +16,20 @@ const Data = { /** * Create a new `Data` with `attrs`. * - * @param {Object} attrs + * @param {Object|Data|Map} attrs * @return {Data} data */ create(attrs = {}) { - return Map.isMap(attrs) - ? attrs - : new Map(attrs) + if (Map.isMap(attrs)) { + return attrs + } + + if (isPlainObject(attrs)) { + return new Map(attrs) + } + + throw new Error(`\`Data.create\` only accepts objects or maps, but you passed it: ${attrs}`) } } diff --git a/src/models/document.js b/src/models/document.js index a947f4784..b725d8757 100644 --- a/src/models/document.js +++ b/src/models/document.js @@ -14,6 +14,7 @@ import Data from './data' import Node from './node' import MODEL_TYPES from '../constants/model-types' import generateKey from '../utils/generate-key' +import isPlainObject from 'is-plain-object' import { List, Map, Record } from 'immutable' /** @@ -24,7 +25,7 @@ import { List, Map, Record } from 'immutable' const DEFAULTS = { data: new Map(), - key: null, + key: undefined, nodes: new List(), } @@ -34,25 +35,36 @@ const DEFAULTS = { * @type {Document} */ -class Document extends new Record(DEFAULTS) { +class Document extends Record(DEFAULTS) { /** * Create a new `Document` with `attrs`. * - * @param {Object|Document} attrs + * @param {Object|Array|List|Text} attrs * @return {Document} */ static create(attrs = {}) { - if (Document.isDocument(attrs)) return attrs + if (Document.isDocument(attrs)) { + return attrs + } - const document = new Document({ - key: attrs.key || generateKey(), - data: Data.create(attrs.data), - nodes: Node.createList(attrs.nodes), - }) + if (List.isList(attrs) || Array.isArray(attrs)) { + attrs = { nodes: attrs } + } - return document + if (isPlainObject(attrs)) { + const { data, key, nodes } = attrs + const document = new Document({ + key: key || generateKey(), + data: Data.create(data), + nodes: Node.createList(nodes), + }) + + return document + } + + throw new Error(`\`Document.create\` only accepts objects, arrays, lists or documents, but you passed it: ${attrs}`) } /** @@ -77,7 +89,7 @@ class Document extends new Record(DEFAULTS) { } /** - * Is the document empty? + * Check if the document is empty. * * @return {Boolean} */ @@ -87,7 +99,7 @@ class Document extends new Record(DEFAULTS) { } /** - * Get the concatenated text `string` of all child nodes. + * Get the concatenated text of all the document's children. * * @return {String} */ diff --git a/src/models/history.js b/src/models/history.js index 3621faa68..73fd4b3c3 100644 --- a/src/models/history.js +++ b/src/models/history.js @@ -2,6 +2,7 @@ import MODEL_TYPES from '../constants/model-types' import Debug from 'debug' import isEqual from 'lodash/isEqual' +import isPlainObject from 'is-plain-object' import { Record, Stack } from 'immutable' /** @@ -29,24 +30,30 @@ const DEFAULTS = { * @type {History} */ -class History extends new Record(DEFAULTS) { +class History extends Record(DEFAULTS) { /** * Create a new `History` with `attrs`. * - * @param {Object} attrs + * @param {Object|History} attrs * @return {History} */ static create(attrs = {}) { - if (History.isHistory(attrs)) return attrs + if (History.isHistory(attrs)) { + return attrs + } - const history = new History({ - undos: attrs.undos || new Stack(), - redos: attrs.redos || new Stack(), - }) + if (isPlainObject(attrs)) { + const history = new History({ + undos: attrs.undos || new Stack(), + redos: attrs.redos || new Stack(), + }) - return history + return history + } + + throw new Error(`\`History.create\` only accepts objects or histories, but you passed it: ${attrs}`) } /** diff --git a/src/models/inline.js b/src/models/inline.js index d50972ac0..61187493d 100644 --- a/src/models/inline.js +++ b/src/models/inline.js @@ -9,12 +9,12 @@ import './document' * Dependencies. */ -import Block from './block' import Data from './data' import Node from './node' import Text from './text' import MODEL_TYPES from '../constants/model-types' import generateKey from '../utils/generate-key' +import isPlainObject from 'is-plain-object' import { List, Map, Record } from 'immutable' /** @@ -26,9 +26,9 @@ import { List, Map, Record } from 'immutable' const DEFAULTS = { data: new Map(), isVoid: false, - key: null, + key: undefined, nodes: new List(), - type: null + type: undefined, } /** @@ -37,47 +37,64 @@ const DEFAULTS = { * @type {Inline} */ -class Inline extends new Record(DEFAULTS) { +class Inline extends Record(DEFAULTS) { /** * Create a new `Inline` with `attrs`. * - * @param {Object|Inline} attrs + * @param {Object|String|Inline} attrs * @return {Inline} */ static create(attrs = {}) { - if (Block.isBlock(attrs)) return attrs - if (Inline.isInline(attrs)) return attrs - if (Text.isText(attrs)) return attrs - - if (!attrs.type) { - throw new Error('You must pass an inline `type`.') + if (Inline.isInline(attrs)) { + return attrs } - const { nodes } = attrs - const empty = !nodes || nodes.size == 0 || nodes.length == 0 - const inline = new Inline({ - type: attrs.type, - key: attrs.key || generateKey(), - data: Data.create(attrs.data), - isVoid: !!attrs.isVoid, - nodes: Node.createList(empty ? [Text.create()] : nodes), - }) + if (typeof attrs == 'string') { + attrs = { type: attrs } + } - return inline + if (isPlainObject(attrs)) { + const { data, isVoid, key, type } = attrs + let { nodes } = attrs + + if (typeof type != 'string') { + throw new Error('`Inline.create` requires a block `type` string.') + } + + if (nodes == null || nodes.length == 0) { + nodes = [Text.create()] + } + + const inline = new Inline({ + data: Data.create(data), + isVoid: !!isVoid, + key: key || generateKey(), + nodes: Node.createList(nodes), + type, + }) + + return inline + } + + throw new Error(`\`Inline.create\` only accepts objects, strings or inlines, but you passed it: ${attrs}`) } /** * Create a list of `Inlines` from an array. * - * @param {Array} elements + * @param {Array|List} elements * @return {List} */ static createList(elements = []) { - if (List.isList(elements)) return elements - return new List(elements.map(Inline.create)) + if (List.isList(elements) || Array.isArray(elements)) { + const list = new List(elements.map(Inline.create)) + return list + } + + throw new Error(`\`Inline.createList\` only accepts arrays or lists, but you passed it: ${elements}`) } /** @@ -102,7 +119,7 @@ class Inline extends new Record(DEFAULTS) { } /** - * Is the node empty? + * Check if the inline is empty. * * @return {Boolean} */ @@ -112,7 +129,7 @@ class Inline extends new Record(DEFAULTS) { } /** - * Get the concatenated text `string` of all child nodes. + * Get the concatenated text of all the inline's children. * * @return {String} */ diff --git a/src/models/mark.js b/src/models/mark.js index 68077d3d5..0f62fbb21 100644 --- a/src/models/mark.js +++ b/src/models/mark.js @@ -1,7 +1,8 @@ -import Data from './data' -import memoize from '../utils/memoize' import MODEL_TYPES from '../constants/model-types' +import Data from './data' +import isPlainObject from 'is-plain-object' +import memoize from '../utils/memoize' import { Map, Record, Set } from 'immutable' /** @@ -12,7 +13,7 @@ import { Map, Record, Set } from 'immutable' const DEFAULTS = { data: new Map(), - type: null + type: undefined, } /** @@ -21,7 +22,7 @@ const DEFAULTS = { * @type {Mark} */ -class Mark extends new Record(DEFAULTS) { +class Mark extends Record(DEFAULTS) { /** * Create a new `Mark` with `attrs`. @@ -31,18 +32,30 @@ class Mark extends new Record(DEFAULTS) { */ static create(attrs = {}) { - if (Mark.isMark(attrs)) return attrs - - if (!attrs.type) { - throw new Error(`You must provide \`attrs.type\` to \`Mark.create(attrs)\`.`) + if (Mark.isMark(attrs)) { + return attrs } - const mark = new Mark({ - type: attrs.type, - data: Data.create(attrs.data), - }) + if (typeof attrs == 'string') { + attrs = { type: attrs } + } - return mark + if (isPlainObject(attrs)) { + const { data, type } = attrs + + if (typeof type != 'string') { + throw new Error('`Mark.create` requires a mark `type` string.') + } + + const mark = new Mark({ + type, + data: Data.create(data), + }) + + return mark + } + + throw new Error(`\`Mark.create\` only accepts objects, strings or marks, but you passed it: ${attrs}`) } /** @@ -53,11 +66,7 @@ class Mark extends new Record(DEFAULTS) { */ static createSet(elements) { - if (Set.isSet(elements)) { - return elements - } - - if (Array.isArray(elements)) { + if (Set.isSet(elements) || Array.isArray(elements)) { const marks = new Set(elements.map(Mark.create)) return marks } @@ -66,7 +75,36 @@ class Mark extends new Record(DEFAULTS) { return new Set() } - throw new Error(`Mark.createSet() must be passed an \`Array\`, a \`List\` or \`null\`. You passed: ${elements}`) + throw new Error(`\`Mark.createSet\` only accepts sets, arrays or null, but you passed it: ${elements}`) + } + + /** + * Create a dictionary of settable mark properties from `attrs`. + * + * @param {Object|String|Mark} attrs + * @return {Object} + */ + + static createProperties(attrs = {}) { + if (Mark.isMark(attrs)) { + return { + data: attrs.data, + type: attrs.type, + } + } + + if (typeof attrs == 'string') { + return { type: attrs } + } + + if (isPlainObject(attrs)) { + const props = {} + if ('type' in attrs) props.type = attrs.type + if ('data' in attrs) props.data = Data.create(attrs.data) + return props + } + + throw new Error(`\`Mark.createProperties\` only accepts objects, strings or marks, but you passed it: ${attrs}`) } /** diff --git a/src/models/node.js b/src/models/node.js index 6787b0299..36a14f99f 100644 --- a/src/models/node.js +++ b/src/models/node.js @@ -1,12 +1,13 @@ import Block from './block' +import Data from './data' import Document from './document' import Inline from './inline' -import Normalize from '../utils/normalize' import Text from './text' import direction from 'direction' import generateKey from '../utils/generate-key' import isInRange from '../utils/is-in-range' +import isPlainObject from 'is-plain-object' import logger from '../utils/logger' import memoize from '../utils/memoize' import { List, OrderedSet, Set } from 'immutable' @@ -30,20 +31,23 @@ class Node { */ static create(attrs = {}) { - if (Block.isBlock(attrs)) return attrs - if (Document.isDocument(attrs)) return attrs - if (Inline.isInline(attrs)) return attrs - if (Text.isText(attrs)) return attrs + if (Node.isNode(attrs)) { + return attrs + } - switch (attrs.kind) { - case 'block': return Block.create(attrs) - case 'document': return Document.create(attrs) - case 'inline': return Inline.create(attrs) - case 'text': return Text.create(attrs) - default: { - throw new Error(`You must pass a \`kind\` attribute to create a \`Node\`.`) + if (isPlainObject(attrs)) { + switch (attrs.kind) { + case 'block': return Block.create(attrs) + case 'document': return Document.create(attrs) + case 'inline': return Inline.create(attrs) + case 'text': return Text.create(attrs) + default: { + throw new Error('`Node.create` requires a `kind` string.') + } } } + + throw new Error(`\`Node.create\` only accepts objects or nodes but you passed it: ${attrs}`) } /** @@ -54,16 +58,43 @@ class Node { */ static createList(elements = []) { - if (List.isList(elements)) { - return elements - } - - if (Array.isArray(elements)) { + if (List.isList(elements) || Array.isArray(elements)) { const list = new List(elements.map(Node.create)) return list } - throw new Error(`Node.createList() must be passed an \`Array\` or a \`List\`. You passed: ${elements}`) + throw new Error(`\`Node.createList\` only accepts lists or arrays, but you passed it: ${elements}`) + } + + /** + * Create a dictionary of settable node properties from `attrs`. + * + * @param {Object|String|Node} attrs + * @return {Object} + */ + + static createProperties(attrs = {}) { + if (Block.isBlock(attrs) || Inline.isInline(attrs)) { + return { + data: attrs.data, + isVoid: attrs.isVoid, + type: attrs.type, + } + } + + if (typeof attrs == 'string') { + return { type: attrs } + } + + if (isPlainObject(attrs)) { + const props = {} + if ('type' in attrs) props.type = attrs.type + if ('data' in attrs) props.data = Data.create(attrs.data) + if ('isVoid' in attrs) props.isVoid = attrs.isVoid + return props + } + + throw new Error(`\`Node.createProperties\` only accepts objects, strings, blocks or inlines, but you passed it: ${attrs}`) } /** @@ -92,8 +123,8 @@ class Node { */ areDescendantsSorted(first, second) { - first = Normalize.key(first) - second = Normalize.key(second) + first = normalizeKey(first) + second = normalizeKey(second) let sorted @@ -121,7 +152,7 @@ class Node { const child = this.getChild(key) if (!child) { - key = Normalize.key(key) + key = normalizeKey(key) throw new Error(`Could not find a child node with key "${key}".`) } @@ -139,7 +170,7 @@ class Node { const descendant = this.getDescendant(key) if (!descendant) { - key = Normalize.key(key) + key = normalizeKey(key) throw new Error(`Could not find a descendant node with key "${key}".`) } @@ -157,7 +188,7 @@ class Node { const node = this.getNode(key) if (!node) { - key = Normalize.key(key) + key = normalizeKey(key) throw new Error(`Could not find a node with key "${key}".`) } @@ -251,7 +282,7 @@ class Node { */ getAncestors(key) { - key = Normalize.key(key) + key = normalizeKey(key) if (key == this.key) return List() if (this.hasChild(key)) return List([this]) @@ -428,7 +459,7 @@ class Node { */ getChild(key) { - key = Normalize.key(key) + key = normalizeKey(key) return this.nodes.find(node => node.key == key) } @@ -441,7 +472,7 @@ class Node { */ getClosest(key, iterator) { - key = Normalize.key(key) + key = normalizeKey(key) const ancestors = this.getAncestors(key) if (!ancestors) { throw new Error(`Could not find a descendant node with key "${key}".`) @@ -493,8 +524,8 @@ class Node { */ getCommonAncestor(one, two) { - one = Normalize.key(one) - two = Normalize.key(two) + one = normalizeKey(one) + two = normalizeKey(two) if (one == this.key) return this if (two == this.key) return this @@ -562,7 +593,7 @@ class Node { */ getDescendant(key) { - key = Normalize.key(key) + key = normalizeKey(key) let descendantFound = null const found = this.nodes.find((node) => { @@ -710,7 +741,7 @@ class Node { getFurthest(key, iterator) { const ancestors = this.getAncestors(key) if (!ancestors) { - key = Normalize.key(key) + key = normalizeKey(key) throw new Error(`Could not find a descendant node with key "${key}".`) } @@ -748,7 +779,7 @@ class Node { */ getFurthestAncestor(key) { - key = Normalize.key(key) + key = normalizeKey(key) return this.nodes.find((node) => { if (node.key == key) return true if (node.kind == 'text') return false @@ -767,7 +798,7 @@ class Node { const ancestors = this.getAncestors(key) if (!ancestors) { - key = Normalize.key(key) + key = normalizeKey(key) throw new Error(`Could not find a descendant node with key "${key}".`) } @@ -1111,7 +1142,7 @@ class Node { */ getNextSibling(key) { - key = Normalize.key(key) + key = normalizeKey(key) const parent = this.getParent(key) const after = parent.nodes @@ -1131,7 +1162,7 @@ class Node { */ getNextText(key) { - key = Normalize.key(key) + key = normalizeKey(key) return this.getTexts() .skipUntil(text => text.key == key) .get(1) @@ -1145,7 +1176,7 @@ class Node { */ getNode(key) { - key = Normalize.key(key) + key = normalizeKey(key) return this.key == key ? this : this.getDescendant(key) } @@ -1277,7 +1308,7 @@ class Node { */ getPreviousSibling(key) { - key = Normalize.key(key) + key = normalizeKey(key) const parent = this.getParent(key) const before = parent.nodes .takeUntil(child => child.key == key) @@ -1297,7 +1328,7 @@ class Node { */ getPreviousText(key) { - key = Normalize.key(key) + key = normalizeKey(key) return this.getTexts() .takeUntil(text => text.key == key) .last() @@ -1611,7 +1642,7 @@ class Node { */ removeDescendant(key) { - key = Normalize.key(key) + key = normalizeKey(key) let node = this let parent = node.getParent(key) @@ -1872,6 +1903,25 @@ class Node { } +/** + * Normalize a key argument `value`. + * + * @param {String|Node} value + * @return {String} + */ + +function normalizeKey(value) { + if (typeof value == 'string') return value + + logger.deprecate('0.14.0', 'An object was passed to a Node method instead of a `key` string. This was previously supported, but is being deprecated because it can have a negative impact on performance. The object in question was:', value) + + if (Node.isNode(value)) { + return value.key + } + + throw new Error(`Invalid \`key\` argument! It must be either a block, an inline, a text, or a string. You passed: ${value}`) +} + /** * Memoize read methods. */ diff --git a/src/models/range.js b/src/models/range.js index 2e897948e..90c466aa3 100644 --- a/src/models/range.js +++ b/src/models/range.js @@ -1,7 +1,8 @@ +import MODEL_TYPES from '../constants/model-types' import Character from './character' import Mark from './mark' -import MODEL_TYPES from '../constants/model-types' +import isPlainObject from 'is-plain-object' import { Record, Set } from 'immutable' /** @@ -21,7 +22,7 @@ const DEFAULTS = { * @type {Range} */ -class Range extends new Record(DEFAULTS) { +class Range extends Record(DEFAULTS) { /** * Create a new `Range` with `attrs`. @@ -31,14 +32,25 @@ class Range extends new Record(DEFAULTS) { */ static create(attrs = {}) { - if (Range.isRange(attrs)) return attrs + if (Range.isRange(attrs)) { + return attrs + } - const range = new Range({ - text: attrs.text, - marks: Mark.createSet(attrs.marks), - }) + if (typeof attrs == 'string') { + attrs = { text: attrs } + } - return range + if (isPlainObject(attrs)) { + const { marks, text } = attrs + const range = new Range({ + text, + marks: Mark.createSet(marks), + }) + + return range + } + + throw new Error(`\`Range.create\` only accepts objects, strings or ranges, but you passed it: ${attrs}`) } /** diff --git a/src/models/schema.js b/src/models/schema.js index fd06c172a..ba3e784ea 100644 --- a/src/models/schema.js +++ b/src/models/schema.js @@ -2,6 +2,7 @@ import MODEL_TYPES from '../constants/model-types' import React from 'react' import find from 'lodash/find' +import isPlainObject from 'is-plain-object' import isReactComponent from '../utils/is-react-component' import logger from '../utils/logger' import typeOf from 'type-of' @@ -23,7 +24,7 @@ const DEFAULTS = { * @type {Schema} */ -class Schema extends new Record(DEFAULTS) { +class Schema extends Record(DEFAULTS) { /** * Create a new `Schema` with `attrs`. @@ -33,9 +34,16 @@ class Schema extends new Record(DEFAULTS) { */ static create(attrs = {}) { - if (Schema.isSchema(attrs)) return attrs - const schema = new Schema(normalizeProperties(attrs)) - return schema + if (Schema.isSchema(attrs)) { + return attrs + } + + if (isPlainObject(attrs)) { + const schema = new Schema(normalizeProperties(attrs)) + return schema + } + + throw new Error(`\`Schema.create\` only accepts objects or schemas, but you passed it: ${attrs}`) } /** diff --git a/src/models/selection.js b/src/models/selection.js index 540d93621..ffc845fd5 100644 --- a/src/models/selection.js +++ b/src/models/selection.js @@ -1,6 +1,7 @@ -import logger from '../utils/logger' import MODEL_TYPES from '../constants/model-types' +import isPlainObject from 'is-plain-object' +import logger from '../utils/logger' import { Record } from 'immutable' /** @@ -25,7 +26,7 @@ const DEFAULTS = { * @type {Selection} */ -class Selection extends new Record(DEFAULTS) { +class Selection extends Record(DEFAULTS) { /** * Create a new `Selection` with `attrs`. @@ -35,9 +36,51 @@ class Selection extends new Record(DEFAULTS) { */ static create(attrs = {}) { - if (Selection.isSelection(attrs)) return attrs - const selection = new Selection(attrs) - return selection + if (Selection.isSelection(attrs)) { + return attrs + } + + if (isPlainObject(attrs)) { + const selection = new Selection(attrs) + return selection + } + + throw new Error(`\`Selection.create\` only accepts objects or selections, but you passed it: ${attrs}`) + } + + /** + * Create a dictionary of settable selection properties from `attrs`. + * + * @param {Object|String|Selection} attrs + * @return {Object} + */ + + static createProperties(attrs = {}) { + if (Selection.isSelection(attrs)) { + return { + anchorKey: attrs.anchorKey, + anchorOffset: attrs.anchorOffset, + focusKey: attrs.focusKey, + focusOffset: attrs.focusOffset, + isBackward: attrs.isBackward, + isFocused: attrs.isFocused, + marks: attrs.marks, + } + } + + if (isPlainObject(attrs)) { + const props = {} + if ('anchorKey' in attrs) props.anchorKey = attrs.anchorKey + if ('anchorOffset' in attrs) props.anchorOffset = attrs.anchorOffset + if ('focusKey' in attrs) props.focusKey = attrs.focusKey + if ('focusOffset' in attrs) props.focusOffset = attrs.focusOffset + if ('isBackward' in attrs) props.isBackward = attrs.isBackward + if ('isFocused' in attrs) props.isFocused = attrs.isFocused + if ('marks' in attrs) props.marks = attrs.marks + return props + } + + throw new Error(`\`Selection.createProperties\` only accepts objects or selections, but you passed it: ${attrs}`) } /** diff --git a/src/models/stack.js b/src/models/stack.js index 9af555fb9..5117b9554 100644 --- a/src/models/stack.js +++ b/src/models/stack.js @@ -51,7 +51,7 @@ const DEFAULTS = { * @type {Stack} */ -class Stack extends new Record(DEFAULTS) { +class Stack extends Record(DEFAULTS) { /** * Constructor. diff --git a/src/models/state.js b/src/models/state.js index 79654ce2e..f0a4da1a0 100644 --- a/src/models/state.js +++ b/src/models/state.js @@ -5,6 +5,7 @@ import Change from './change' import Document from './document' import History from './history' import Selection from './selection' +import isPlainObject from 'is-plain-object' import logger from '../utils/logger' import { Record, Set, List, Map } from 'immutable' @@ -15,11 +16,11 @@ import { Record, Set, List, Map } from 'immutable' */ const DEFAULTS = { - document: new Document(), - selection: new Selection(), - history: new History(), + document: Document.create(), + selection: Selection.create(), + history: History.create(), data: new Map(), - isNative: false + isNative: false, } /** @@ -28,7 +29,7 @@ const DEFAULTS = { * @type {State} */ -class State extends new Record(DEFAULTS) { +class State extends Record(DEFAULTS) { /** * Create a new `State` with `attrs`. @@ -40,37 +41,43 @@ class State extends new Record(DEFAULTS) { */ static create(attrs = {}, options = {}) { - if (State.isState(attrs)) return attrs - - const document = Document.create(attrs.document) - let selection = Selection.create(attrs.selection) - let data = new Map() - - if (selection.isUnset) { - const text = document.getFirstText() - if (text) selection = selection.collapseToStartOf(text) + if (State.isState(attrs)) { + return attrs } - // Set default value for `data`. - if (options.plugins) { - for (const plugin of options.plugins) { - if (plugin.data) data = data.merge(plugin.data) + if (isPlainObject(attrs)) { + const document = Document.create(attrs.document) + let selection = Selection.create(attrs.selection) + let data = new Map() + + if (selection.isUnset) { + const text = document.getFirstText() + if (text) selection = selection.collapseToStartOf(text) } + + // Set default value for `data`. + if (options.plugins) { + for (const plugin of options.plugins) { + if (plugin.data) data = data.merge(plugin.data) + } + } + + // Then add data provided in `attrs`. + if (attrs.data) data = data.merge(attrs.data) + + let state = new State({ document, selection, data }) + + if (options.normalize !== false) { + state = state + .change({ save: false }) + .normalize(SCHEMA) + .state + } + + return state } - // Then add data provided in `attrs`. - if (attrs.data) data = data.merge(attrs.data) - - let state = new State({ document, selection, data }) - - if (options.normalize !== false) { - state = state - .change({ save: false }) - .normalize(SCHEMA) - .state - } - - return state + throw new Error(`\`State.create\` only accepts objects or states, but you passed it: ${attrs}`) } /** diff --git a/src/models/text.js b/src/models/text.js index 7acd5abe1..449291329 100644 --- a/src/models/text.js +++ b/src/models/text.js @@ -3,9 +3,11 @@ import Character from './character' import Mark from './mark' import Range from './range' import MODEL_TYPES from '../constants/model-types' -import memoize from '../utils/memoize' import generateKey from '../utils/generate-key' -import { List, Record, OrderedSet, Set, is } from 'immutable' +import isPlainObject from 'is-plain-object' +import logger from '../utils/logger' +import memoize from '../utils/memoize' +import { List, Record, OrderedSet, is } from 'immutable' /** * Default properties. @@ -15,7 +17,7 @@ import { List, Record, OrderedSet, Set, is } from 'immutable' const DEFAULTS = { characters: new List(), - key: null + key: undefined, } /** @@ -24,57 +26,45 @@ const DEFAULTS = { * @type {Text} */ -class Text extends new Record(DEFAULTS) { +class Text extends Record(DEFAULTS) { /** * Create a new `Text` with `attrs`. * - * @param {Object|Text} attrs + * @param {Object|Array|List|String|Text} attrs * @return {Text} */ static create(attrs = {}) { - if (Text.isText(attrs)) return attrs - if (attrs.ranges) return Text.createFromRanges(attrs.ranges) + if (Text.isText(attrs)) { + return attrs + } - const text = new Text({ - characters: Character.createList(attrs.characters), - key: attrs.key || generateKey(), - }) + if (List.isList(attrs) || Array.isArray(attrs)) { + attrs = { ranges: attrs } + } - return text - } + if (typeof attrs == 'string') { + attrs = { ranges: [{ text: attrs }] } + } - /** - * Create a new `Text` from a string - * - * @param {String} text - * @param {Set} marks (optional) - * @return {Text} - */ + if (isPlainObject(attrs)) { + const { characters, ranges, key } = attrs + const chars = ranges + ? ranges + .map(Range.create) + .reduce((l, r) => l.concat(r.getCharacters()), Character.createList()) + : Character.createList(characters) - static createFromString(string, marks = Set()) { - const range = Range.create({ text: string, marks }) - const text = Text.createFromRanges([range]) - return text - } + const text = new Text({ + characters: chars, + key: key || generateKey(), + }) - /** - * Create a new `Text` from a list of ranges - * - * @param {List|Array} ranges - * @return {Text} - */ + return text + } - static createFromRanges(ranges) { - const characters = ranges - .map(Range.create) - .reduce((list, range) => { - return list.concat(range.getCharacters()) - }, Character.createList()) - - const text = Text.create({ characters }) - return text + throw new Error(`\`Text.create\` only accepts objects, arrays, strings or texts, but you passed it: ${attrs}`) } /** @@ -104,6 +94,24 @@ class Text extends new Record(DEFAULTS) { return !!(value && value[MODEL_TYPES.TEXT]) } + /** + * Deprecated. + */ + + static createFromString(string) { + logger.deprecate('0.22.0', 'The `Text.createFromString(string)` method is deprecated, use `Text.create(string)` instead.') + return Text.create(string) + } + + /** + * Deprecated. + */ + + static createFromRanges(ranges) { + logger.deprecate('0.22.0', 'The `Text.createFromRanges(ranges)` method is deprecated, use `Text.create(ranges)` instead.') + return Text.create(ranges) + } + /** * Get the node's kind. * @@ -319,7 +327,7 @@ class Text extends new Record(DEFAULTS) { insertText(index, text, marks) { let { characters } = this - const chars = Character.createListFromText(text, marks) + const chars = Character.createList(text.split('').map(char => ({ text: char, marks }))) characters = characters.slice(0, index) .concat(chars) diff --git a/src/operations/apply.js b/src/operations/apply.js index 1c10beee1..f444065e5 100644 --- a/src/operations/apply.js +++ b/src/operations/apply.js @@ -1,6 +1,7 @@ import Debug from 'debug' -import Normalize from '../utils/normalize' +import Node from '../models/node' +import Mark from '../models/mark' import logger from '../utils/logger' /** @@ -29,7 +30,7 @@ const APPLIERS = { add_mark(state, operation) { const { path, offset, length } = operation - const mark = Normalize.mark(operation.mark) + const mark = Mark.create(operation.mark) let { document } = state let node = document.assertPath(path) node = node.addMark(offset, length, mark) @@ -48,7 +49,7 @@ const APPLIERS = { insert_node(state, operation) { const { path } = operation - const node = Normalize.node(operation.node) + const node = Node.create(operation.node) const index = path[path.length - 1] const rest = path.slice(0, -1) let { document } = state @@ -71,7 +72,7 @@ const APPLIERS = { const { path, offset, text } = operation let { marks } = operation - if (Array.isArray(marks)) marks = Normalize.marks(marks) + if (Array.isArray(marks)) marks = Mark.createSet(marks) let { document, selection } = state const { anchorKey, focusKey, anchorOffset, focusOffset } = selection @@ -207,7 +208,7 @@ const APPLIERS = { remove_mark(state, operation) { const { path, offset, length } = operation - const mark = Normalize.mark(operation.mark) + const mark = Mark.create(operation.mark) let { document } = state let node = document.assertPath(path) node = node.removeMark(offset, length, mark) @@ -341,7 +342,7 @@ const APPLIERS = { set_mark(state, operation) { const { path, offset, length, properties } = operation - const mark = Normalize.mark(operation.mark) + const mark = Mark.create(operation.mark) let { document } = state let node = document.assertPath(path) node = node.updateMark(offset, length, mark, properties) @@ -393,8 +394,8 @@ const APPLIERS = { const properties = { ...operation.properties } let { document, selection } = state - if (properties.marks !== undefined) { - properties.marks = Normalize.marks(properties.marks) + if (properties.marks != null) { + properties.marks = Mark.createSet(properties.marks) } if (properties.anchorPath !== undefined) { diff --git a/src/schemas/core.js b/src/schemas/core.js index 07a10bdff..3edba9ddd 100644 --- a/src/schemas/core.js +++ b/src/schemas/core.js @@ -125,7 +125,7 @@ const rules = [ return node.text !== ' ' || node.nodes.size !== 1 }, normalize: (change, node, result) => { - const text = Text.createFromString(' ') + const text = Text.create(' ') const index = node.nodes.size change.insertNodeByKey(node.key, index, text, OPTS) diff --git a/src/utils/normalize.js b/src/utils/normalize.js deleted file mode 100644 index 06eee74a2..000000000 --- a/src/utils/normalize.js +++ /dev/null @@ -1,351 +0,0 @@ - -import Block from '../models/block' -import Document from '../models/document' -import Inline from '../models/inline' -import Data from '../models/data' -import Mark from '../models/mark' -import Selection from '../models/selection' -import Text from '../models/text' -import logger from './logger' -import typeOf from 'type-of' -import { Set } from 'immutable' - -/** - * Normalize a block argument `value`. - * - * @param {Block|String|Object} value - * @return {Block} - */ - -function block(value) { - if (Block.isBlock(value)) return value - - if ( - Inline.isInline(value) || - Mark.isMark(value) || - Text.isText(value) || - Selection.isSelection(value) - ) { - throw new Error(`Invalid \`block\` argument! It must be a block, an object, or a string. You passed: ${value}`) - } - - - switch (typeOf(value)) { - case 'string': - case 'object': { - return Block.create(nodeProperties(value)) - } - default: { - throw new Error(`Invalid \`block\` argument! It must be a block, an object, or a string. You passed: ${value}`) - } - } -} - -/** - * Normalize an inline argument `value`. - * - * @param {Inline|String|Object} value - * @return {Inline} - */ - -function inline(value) { - if (Inline.isInline(value)) return value - - if ( - Block.isBlock(value) || - Mark.isMark(value) || - Text.isText(value) || - Selection.isSelection(value) - ) { - throw new Error(`Invalid \`inline\` argument! It must be an inline, an object, or a string. You passed: ${value}`) - } - - switch (typeOf(value)) { - case 'string': - case 'object': { - return Inline.create(nodeProperties(value)) - } - default: { - throw new Error(`Invalid \`inline\` argument! It must be an inline, an object, or a string. You passed: ${value}`) - } - } -} - -/** - * Normalize an text argument `value`. - * - * @param {Text|String|Object} value - * @return {Text} - */ - -function text(value) { - if (Text.isText(value)) return value - - if ( - Block.isBlock(value) || - Inline.isInline(value) || - Mark.isMark(value) || - Selection.isSelection(value) - ) { - throw new Error(`Invalid \`text\` argument! It must be a text, an object, or a string. You passed: ${value}`) - } - - switch (typeOf(value)) { - case 'object': { - return Text.create(value) - } - default: { - throw new Error(`Invalid \`text\` argument! It must be an text, an object, or a string. You passed: ${value}`) - } - } -} - -/** - * Normalize a node `value`. - * - * @param {Node|Object} value - * @return {Node} - */ - -function node(value) { - if (Block.isBlock(value)) return value - if (Document.isDocument(value)) return value - if (Inline.isInline(value)) return value - if (Text.isText(value)) return value - - switch (typeOf(value)) { - case 'object': { - switch (value.kind) { - case 'block': return block(value) - case 'inline': return inline(value) - case 'text': return text(value) - default: { - throw new Error(`Invalid \`node.kind\` property. It must be either "block" or "inline". You passed: ${value}`) - } - } - } - default: { - throw new Error(`Invalid \`node\` argument! It must be a block, an inline, a text, or an object. You passed: ${value}`) - } - } -} - -/** - * Normalize a key argument `value`. - * - * @param {String|Node} value - * @return {String} - */ - -function key(value) { - if (typeOf(value) == 'string') return value - - logger.warn('An object was passed to a Node method instead of a `key` string. This was previously supported, but is being deprecated because it can have a negative impact on performance. The object in question was:', value) - - if ( - Block.isBlock(value) || - Document.isDocument(value) || - Inline.isInline(value) || - Text.isText(value) - ) { - return value.key - } - - throw new Error(`Invalid \`key\` argument! It must be either a block, an inline, a text, or a string. You passed: ${value}`) -} - -/** - * Normalize a mark argument `value`. - * - * @param {Mark|String|Object} value - * @return {Mark} - */ - -function mark(value) { - if (Mark.isMark(value)) return value - if ( - Block.isBlock(value) || - Inline.isInline(value) || - Text.isText(value) || - Selection.isSelection(value) - ) { - throw new Error(`Invalid \`mark\` argument! It must be a mark, an object, or a string. You passed: ${value}`) - } - - switch (typeOf(value)) { - case 'string': - case 'object': { - return Mark.create(markProperties(value)) - } - default: { - throw new Error(`Invalid \`mark\` argument! It must be a mark, an object, or a string. You passed: ${value}`) - } - } -} - -/** - * Normalize a set of marks argument `values`. - * - * @param {Set|Array} values - * @return {Set} - */ - -function marks(values) { - if (Set.isSet(values)) return values - - switch (typeOf(values)) { - case 'array': { - return Mark.createSet(values) - } - case 'null': { - return null - } - default: { - throw new Error(`Invalid \`marks\` argument! It must be a set of marks or an array. You passed: ${values}`) - } - } -} - -/** - * Normalize a mark properties argument `value`. - * - * @param {String|Object|Mark} value - * @return {Object} - */ - -function markProperties(value = {}) { - const ret = {} - - switch (typeOf(value)) { - case 'string': { - ret.type = value - break - } - case 'object': { - for (const k in value) { - if (k == 'data') { - if (value[k] !== undefined) ret[k] = Data.create(value[k]) - } else if (!k.startsWith('@@__SLATE')) { - ret[k] = value[k] - } - } - break - } - default: { - throw new Error(`Invalid mark \`properties\` argument! It must be an object, a string or a mark. You passed: ${value}`) - } - } - - return ret -} - -/** - * Normalize a node properties argument `value`. - * - * @param {String|Object|Node} value - * @return {Object} - */ - -function nodeProperties(value = {}) { - const ret = {} - - switch (typeOf(value)) { - case 'string': { - ret.type = value - break - } - case 'object': { - if (value.isVoid !== undefined) ret.isVoid = !!value.isVoid - for (const k in value) { - if (k == 'data') { - if (value[k] !== undefined) ret[k] = Data.create(value[k]) - } else if (!k.startsWith('@@__SLATE')) { - ret[k] = value[k] - } - } - break - } - default: { - throw new Error(`Invalid node \`properties\` argument! It must be an object, a string or a node. You passed: ${value}`) - } - } - - return ret -} - -/** - * Normalize a selection argument `value`. - * - * @param {Selection|Object} value - * @return {Selection} - */ - -function selection(value) { - if (Selection.isSelection(value)) return value - if ( - Mark.isMark(value) || - Block.isBlock(value) || - Inline.isInline(value) || - Text.isText(value) - ) { - throw new Error(`Invalid \`selection\` argument! It must be a selection or an object. You passed: ${value}`)`` - } - - switch (typeOf(value)) { - case 'object': { - return Selection.create(value) - } - default: { - throw new Error(`Invalid \`selection\` argument! It must be a selection or an object. You passed: ${value}`) - } - } -} - -/** - * Normalize a selection properties argument `value`. - * - * @param {Object|Selection} value - * @return {Object} - */ - -function selectionProperties(value = {}) { - const ret = {} - - switch (typeOf(value)) { - case 'object': { - if (value.anchorKey !== undefined) ret.anchorKey = value.anchorKey - if (value.anchorOffset !== undefined) ret.anchorOffset = value.anchorOffset - if (value.focusKey !== undefined) ret.focusKey = value.focusKey - if (value.focusOffset !== undefined) ret.focusOffset = value.focusOffset - if (value.isBackward !== undefined) ret.isBackward = !!value.isBackward - if (value.isFocused !== undefined) ret.isFocused = !!value.isFocused - if (value.marks !== undefined) ret.marks = value.marks - break - } - default: { - throw new Error(`Invalid selection \`properties\` argument! It must be an object or a selection. You passed: ${value}`) - } - } - - return ret -} - -/** - * Export. - * - * @type {Object} - */ - -export default { - block, - inline, - node, - key, - mark, - marks, - markProperties, - nodeProperties, - selection, - selectionProperties, - text, -} diff --git a/yarn.lock b/yarn.lock index 568458884..3d9e9f932 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3251,6 +3251,12 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -3329,6 +3335,10 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"