1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-29 18:09:49 +02:00

refactor to eliminate ./utils/normalize, fixes #372 (#1060)

* refactor to eliminate ./utils/normalize, fixes #372

* fix mark default value
This commit is contained in:
Ian Storm Taylor
2017-09-06 14:24:48 -07:00
committed by GitHub
parent e77cae7bf7
commit 8d47f8e8b6
24 changed files with 537 additions and 644 deletions

View File

@@ -14,6 +14,7 @@
"immutable": "^3.8.1", "immutable": "^3.8.1",
"is-empty": "^1.0.0", "is-empty": "^1.0.0",
"is-in-browser": "^1.1.3", "is-in-browser": "^1.1.3",
"is-plain-object": "^2.0.4",
"is-window": "^1.0.2", "is-window": "^1.0.2",
"keycode": "^2.1.2", "keycode": "^2.1.2",
"lodash": "^4.17.4", "lodash": "^4.17.4",

View File

@@ -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. * Changes.
@@ -49,7 +51,7 @@ PROXY_TRANSFORMS.forEach((method) => {
*/ */
Changes.addMark = (change, mark) => { Changes.addMark = (change, mark) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { state } = change const { state } = change
const { document, selection } = state const { document, selection } = state
@@ -95,7 +97,7 @@ Changes.delete = (change) => {
*/ */
Changes.insertBlock = (change, block) => { Changes.insertBlock = (change, block) => {
block = Normalize.block(block) block = Block.create(block)
const { state } = change const { state } = change
const { selection } = state const { selection } = state
change.insertBlockAtRange(selection, block) change.insertBlockAtRange(selection, block)
@@ -155,7 +157,7 @@ Changes.insertFragment = (change, fragment) => {
*/ */
Changes.insertInline = (change, inline) => { Changes.insertInline = (change, inline) => {
inline = Normalize.inline(inline) inline = Inline.create(inline)
const { state } = change const { state } = change
const { selection } = state const { selection } = state
change.insertInlineAtRange(selection, inline) change.insertInlineAtRange(selection, inline)
@@ -209,7 +211,7 @@ Changes.splitBlock = (change, depth = 1) => {
*/ */
Changes.removeMark = (change, mark) => { Changes.removeMark = (change, mark) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { state } = change const { state } = change
const { document, selection } = state const { document, selection } = state
@@ -239,7 +241,7 @@ Changes.removeMark = (change, mark) => {
*/ */
Changes.toggleMark = (change, mark) => { Changes.toggleMark = (change, mark) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { state } = change const { state } = change
const exists = state.activeMarks.has(mark) const exists = state.activeMarks.has(mark)

View File

@@ -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 String from '../utils/string'
import SCHEMA from '../schemas/core' import SCHEMA from '../schemas/core'
import { List } from 'immutable' import { List } from 'immutable'
@@ -597,7 +600,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
*/ */
Changes.insertBlockAtRange = (change, range, block, options = {}) => { Changes.insertBlockAtRange = (change, range, block, options = {}) => {
block = Normalize.block(block) block = Block.create(block)
const { normalize = true } = options const { normalize = true } = options
if (range.isExpanded) { if (range.isExpanded) {
@@ -766,7 +769,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
Changes.insertInlineAtRange = (change, range, inline, options = {}) => { Changes.insertInlineAtRange = (change, range, inline, options = {}) => {
const { normalize = true } = options const { normalize = true } = options
inline = Normalize.inline(inline) inline = Inline.create(inline)
if (range.isExpanded) { if (range.isExpanded) {
change.deleteAtRange(range, OPTS) change.deleteAtRange(range, OPTS)
@@ -978,7 +981,7 @@ Changes.splitInlineAtRange = (change, range, height = Infinity, options = {}) =>
Changes.toggleMarkAtRange = (change, range, mark, options = {}) => { Changes.toggleMarkAtRange = (change, range, mark, options = {}) => {
if (range.isCollapsed) return if (range.isCollapsed) return
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
@@ -1004,7 +1007,7 @@ Changes.toggleMarkAtRange = (change, range, mark, options = {}) => {
*/ */
Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => { Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
properties = Normalize.nodeProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const { normalize = true } = options
let { state } = change let { state } = change
@@ -1097,7 +1100,7 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
*/ */
Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => { Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
properties = Normalize.nodeProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
@@ -1143,7 +1146,7 @@ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
*/ */
Changes.wrapBlockAtRange = (change, range, block, options = {}) => { Changes.wrapBlockAtRange = (change, range, block, options = {}) => {
block = Normalize.block(block) block = Block.create(block)
block = block.set('nodes', block.nodes.clear()) block = block.set('nodes', block.nodes.clear())
const { normalize = true } = options const { normalize = true } = options
@@ -1229,7 +1232,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
return change.wrapInlineByKey(inlineParent.key, inline, options) return change.wrapInlineByKey(inlineParent.key, inline, options)
} }
inline = Normalize.inline(inline) inline = Inline.create(inline)
inline = inline.set('nodes', inline.nodes.clear()) inline = inline.set('nodes', inline.nodes.clear())
const blocks = document.getBlocksAtRange(range) const blocks = document.getBlocksAtRange(range)

View File

@@ -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' import SCHEMA from '../schemas/core'
/** /**
@@ -23,7 +26,7 @@ const Changes = {}
*/ */
Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => { Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
const { document } = state const { document } = state
@@ -232,7 +235,7 @@ Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => {
*/ */
Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => { Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
const { document } = state const { document } = state
@@ -376,8 +379,8 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
*/ */
Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = {}) => { Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = {}) => {
mark = Normalize.mark(mark) mark = Mark.create(mark)
properties = Normalize.markProperties(properties) properties = Mark.createProperties(properties)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
const { document } = state const { document } = state
@@ -409,7 +412,7 @@ Changes.setMarkByKey = (change, key, offset, length, mark, properties, options =
*/ */
Changes.setNodeByKey = (change, key, properties, options = {}) => { Changes.setNodeByKey = (change, key, properties, options = {}) => {
properties = Normalize.nodeProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const { normalize = true } = options
const { state } = change const { state } = change
const { document } = state const { document } = state
@@ -599,7 +602,7 @@ Changes.unwrapNodeByKey = (change, key, options = {}) => {
*/ */
Changes.wrapInlineByKey = (change, key, inline, options) => { Changes.wrapInlineByKey = (change, key, inline, options) => {
inline = Normalize.inline(inline) inline = Inline.create(inline)
inline = inline.set('nodes', inline.nodes.clear()) inline = inline.set('nodes', inline.nodes.clear())
const { document } = change.state const { document } = change.state
@@ -622,7 +625,7 @@ Changes.wrapInlineByKey = (change, key, inline, options) => {
*/ */
Changes.wrapBlockByKey = (change, key, block, options) => { Changes.wrapBlockByKey = (change, key, block, options) => {
block = Normalize.block(block) block = Block.create(block)
block = block.set('nodes', block.nodes.clear()) block = block.set('nodes', block.nodes.clear())
const { document } = change.state const { document } = change.state

View File

@@ -1,5 +1,4 @@
import Normalize from '../utils/normalize'
import Schema from '../models/schema' import Schema from '../models/schema'
import { Set } from 'immutable' 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 the schema has no validation rules, there's nothing to normalize.
if (!schema.hasValidators) return if (!schema.hasValidators) return
key = Normalize.key(key)
const { state } = change const { state } = change
const { document } = state const { document } = state
const node = document.assertNode(key) const node = document.assertNode(key)
normalizeNodeAndChildren(change, node, schema) normalizeNodeAndChildren(change, node, schema)
} }

View File

@@ -1,5 +1,5 @@
import Normalize from '../utils/normalize' import Selection from '../models/selection'
import isEmpty from 'is-empty' import isEmpty from 'is-empty'
import logger from '../utils/logger' import logger from '../utils/logger'
import pick from 'lodash/pick' import pick from 'lodash/pick'
@@ -20,7 +20,7 @@ const Changes = {}
*/ */
Changes.select = (change, properties, options = {}) => { Changes.select = (change, properties, options = {}) => {
properties = Normalize.selectionProperties(properties) properties = Selection.createProperties(properties)
const { snapshot = false } = options const { snapshot = false } = options
const { state } = change const { state } = change

View File

@@ -11,11 +11,11 @@ import './document'
import Data from './data' import Data from './data'
import Node from './node' import Node from './node'
import Inline from './inline'
import Text from './text' import Text from './text'
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import generateKey from '../utils/generate-key' 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. * Default properties.
@@ -26,9 +26,9 @@ import { Map, List, Record } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
isVoid: false, isVoid: false,
key: null, key: undefined,
nodes: new List(), nodes: new List(),
type: null type: undefined,
} }
/** /**
@@ -37,55 +37,64 @@ const DEFAULTS = {
* @type {Block} * @type {Block}
*/ */
class Block extends new Record(DEFAULTS) { class Block extends Record(DEFAULTS) {
/** /**
* Create a new `Block` with `attrs`. * Create a new `Block` with `attrs`.
* *
* @param {Object|Block} attrs * @param {Object|String|Block} attrs
* @return {Block} * @return {Block}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Block.isBlock(attrs)) return attrs if (Block.isBlock(attrs)) {
if (Inline.isInline(attrs)) return attrs return attrs
if (Text.isText(attrs)) return attrs }
if (!attrs.type) { if (typeof attrs == 'string') {
throw new Error('You must pass a block `type`.') attrs = { type: attrs }
}
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 { nodes } = attrs
const empty = !nodes || nodes.size == 0 || nodes.length == 0
const block = new Block({ const block = new Block({
type: attrs.type, data: Data.create(data),
key: attrs.key || generateKey(), isVoid: !!isVoid,
data: Data.create(attrs.data), key: key || generateKey(),
isVoid: !!attrs.isVoid, nodes: Node.createList(nodes),
nodes: Node.createList(empty ? [Text.create()] : nodes), type,
}) })
return block 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`. * Create a list of `Blocks` from `elements`.
* *
* @param {Array<Object|Block>|List<Block>} elements * @param {Array<Block|Object>|List<Block|Object>} elements
* @return {List<Block>} * @return {List<Block>}
*/ */
static createList(elements = []) { static createList(elements = []) {
if (List.isList(elements)) { if (List.isList(elements) || Array.isArray(elements)) {
return elements
}
if (Array.isArray(elements)) {
const list = new List(elements.map(Block.create)) const list = new List(elements.map(Block.create))
return list 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} * @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} * @return {String}
*/ */

View File

@@ -1,6 +1,8 @@
import Mark from './mark'
import MODEL_TYPES from '../constants/model-types' 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' import { List, Record, Set } from 'immutable'
/** /**
@@ -11,7 +13,7 @@ import { List, Record, Set } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
marks: new Set(), marks: new Set(),
text: '' text: '',
} }
/** /**
@@ -20,58 +22,56 @@ const DEFAULTS = {
* @type {Character} * @type {Character}
*/ */
class Character extends new Record(DEFAULTS) { class Character extends Record(DEFAULTS) {
/** /**
* Create a `Character` with `attrs`. * Create a `Character` with `attrs`.
* *
* @param {Object|Character} attrs * @param {Object|String|Character} attrs
* @return {Character} * @return {Character}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Character.isCharacter(attrs)) return attrs if (Character.isCharacter(attrs)) {
return attrs
}
if (typeof attrs == 'string') {
attrs = { text: attrs }
}
if (isPlainObject(attrs)) {
const { marks, text } = attrs
const character = new Character({ const character = new Character({
text: attrs.text, text,
marks: Mark.createSet(attrs.marks), marks: Mark.createSet(marks),
}) })
return character 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`. * Create a list of `Characters` from `elements`.
* *
* @param {Array<Object|Character>|List<Character>} elements * @param {String|Array<Object|Character|String>|List<Object|Character|String>} elements
* @return {List<Character>} * @return {List<Character>}
*/ */
static createList(elements = []) { static createList(elements = []) {
if (List.isList(elements)) { if (typeof elements == 'string') {
return elements elements = elements.split('')
} }
if (Array.isArray(elements)) { if (List.isList(elements) || Array.isArray(elements)) {
const list = new List(elements.map(Character.create)) const list = new List(elements.map(Character.create))
return list return list
} }
throw new Error(`Character.createList() must be passed an \`Array\` or a \`List\`. You passed: ${elements}`) throw new Error(`\`Block.createList\` only accepts strings, arrays or lists, but you passed it: ${elements}`)
}
/**
* Create a characters list from a `string` and optional `marks`.
*
* @param {String} string
* @param {Set<Mark>} marks (optional)
* @return {List<Character>}
*/
static createListFromText(string, marks) {
const chars = string.split('').map(text => ({ text, marks }))
const list = Character.createList(chars)
return list
} }
/** /**
@@ -85,6 +85,15 @@ class Character extends new Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.CHARACTER]) 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. * Get the kind.
* *

View File

@@ -1,4 +1,5 @@
import isPlainObject from 'is-plain-object'
import { Map } from 'immutable' import { Map } from 'immutable'
/** /**
@@ -15,14 +16,20 @@ const Data = {
/** /**
* Create a new `Data` with `attrs`. * Create a new `Data` with `attrs`.
* *
* @param {Object} attrs * @param {Object|Data|Map} attrs
* @return {Data} data * @return {Data} data
*/ */
create(attrs = {}) { create(attrs = {}) {
return Map.isMap(attrs) if (Map.isMap(attrs)) {
? attrs return attrs
: new Map(attrs) }
if (isPlainObject(attrs)) {
return new Map(attrs)
}
throw new Error(`\`Data.create\` only accepts objects or maps, but you passed it: ${attrs}`)
} }
} }

View File

@@ -14,6 +14,7 @@ import Data from './data'
import Node from './node' import Node from './node'
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import generateKey from '../utils/generate-key' import generateKey from '../utils/generate-key'
import isPlainObject from 'is-plain-object'
import { List, Map, Record } from 'immutable' import { List, Map, Record } from 'immutable'
/** /**
@@ -24,7 +25,7 @@ import { List, Map, Record } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
key: null, key: undefined,
nodes: new List(), nodes: new List(),
} }
@@ -34,27 +35,38 @@ const DEFAULTS = {
* @type {Document} * @type {Document}
*/ */
class Document extends new Record(DEFAULTS) { class Document extends Record(DEFAULTS) {
/** /**
* Create a new `Document` with `attrs`. * Create a new `Document` with `attrs`.
* *
* @param {Object|Document} attrs * @param {Object|Array|List|Text} attrs
* @return {Document} * @return {Document}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Document.isDocument(attrs)) return attrs if (Document.isDocument(attrs)) {
return attrs
}
if (List.isList(attrs) || Array.isArray(attrs)) {
attrs = { nodes: attrs }
}
if (isPlainObject(attrs)) {
const { data, key, nodes } = attrs
const document = new Document({ const document = new Document({
key: attrs.key || generateKey(), key: key || generateKey(),
data: Data.create(attrs.data), data: Data.create(data),
nodes: Node.createList(attrs.nodes), nodes: Node.createList(nodes),
}) })
return document return document
} }
throw new Error(`\`Document.create\` only accepts objects, arrays, lists or documents, but you passed it: ${attrs}`)
}
/** /**
* Check if a `value` is a `Document`. * Check if a `value` is a `Document`.
* *
@@ -77,7 +89,7 @@ class Document extends new Record(DEFAULTS) {
} }
/** /**
* Is the document empty? * Check if the document is empty.
* *
* @return {Boolean} * @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} * @return {String}
*/ */

View File

@@ -2,6 +2,7 @@
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import Debug from 'debug' import Debug from 'debug'
import isEqual from 'lodash/isEqual' import isEqual from 'lodash/isEqual'
import isPlainObject from 'is-plain-object'
import { Record, Stack } from 'immutable' import { Record, Stack } from 'immutable'
/** /**
@@ -29,18 +30,21 @@ const DEFAULTS = {
* @type {History} * @type {History}
*/ */
class History extends new Record(DEFAULTS) { class History extends Record(DEFAULTS) {
/** /**
* Create a new `History` with `attrs`. * Create a new `History` with `attrs`.
* *
* @param {Object} attrs * @param {Object|History} attrs
* @return {History} * @return {History}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (History.isHistory(attrs)) return attrs if (History.isHistory(attrs)) {
return attrs
}
if (isPlainObject(attrs)) {
const history = new History({ const history = new History({
undos: attrs.undos || new Stack(), undos: attrs.undos || new Stack(),
redos: attrs.redos || new Stack(), redos: attrs.redos || new Stack(),
@@ -49,6 +53,9 @@ class History extends new Record(DEFAULTS) {
return history return history
} }
throw new Error(`\`History.create\` only accepts objects or histories, but you passed it: ${attrs}`)
}
/** /**
* Check if a `value` is a `History`. * Check if a `value` is a `History`.
* *

View File

@@ -9,12 +9,12 @@ import './document'
* Dependencies. * Dependencies.
*/ */
import Block from './block'
import Data from './data' import Data from './data'
import Node from './node' import Node from './node'
import Text from './text' import Text from './text'
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import generateKey from '../utils/generate-key' import generateKey from '../utils/generate-key'
import isPlainObject from 'is-plain-object'
import { List, Map, Record } from 'immutable' import { List, Map, Record } from 'immutable'
/** /**
@@ -26,9 +26,9 @@ import { List, Map, Record } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
isVoid: false, isVoid: false,
key: null, key: undefined,
nodes: new List(), nodes: new List(),
type: null type: undefined,
} }
/** /**
@@ -37,47 +37,64 @@ const DEFAULTS = {
* @type {Inline} * @type {Inline}
*/ */
class Inline extends new Record(DEFAULTS) { class Inline extends Record(DEFAULTS) {
/** /**
* Create a new `Inline` with `attrs`. * Create a new `Inline` with `attrs`.
* *
* @param {Object|Inline} attrs * @param {Object|String|Inline} attrs
* @return {Inline} * @return {Inline}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Block.isBlock(attrs)) return attrs if (Inline.isInline(attrs)) {
if (Inline.isInline(attrs)) return attrs return attrs
if (Text.isText(attrs)) return attrs }
if (!attrs.type) { if (typeof attrs == 'string') {
throw new Error('You must pass an inline `type`.') attrs = { type: attrs }
}
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 { nodes } = attrs
const empty = !nodes || nodes.size == 0 || nodes.length == 0
const inline = new Inline({ const inline = new Inline({
type: attrs.type, data: Data.create(data),
key: attrs.key || generateKey(), isVoid: !!isVoid,
data: Data.create(attrs.data), key: key || generateKey(),
isVoid: !!attrs.isVoid, nodes: Node.createList(nodes),
nodes: Node.createList(empty ? [Text.create()] : nodes), type,
}) })
return inline 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. * Create a list of `Inlines` from an array.
* *
* @param {Array<Object|Inline>} elements * @param {Array<Inline|Object>|List<Inline|Object>} elements
* @return {List<Inline>} * @return {List<Inline>}
*/ */
static createList(elements = []) { static createList(elements = []) {
if (List.isList(elements)) return elements if (List.isList(elements) || Array.isArray(elements)) {
return new List(elements.map(Inline.create)) 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} * @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} * @return {String}
*/ */

View File

@@ -1,7 +1,8 @@
import Data from './data'
import memoize from '../utils/memoize'
import MODEL_TYPES from '../constants/model-types' 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' import { Map, Record, Set } from 'immutable'
/** /**
@@ -12,7 +13,7 @@ import { Map, Record, Set } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
type: null type: undefined,
} }
/** /**
@@ -21,7 +22,7 @@ const DEFAULTS = {
* @type {Mark} * @type {Mark}
*/ */
class Mark extends new Record(DEFAULTS) { class Mark extends Record(DEFAULTS) {
/** /**
* Create a new `Mark` with `attrs`. * Create a new `Mark` with `attrs`.
@@ -31,20 +32,32 @@ class Mark extends new Record(DEFAULTS) {
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Mark.isMark(attrs)) return attrs if (Mark.isMark(attrs)) {
return attrs
}
if (!attrs.type) { if (typeof attrs == 'string') {
throw new Error(`You must provide \`attrs.type\` to \`Mark.create(attrs)\`.`) attrs = { type: attrs }
}
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({ const mark = new Mark({
type: attrs.type, type,
data: Data.create(attrs.data), data: Data.create(data),
}) })
return mark return mark
} }
throw new Error(`\`Mark.create\` only accepts objects, strings or marks, but you passed it: ${attrs}`)
}
/** /**
* Create a set of marks. * Create a set of marks.
* *
@@ -53,11 +66,7 @@ class Mark extends new Record(DEFAULTS) {
*/ */
static createSet(elements) { static createSet(elements) {
if (Set.isSet(elements)) { if (Set.isSet(elements) || Array.isArray(elements)) {
return elements
}
if (Array.isArray(elements)) {
const marks = new Set(elements.map(Mark.create)) const marks = new Set(elements.map(Mark.create))
return marks return marks
} }
@@ -66,7 +75,36 @@ class Mark extends new Record(DEFAULTS) {
return new Set() 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}`)
} }
/** /**

View File

@@ -1,12 +1,13 @@
import Block from './block' import Block from './block'
import Data from './data'
import Document from './document' import Document from './document'
import Inline from './inline' import Inline from './inline'
import Normalize from '../utils/normalize'
import Text from './text' import Text from './text'
import direction from 'direction' import direction from 'direction'
import generateKey from '../utils/generate-key' import generateKey from '../utils/generate-key'
import isInRange from '../utils/is-in-range' import isInRange from '../utils/is-in-range'
import isPlainObject from 'is-plain-object'
import logger from '../utils/logger' import logger from '../utils/logger'
import memoize from '../utils/memoize' import memoize from '../utils/memoize'
import { List, OrderedSet, Set } from 'immutable' import { List, OrderedSet, Set } from 'immutable'
@@ -30,22 +31,25 @@ class Node {
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Block.isBlock(attrs)) return attrs if (Node.isNode(attrs)) {
if (Document.isDocument(attrs)) return attrs return attrs
if (Inline.isInline(attrs)) return attrs }
if (Text.isText(attrs)) return attrs
if (isPlainObject(attrs)) {
switch (attrs.kind) { switch (attrs.kind) {
case 'block': return Block.create(attrs) case 'block': return Block.create(attrs)
case 'document': return Document.create(attrs) case 'document': return Document.create(attrs)
case 'inline': return Inline.create(attrs) case 'inline': return Inline.create(attrs)
case 'text': return Text.create(attrs) case 'text': return Text.create(attrs)
default: { default: {
throw new Error(`You must pass a \`kind\` attribute to create a \`Node\`.`) throw new Error('`Node.create` requires a `kind` string.')
} }
} }
} }
throw new Error(`\`Node.create\` only accepts objects or nodes but you passed it: ${attrs}`)
}
/** /**
* Create a list of `Nodes` from an array. * Create a list of `Nodes` from an array.
* *
@@ -54,16 +58,43 @@ class Node {
*/ */
static createList(elements = []) { static createList(elements = []) {
if (List.isList(elements)) { if (List.isList(elements) || Array.isArray(elements)) {
return elements
}
if (Array.isArray(elements)) {
const list = new List(elements.map(Node.create)) const list = new List(elements.map(Node.create))
return list 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) { areDescendantsSorted(first, second) {
first = Normalize.key(first) first = normalizeKey(first)
second = Normalize.key(second) second = normalizeKey(second)
let sorted let sorted
@@ -121,7 +152,7 @@ class Node {
const child = this.getChild(key) const child = this.getChild(key)
if (!child) { if (!child) {
key = Normalize.key(key) key = normalizeKey(key)
throw new Error(`Could not find a child node with key "${key}".`) throw new Error(`Could not find a child node with key "${key}".`)
} }
@@ -139,7 +170,7 @@ class Node {
const descendant = this.getDescendant(key) const descendant = this.getDescendant(key)
if (!descendant) { if (!descendant) {
key = Normalize.key(key) key = normalizeKey(key)
throw new Error(`Could not find a descendant node with key "${key}".`) throw new Error(`Could not find a descendant node with key "${key}".`)
} }
@@ -157,7 +188,7 @@ class Node {
const node = this.getNode(key) const node = this.getNode(key)
if (!node) { if (!node) {
key = Normalize.key(key) key = normalizeKey(key)
throw new Error(`Could not find a node with key "${key}".`) throw new Error(`Could not find a node with key "${key}".`)
} }
@@ -251,7 +282,7 @@ class Node {
*/ */
getAncestors(key) { getAncestors(key) {
key = Normalize.key(key) key = normalizeKey(key)
if (key == this.key) return List() if (key == this.key) return List()
if (this.hasChild(key)) return List([this]) if (this.hasChild(key)) return List([this])
@@ -428,7 +459,7 @@ class Node {
*/ */
getChild(key) { getChild(key) {
key = Normalize.key(key) key = normalizeKey(key)
return this.nodes.find(node => node.key == key) return this.nodes.find(node => node.key == key)
} }
@@ -441,7 +472,7 @@ class Node {
*/ */
getClosest(key, iterator) { getClosest(key, iterator) {
key = Normalize.key(key) key = normalizeKey(key)
const ancestors = this.getAncestors(key) const ancestors = this.getAncestors(key)
if (!ancestors) { if (!ancestors) {
throw new Error(`Could not find a descendant node with key "${key}".`) throw new Error(`Could not find a descendant node with key "${key}".`)
@@ -493,8 +524,8 @@ class Node {
*/ */
getCommonAncestor(one, two) { getCommonAncestor(one, two) {
one = Normalize.key(one) one = normalizeKey(one)
two = Normalize.key(two) two = normalizeKey(two)
if (one == this.key) return this if (one == this.key) return this
if (two == this.key) return this if (two == this.key) return this
@@ -562,7 +593,7 @@ class Node {
*/ */
getDescendant(key) { getDescendant(key) {
key = Normalize.key(key) key = normalizeKey(key)
let descendantFound = null let descendantFound = null
const found = this.nodes.find((node) => { const found = this.nodes.find((node) => {
@@ -710,7 +741,7 @@ class Node {
getFurthest(key, iterator) { getFurthest(key, iterator) {
const ancestors = this.getAncestors(key) const ancestors = this.getAncestors(key)
if (!ancestors) { if (!ancestors) {
key = Normalize.key(key) key = normalizeKey(key)
throw new Error(`Could not find a descendant node with key "${key}".`) throw new Error(`Could not find a descendant node with key "${key}".`)
} }
@@ -748,7 +779,7 @@ class Node {
*/ */
getFurthestAncestor(key) { getFurthestAncestor(key) {
key = Normalize.key(key) key = normalizeKey(key)
return this.nodes.find((node) => { return this.nodes.find((node) => {
if (node.key == key) return true if (node.key == key) return true
if (node.kind == 'text') return false if (node.kind == 'text') return false
@@ -767,7 +798,7 @@ class Node {
const ancestors = this.getAncestors(key) const ancestors = this.getAncestors(key)
if (!ancestors) { if (!ancestors) {
key = Normalize.key(key) key = normalizeKey(key)
throw new Error(`Could not find a descendant node with key "${key}".`) throw new Error(`Could not find a descendant node with key "${key}".`)
} }
@@ -1111,7 +1142,7 @@ class Node {
*/ */
getNextSibling(key) { getNextSibling(key) {
key = Normalize.key(key) key = normalizeKey(key)
const parent = this.getParent(key) const parent = this.getParent(key)
const after = parent.nodes const after = parent.nodes
@@ -1131,7 +1162,7 @@ class Node {
*/ */
getNextText(key) { getNextText(key) {
key = Normalize.key(key) key = normalizeKey(key)
return this.getTexts() return this.getTexts()
.skipUntil(text => text.key == key) .skipUntil(text => text.key == key)
.get(1) .get(1)
@@ -1145,7 +1176,7 @@ class Node {
*/ */
getNode(key) { getNode(key) {
key = Normalize.key(key) key = normalizeKey(key)
return this.key == key ? this : this.getDescendant(key) return this.key == key ? this : this.getDescendant(key)
} }
@@ -1277,7 +1308,7 @@ class Node {
*/ */
getPreviousSibling(key) { getPreviousSibling(key) {
key = Normalize.key(key) key = normalizeKey(key)
const parent = this.getParent(key) const parent = this.getParent(key)
const before = parent.nodes const before = parent.nodes
.takeUntil(child => child.key == key) .takeUntil(child => child.key == key)
@@ -1297,7 +1328,7 @@ class Node {
*/ */
getPreviousText(key) { getPreviousText(key) {
key = Normalize.key(key) key = normalizeKey(key)
return this.getTexts() return this.getTexts()
.takeUntil(text => text.key == key) .takeUntil(text => text.key == key)
.last() .last()
@@ -1611,7 +1642,7 @@ class Node {
*/ */
removeDescendant(key) { removeDescendant(key) {
key = Normalize.key(key) key = normalizeKey(key)
let node = this let node = this
let parent = node.getParent(key) 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. * Memoize read methods.
*/ */

View File

@@ -1,7 +1,8 @@
import MODEL_TYPES from '../constants/model-types'
import Character from './character' import Character from './character'
import Mark from './mark' import Mark from './mark'
import MODEL_TYPES from '../constants/model-types' import isPlainObject from 'is-plain-object'
import { Record, Set } from 'immutable' import { Record, Set } from 'immutable'
/** /**
@@ -21,7 +22,7 @@ const DEFAULTS = {
* @type {Range} * @type {Range}
*/ */
class Range extends new Record(DEFAULTS) { class Range extends Record(DEFAULTS) {
/** /**
* Create a new `Range` with `attrs`. * Create a new `Range` with `attrs`.
@@ -31,16 +32,27 @@ class Range extends new Record(DEFAULTS) {
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Range.isRange(attrs)) return attrs if (Range.isRange(attrs)) {
return attrs
}
if (typeof attrs == 'string') {
attrs = { text: attrs }
}
if (isPlainObject(attrs)) {
const { marks, text } = attrs
const range = new Range({ const range = new Range({
text: attrs.text, text,
marks: Mark.createSet(attrs.marks), marks: Mark.createSet(marks),
}) })
return range return range
} }
throw new Error(`\`Range.create\` only accepts objects, strings or ranges, but you passed it: ${attrs}`)
}
/** /**
* Check if a `value` is a `Range`. * Check if a `value` is a `Range`.
* *

View File

@@ -2,6 +2,7 @@
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import React from 'react' import React from 'react'
import find from 'lodash/find' import find from 'lodash/find'
import isPlainObject from 'is-plain-object'
import isReactComponent from '../utils/is-react-component' import isReactComponent from '../utils/is-react-component'
import logger from '../utils/logger' import logger from '../utils/logger'
import typeOf from 'type-of' import typeOf from 'type-of'
@@ -23,7 +24,7 @@ const DEFAULTS = {
* @type {Schema} * @type {Schema}
*/ */
class Schema extends new Record(DEFAULTS) { class Schema extends Record(DEFAULTS) {
/** /**
* Create a new `Schema` with `attrs`. * Create a new `Schema` with `attrs`.
@@ -33,11 +34,18 @@ class Schema extends new Record(DEFAULTS) {
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Schema.isSchema(attrs)) return attrs if (Schema.isSchema(attrs)) {
return attrs
}
if (isPlainObject(attrs)) {
const schema = new Schema(normalizeProperties(attrs)) const schema = new Schema(normalizeProperties(attrs))
return schema return schema
} }
throw new Error(`\`Schema.create\` only accepts objects or schemas, but you passed it: ${attrs}`)
}
/** /**
* Check if a `value` is a `Schema`. * Check if a `value` is a `Schema`.
* *

View File

@@ -1,6 +1,7 @@
import logger from '../utils/logger'
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import isPlainObject from 'is-plain-object'
import logger from '../utils/logger'
import { Record } from 'immutable' import { Record } from 'immutable'
/** /**
@@ -25,7 +26,7 @@ const DEFAULTS = {
* @type {Selection} * @type {Selection}
*/ */
class Selection extends new Record(DEFAULTS) { class Selection extends Record(DEFAULTS) {
/** /**
* Create a new `Selection` with `attrs`. * Create a new `Selection` with `attrs`.
@@ -35,11 +36,53 @@ class Selection extends new Record(DEFAULTS) {
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Selection.isSelection(attrs)) return attrs if (Selection.isSelection(attrs)) {
return attrs
}
if (isPlainObject(attrs)) {
const selection = new Selection(attrs) const selection = new Selection(attrs)
return selection 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}`)
}
/** /**
* Check if a `value` is a `Selection`. * Check if a `value` is a `Selection`.
* *

View File

@@ -51,7 +51,7 @@ const DEFAULTS = {
* @type {Stack} * @type {Stack}
*/ */
class Stack extends new Record(DEFAULTS) { class Stack extends Record(DEFAULTS) {
/** /**
* Constructor. * Constructor.

View File

@@ -5,6 +5,7 @@ import Change from './change'
import Document from './document' import Document from './document'
import History from './history' import History from './history'
import Selection from './selection' import Selection from './selection'
import isPlainObject from 'is-plain-object'
import logger from '../utils/logger' import logger from '../utils/logger'
import { Record, Set, List, Map } from 'immutable' import { Record, Set, List, Map } from 'immutable'
@@ -15,11 +16,11 @@ import { Record, Set, List, Map } from 'immutable'
*/ */
const DEFAULTS = { const DEFAULTS = {
document: new Document(), document: Document.create(),
selection: new Selection(), selection: Selection.create(),
history: new History(), history: History.create(),
data: new Map(), data: new Map(),
isNative: false isNative: false,
} }
/** /**
@@ -28,7 +29,7 @@ const DEFAULTS = {
* @type {State} * @type {State}
*/ */
class State extends new Record(DEFAULTS) { class State extends Record(DEFAULTS) {
/** /**
* Create a new `State` with `attrs`. * Create a new `State` with `attrs`.
@@ -40,8 +41,11 @@ class State extends new Record(DEFAULTS) {
*/ */
static create(attrs = {}, options = {}) { static create(attrs = {}, options = {}) {
if (State.isState(attrs)) return attrs if (State.isState(attrs)) {
return attrs
}
if (isPlainObject(attrs)) {
const document = Document.create(attrs.document) const document = Document.create(attrs.document)
let selection = Selection.create(attrs.selection) let selection = Selection.create(attrs.selection)
let data = new Map() let data = new Map()
@@ -73,6 +77,9 @@ class State extends new Record(DEFAULTS) {
return state return state
} }
throw new Error(`\`State.create\` only accepts objects or states, but you passed it: ${attrs}`)
}
/** /**
* Check if a `value` is a `State`. * Check if a `value` is a `State`.
* *

View File

@@ -3,9 +3,11 @@ import Character from './character'
import Mark from './mark' import Mark from './mark'
import Range from './range' import Range from './range'
import MODEL_TYPES from '../constants/model-types' import MODEL_TYPES from '../constants/model-types'
import memoize from '../utils/memoize'
import generateKey from '../utils/generate-key' 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. * Default properties.
@@ -15,7 +17,7 @@ import { List, Record, OrderedSet, Set, is } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
characters: new List(), characters: new List(),
key: null key: undefined,
} }
/** /**
@@ -24,57 +26,45 @@ const DEFAULTS = {
* @type {Text} * @type {Text}
*/ */
class Text extends new Record(DEFAULTS) { class Text extends Record(DEFAULTS) {
/** /**
* Create a new `Text` with `attrs`. * Create a new `Text` with `attrs`.
* *
* @param {Object|Text} attrs * @param {Object|Array|List|String|Text} attrs
* @return {Text} * @return {Text}
*/ */
static create(attrs = {}) { static create(attrs = {}) {
if (Text.isText(attrs)) return attrs if (Text.isText(attrs)) {
if (attrs.ranges) return Text.createFromRanges(attrs.ranges) return attrs
}
if (List.isList(attrs) || Array.isArray(attrs)) {
attrs = { ranges: attrs }
}
if (typeof attrs == 'string') {
attrs = { ranges: [{ text: attrs }] }
}
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)
const text = new Text({ const text = new Text({
characters: Character.createList(attrs.characters), characters: chars,
key: attrs.key || generateKey(), key: key || generateKey(),
}) })
return text return text
} }
/** throw new Error(`\`Text.create\` only accepts objects, arrays, strings or texts, but you passed it: ${attrs}`)
* Create a new `Text` from a string
*
* @param {String} text
* @param {Set<Mark>} marks (optional)
* @return {Text}
*/
static createFromString(string, marks = Set()) {
const range = Range.create({ text: string, marks })
const text = Text.createFromRanges([range])
return text
}
/**
* Create a new `Text` from a list of ranges
*
* @param {List<Range>|Array<Range>} ranges
* @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
} }
/** /**
@@ -104,6 +94,24 @@ class Text extends new Record(DEFAULTS) {
return !!(value && value[MODEL_TYPES.TEXT]) 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. * Get the node's kind.
* *
@@ -319,7 +327,7 @@ class Text extends new Record(DEFAULTS) {
insertText(index, text, marks) { insertText(index, text, marks) {
let { characters } = this 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) characters = characters.slice(0, index)
.concat(chars) .concat(chars)

View File

@@ -1,6 +1,7 @@
import Debug from 'debug' import Debug from 'debug'
import Normalize from '../utils/normalize' import Node from '../models/node'
import Mark from '../models/mark'
import logger from '../utils/logger' import logger from '../utils/logger'
/** /**
@@ -29,7 +30,7 @@ const APPLIERS = {
add_mark(state, operation) { add_mark(state, operation) {
const { path, offset, length } = operation const { path, offset, length } = operation
const mark = Normalize.mark(operation.mark) const mark = Mark.create(operation.mark)
let { document } = state let { document } = state
let node = document.assertPath(path) let node = document.assertPath(path)
node = node.addMark(offset, length, mark) node = node.addMark(offset, length, mark)
@@ -48,7 +49,7 @@ const APPLIERS = {
insert_node(state, operation) { insert_node(state, operation) {
const { path } = operation const { path } = operation
const node = Normalize.node(operation.node) const node = Node.create(operation.node)
const index = path[path.length - 1] const index = path[path.length - 1]
const rest = path.slice(0, -1) const rest = path.slice(0, -1)
let { document } = state let { document } = state
@@ -71,7 +72,7 @@ const APPLIERS = {
const { path, offset, text } = operation const { path, offset, text } = operation
let { marks } = operation let { marks } = operation
if (Array.isArray(marks)) marks = Normalize.marks(marks) if (Array.isArray(marks)) marks = Mark.createSet(marks)
let { document, selection } = state let { document, selection } = state
const { anchorKey, focusKey, anchorOffset, focusOffset } = selection const { anchorKey, focusKey, anchorOffset, focusOffset } = selection
@@ -207,7 +208,7 @@ const APPLIERS = {
remove_mark(state, operation) { remove_mark(state, operation) {
const { path, offset, length } = operation const { path, offset, length } = operation
const mark = Normalize.mark(operation.mark) const mark = Mark.create(operation.mark)
let { document } = state let { document } = state
let node = document.assertPath(path) let node = document.assertPath(path)
node = node.removeMark(offset, length, mark) node = node.removeMark(offset, length, mark)
@@ -341,7 +342,7 @@ const APPLIERS = {
set_mark(state, operation) { set_mark(state, operation) {
const { path, offset, length, properties } = operation const { path, offset, length, properties } = operation
const mark = Normalize.mark(operation.mark) const mark = Mark.create(operation.mark)
let { document } = state let { document } = state
let node = document.assertPath(path) let node = document.assertPath(path)
node = node.updateMark(offset, length, mark, properties) node = node.updateMark(offset, length, mark, properties)
@@ -393,8 +394,8 @@ const APPLIERS = {
const properties = { ...operation.properties } const properties = { ...operation.properties }
let { document, selection } = state let { document, selection } = state
if (properties.marks !== undefined) { if (properties.marks != null) {
properties.marks = Normalize.marks(properties.marks) properties.marks = Mark.createSet(properties.marks)
} }
if (properties.anchorPath !== undefined) { if (properties.anchorPath !== undefined) {

View File

@@ -125,7 +125,7 @@ const rules = [
return node.text !== ' ' || node.nodes.size !== 1 return node.text !== ' ' || node.nodes.size !== 1
}, },
normalize: (change, node, result) => { normalize: (change, node, result) => {
const text = Text.createFromString(' ') const text = Text.create(' ')
const index = node.nodes.size const index = node.nodes.size
change.insertNodeByKey(node.key, index, text, OPTS) change.insertNodeByKey(node.key, index, text, OPTS)

View File

@@ -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<Marks>|Array} values
* @return {Set<Marks>}
*/
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,
}

View File

@@ -3251,6 +3251,12 @@ is-path-inside@^1.0.0:
dependencies: dependencies:
path-is-inside "^1.0.1" 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: is-posix-bracket@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" 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: dependencies:
isarray "1.0.0" 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: isomorphic-fetch@^2.1.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"