mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-29 18:09:49 +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 Selection from './selection'
|
||||
import Transform from './transform'
|
||||
import { Record, Set, Stack, List } from 'immutable'
|
||||
import { Record, Set, Stack, List, Map } from 'immutable'
|
||||
|
||||
/**
|
||||
* History.
|
||||
@@ -27,6 +27,7 @@ const DEFAULTS = {
|
||||
document: new Document(),
|
||||
selection: new Selection(),
|
||||
history: new History(),
|
||||
data: new Map(),
|
||||
isNative: false
|
||||
}
|
||||
|
||||
@@ -52,13 +53,24 @@ class State extends new Record(DEFAULTS) {
|
||||
|
||||
const document = Document.create(properties.document)
|
||||
let selection = Selection.create(properties.selection)
|
||||
let data = new Map()
|
||||
|
||||
if (selection.isUnset) {
|
||||
const text = document.getFirstText()
|
||||
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
|
||||
? state
|
||||
|
@@ -184,7 +184,7 @@ const Raw = {
|
||||
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 = {}) {
|
||||
const object = {
|
||||
document: Raw.serializeDocument(state.document, options),
|
||||
selection: Raw.serializeSelection(state.selection, options),
|
||||
kind: state.kind
|
||||
}
|
||||
|
||||
if (!options.preserveSelection) {
|
||||
delete object.selection
|
||||
if (options.preserveSelection) {
|
||||
object.selection = Raw.serializeSelection(state.selection, options)
|
||||
}
|
||||
|
||||
if (options.preserveStateData) {
|
||||
object.data = state.data.toJSON()
|
||||
}
|
||||
|
||||
const ret = options.terse
|
||||
@@ -546,14 +549,17 @@ const Raw = {
|
||||
*/
|
||||
|
||||
tersifyState(object) {
|
||||
if (object.selection == null) {
|
||||
return object.document
|
||||
const { data, document, selection } = object
|
||||
const emptyData = isEmpty(data)
|
||||
|
||||
if (!selection && emptyData) {
|
||||
return document
|
||||
}
|
||||
|
||||
return {
|
||||
document: object.document,
|
||||
selection: object.selection
|
||||
}
|
||||
const ret = { document }
|
||||
if (!emptyData) ret.data = data
|
||||
if (selection) ret.selection = selection
|
||||
return ret
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -673,9 +679,10 @@ const Raw = {
|
||||
*/
|
||||
|
||||
untersifyState(object) {
|
||||
if (object.selection || object.document) {
|
||||
if (object.document) {
|
||||
return {
|
||||
kind: 'state',
|
||||
data: object.data,
|
||||
document: object.document,
|
||||
selection: object.selection,
|
||||
}
|
||||
|
@@ -40,7 +40,9 @@ const OPERATIONS = {
|
||||
set_node: setNode,
|
||||
split_node: splitNode,
|
||||
// Selection operations.
|
||||
set_selection: setSelection
|
||||
set_selection: setSelection,
|
||||
// State data operations.
|
||||
set_data: setData
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,6 +336,23 @@ function removeText(state, operation) {
|
||||
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`.
|
||||
*
|
||||
|
@@ -285,6 +285,36 @@ Transforms.removeTextOperation = (transform, path, offset, length) => {
|
||||
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`.
|
||||
*
|
||||
|
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