mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
Introduce annotations (#2747)
* first stab at removing leaves with tests passing * fixes * add iterables to the element interface * use iterables in more places * update examples to use iterables * update naming * fix tests * convert more key-based logic to paths * add range support to iterables * refactor many methods to use iterables, deprecate cruft * clean up existing iterables * more cleanup * more cleaning * fix word count example * work * split decoration and annotations * update examples for `renderNode` useage * deprecate old DOM-based helpers, update examples * make formats first class, refactor leaf rendering * fix examples, fix isAtomic checking * deprecate leaf model * convert Text and Leaf to functional components * fix lint and tests
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Decoration,
|
||||
Annotation,
|
||||
Document,
|
||||
Mark,
|
||||
Node,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'slate'
|
||||
|
||||
/**
|
||||
* Auto-incrementing ID to keep track of paired decorations.
|
||||
* Auto-incrementing ID to keep track of paired annotations.
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
@@ -59,29 +59,29 @@ export function createCursor(tagName, attributes, children) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a decoration point, or wrap a list of leaves and set the decoration
|
||||
* Create a annotation point, or wrap a list of leaves and set the annotation
|
||||
* point tracker on them.
|
||||
*
|
||||
* @param {String} tagName
|
||||
* @param {Object} attributes
|
||||
* @param {Array} children
|
||||
* @return {DecorationPoint|List<Leaf>}
|
||||
* @return {AnnotationPoint|List<Leaf>}
|
||||
*/
|
||||
|
||||
export function createDecoration(tagName, attributes, children) {
|
||||
export function createAnnotation(tagName, attributes, children) {
|
||||
const { key, data } = attributes
|
||||
const type = tagName
|
||||
|
||||
if (key) {
|
||||
return new DecorationPoint({ id: key, type, data })
|
||||
return new AnnotationPoint({ id: key, type, data })
|
||||
}
|
||||
|
||||
const texts = createChildren(children)
|
||||
const first = texts.first()
|
||||
const last = texts.last()
|
||||
const id = `__decoration_${uid++}__`
|
||||
const start = new DecorationPoint({ id, type, data })
|
||||
const end = new DecorationPoint({ id, type, data })
|
||||
const id = `${uid++}`
|
||||
const start = new AnnotationPoint({ id, type, data })
|
||||
const end = new AnnotationPoint({ id, type, data })
|
||||
setPoint(first, start, 0)
|
||||
setPoint(last, end, last.text.length)
|
||||
return texts
|
||||
@@ -267,63 +267,64 @@ export function createValue(tagName, attributes, children) {
|
||||
let focus
|
||||
let marks
|
||||
let isFocused
|
||||
let decorations = []
|
||||
let annotations = {}
|
||||
const partials = {}
|
||||
|
||||
// Search the document's texts to see if any of them have the anchor or
|
||||
// focus information saved, or decorations applied.
|
||||
// focus information saved, or annotations applied.
|
||||
if (document) {
|
||||
document.getTexts().forEach(text => {
|
||||
const { __anchor, __decorations, __focus } = text
|
||||
for (const [node, path] of document.texts()) {
|
||||
const { __anchor, __annotations, __focus } = node
|
||||
|
||||
if (__anchor != null) {
|
||||
anchor = Point.create({ key: text.key, offset: __anchor.offset })
|
||||
anchor = Point.create({ path, key: node.key, offset: __anchor.offset })
|
||||
marks = __anchor.marks
|
||||
isFocused = __anchor.isFocused
|
||||
}
|
||||
|
||||
if (__focus != null) {
|
||||
focus = Point.create({ key: text.key, offset: __focus.offset })
|
||||
focus = Point.create({ path, key: node.key, offset: __focus.offset })
|
||||
marks = __focus.marks
|
||||
isFocused = __focus.isFocused
|
||||
}
|
||||
|
||||
if (__decorations != null) {
|
||||
for (const dec of __decorations) {
|
||||
const { id } = dec
|
||||
if (__annotations != null) {
|
||||
for (const ann of __annotations) {
|
||||
const { id } = ann
|
||||
const partial = partials[id]
|
||||
delete partials[id]
|
||||
|
||||
if (!partial) {
|
||||
dec.key = text.key
|
||||
partials[id] = dec
|
||||
ann.key = node.key
|
||||
partials[id] = ann
|
||||
continue
|
||||
}
|
||||
|
||||
const decoration = Decoration.create({
|
||||
const annotation = Annotation.create({
|
||||
key: id,
|
||||
type: ann.type,
|
||||
data: ann.data,
|
||||
anchor: {
|
||||
key: partial.key,
|
||||
path: document.getPath(partial.key),
|
||||
offset: partial.offset,
|
||||
},
|
||||
focus: {
|
||||
key: text.key,
|
||||
offset: dec.offset,
|
||||
},
|
||||
mark: {
|
||||
type: dec.type,
|
||||
data: dec.data,
|
||||
path,
|
||||
key: node.key,
|
||||
offset: ann.offset,
|
||||
},
|
||||
})
|
||||
|
||||
decorations.push(decoration)
|
||||
annotations[id] = annotation
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(partials).length > 0) {
|
||||
throw new Error(
|
||||
`Slate hyperscript must have both a start and an end defined for each decoration using the \`key=\` prop.`
|
||||
`Slate hyperscript must have both a start and an end defined for each annotation using the \`key=\` prop.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -351,13 +352,13 @@ export function createValue(tagName, attributes, children) {
|
||||
|
||||
selection = selection.normalize(document)
|
||||
|
||||
if (decorations.length > 0) {
|
||||
decorations = decorations.map(d => d.normalize(document))
|
||||
if (annotations.length > 0) {
|
||||
annotations = annotations.map(a => a.normalize(document))
|
||||
}
|
||||
|
||||
const value = Value.fromJSON({
|
||||
data,
|
||||
decorations,
|
||||
annotations,
|
||||
document,
|
||||
selection,
|
||||
...attributes,
|
||||
@@ -484,7 +485,7 @@ class FocusPoint {
|
||||
}
|
||||
}
|
||||
|
||||
class DecorationPoint {
|
||||
class AnnotationPoint {
|
||||
constructor(attrs) {
|
||||
const { id = null, data = {}, type } = attrs
|
||||
this.id = id
|
||||
@@ -502,7 +503,7 @@ class DecorationPoint {
|
||||
*/
|
||||
|
||||
function incrementPoints(object, n) {
|
||||
const { __anchor, __focus, __decorations } = object
|
||||
const { __anchor, __focus, __annotations } = object
|
||||
|
||||
if (__anchor != null) {
|
||||
__anchor.offset += n
|
||||
@@ -512,8 +513,8 @@ function incrementPoints(object, n) {
|
||||
__focus.offset += n
|
||||
}
|
||||
|
||||
if (__decorations != null) {
|
||||
__decorations.forEach(d => (d.offset += n))
|
||||
if (__annotations != null) {
|
||||
__annotations.forEach(a => (a.offset += n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +529,7 @@ function isPoint(object) {
|
||||
return (
|
||||
object instanceof AnchorPoint ||
|
||||
object instanceof CursorPoint ||
|
||||
object instanceof DecorationPoint ||
|
||||
object instanceof AnnotationPoint ||
|
||||
object instanceof FocusPoint
|
||||
)
|
||||
}
|
||||
@@ -548,10 +549,10 @@ function preservePoints(object, updator) {
|
||||
}
|
||||
|
||||
function copyPoints(object, other) {
|
||||
const { __anchor, __focus, __decorations } = object
|
||||
const { __anchor, __focus, __annotations } = object
|
||||
if (__anchor != null) other.__anchor = __anchor
|
||||
if (__focus != null) other.__focus = __focus
|
||||
if (__decorations != null) other.__decorations = __decorations
|
||||
if (__annotations != null) other.__annotations = __annotations
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,9 +574,9 @@ function setPoint(object, point, offset) {
|
||||
object.__focus = point
|
||||
}
|
||||
|
||||
if (point instanceof DecorationPoint) {
|
||||
if (point instanceof AnnotationPoint) {
|
||||
point.offset = offset
|
||||
object.__decorations = object.__decorations || []
|
||||
object.__decorations = object.__decorations.concat(point)
|
||||
object.__annotations = object.__annotations || []
|
||||
object.__annotations = object.__annotations.concat(point)
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import {
|
||||
createAnchor,
|
||||
createBlock,
|
||||
createCursor,
|
||||
createDecoration,
|
||||
createAnnotation,
|
||||
createDocument,
|
||||
createFocus,
|
||||
createInline,
|
||||
@@ -23,13 +23,13 @@ import {
|
||||
*/
|
||||
|
||||
function createHyperscript(options = {}) {
|
||||
const { blocks = {}, inlines = {}, marks = {}, decorations = {} } = options
|
||||
const { blocks = {}, inlines = {}, marks = {}, annotations = {} } = options
|
||||
|
||||
const creators = {
|
||||
anchor: createAnchor,
|
||||
annotation: createAnnotation,
|
||||
block: createBlock,
|
||||
cursor: createCursor,
|
||||
decoration: createDecoration,
|
||||
document: createDocument,
|
||||
focus: createFocus,
|
||||
inline: createInline,
|
||||
@@ -53,8 +53,8 @@ function createHyperscript(options = {}) {
|
||||
creators[key] = normalizeCreator(marks[key], createMark)
|
||||
}
|
||||
|
||||
for (const key in decorations) {
|
||||
creators[key] = normalizeCreator(decorations[key], createDecoration)
|
||||
for (const key in annotations) {
|
||||
creators[key] = normalizeCreator(annotations[key], createAnnotation)
|
||||
}
|
||||
|
||||
function create(tagName, attributes, ...children) {
|
||||
|
@@ -6,7 +6,7 @@ const h = createHyperscript({
|
||||
blocks: {
|
||||
paragraph: 'paragraph',
|
||||
},
|
||||
decorations: {
|
||||
annotations: {
|
||||
highlight: 'highlight',
|
||||
},
|
||||
})
|
||||
@@ -68,18 +68,13 @@ export const output = {
|
||||
},
|
||||
}
|
||||
|
||||
export const expectDecorations = [
|
||||
export const expectAnnotations = [
|
||||
{
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
anchorOffset: 12,
|
||||
focusOffset: 13,
|
||||
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||
marks: [
|
||||
{
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
@@ -6,7 +6,7 @@ const h = createHyperscript({
|
||||
blocks: {
|
||||
paragraph: 'paragraph',
|
||||
},
|
||||
decorations: {
|
||||
annotations: {
|
||||
highlight: 'highlight',
|
||||
},
|
||||
})
|
||||
@@ -22,7 +22,7 @@ export const input = (
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveDecorations: true,
|
||||
preserveAnnotations: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
@@ -49,9 +49,12 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
decorations: [
|
||||
annotations: [
|
||||
{
|
||||
object: 'decoration',
|
||||
key: '0',
|
||||
object: 'annotation',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '1',
|
||||
@@ -64,11 +67,6 @@ export const output = {
|
||||
path: [0, 0],
|
||||
offset: 6,
|
||||
},
|
||||
mark: {
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
@@ -6,7 +6,7 @@ const h = createHyperscript({
|
||||
blocks: {
|
||||
paragraph: 'paragraph',
|
||||
},
|
||||
decorations: {
|
||||
annotations: {
|
||||
highlight: 'highlight',
|
||||
},
|
||||
})
|
||||
@@ -25,7 +25,7 @@ export const input = (
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveDecorations: true,
|
||||
preserveAnnotations: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
@@ -66,9 +66,12 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
decorations: [
|
||||
annotations: [
|
||||
{
|
||||
object: 'decoration',
|
||||
object: 'annotation',
|
||||
key: 'a',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
anchor: {
|
||||
object: 'point',
|
||||
key: '0',
|
||||
@@ -81,11 +84,6 @@ export const output = {
|
||||
path: [1, 0],
|
||||
offset: 2,
|
||||
},
|
||||
mark: {
|
||||
object: 'mark',
|
||||
type: 'highlight',
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
@@ -9,21 +9,4 @@ describe('slate-hyperscript', () => {
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
fixtures.skip(__dirname, 'decorations', ({ module }) => {
|
||||
const { input, output, expectDecorations } = module
|
||||
const actual = input.toJSON()
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
|
||||
expectDecorations.forEach((decoration, i) => {
|
||||
Object.keys(decoration).forEach(prop => {
|
||||
assert.deepEqual(
|
||||
decoration[prop],
|
||||
input.decorations.toJS()[i][prop],
|
||||
`decoration ${i} had incorrect prop: ${prop}`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user