mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 22:45:18 +02:00
add Decoration
and Selection
models (#2112)
#### Is this adding or improving a _feature_ or fixing a _bug_? Improvement. #### What's the new behavior? This introduces two new models: `Decoration` and `Selection`, which both implement the simpler `Range` interface. This way we can introduce properties to these concepts without having to have them live on all ranges, and we can start to introduce more helpful methods specific to each one's needs. It also means we don't need to move `isFocused` to value, which saves some complexity on the operations side, retaining `set_selection` as the only way selections are modified. In the process, it also cleans up a lot of the existing model logic for implementing the `Node` interface, and introduces another `Common` interface for shared properties of all Slate models. #### How does this change work? It introduces a new `interfaces/` directory where common sets of properties can be declared, and mixed in to the models with the new (simple) `mixin` utility. #### Have you checked that...? * [x] The new code matches the existing patterns and styles. * [x] The tests pass with `yarn test`. * [x] The linter passes with `yarn lint`. (Fix errors with `yarn prettier`.) * [x] The relevant examples still work. (Run examples with `yarn watch`.) #### Does this fix any issues or need any specific reviewers? Fixes: #1952 Fixes: #1807 Fixes: https://github.com/ianstormtaylor/slate/issues/2110
This commit is contained in:
@@ -4,6 +4,20 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
||||
|
||||
---
|
||||
|
||||
### `0.9.0` — August 22, 2018
|
||||
|
||||
###### NEW
|
||||
|
||||
**Introducing the `schema` option.** You can now pass in a `schema` option to the `createHyperscript` factory, which will ensure that schema rules are bound whenever you use the `<value>` tag. This is helpful for defining atomicity of decorations, or the voidness of nodes in the future.
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**The `isFocused` prop of `<selection>` is now `focused`.** This is just to match the other boolean properties in this library which all omit the `is*` prefix to stay consistent with the DOM-style.
|
||||
|
||||
**The `atomic` prop of decorations is now controlled by the schema.** Previously each individual decoration could control whether it was atomic or not, but now this is controlled in the schema definition for the mark itself.
|
||||
|
||||
---
|
||||
|
||||
### `0.8.0` — August 15, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
|
||||
import {
|
||||
Block,
|
||||
Decoration,
|
||||
Document,
|
||||
Inline,
|
||||
Mark,
|
||||
Node,
|
||||
Point,
|
||||
Range,
|
||||
Schema,
|
||||
Selection,
|
||||
Text,
|
||||
Value,
|
||||
} from 'slate'
|
||||
@@ -45,19 +46,19 @@ class FocusPoint {
|
||||
|
||||
class DecorationPoint {
|
||||
constructor(attrs) {
|
||||
const { key = null, data = {}, marks } = attrs
|
||||
const { key = null, data = {}, type } = attrs
|
||||
this.id = key
|
||||
this.offset = 0
|
||||
this.marks = marks
|
||||
this.attribs = data || {}
|
||||
this.isAtomic = !!this.attribs.atomic
|
||||
delete this.attribs.atomic
|
||||
return this
|
||||
this.type = type
|
||||
this.data = data
|
||||
}
|
||||
|
||||
combine = focus => {
|
||||
if (!(focus instanceof DecorationPoint))
|
||||
if (!(focus instanceof DecorationPoint)) {
|
||||
throw new Error('misaligned decorations')
|
||||
return Range.create({
|
||||
}
|
||||
|
||||
return Decoration.create({
|
||||
anchor: {
|
||||
key: this.key,
|
||||
offset: this.offset,
|
||||
@@ -66,9 +67,10 @@ class DecorationPoint {
|
||||
key: focus.key,
|
||||
offset: focus.offset,
|
||||
},
|
||||
marks: this.marks,
|
||||
isAtomic: this.isAtomic,
|
||||
...this.attribs,
|
||||
mark: {
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -95,6 +97,29 @@ const CREATORS = {
|
||||
return new CursorPoint()
|
||||
},
|
||||
|
||||
decoration(tagName, attributes, children) {
|
||||
const { key, data } = attributes
|
||||
const type = tagName
|
||||
|
||||
if (key) {
|
||||
return new DecorationPoint({ key, type, data })
|
||||
}
|
||||
|
||||
const nodes = createChildren(children)
|
||||
const node = nodes[0]
|
||||
const { __decorations = [] } = node
|
||||
const __decoration = {
|
||||
anchorOffset: 0,
|
||||
focusOffset: nodes.reduce((len, n) => len + n.text.length, 0),
|
||||
type,
|
||||
data,
|
||||
}
|
||||
|
||||
__decorations.push(__decoration)
|
||||
node.__decorations = __decorations
|
||||
return nodes
|
||||
},
|
||||
|
||||
document(tagName, attributes, children) {
|
||||
return Document.create({
|
||||
...attributes,
|
||||
@@ -119,34 +144,13 @@ const CREATORS = {
|
||||
return nodes
|
||||
},
|
||||
|
||||
decoration(tagName, attributes, children) {
|
||||
if (attributes.key) {
|
||||
return new DecorationPoint({
|
||||
...attributes,
|
||||
marks: [{ type: tagName }],
|
||||
})
|
||||
}
|
||||
|
||||
const nodes = createChildren(children)
|
||||
const node = nodes[0]
|
||||
const { __decorations = [] } = node
|
||||
const __decoration = {
|
||||
anchorOffset: 0,
|
||||
focusOffset: nodes.reduce((len, n) => len + n.text.length, 0),
|
||||
marks: [{ type: tagName }],
|
||||
isAtomic: !!attributes.data.atomic,
|
||||
}
|
||||
|
||||
__decorations.push(__decoration)
|
||||
node.__decorations = __decorations
|
||||
return nodes
|
||||
},
|
||||
|
||||
selection(tagName, attributes, children) {
|
||||
const anchor = children.find(c => c instanceof AnchorPoint)
|
||||
const focus = children.find(c => c instanceof FocusPoint)
|
||||
const selection = Range.create({
|
||||
...attributes,
|
||||
const { marks, focused } = attributes
|
||||
const selection = Selection.create({
|
||||
marks,
|
||||
isFocused: focused,
|
||||
anchor: anchor && {
|
||||
key: anchor.key,
|
||||
offset: anchor.offset,
|
||||
@@ -162,10 +166,16 @@ const CREATORS = {
|
||||
return selection
|
||||
},
|
||||
|
||||
text(tagName, attributes, children) {
|
||||
const nodes = createChildren(children, { key: attributes.key })
|
||||
return nodes
|
||||
},
|
||||
|
||||
value(tagName, attributes, children) {
|
||||
const { data, normalize = true } = attributes
|
||||
const schema = Schema.create(attributes.schema || {})
|
||||
const document = children.find(Document.isDocument)
|
||||
let selection = children.find(Range.isRange) || Range.create()
|
||||
let selection = children.find(Selection.isSelection) || Selection.create()
|
||||
let anchor
|
||||
let focus
|
||||
let decorations = []
|
||||
@@ -189,7 +199,7 @@ const CREATORS = {
|
||||
let range
|
||||
|
||||
if (!id) {
|
||||
range = Range.create({
|
||||
range = Decoration.create({
|
||||
anchor: {
|
||||
key: text.key,
|
||||
offset: dec.anchorOffset,
|
||||
@@ -198,14 +208,16 @@ const CREATORS = {
|
||||
key: text.key,
|
||||
offset: dec.focusOffset,
|
||||
},
|
||||
marks: dec.marks,
|
||||
isAtomic: dec.isAtomic,
|
||||
mark: {
|
||||
type: dec.type,
|
||||
data: dec.data,
|
||||
},
|
||||
})
|
||||
} else if (partials[id]) {
|
||||
const partial = partials[id]
|
||||
delete partials[id]
|
||||
|
||||
range = Range.create({
|
||||
range = Decoration.create({
|
||||
anchor: {
|
||||
key: partial.key,
|
||||
offset: partial.offset,
|
||||
@@ -214,8 +226,10 @@ const CREATORS = {
|
||||
key: text.key,
|
||||
offset: dec.offset,
|
||||
},
|
||||
marks: partial.marks,
|
||||
isAtomic: partial.isAtomic,
|
||||
mark: {
|
||||
type: dec.type,
|
||||
data: dec.data,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
dec.key = text.key
|
||||
@@ -248,28 +262,26 @@ const CREATORS = {
|
||||
)
|
||||
}
|
||||
|
||||
let value = Value.fromJSON({ data, document, selection }, { normalize })
|
||||
let value = Value.fromJSON(
|
||||
{ data, document, selection, schema },
|
||||
{ normalize }
|
||||
)
|
||||
|
||||
if (anchor || focus) {
|
||||
selection = selection.setPoints([anchor, focus])
|
||||
selection = selection.merge({ isFocused: true })
|
||||
selection = selection.setIsFocused(true)
|
||||
selection = selection.normalize(value.document)
|
||||
value = value.set('selection', selection)
|
||||
}
|
||||
|
||||
if (decorations.length > 0) {
|
||||
decorations = decorations.map(d => d.normalize(value.document))
|
||||
decorations = Range.createList(decorations)
|
||||
decorations = Decoration.createList(decorations)
|
||||
value = value.set('decorations', decorations)
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
text(tagName, attributes, children) {
|
||||
const nodes = createChildren(children, { key: attributes.key })
|
||||
return nodes
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,7 +457,13 @@ function createChildren(children, options = {}) {
|
||||
*/
|
||||
|
||||
function resolveCreators(options) {
|
||||
const { blocks = {}, inlines = {}, marks = {}, decorations = {} } = options
|
||||
const {
|
||||
blocks = {},
|
||||
inlines = {},
|
||||
marks = {},
|
||||
decorations = {},
|
||||
schema,
|
||||
} = options
|
||||
|
||||
const creators = {
|
||||
...CREATORS,
|
||||
@@ -468,6 +486,11 @@ function resolveCreators(options) {
|
||||
creators[key] = normalizeNode(key, decorations[key], 'decoration')
|
||||
})
|
||||
|
||||
creators.value = (tagName, attributes = {}, children) => {
|
||||
const attrs = { schema, ...attributes }
|
||||
return CREATORS.value(tagName, attrs, children)
|
||||
}
|
||||
|
||||
return creators
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -61,7 +61,6 @@ export const output = {
|
||||
offset: 3,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -136,7 +136,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -150,7 +150,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -84,7 +84,6 @@ export const output = {
|
||||
offset: 3,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -84,7 +84,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -84,7 +84,6 @@ export const output = {
|
||||
offset: 0,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -105,7 +105,6 @@ export const output = {
|
||||
offset: 5,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -105,7 +105,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -105,7 +105,6 @@ export const output = {
|
||||
offset: 0,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -61,7 +61,6 @@ export const output = {
|
||||
offset: 3,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -61,7 +61,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -61,7 +61,6 @@ export const output = {
|
||||
offset: 0,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -67,7 +67,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -96,7 +96,6 @@ export const output = {
|
||||
offset: 3,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -96,7 +96,6 @@ export const output = {
|
||||
offset: 0,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -96,7 +96,6 @@ export const output = {
|
||||
offset: 1,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -81,7 +81,6 @@ export const output = {
|
||||
offset: 6,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -81,7 +81,6 @@ export const output = {
|
||||
offset: 3,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -81,7 +81,6 @@ export const output = {
|
||||
offset: 4,
|
||||
},
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@ export const output = {
|
||||
},
|
||||
decorations: [
|
||||
{
|
||||
object: 'range',
|
||||
object: 'decoration',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -93,15 +93,11 @@ export const output = {
|
||||
path: [1, 0],
|
||||
offset: 2,
|
||||
},
|
||||
isFocused: false,
|
||||
isAtomic: false,
|
||||
marks: [
|
||||
{
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
mark: {
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ export const output = {
|
||||
},
|
||||
decorations: [
|
||||
{
|
||||
object: 'range',
|
||||
object: 'decoration',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -70,15 +70,11 @@ export const output = {
|
||||
path: [0, 0],
|
||||
offset: 6,
|
||||
},
|
||||
isFocused: false,
|
||||
isAtomic: false,
|
||||
marks: [
|
||||
{
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
mark: {
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ export const output = {
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
object: 'selection',
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: 'a',
|
||||
@@ -65,7 +65,6 @@ export const output = {
|
||||
offset: 2,
|
||||
},
|
||||
isFocused: false,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user