From b773d44ae949bb917a8943da871e0f9ddba51ebb Mon Sep 17 00:00:00 2001 From: Dundercover Date: Mon, 5 Nov 2018 06:02:05 +0100 Subject: [PATCH] Data property for Operation model (#2373) * Add possibility to add arbitrary data to operations Using a data property similar to the node models (block, inline, etc) * Fix broken example 'Syncing Operations' Using data property on operations that are applied --- examples/syncing-operations/index.js | 28 ++++++++----- packages/slate/src/models/operation.js | 40 ++++++++++++------- packages/slate/test/index.js | 8 ++++ .../operation/create/add-mark-with-data.js | 28 +++++++++++++ .../operation/create/add-mark-without-data.js | 27 +++++++++++++ 5 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 packages/slate/test/models/operation/create/add-mark-with-data.js create mode 100644 packages/slate/test/models/operation/create/add-mark-without-data.js diff --git a/examples/syncing-operations/index.js b/examples/syncing-operations/index.js index 1dc7023d9..2870bbcbb 100644 --- a/examples/syncing-operations/index.js +++ b/examples/syncing-operations/index.js @@ -239,35 +239,43 @@ class SyncingOperationsExample extends React.Component { } /** - * When editor one changes, send document-alterting operations to edtior two. + * When editor one changes, send document-altering operations to editor two. * * @param {Array} operations */ onOneChange = change => { const ops = change.operations - .filter(o => o.type != 'set_selection' && o.type != 'set_value') + .filter( + o => + o.type != 'set_selection' && + o.type != 'set_value' && + (!o.data || !o.data.has('source')) + ) .toJS() + .map(o => ({ ...o, data: { source: 'one' } })) - setTimeout(() => { - ops.forEach(o => this.two.applyOperation(o)) - }) + setTimeout(() => this.two.applyOperations(ops)) } /** - * When editor two changes, send document-alterting operations to edtior one. + * When editor two changes, send document-altering operations to editor one. * * @param {Array} operations */ onTwoChange = change => { const ops = change.operations - .filter(o => o.type != 'set_selection' && o.type != 'set_value') + .filter( + o => + o.type != 'set_selection' && + o.type != 'set_value' && + (!o.data || !o.data.has('source')) + ) .toJS() + .map(o => ({ ...o, data: { source: 'two' } })) - setTimeout(() => { - ops.forEach(o => this.one.applyOperation(o)) - }) + setTimeout(() => this.one.applyOperations(ops)) } } diff --git a/packages/slate/src/models/operation.js b/packages/slate/src/models/operation.js index df4769683..49f485bde 100644 --- a/packages/slate/src/models/operation.js +++ b/packages/slate/src/models/operation.js @@ -1,5 +1,5 @@ import isPlainObject from 'is-plain-object' -import { List, Record } from 'immutable' +import { List, Record, Map } from 'immutable' import Mark from './mark' import Node from './node' @@ -16,19 +16,19 @@ import invert from '../operations/invert' */ const OPERATION_ATTRIBUTES = { - add_mark: ['value', 'path', 'offset', 'length', 'mark'], - insert_node: ['value', 'path', 'node'], - insert_text: ['value', 'path', 'offset', 'text', 'marks'], - merge_node: ['value', 'path', 'position', 'properties', 'target'], - move_node: ['value', 'path', 'newPath'], - remove_mark: ['value', 'path', 'offset', 'length', 'mark'], - remove_node: ['value', 'path', 'node'], - remove_text: ['value', 'path', 'offset', 'text', 'marks'], - set_mark: ['value', 'path', 'offset', 'length', 'mark', 'properties'], - set_node: ['value', 'path', 'node', 'properties'], - set_selection: ['value', 'selection', 'properties'], - set_value: ['value', 'properties'], - split_node: ['value', 'path', 'position', 'properties', 'target'], + add_mark: ['value', 'path', 'offset', 'length', 'mark', 'data'], + insert_node: ['value', 'path', 'node', 'data'], + insert_text: ['value', 'path', 'offset', 'text', 'marks', 'data'], + merge_node: ['value', 'path', 'position', 'properties', 'target', 'data'], + move_node: ['value', 'path', 'newPath', 'data'], + remove_mark: ['value', 'path', 'offset', 'length', 'mark', 'data'], + remove_node: ['value', 'path', 'node', 'data'], + remove_text: ['value', 'path', 'offset', 'text', 'marks', 'data'], + set_mark: ['value', 'path', 'offset', 'length', 'mark', 'properties', 'data'], + set_node: ['value', 'path', 'node', 'properties', 'data'], + set_selection: ['value', 'selection', 'properties', 'data'], + set_value: ['value', 'properties', 'data'], + split_node: ['value', 'path', 'position', 'properties', 'target', 'data'], } /** @@ -52,6 +52,7 @@ const DEFAULTS = { text: undefined, type: undefined, value: undefined, + data: undefined, } /** @@ -133,6 +134,9 @@ class Operation extends Record(DEFAULTS) { if (key == 'value') continue if (key == 'node' && type != 'insert_node') continue + // Skip optional user defined data + if (key == 'data') continue + throw new Error( `\`Operation.fromJSON\` was passed a "${type}" operation without the required "${key}" attribute.` ) @@ -186,6 +190,10 @@ class Operation extends Record(DEFAULTS) { v = Node.createProperties(v) } + if (key === 'data') { + v = Map(v) + } + attrs[key] = v } @@ -303,6 +311,10 @@ class Operation extends Record(DEFAULTS) { value = v } + if (key === 'data' && value) { + value = value.toJSON() + } + json[key] = value } diff --git a/packages/slate/test/index.js b/packages/slate/test/index.js index f33cc35f2..c5294c3bf 100644 --- a/packages/slate/test/index.js +++ b/packages/slate/test/index.js @@ -33,6 +33,14 @@ describe('slate', () => { assert.deepEqual(actual, expected) }) + fixtures(__dirname, 'models/operation', ({ module }) => { + const { input, output } = module + const fn = module.default + const actual = fn(input).toJSON() + const expected = output + assert.deepEqual(actual, expected) + }) + fixtures(__dirname, 'models/point', ({ module }) => { const { input, output } = module const fn = module.default diff --git a/packages/slate/test/models/operation/create/add-mark-with-data.js b/packages/slate/test/models/operation/create/add-mark-with-data.js new file mode 100644 index 000000000..94a805a8d --- /dev/null +++ b/packages/slate/test/models/operation/create/add-mark-with-data.js @@ -0,0 +1,28 @@ +import Operation from '../../../../src/models/operation' + +export const input = { + type: 'add_mark', + path: [2, 1], + offset: 3, + length: 5, + mark: 'b', + data: { info: 'user supplied text', flag: true }, +} + +export default function(op) { + return Operation.create(op) +} + +export const output = { + object: 'operation', + type: 'add_mark', + path: [2, 1], + offset: 3, + length: 5, + mark: { + data: {}, + object: 'mark', + type: 'b', + }, + data: { info: 'user supplied text', flag: true }, +} diff --git a/packages/slate/test/models/operation/create/add-mark-without-data.js b/packages/slate/test/models/operation/create/add-mark-without-data.js new file mode 100644 index 000000000..55150a7b1 --- /dev/null +++ b/packages/slate/test/models/operation/create/add-mark-without-data.js @@ -0,0 +1,27 @@ +import Operation from '../../../../src/models/operation' + +export const input = { + type: 'add_mark', + path: [2, 1], + offset: 3, + length: 5, + mark: 'b', +} + +export default function(op) { + return Operation.create(op) +} + +export const output = { + object: 'operation', + type: 'add_mark', + path: [2, 1], + offset: 3, + length: 5, + mark: { + data: {}, + object: 'mark', + type: 'b', + }, + data: undefined, +}