mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 18:39:51 +02:00
Add data
field to editor State
. (#830)
* Add `data` field to editor `State`. Plugins can use it to store their own internal state. * Remove `serialize` and `deserialize` plugin methods. * Add operation to set `data` on a state. * Add `setDataOperation` tests. * Remove the possibility to use keys different from strings. Add `preserveData` option to `raw.serialize`. Rewrite `set-data` test exploiting the new option.
This commit is contained in:
committed by
Ian Storm Taylor
parent
74bf684ec9
commit
17cfde67ce
@@ -4,7 +4,7 @@ import Document from './document'
|
|||||||
import SCHEMA from '../schemas/core'
|
import SCHEMA from '../schemas/core'
|
||||||
import Selection from './selection'
|
import Selection from './selection'
|
||||||
import Transform from './transform'
|
import Transform from './transform'
|
||||||
import { Record, Set, Stack, List } from 'immutable'
|
import { Record, Set, Stack, List, Map } from 'immutable'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* History.
|
* History.
|
||||||
@@ -27,6 +27,7 @@ const DEFAULTS = {
|
|||||||
document: new Document(),
|
document: new Document(),
|
||||||
selection: new Selection(),
|
selection: new Selection(),
|
||||||
history: new History(),
|
history: new History(),
|
||||||
|
data: new Map(),
|
||||||
isNative: false
|
isNative: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +53,24 @@ class State extends new Record(DEFAULTS) {
|
|||||||
|
|
||||||
const document = Document.create(properties.document)
|
const document = Document.create(properties.document)
|
||||||
let selection = Selection.create(properties.selection)
|
let selection = Selection.create(properties.selection)
|
||||||
|
let data = new Map()
|
||||||
|
|
||||||
if (selection.isUnset) {
|
if (selection.isUnset) {
|
||||||
const text = document.getFirstText()
|
const text = document.getFirstText()
|
||||||
selection = selection.collapseToStartOf(text)
|
selection = selection.collapseToStartOf(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = new State({ document, selection })
|
// Set default value for `data`.
|
||||||
|
if (options.plugins) {
|
||||||
|
for (const plugin of options.plugins) {
|
||||||
|
if (plugin.data) data = data.merge(plugin.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then add data provided in `properties`.
|
||||||
|
if (properties.data) data = data.merge(properties.data)
|
||||||
|
|
||||||
|
const state = new State({ document, selection, data })
|
||||||
|
|
||||||
return options.normalize === false
|
return options.normalize === false
|
||||||
? state
|
? state
|
||||||
|
@@ -184,7 +184,7 @@ const Raw = {
|
|||||||
selection = Raw.deserializeSelection(object.selection, options)
|
selection = Raw.deserializeSelection(object.selection, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return State.create({ document, selection }, options)
|
return State.create({ data: object.data, document, selection }, options)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,12 +400,15 @@ const Raw = {
|
|||||||
serializeState(state, options = {}) {
|
serializeState(state, options = {}) {
|
||||||
const object = {
|
const object = {
|
||||||
document: Raw.serializeDocument(state.document, options),
|
document: Raw.serializeDocument(state.document, options),
|
||||||
selection: Raw.serializeSelection(state.selection, options),
|
|
||||||
kind: state.kind
|
kind: state.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.preserveSelection) {
|
if (options.preserveSelection) {
|
||||||
delete object.selection
|
object.selection = Raw.serializeSelection(state.selection, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.preserveStateData) {
|
||||||
|
object.data = state.data.toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = options.terse
|
const ret = options.terse
|
||||||
@@ -546,14 +549,17 @@ const Raw = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
tersifyState(object) {
|
tersifyState(object) {
|
||||||
if (object.selection == null) {
|
const { data, document, selection } = object
|
||||||
return object.document
|
const emptyData = isEmpty(data)
|
||||||
|
|
||||||
|
if (!selection && emptyData) {
|
||||||
|
return document
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const ret = { document }
|
||||||
document: object.document,
|
if (!emptyData) ret.data = data
|
||||||
selection: object.selection
|
if (selection) ret.selection = selection
|
||||||
}
|
return ret
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -673,9 +679,10 @@ const Raw = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
untersifyState(object) {
|
untersifyState(object) {
|
||||||
if (object.selection || object.document) {
|
if (object.document) {
|
||||||
return {
|
return {
|
||||||
kind: 'state',
|
kind: 'state',
|
||||||
|
data: object.data,
|
||||||
document: object.document,
|
document: object.document,
|
||||||
selection: object.selection,
|
selection: object.selection,
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,9 @@ const OPERATIONS = {
|
|||||||
set_node: setNode,
|
set_node: setNode,
|
||||||
split_node: splitNode,
|
split_node: splitNode,
|
||||||
// Selection operations.
|
// Selection operations.
|
||||||
set_selection: setSelection
|
set_selection: setSelection,
|
||||||
|
// State data operations.
|
||||||
|
set_data: setData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,6 +336,23 @@ function removeText(state, operation) {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set `data` on `state`.
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Object} operation
|
||||||
|
* @return {State}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setData(state, operation) {
|
||||||
|
const { properties } = operation
|
||||||
|
let { data } = state
|
||||||
|
|
||||||
|
data = data.merge(properties)
|
||||||
|
state = state.set('data', data)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
|
@@ -285,6 +285,36 @@ Transforms.removeTextOperation = (transform, path, offset, length) => {
|
|||||||
transform.applyOperation(operation)
|
transform.applyOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge `properties` into state `data`.
|
||||||
|
*
|
||||||
|
* @param {Transform} transform
|
||||||
|
* @param {Object} properties
|
||||||
|
*/
|
||||||
|
|
||||||
|
Transforms.setDataOperation = (transform, properties) => {
|
||||||
|
const { state } = transform
|
||||||
|
const { data } = state
|
||||||
|
const inverseProps = {}
|
||||||
|
|
||||||
|
for (const k in properties) {
|
||||||
|
inverseProps[k] = data[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inverse = [{
|
||||||
|
type: 'set_data',
|
||||||
|
properties: inverseProps
|
||||||
|
}]
|
||||||
|
|
||||||
|
const operation = {
|
||||||
|
type: 'set_data',
|
||||||
|
properties,
|
||||||
|
inverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.applyOperation(operation)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||||
*
|
*
|
||||||
|
14
test/transforms/fixtures/state-data/set-data/index.js
Normal file
14
test/transforms/fixtures/state-data/set-data/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const data = {
|
||||||
|
key1: "value1",
|
||||||
|
key2: "value2"
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = state
|
||||||
|
.transform()
|
||||||
|
.setDataOperation(data)
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
12
test/transforms/fixtures/state-data/set-data/input.yaml
Normal file
12
test/transforms/fixtures/state-data/set-data/input.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: word
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: another
|
16
test/transforms/fixtures/state-data/set-data/output.yaml
Normal file
16
test/transforms/fixtures/state-data/set-data/output.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
data:
|
||||||
|
key1: value1
|
||||||
|
key2: value2
|
||||||
|
document:
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: word
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: another
|
@@ -177,4 +177,25 @@ describe('transforms', async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('state-data', () => {
|
||||||
|
const dir = resolve(__dirname, './fixtures/state-data')
|
||||||
|
const tests = fs.readdirSync(dir)
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
if (test[0] == '.') continue
|
||||||
|
|
||||||
|
it(test, async () => {
|
||||||
|
const testDir = resolve(dir, test)
|
||||||
|
const fn = require(testDir).default
|
||||||
|
const input = await readYaml(resolve(testDir, 'input.yaml'))
|
||||||
|
const expected = await readYaml(resolve(testDir, 'output.yaml'))
|
||||||
|
|
||||||
|
let state = Raw.deserialize(input, { terse: true })
|
||||||
|
state = fn(state)
|
||||||
|
const output = Raw.serialize(state, { terse: true, preserveStateData: true })
|
||||||
|
assert.deepEqual(strip(output), strip(expected))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user