mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-04-15 10:52:34 +02:00
more work on draggable nodes
This commit is contained in:
parent
3bd000d118
commit
81c956228b
@ -44,6 +44,7 @@ Transform methods can either operate on the [`Document`](./document.md), the [`S
|
||||
- [`moveTo`](#moveto)
|
||||
- [`move{Direction}`](#movedirection)
|
||||
- [Node Transforms](#node-transforms)
|
||||
- [`removeNodeByKey`](#removeNodeByKey)
|
||||
- [`setNodeByKey`](#setNodeByKey)
|
||||
- [Document Transforms](#document-transforms)
|
||||
- [`deleteAtRange`](#deleteatrange)
|
||||
@ -233,11 +234,16 @@ Move the current selection to a selection with merged `properties`. The `propert
|
||||
|
||||
## Node Transforms
|
||||
|
||||
### `removeNodeByKey`
|
||||
`removeNodeByKey(key: String) => Transform`
|
||||
|
||||
Remove a [`Node`](./node.md) from the document by its `key`.
|
||||
|
||||
### `setNodeByKey`
|
||||
`setNodeByKey(key: String, properties: Object) => Transform`
|
||||
`setNodeByKey(key: String, type: String) => Transform`
|
||||
|
||||
Set a dictionary of `properties` on the [`Node`](./node.md) with a `key`. For convenience, you can pass a `type` string or `properties` object.
|
||||
Set a dictionary of `properties` on a [`Node`](./node.md) by its `key`. For convenience, you can pass a `type` string or `properties` object.
|
||||
|
||||
|
||||
## Document Transforms
|
||||
|
@ -48,7 +48,7 @@ class Block extends new Record(DEFAULTS) {
|
||||
if (properties instanceof Text) return properties
|
||||
if (!properties.type) throw new Error('You must pass a block `type`.')
|
||||
|
||||
properties.key = uid(4)
|
||||
properties.key = properties.key || uid(4)
|
||||
properties.data = Data.create(properties.data)
|
||||
properties.isVoid = !!properties.isVoid
|
||||
properties.nodes = Block.createList(properties.nodes)
|
||||
|
@ -48,7 +48,7 @@ class Inline extends new Record(DEFAULTS) {
|
||||
if (properties instanceof Text) return properties
|
||||
if (!properties.type) throw new Error('You must pass an inline `type`.')
|
||||
|
||||
properties.key = uid(4)
|
||||
properties.key = properties.key || uid(4)
|
||||
properties.data = Data.create(properties.data)
|
||||
properties.isVoid = !!properties.isVoid
|
||||
properties.nodes = Inline.createList(properties.nodes)
|
||||
|
@ -452,6 +452,21 @@ const Node = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the furthest inline nodes for each text node in the node.
|
||||
*
|
||||
* @return {List} nodes
|
||||
*/
|
||||
|
||||
getInlines() {
|
||||
return this
|
||||
.getTexts()
|
||||
.map(text => this.getFurthestInline(text))
|
||||
.filter(exists => exists)
|
||||
.toSet()
|
||||
.toList()
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the closest inline nodes for each text node in a `range`.
|
||||
*
|
||||
@ -957,17 +972,19 @@ const Node = {
|
||||
*/
|
||||
|
||||
removeDescendant(key) {
|
||||
this.assertDescendant(key)
|
||||
let node = this
|
||||
const desc = node.assertDescendant(key)
|
||||
let parent = node.getParent(desc)
|
||||
const index = parent.nodes.indexOf(desc)
|
||||
const isParent = node == parent
|
||||
const nodes = parent.nodes.splice(index, 1)
|
||||
|
||||
const child = this.getChild(key)
|
||||
parent = parent.merge({ nodes })
|
||||
node = isParent
|
||||
? parent
|
||||
: node.updateDescendant(parent)
|
||||
|
||||
if (child) {
|
||||
const nodes = this.nodes.filterNot(node => node == child)
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
|
||||
const nodes = this.mapChildren(n => n.kind == 'text' ? n : n.removeDescendant(key))
|
||||
return this.merge({ nodes })
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -41,7 +41,7 @@ class Text extends new Record(DEFAULTS) {
|
||||
|
||||
static create(properties = {}) {
|
||||
if (properties instanceof Text) return properties
|
||||
properties.key = uid(4)
|
||||
properties.key = properties.key || uid(4)
|
||||
properties.characters = Character.createList(properties.characters)
|
||||
properties.decorations = null
|
||||
properties.cache = null
|
||||
|
@ -52,6 +52,7 @@ const DOCUMENT_RANGE_TRANSFORMS = [
|
||||
*/
|
||||
|
||||
const DOCUMENT_NODE_TRANSFORMS = [
|
||||
'removeNodeByKey',
|
||||
'setNodeByKey',
|
||||
]
|
||||
|
||||
|
@ -491,6 +491,19 @@ const Transforms = {
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
removeNodeByKey(key) {
|
||||
return this
|
||||
.removeDescendant(key)
|
||||
.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the `properties` of block nodes in a `range`.
|
||||
*
|
||||
@ -545,6 +558,29 @@ const Transforms = {
|
||||
return node.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on a node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Object or String} properties
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
setNodeByKey(key, properties) {
|
||||
let node = this
|
||||
let descendant = node.assertDescendant(key)
|
||||
|
||||
// Allow for properties to be a string `type` for convenience.
|
||||
if (typeof properties == 'string') properties = { type: properties }
|
||||
|
||||
// Ensure that `data` is immutable.
|
||||
if (properties.data) properties.data = Data.create(properties.data)
|
||||
|
||||
descendant = descendant.merge(properties)
|
||||
node = node.updateDescendant(descendant)
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Split the block nodes at a `range`, to optional `depth`.
|
||||
*
|
||||
@ -1009,30 +1045,6 @@ const Transforms = {
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on a node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Object or String} properties
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
setNodeByKey(key, properties) {
|
||||
const node = this
|
||||
|
||||
let newNode = node.assertDescendant(key)
|
||||
|
||||
// Allow for properties to be a string `type` for convenience.
|
||||
if (typeof properties == 'string') {
|
||||
properties = { type: properties }
|
||||
}
|
||||
|
||||
if (properties.data) properties.data = Data.create(properties.data)
|
||||
|
||||
newNode = newNode.merge(properties)
|
||||
return node.mapDescendants(d => d.key == key ? newNode : d)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -177,20 +177,25 @@ function Plugin(options = {}) {
|
||||
}
|
||||
|
||||
case 'node': {
|
||||
return state
|
||||
.transform()
|
||||
.moveTo(drop.target)
|
||||
[drop.node.kind == 'block' ? 'insertBlock' : 'insertInline'](drop.node)
|
||||
const { node, target, isInternal } = drop
|
||||
let transform = state.transform()
|
||||
|
||||
if (isInternal) transform = transform.removeNodeByKey(node.key)
|
||||
|
||||
return transform
|
||||
.moveTo(target)
|
||||
[node.kind == 'block' ? 'insertBlock' : 'insertInline'](node)
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'html': {
|
||||
const { text, target } = drop
|
||||
let transform = state
|
||||
.transform()
|
||||
.moveTo(drop.target)
|
||||
.moveTo(target)
|
||||
|
||||
drop.text
|
||||
text
|
||||
.split('\n')
|
||||
.forEach((line, i) => {
|
||||
if (i > 0) transform = transform.splitBlock()
|
||||
|
@ -34,6 +34,7 @@ function serializeNode(node) {
|
||||
}
|
||||
case 'text': {
|
||||
const obj = {}
|
||||
obj.key = node.key
|
||||
obj.kind = node.kind
|
||||
obj.ranges = serializeRanges(node)
|
||||
return obj
|
||||
@ -41,6 +42,7 @@ function serializeNode(node) {
|
||||
case 'block':
|
||||
case 'inline': {
|
||||
const obj = {}
|
||||
obj.key = node.key
|
||||
obj.kind = node.kind
|
||||
obj.type = node.type
|
||||
obj.nodes = node.nodes.toArray().map(child => serializeNode(child))
|
||||
@ -113,6 +115,7 @@ function deserializeNode(object) {
|
||||
switch (object.kind) {
|
||||
case 'block': {
|
||||
return Block.create({
|
||||
key: object.key,
|
||||
type: object.type,
|
||||
data: object.data,
|
||||
isVoid: object.isVoid,
|
||||
@ -121,6 +124,7 @@ function deserializeNode(object) {
|
||||
}
|
||||
case 'inline': {
|
||||
return Inline.create({
|
||||
key: object.key,
|
||||
type: object.type,
|
||||
data: object.data,
|
||||
isVoid: object.isVoid,
|
||||
@ -133,6 +137,7 @@ function deserializeNode(object) {
|
||||
}
|
||||
|
||||
return Text.create({
|
||||
key: object.key,
|
||||
characters: object.ranges ? deserializeRanges(object.ranges) : ''
|
||||
})
|
||||
}
|
||||
|
23
test/helpers/strip-dynamic.js
Normal file
23
test/helpers/strip-dynamic.js
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
/**
|
||||
* Strip all of the dynamic properties from a `json` object.
|
||||
*
|
||||
* @param {Object} json
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function stripDynamic(json) {
|
||||
const { key, cache, decorations, ...props } = json
|
||||
|
||||
if (props.nodes) {
|
||||
props.nodes = props.nodes.map(stripDynamic)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default stripDynamic
|
@ -2,6 +2,7 @@
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import readMetadata from 'read-metadata'
|
||||
import strip from '../helpers/strip-dynamic'
|
||||
import { Html, Plain, Raw } from '../..'
|
||||
import { equal, strictEqual } from '../helpers/assert-json'
|
||||
import { resolve } from 'path'
|
||||
@ -24,7 +25,7 @@ describe('serializers', () => {
|
||||
const input = fs.readFileSync(resolve(innerDir, 'input.html'), 'utf8')
|
||||
const state = html.deserialize(input)
|
||||
const json = state.document.toJS()
|
||||
strictEqual(clean(json), expected)
|
||||
strictEqual(strip(json), expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -58,7 +59,7 @@ describe('serializers', () => {
|
||||
const input = fs.readFileSync(resolve(innerDir, 'input.txt'), 'utf8')
|
||||
const state = Plain.deserialize(input.trim())
|
||||
const json = state.document.toJS()
|
||||
strictEqual(clean(json), expected)
|
||||
strictEqual(strip(json), expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -91,7 +92,7 @@ describe('serializers', () => {
|
||||
const input = readMetadata.sync(resolve(innerDir, 'input.yaml'))
|
||||
const state = Raw.deserialize(input)
|
||||
const json = state.document.toJS()
|
||||
strictEqual(clean(json), expected)
|
||||
strictEqual(strip(json), expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -106,26 +107,9 @@ describe('serializers', () => {
|
||||
const input = require(resolve(innerDir, 'input.js')).default
|
||||
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
||||
const serialized = Raw.serialize(input)
|
||||
strictEqual(serialized, expected)
|
||||
strictEqual(strip(serialized), expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean a `json` object of dynamic `key` properties.
|
||||
*
|
||||
* @param {Object} json
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function clean(json) {
|
||||
const { key, cache, decorations, ...props } = json
|
||||
|
||||
if (props.nodes) {
|
||||
props.nodes = props.nodes.map(clean)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
10
test/transforms/fixtures/remove-node-by-key/block/index.js
Normal file
10
test/transforms/fixtures/remove-node-by-key/block/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const first = document.getBlocks().first()
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.removeNodeByKey(first.key)
|
||||
.apply()
|
||||
}
|
14
test/transforms/fixtures/remove-node-by-key/block/input.yaml
Normal file
14
test/transforms/fixtures/remove-node-by-key/block/input.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
10
test/transforms/fixtures/remove-node-by-key/inline/index.js
Normal file
10
test/transforms/fixtures/remove-node-by-key/inline/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const first = document.getInlines().first()
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.removeNodeByKey(first.key)
|
||||
.apply()
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: link
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: inline
|
||||
type: link
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@ -0,0 +1,11 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: link
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
10
test/transforms/fixtures/remove-node-by-key/text/index.js
Normal file
10
test/transforms/fixtures/remove-node-by-key/text/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const first = document.getTexts().first()
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.removeNodeByKey(first.key)
|
||||
.apply()
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@ -2,6 +2,7 @@
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import readMetadata from 'read-metadata'
|
||||
import strip from '../helpers/strip-dynamic'
|
||||
import toCamel from 'to-camel-case'
|
||||
import { Raw, State } from '../..'
|
||||
import { equal, strictEqual } from '../helpers/assert-json'
|
||||
@ -30,7 +31,7 @@ describe('transforms', () => {
|
||||
let state = Raw.deserialize(input)
|
||||
state = fn(state)
|
||||
const output = Raw.serialize(state)
|
||||
strictEqual(output, expected)
|
||||
strictEqual(strip(output), strip(expected))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user