1
0
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:
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) - [`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

View File

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

View File

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

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`. * 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 })
}, },
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

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

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