simplifying type system

This commit is contained in:
Kevin Jahns
2025-12-24 09:18:38 +01:00
parent 8cc17fc584
commit 301d3836ab
9 changed files with 360 additions and 106 deletions

55
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "14.0.0-19",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.115"
"lib0": "file:../lib0"
},
"devDependencies": {
"@types/node": "^22.14.1",
@@ -29,6 +29,28 @@
"url": "https://github.com/sponsors/dmonad"
}
},
"../lib0": {
"version": "0.2.115",
"license": "MIT",
"bin": {
"0ecdsa-generate-keypair": "src/bin/0ecdsa-generate-keypair.js",
"0gentesthtml": "src/bin/gentesthtml.js",
"0serve": "src/bin/0serve.js"
},
"devDependencies": {
"@types/node": "^24.0.14",
"c8": "^10.1.3",
"standard": "^17.1.0",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@@ -3143,16 +3165,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/isomorphic.js": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
"license": "MIT",
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -3333,25 +3345,8 @@
}
},
"node_modules/lib0": {
"version": "0.2.115",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115.tgz",
"integrity": "sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g==",
"license": "MIT",
"dependencies": {
"isomorphic.js": "^0.2.4"
},
"bin": {
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
"0gentesthtml": "bin/gentesthtml.js",
"0serve": "bin/0serve.js"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
"resolved": "../lib0",
"link": true
},
"node_modules/linkify-it": {
"version": "5.0.0",

View File

@@ -73,7 +73,7 @@
},
"homepage": "https://docs.yjs.dev",
"dependencies": {
"lib0": "^0.2.115"
"lib0": "file:../lib0"
},
"devDependencies": {
"@types/node": "^22.14.1",

View File

@@ -275,8 +275,7 @@ export const callTypeObservers = (type, transaction, event) => {
/**
* Abstract Yjs Type class
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=any]
* @template {AbstractType<any,any>} [Self=any]
* @template {delta.DeltaConf} [DConf=any]
*/
export class AbstractType {
constructor () {
@@ -299,7 +298,7 @@ export class AbstractType {
this._length = 0
/**
* Event handlers
* @type {EventHandler<YEvent<Self>,Transaction>}
* @type {EventHandler<YEvent<DConf>,Transaction>}
*/
this._eH = createEventHandler()
/**
@@ -312,14 +311,15 @@ export class AbstractType {
*/
this._searchMarker = null
/**
* @type {EventDelta?}
* @type {delta.DeltaBuilder<DConf>}
* @private
*/
this._prelim = null
this._content = /** @type {delta.DeltaBuilderAny} */ (delta.create())
}
/**
* Returns a fresh delta that can be used to change this YType.
* @type {EventDelta}
* @type {delta.DeltaBuilder<DConf>}
*/
get change () {
return /** @type {any} */ (delta.create())
@@ -352,7 +352,7 @@ export class AbstractType {
}
/**
* @return {Self}
* @return {this}
*/
_copy () {
// @ts-ignore
@@ -364,7 +364,7 @@ export class AbstractType {
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {Self}
* @return {this}
*/
clone () {
// @todo remove this method from othern types by doing `_copy().apply(this.getContent())`
@@ -405,7 +405,7 @@ export class AbstractType {
/**
* Observe all events that are created on this type.
*
* @template {(target: YEvent<Self>, tr: Transaction) => void} F
* @template {(target: YEvent<DConf>, tr: Transaction) => void} F
* @param {F} f Observer function
* @return {F}
*/
@@ -429,7 +429,7 @@ export class AbstractType {
/**
* Unregister an observer function.
*
* @param {(type:YEvent<Self>,tr:Transaction)=>void} f Observer function
* @param {(type:YEvent<DConf>,tr:Transaction)=>void} f Observer function
*/
unobserve (f) {
removeEventHandlerListener(this._eH, f)
@@ -467,16 +467,16 @@ export class AbstractType {
* @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
* @param {Map<import('../utils/types.js').YType,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
* @param {Deep} [opts.deep] - render child types as delta
* @return {Deep extends true ? ToDeepEventDelta<EventDelta> : EventDelta} The Delta representation of this type.
* @return {Deep extends true ? delta.Delta<DeltaConfTypesToDelta<DConf>> : delta.Delta<DConf>} The Delta representation of this type.
*
* @public
*/
getContent (am = noAttributionsManager, opts = {}) {
const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, modified = null, deep = false } = opts
const renderAttrs = modified?.get(this) || null
const renderChildren = (modified == null || opts.modified.get(this)?.has(null))
const renderChildren = !!(modified == null || modified.get(this)?.has(null))
/**
* @type {EventDelta extends delta.Delta<infer N,infer Attrs,infer Children,infer Text,any> ? delta.DeltaBuilder<N,Attrs,Children,Text,any> : never}
* @type {delta.DeltaBuilderAny}
*/
const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null))
const optsAll = modified == null ? opts : object.assign({}, opts, { modified: null })
@@ -693,7 +693,7 @@ export class AbstractType {
* attributions.
*
* @param {AbstractAttributionManager} am
* @return {ToDeepEventDelta<EventDelta>}
* @return {delta.Delta<DeltaConfTypesToDelta<DConf>>}
*/
getContentDeep (am = noAttributionsManager) {
return /** @type {any} */ (this.getContent(am, { deep: true }))
@@ -702,7 +702,7 @@ export class AbstractType {
/**
* Apply a {@link Delta} on this shared type.
*
* @param {delta.Delta<any,any,any,any,any>} d The changes to apply on this element.
* @param {delta.DeltaAny} d The changes to apply on this element.
* @param {AbstractAttributionManager} am
*
* @public
@@ -747,12 +747,12 @@ export class AbstractType {
}
}
for (const op of d.attrs) {
if (delta.$insertOp.check(op)) {
typeMapSet(transaction, /** @type {any} */ (this), op.key, op.value)
if (delta.$setAttrOp.check(op)) {
typeMapSet(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key), op.value)
} else if (delta.$deleteOp.check(op)) {
typeMapDelete(transaction, /** @type {any} */ (this), op.key)
typeMapDelete(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key))
} else {
const sub = typeMapGet(/** @type {any} */ (this), op.key)
const sub = typeMapGet(/** @type {any} */ (this), /** @type {any} */ (op.key))
if (!(sub instanceof AbstractType)) {
error.unexpectedCase()
}
@@ -772,21 +772,14 @@ export class AbstractType {
export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
/**
* @template {delta.Delta<any,any,any,any,any>} D
* @typedef {D extends delta.Delta<infer N,infer Attrs,infer Cs,infer Text,any>
* ? delta.Delta<
* N,
* { [K in keyof Attrs]: TypeToDelta<Attrs[K]> },
* TypeToDelta<Cs>,
* Text
* >
* : D
* } ToDeepEventDelta
*/
/**
* @template {any} T
* @typedef {(Extract<T,AbstractType<any>> extends AbstractType<infer D> ? (unknown extends D ? never : ToDeepEventDelta<D>) : never) | Exclude<T,AbstractType<any>>} TypeToDelta
* @template {delta.DeltaConf} DConf
* @typedef {delta.DeltaConfOverwrite<DConf, {
* attrs: { [K in keyof delta.DeltaConfGetAttrs<DConf>]: delta.DeltaConfGetAttrs<DConf>[K] },
* children: (Extract<delta.DeltaConfGetChildren<DConf>,AbstractType<any>> extends AbstractType<infer SubDConf> ? (
* unknown extends SubDConf ? never : (delta.Delta<DeltaConfTypesToDelta<SubDConf>>)
* ) : never) | Exclude<delta.DeltaConfGetChildren<DConf>,AbstractType<any>>
* }>
* } DeltaConfTypesToDelta
*/
/**
@@ -1309,7 +1302,7 @@ export const typeMapGetAll = (parent) => {
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @template {delta.DeltaBuilder<any,any,any,any>} TypeDelta
* @template {delta.DeltaBuilderAny} TypeDelta
* @param {TypeDelta} d
* @param {YType_} parent
* @param {Set<string|null>?} attrsToRender
@@ -1341,10 +1334,10 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de
let c = array.last(content.getContent())
if (deleted) {
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
d.unset(key, attribution, c)
d.deleteAttr(key, attribution, c)
}
} else if (deep && c instanceof AbstractType && modified?.has(c)) {
d.update(key, c.getContent(am, opts))
d.modifyAttr(key, c.getContent(am, opts))
} else {
// find prev content
let prevContentItem = item
@@ -1356,7 +1349,7 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de
if (deep && c instanceof AbstractType) {
c = /** @type {any} */(c).getContent(am, optsAll)
}
d.set(key, c, attribution, prevValue)
d.setAttr(key, c, attribution, prevValue)
}
}
if (attrsToRender == null) {

270
src/types/Type.js Normal file
View File

@@ -0,0 +1,270 @@
/**
* @module YArray
*/
import {
AbstractType,
typeListGet,
typeListToArray,
typeListForEach,
typeListCreateIterator,
typeListInsertGenerics,
typeListPushGenerics,
typeListDelete,
typeListMap,
YArrayRefID,
transact,
warnPrematureAccess,
typeListSlice,
noAttributionsManager,
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js'
import * as delta from 'lib0/delta'
/**
* A shared Array implementation.
* @template {import('../utils/types.js').YValue} T
* @extends {AbstractType<delta.Delta<T>,YArray<T>>}
* @implements {Iterable<T>}
*/
// @todo remove this
// @ts-ignore
export class YType extends AbstractType {
constructor () {
super()
/**
* @type {Array<any>?}
* @private
*/
this._prelimContent = []
/**
* @type {Array<ArraySearchMarker>}
*/
this._searchMarker = []
}
/**
* Construct a new YArray containing the specified items.
* @template {import('../utils/types.js').YValue} T
* @param {Array<T>} items
* @return {YArray<T>}
*/
static from (items) {
/**
* @type {YArray<T>}
*/
const a = new YArray()
a.push(items)
return a
}
/**
* Integrate this type into the Yjs instance.
*
* * Save this struct in the os
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Doc} y The Yjs instance
* @param {Item?} item
*/
_integrate (y, item) {
super._integrate(y, item)
this.insert(0, /** @type {Array<any>} */ (this._prelimContent))
this._prelimContent = null
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YArray<T>}
*/
clone () {
/**
* @type {this}
*/
const arr = /** @type {this} */ (new YArray())
arr.insert(0, this.toArray().map(el =>
// @ts-ignore
el instanceof AbstractType ? /** @type {any} */ (el.clone()) : el
))
return arr
}
get length () {
this.doc ?? warnPrematureAccess()
return this._length
}
/**
* Inserts new content at an index.
*
* Important: This function expects an array of content. Not just a content
* object. The reason for this "weirdness" is that inserting several elements
* is very efficient when it is done as a single operation.
*
* @example
* // Insert character 'a' at position 0
* yarray.insert(0, ['a'])
* // Insert numbers 1, 2 at position 1
* yarray.insert(1, [1, 2])
*
* @param {number} index The index to insert content at.
* @param {Array<T>} content The array of content
*/
insert (index, content) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListInsertGenerics(transaction, this, index, /** @type {any} */ (content))
})
} else {
/** @type {Array<any>} */ (this._prelimContent).splice(index, 0, ...content)
}
}
/**
* Appends content to this YArray.
*
* @param {Array<T>} content Array of content to append.
*
* @todo Use the following implementation in all types.
*/
push (content) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListPushGenerics(transaction, this, /** @type {any} */ (content))
})
} else {
/** @type {Array<any>} */ (this._prelimContent).push(...content)
}
}
/**
* Prepends content to this YArray.
*
* @param {Array<T>} content Array of content to prepend.
*/
unshift (content) {
this.insert(0, content)
}
/**
* Deletes elements starting from an index.
*
* @param {number} index Index at which to start deleting elements
* @param {number} length The number of elements to remove. Defaults to 1.
*/
delete (index, length = 1) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListDelete(transaction, this, index, length)
})
} else {
/** @type {Array<any>} */ (this._prelimContent).splice(index, length)
}
}
/**
* Returns the i-th element from a YArray.
*
* @param {number} index The index of the element to return from the YArray
* @return {T}
*/
get (index) {
return typeListGet(this, index)
}
/**
* Transforms this YArray to a JavaScript Array.
*
* @return {Array<T>}
*/
toArray () {
return typeListToArray(this)
}
/**
* Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions.
*
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {delta.ArrayDelta<import('./AbstractType.js').TypeToDelta<T>>} The Delta representation of this type.
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
return super.getContentDeep(am)
}
/**
* Returns a portion of this YArray into a JavaScript Array selected
* from start to end (end not included).
*
* @param {number} [start]
* @param {number} [end]
* @return {Array<T>}
*/
slice (start = 0, end = this.length) {
return typeListSlice(this, start, end)
}
/**
* Transforms this Shared Type to a JSON object.
*
* @return {Array<any>}
*/
toJSON () {
return this.map(c => c instanceof AbstractType ? c.toJSON() : c)
}
/**
* Returns an Array with the result of calling a provided function on every
* element of this YArray.
*
* @template M
* @param {function(T,number,YArray<T>):M} f Function that produces an element of the new Array
* @return {Array<M>} A new array with each element being the result of the
* callback function
*/
map (f) {
return typeListMap(this, /** @type {any} */ (f))
}
/**
* Executes a provided function once on every element of this YArray.
*
* @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray.
*/
forEach (f) {
typeListForEach(this, f)
}
/**
* @return {IterableIterator<T>}
*/
[Symbol.iterator] () {
return typeListCreateIterator(this)
}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
*/
_write (encoder) {
encoder.writeTypeRef(YArrayRefID)
}
}
/**
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
* @return {import('../utils/types.js').YType}
*
* @private
* @function
*/
export const readYArray = _decoder => new YArray()

View File

@@ -13,28 +13,24 @@ import * as delta from 'lib0/delta' // eslint-disable-line
import * as set from 'lib0/set'
/**
* @typedef {import('./types.js').YType} _YType
*/
/**
* @template {AbstractType<any,any>} Target
* @template {delta.DeltaConf} DConf
* YEvent describes the changes on a YType.
*/
export class YEvent {
/**
* @param {Target} target The changed type.
* @param {AbstractType<DConf>} target The changed type.
* @param {Transaction} transaction
* @param {Set<any>?} subs The keys that changed
*/
constructor (target, transaction, subs) {
/**
* The type on which this event was created on.
* @type {Target}
* @type {AbstractType<DConf>}
*/
this.target = target
/**
* The current target on which the observe callback is called.
* @type {_YType}
* @type {AbstractType<any>}
*/
this.currentTarget = target
/**
@@ -43,11 +39,11 @@ export class YEvent {
*/
this.transaction = transaction
/**
* @type {(Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>)|null}
* @type {delta.Delta<DConf>|null}
*/
this._delta = null
/**
* @type {(Target extends AbstractType<infer D,any> ? import('../internals.js').ToDeepEventDelta<D> : delta.Delta<any,any,any,any,any>)|null}
* @type {import('../types/AbstractType.js').DeltaConfTypesToDeltaDelta<DConf>|null}
*/
this._deltaDeep = null
/**
@@ -120,7 +116,7 @@ export class YEvent {
* @param {AbstractAttributionManager} am
* @param {object} [opts]
* @param {Deep} [opts.deep]
* @return {Target extends AbstractType<infer D,any> ? (Deep extends true ? import('../internals.js').ToDeepEventDelta<D> : D) : delta.Delta<any,any,any,any>} The Delta representation of this type.
* @return {Deep extends true ? delta.Delta<import('../internals.js').DeltaConfTypesToDelta<DConf>> : delta.Delta<DConf>} The Delta representation of this type.
*
* @public
*/
@@ -159,7 +155,7 @@ export class YEvent {
* Compute the changes in the delta format.
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
*
* @type {Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>} The Delta representation of this type.
* @type {delta.Delta<DConf>} The Delta representation of this type.
* @public
*/
get delta () {
@@ -170,11 +166,11 @@ export class YEvent {
* Compute the changes in the delta format.
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
*
* @type {Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>} The Delta representation of this type.
* @type {import('../internals.js').DeltaConfTypesToDeltaDelta<DConf>} The Delta representation of this type.
* @public
*/
get deltaDeep () {
return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = this.getDelta(noAttributionsManager, { deep: true })))
return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = /** @type {any} */ (this.getDelta(noAttributionsManager, { deep: true }))))
}
}
@@ -188,8 +184,8 @@ export class YEvent {
* console.log(path) // might look like => [2, 'key1']
* child === type.get(path[0]).get(path[1])
*
* @param {_YType} parent
* @param {_YType} child target
* @param {AbstractType} parent
* @param {AbstractType} child target
* @param {AbstractAttributionManager} am
* @return {Array<string|number>} Path to the target
*
@@ -209,7 +205,7 @@ export const getPathTo = (parent, child, am = noAttributionsManager) => {
const apos = /** @type {AbsolutePosition} */ (createAbsolutePositionFromRelativePosition(createRelativePosition(parent, child._item.id), doc, false, am))
path.unshift(apos.index)
}
child = /** @type {_YType} */ (child._item.parent)
child = /** @type {AbstractType} */ (child._item.parent)
}
return path
}

View File

@@ -46,7 +46,7 @@ export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff(
const shareDelta = type.getContent(am, {
itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true
})
d.update(typename, shareDelta)
d.modifyAttr(typename, shareDelta)
}
})
})

View File

@@ -40,11 +40,11 @@ export const testAttributedEvents = _tc => {
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getContent(am)
t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] }))
t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] }))
let calledObserver = false
ytext.observe(event => {
const d = event.getDelta(am)
t.compare(d, delta.text().retain(11).insert('!', null, { insert: [] }))
t.compare(d, delta.create().retain(11).insert('!', null, { insert: [] }))
calledObserver = true
})
ytext.insert(11, '!')
@@ -64,8 +64,8 @@ export const testInsertionsMindingAttributedContent = _tc => {
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getContent(am)
t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] }))
ytext.applyDelta(delta.text().retain(11).insert('content'), am)
t.compare(c1, delta.create().insert('hello ').insert('world', null, { delete: [] }))
ytext.applyDelta(delta.create().retain(11).insert('content'), am)
t.assert(ytext.toString() === 'hello content')
}
@@ -82,8 +82,8 @@ export const testInsertionsIntoAttributedContent = _tc => {
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getContent(am)
t.compare(c1, delta.text().insert('hello ').insert('word', null, { insert: [] }))
ytext.applyDelta(delta.text().retain(9).insert('l'), am)
t.compare(c1, delta.create().insert('hello ').insert('word', null, { insert: [] }))
ytext.applyDelta(delta.create().retain(9).insert('l'), am)
t.assert(ytext.toString() === 'hello world')
}
@@ -102,9 +102,9 @@ export const testYdocDiff = () => {
const d = Y.diffDocsToDelta(ydocStart, ydocUpdated)
console.log('calculated diff', d.toJSON())
t.compare(d, delta.create()
.update('text', delta.create().retain(5).insert(' world', null, { insert: [] }))
.update('array', delta.create().retain(1).insert(['x'], null, { insert: [] }))
.update('map', delta.create().set('newk', 42, { insert: [] }).update('nested', delta.create().insert([1], null, { insert: [] })))
.modifyAttr('text', delta.create().retain(5).insert(' world', null, { insert: [] }))
.modifyAttr('array', delta.create().retain(1).insert(['x'], null, { insert: [] }))
.modifyAttr('map', delta.create().setAttr('newk', 42, { insert: [] }).modifyAttr('nested', delta.create().insert([1], null, { insert: [] })))
)
}
@@ -113,21 +113,21 @@ export const testChildListContent = () => {
const ydocUpdated = Y.cloneDoc(ydocStart)
const yf = new Y.XmlElement('test')
let calledEvent = 0
yf.applyDelta(delta.create().insert('test content').set('k', 'v'))
yf.applyDelta(delta.create().insert('test content').setAttr('k', 'v'))
const yarray = ydocUpdated.getArray('array')
yarray.observeDeep((events, tr) => {
calledEvent++
const event = events.find(event => event.target === yarray) || new Y.YEvent(yarray, tr, new Set(null))
const d = event.deltaDeep
const expectedD = delta.create().insert([delta.create('test').insert('test content').set('k', 'v')])
const expectedD = delta.create().insert([delta.create('test').insert('test content').setAttr('k', 'v')])
t.compare(d, expectedD)
})
ydocUpdated.getArray('array').insert(0, [yf])
t.assert(calledEvent === 1)
const d = Y.diffDocsToDelta(ydocStart, ydocUpdated)
console.log('calculated diff', d.toJSON())
t.compare(d, delta.create()
.update('array', delta.create().insert([delta.create('test').insert('test content', null, { insert: [] }).set('k', 'v', { insert: [] })], null, { insert: [] }))
)
const expected = delta.create()
.modifyAttr('array', delta.create().insert([delta.create('test').insert('test content', null, { insert: [] }).setAttr('k', 'v', { insert: [] })], null, { insert: [] }))
t.compare(d.done(), expected.done())
}

View File

@@ -84,7 +84,7 @@ export const testDeltaBasicSchema = _tc => {
* @param {t.TestCase} _tc
*/
export const testDeltaValues = _tc => {
const change = delta.create().set('a', 42).unset('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }])
const change = delta.create().setAttr('a', 42).deleteAttr('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }])
// iterate through attribute changes
for (const attrChange of change.attrs) {
if (delta.$insertOp.check(attrChange)) {
@@ -122,7 +122,7 @@ export const testBasics = _tc => {
const ydoc = new Y.Doc()
const ytype = ydoc.get('my data')
/**
* @type {delta.Delta<any,{ a: number }, { my: string }, string>}
* @type {delta.Delta<{attrs: { a: number }, children: { my: string }, text: true }>}
*/
let observedDelta = delta.create()
ytype.observe(event => {
@@ -130,7 +130,7 @@ export const testBasics = _tc => {
console.log('ytype changed:', observedDelta.toJSON())
})
// define a change: set attribute: a=42
const attrChange = delta.create().set('a', 42).done()
const attrChange = delta.create().setAttr('a', 42).done()
// define a change: insert textual content and an object
const childChange = delta.create().insert('hello').insert([{ my: 'object' }]).done()
// merge changes

View File

@@ -299,8 +299,8 @@ export const testUndoXml = tc => {
// format textchild and revert that change
undoManager.stopCapturing()
textchild.format(3, 4, { bold: true })
const v1 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('con').insert('tent', { bold: true }).done()]).done()]).done()
const v2 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.text().insert('content').done()]).done()]).done()
const v1 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.create().insert('con').insert('tent', { bold: true }).done()]).done()]).done()
const v2 = delta.create('UNDEFINED').insert([delta.create('p').insert([delta.create().insert('content').done()]).done()]).done()
t.compare(xml0.getContentDeep(), v1)
undoManager.undo()
t.compare(xml0.getContentDeep(), v2)