1
0
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:
Ian Storm Taylor 2016-07-24 18:57:09 -07:00
parent 3bd000d118
commit 81c956228b
21 changed files with 215 additions and 65 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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

View File

@ -52,6 +52,7 @@ const DOCUMENT_RANGE_TRANSFORMS = [
*/
const DOCUMENT_NODE_TRANSFORMS = [
'removeNodeByKey',
'setNodeByKey',
]

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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) : ''
})
}

View 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

View File

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

View 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()
}

View 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

View File

@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: another

View 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()
}

View File

@ -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

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: another

View 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()
}

View File

@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: ""

View File

@ -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))
})
}
})