mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-11 17:53:59 +02:00
more work on draggable nodes
This commit is contained in:
@@ -44,6 +44,7 @@ Transform methods can either operate on the [`Document`](./document.md), the [`S
|
|||||||
- [`moveTo`](#moveto)
|
- [`moveTo`](#moveto)
|
||||||
- [`move{Direction}`](#movedirection)
|
- [`move{Direction}`](#movedirection)
|
||||||
- [Node Transforms](#node-transforms)
|
- [Node Transforms](#node-transforms)
|
||||||
|
- [`removeNodeByKey`](#removeNodeByKey)
|
||||||
- [`setNodeByKey`](#setNodeByKey)
|
- [`setNodeByKey`](#setNodeByKey)
|
||||||
- [Document Transforms](#document-transforms)
|
- [Document Transforms](#document-transforms)
|
||||||
- [`deleteAtRange`](#deleteatrange)
|
- [`deleteAtRange`](#deleteatrange)
|
||||||
@@ -233,11 +234,16 @@ Move the current selection to a selection with merged `properties`. The `propert
|
|||||||
|
|
||||||
## Node Transforms
|
## Node Transforms
|
||||||
|
|
||||||
|
### `removeNodeByKey`
|
||||||
|
`removeNodeByKey(key: String) => Transform`
|
||||||
|
|
||||||
|
Remove a [`Node`](./node.md) from the document by its `key`.
|
||||||
|
|
||||||
### `setNodeByKey`
|
### `setNodeByKey`
|
||||||
`setNodeByKey(key: String, properties: Object) => Transform`
|
`setNodeByKey(key: String, properties: Object) => Transform`
|
||||||
`setNodeByKey(key: String, type: String) => 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
|
## Document Transforms
|
||||||
|
@@ -48,7 +48,7 @@ class Block extends new Record(DEFAULTS) {
|
|||||||
if (properties instanceof Text) return properties
|
if (properties instanceof Text) return properties
|
||||||
if (!properties.type) throw new Error('You must pass a block `type`.')
|
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.data = Data.create(properties.data)
|
||||||
properties.isVoid = !!properties.isVoid
|
properties.isVoid = !!properties.isVoid
|
||||||
properties.nodes = Block.createList(properties.nodes)
|
properties.nodes = Block.createList(properties.nodes)
|
||||||
|
@@ -48,7 +48,7 @@ class Inline extends new Record(DEFAULTS) {
|
|||||||
if (properties instanceof Text) return properties
|
if (properties instanceof Text) return properties
|
||||||
if (!properties.type) throw new Error('You must pass an inline `type`.')
|
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.data = Data.create(properties.data)
|
||||||
properties.isVoid = !!properties.isVoid
|
properties.isVoid = !!properties.isVoid
|
||||||
properties.nodes = Inline.createList(properties.nodes)
|
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`.
|
* Get the closest inline nodes for each text node in a `range`.
|
||||||
*
|
*
|
||||||
@@ -957,17 +972,19 @@ const Node = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
removeDescendant(key) {
|
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) {
|
return node
|
||||||
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 })
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -41,7 +41,7 @@ class Text extends new Record(DEFAULTS) {
|
|||||||
|
|
||||||
static create(properties = {}) {
|
static create(properties = {}) {
|
||||||
if (properties instanceof Text) return properties
|
if (properties instanceof Text) return properties
|
||||||
properties.key = uid(4)
|
properties.key = properties.key || uid(4)
|
||||||
properties.characters = Character.createList(properties.characters)
|
properties.characters = Character.createList(properties.characters)
|
||||||
properties.decorations = null
|
properties.decorations = null
|
||||||
properties.cache = null
|
properties.cache = null
|
||||||
|
@@ -52,6 +52,7 @@ const DOCUMENT_RANGE_TRANSFORMS = [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DOCUMENT_NODE_TRANSFORMS = [
|
const DOCUMENT_NODE_TRANSFORMS = [
|
||||||
|
'removeNodeByKey',
|
||||||
'setNodeByKey',
|
'setNodeByKey',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -491,6 +491,19 @@ const Transforms = {
|
|||||||
return node
|
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`.
|
* Set the `properties` of block nodes in a `range`.
|
||||||
*
|
*
|
||||||
@@ -545,6 +558,29 @@ const Transforms = {
|
|||||||
return node.normalize()
|
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`.
|
* Split the block nodes at a `range`, to optional `depth`.
|
||||||
*
|
*
|
||||||
@@ -1009,30 +1045,6 @@ const Transforms = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return node
|
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': {
|
case 'node': {
|
||||||
return state
|
const { node, target, isInternal } = drop
|
||||||
.transform()
|
let transform = state.transform()
|
||||||
.moveTo(drop.target)
|
|
||||||
[drop.node.kind == 'block' ? 'insertBlock' : 'insertInline'](drop.node)
|
if (isInternal) transform = transform.removeNodeByKey(node.key)
|
||||||
|
|
||||||
|
return transform
|
||||||
|
.moveTo(target)
|
||||||
|
[node.kind == 'block' ? 'insertBlock' : 'insertInline'](node)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'html': {
|
case 'html': {
|
||||||
|
const { text, target } = drop
|
||||||
let transform = state
|
let transform = state
|
||||||
.transform()
|
.transform()
|
||||||
.moveTo(drop.target)
|
.moveTo(target)
|
||||||
|
|
||||||
drop.text
|
text
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.forEach((line, i) => {
|
.forEach((line, i) => {
|
||||||
if (i > 0) transform = transform.splitBlock()
|
if (i > 0) transform = transform.splitBlock()
|
||||||
|
@@ -34,6 +34,7 @@ function serializeNode(node) {
|
|||||||
}
|
}
|
||||||
case 'text': {
|
case 'text': {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
|
obj.key = node.key
|
||||||
obj.kind = node.kind
|
obj.kind = node.kind
|
||||||
obj.ranges = serializeRanges(node)
|
obj.ranges = serializeRanges(node)
|
||||||
return obj
|
return obj
|
||||||
@@ -41,6 +42,7 @@ function serializeNode(node) {
|
|||||||
case 'block':
|
case 'block':
|
||||||
case 'inline': {
|
case 'inline': {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
|
obj.key = node.key
|
||||||
obj.kind = node.kind
|
obj.kind = node.kind
|
||||||
obj.type = node.type
|
obj.type = node.type
|
||||||
obj.nodes = node.nodes.toArray().map(child => serializeNode(child))
|
obj.nodes = node.nodes.toArray().map(child => serializeNode(child))
|
||||||
@@ -113,6 +115,7 @@ function deserializeNode(object) {
|
|||||||
switch (object.kind) {
|
switch (object.kind) {
|
||||||
case 'block': {
|
case 'block': {
|
||||||
return Block.create({
|
return Block.create({
|
||||||
|
key: object.key,
|
||||||
type: object.type,
|
type: object.type,
|
||||||
data: object.data,
|
data: object.data,
|
||||||
isVoid: object.isVoid,
|
isVoid: object.isVoid,
|
||||||
@@ -121,6 +124,7 @@ function deserializeNode(object) {
|
|||||||
}
|
}
|
||||||
case 'inline': {
|
case 'inline': {
|
||||||
return Inline.create({
|
return Inline.create({
|
||||||
|
key: object.key,
|
||||||
type: object.type,
|
type: object.type,
|
||||||
data: object.data,
|
data: object.data,
|
||||||
isVoid: object.isVoid,
|
isVoid: object.isVoid,
|
||||||
@@ -133,6 +137,7 @@ function deserializeNode(object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Text.create({
|
return Text.create({
|
||||||
|
key: object.key,
|
||||||
characters: object.ranges ? deserializeRanges(object.ranges) : ''
|
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 assert from 'assert'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import readMetadata from 'read-metadata'
|
import readMetadata from 'read-metadata'
|
||||||
|
import strip from '../helpers/strip-dynamic'
|
||||||
import { Html, Plain, Raw } from '../..'
|
import { Html, Plain, Raw } from '../..'
|
||||||
import { equal, strictEqual } from '../helpers/assert-json'
|
import { equal, strictEqual } from '../helpers/assert-json'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
@@ -24,7 +25,7 @@ describe('serializers', () => {
|
|||||||
const input = fs.readFileSync(resolve(innerDir, 'input.html'), 'utf8')
|
const input = fs.readFileSync(resolve(innerDir, 'input.html'), 'utf8')
|
||||||
const state = html.deserialize(input)
|
const state = html.deserialize(input)
|
||||||
const json = state.document.toJS()
|
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 input = fs.readFileSync(resolve(innerDir, 'input.txt'), 'utf8')
|
||||||
const state = Plain.deserialize(input.trim())
|
const state = Plain.deserialize(input.trim())
|
||||||
const json = state.document.toJS()
|
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 input = readMetadata.sync(resolve(innerDir, 'input.yaml'))
|
||||||
const state = Raw.deserialize(input)
|
const state = Raw.deserialize(input)
|
||||||
const json = state.document.toJS()
|
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 input = require(resolve(innerDir, 'input.js')).default
|
||||||
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
||||||
const serialized = Raw.serialize(input)
|
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 assert from 'assert'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import readMetadata from 'read-metadata'
|
import readMetadata from 'read-metadata'
|
||||||
|
import strip from '../helpers/strip-dynamic'
|
||||||
import toCamel from 'to-camel-case'
|
import toCamel from 'to-camel-case'
|
||||||
import { Raw, State } from '../..'
|
import { Raw, State } from '../..'
|
||||||
import { equal, strictEqual } from '../helpers/assert-json'
|
import { equal, strictEqual } from '../helpers/assert-json'
|
||||||
@@ -30,7 +31,7 @@ describe('transforms', () => {
|
|||||||
let state = Raw.deserialize(input)
|
let state = Raw.deserialize(input)
|
||||||
state = fn(state)
|
state = fn(state)
|
||||||
const output = Raw.serialize(state)
|
const output = Raw.serialize(state)
|
||||||
strictEqual(output, expected)
|
strictEqual(strip(output), strip(expected))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user