major update on the (nested) event system. created dedicated delta classes to represent changes and content representations on all types.

This commit is contained in:
Kevin Jahns
2025-07-19 16:17:05 +02:00
parent 72393e6ce8
commit e6ab2bbc12
23 changed files with 428 additions and 247 deletions

2
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "14.0.0-8",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.107"
"lib0": "^0.2.114"
},
"devDependencies": {
"@types/node": "^22.14.1",

View File

@@ -85,7 +85,7 @@
},
"homepage": "https://docs.yjs.dev",
"dependencies": {
"lib0": "^0.2.107"
"lib0": "^0.2.114"
},
"devDependencies": {
"@y/protocols": "^1.0.6-1",

View File

@@ -21,13 +21,6 @@ import * as error from 'lib0/error'
import * as math from 'lib0/math'
import * as log from 'lib0/logging'
/**
* @typedef {delta.ArrayDelta<any>|delta.TextDelta<any>|{ children: delta.ArrayDelta<Array<YXmlDeepContent>> }|{ children: delta.ArrayDelta<any>, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution } } }} YXmlDeepContent
*/
/**
* @typedef {delta.ArrayDelta<any>|delta.TextDelta<any>|{ children: delta.ArrayDelta<Array<DeepContent>> }|{ children: delta.ArrayDelta<any>, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution} } }} DeepContent
*/
/**
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
*/
@@ -264,8 +257,11 @@ export const callTypeObservers = (type, transaction, event) => {
}
/**
* @template EventType
* Abstract Yjs Type class
*
* @template EventType
* @template {import('../utils/Delta.js').Delta} [EventDelta=any]
* @template {import('../utils/Delta.js').Delta} [EventDeltaDeep=any]
*/
export class AbstractType {
constructor () {
@@ -303,10 +299,10 @@ export class AbstractType {
}
/**
* @return {AbstractType<any>|null}
* @return {AbstractType<any,any>|null}
*/
get parent () {
return this._item ? /** @type {AbstractType<any>} */ (this._item.parent) : null
return this._item ? /** @type {AbstractType<any,any>} */ (this._item.parent) : null
}
/**
@@ -325,7 +321,7 @@ export class AbstractType {
}
/**
* @return {AbstractType<EventType>}
* @return {AbstractType<EventType,any>}
*/
_copy () {
throw error.methodUnimplemented()
@@ -336,7 +332,7 @@ export class AbstractType {
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {AbstractType<EventType>}
* @return {AbstractType<EventType,any>}
*/
clone () {
throw error.methodUnimplemented()
@@ -415,15 +411,15 @@ export class AbstractType {
/**
* @param {AbstractAttributionManager} _am
* @return {any}
* @return {EventDelta}
*/
getDelta (_am) {
getContent (_am) {
error.methodUnimplemented()
}
/**
* @param {AbstractAttributionManager} _am
* @return {DeepContent}
* @return {EventDeltaDeep}
*/
getContentDeep (_am) {
error.methodUnimplemented()
@@ -431,7 +427,7 @@ export class AbstractType {
}
/**
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @param {number} start
* @param {number} end
* @return {Array<any>}
@@ -469,7 +465,7 @@ export const typeListSlice = (type, start, end) => {
}
/**
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @return {Array<any>}
*
* @private
@@ -498,8 +494,10 @@ export const typeListToArray = type => {
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractType<any>} type
* @template {delta.ArrayDelta<any,any>} TypeDelta
* @param {AbstractType<any,TypeDelta,any>} type
* @param {import('../internals.js').AbstractAttributionManager} am
* @return {TypeDelta}
*
* @private
* @function
@@ -528,11 +526,11 @@ export const typeListGetContent = (type, am) => {
}
}
}
return d
return /** @type {TypeDelta} */ (d.done())
}
/**
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @param {Snapshot} snapshot
* @return {Array<any>}
*
@@ -557,7 +555,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
/**
* Executes a provided function on once on every element of this YArray.
*
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @param {function(any,number,any):void} f A function to execute on every element of this YArray.
*
* @private
@@ -580,8 +578,8 @@ export const typeListForEach = (type, f) => {
/**
* @template C,R
* @param {AbstractType<any>} type
* @param {function(C,number,AbstractType<any>):R} f
* @param {AbstractType<any,any>} type
* @param {function(C,number,AbstractType<any,any>):R} f
* @return {Array<R>}
*
* @private
@@ -599,7 +597,7 @@ export const typeListMap = (type, f) => {
}
/**
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @return {IterableIterator<any>}
*
* @private
@@ -651,8 +649,8 @@ export const typeListCreateIterator = type => {
* Executes a provided function on once on every element of this YArray.
* Operates on a snapshotted state of the document.
*
* @param {AbstractType<any>} type
* @param {function(any,number,AbstractType<any>):void} f A function to execute on every element of this YArray.
* @param {AbstractType<any,any>} type
* @param {function(any,number,AbstractType<any,any>):void} f A function to execute on every element of this YArray.
* @param {Snapshot} snapshot
*
* @private
@@ -673,7 +671,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
}
/**
* @param {AbstractType<any>} type
* @param {AbstractType<any,any>} type
* @param {number} index
* @return {any}
*
@@ -700,7 +698,7 @@ export const typeListGet = (type, index) => {
/**
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {Item?} referenceItem
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
*
@@ -768,7 +766,7 @@ const lengthExceeded = () => error.create('Length exceeded!')
/**
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {number} index
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
*
@@ -821,7 +819,7 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => {
* the search marker.
*
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
*
* @private
@@ -841,7 +839,7 @@ export const typeListPushGenerics = (transaction, parent, content) => {
/**
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {number} index
* @param {number} length
*
@@ -888,7 +886,7 @@ export const typeListDelete = (transaction, parent, index, length) => {
/**
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {string} key
*
* @private
@@ -903,9 +901,9 @@ export const typeMapDelete = (transaction, parent, key) => {
/**
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractType<any,any>} parent
* @param {string} key
* @param {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} value
* @param {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any,any>} value
*
* @private
* @function

View File

@@ -23,19 +23,28 @@ import {
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js'
/**
*
* @template Content
* @template {import('../internals.js').Delta|undefined} Modifiers
* @typedef {import('../internals.js').ArrayDelta<Content,Modifiers>} ArrayDelta
*/
import * as delta from '../utils/Delta.js'
/**
* Event that describes the changes on a YArray
* @template T
* @template {import('../utils/types.js').YValue} T
* @extends YEvent<YArray<T>>
*/
export class YArrayEvent extends YEvent {}
/**
* A shared Array implementation.
* @template T
* @extends AbstractType<YArrayEvent<T>>
* @template {import('../utils/types.js').YValue} T
* @template {ArrayDelta<T,undefined>} [TypeDelta=ArrayDelta<T,undefined>]
* @template {T extends AbstractType<any,any,infer DeepD> ? ArrayDelta<Exclude<T,AbstractType<any>>|DeepD,DeepD> : ArrayDelta<T,undefined>} [EventDeltaDeep=T extends AbstractType<any,any,infer DeepD> ? ArrayDelta<Exclude<T,AbstractType<any>>|DeepD,DeepD> : ArrayDelta<T,undefined>]
* @extends AbstractType<YArrayEvent<T>,TypeDelta,EventDeltaDeep>
* @implements {Iterable<T>}
*/
export class YArray extends AbstractType {
@@ -54,7 +63,7 @@ export class YArray extends AbstractType {
/**
* Construct a new YArray containing the specified items.
* @template {Object<string,any>|Array<any>|number|null|string|Uint8Array} T
* @template {import('../utils/types.js').YValue} T
* @param {Array<T>} items
* @return {YArray<T>}
*/
@@ -219,16 +228,16 @@ export class YArray extends AbstractType {
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').ArrayDelta<Array<import('../types/AbstractType.js').DeepContent>>} The Delta representation of this type.
* @return {EventDeltaDeep} The Delta representation of this type.
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
return this.getDelta(am).map(d => /** @type {any} */ (
return /** @type {any} */ (this.getContent(am).map(d => /** @type {any} */ (
d instanceof delta.InsertArrayOp && d.insert instanceof Array
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution)
: d
))
)))
}
/**
@@ -239,11 +248,11 @@ export class YArray extends AbstractType {
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').ArrayDelta<Array<T>>} The Delta representation of this type.
* @return {TypeDelta} The Delta representation of this type.
*
* @public
*/
getDelta (am = noAttributionsManager) {
getContent (am = noAttributionsManager) {
return typeListGetContent(this, am)
}

View File

@@ -195,11 +195,11 @@ export class YMap extends AbstractType {
* attribution `{ isDeleted: true, .. }`.
*
* @param {import('../internals.js').AbstractAttributionManager} am
* @return {MapDelta<{[key:string]: MapType}>} The Delta representation of this type.
* @return {MapDelta<{[key:string]: MapType},undefined>} The Delta representation of this type.
*
* @public
*/
getDelta (am) {
getContent (am) {
return typeMapGetDelta(this, am)
}

View File

@@ -672,12 +672,12 @@ export class YTextEvent extends YEvent {
}
/**
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds>}}
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds,undefined>}}
*/
get changes () {
if (this._changes === null) {
/**
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds>}}
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds,undefined>}}
*/
const changes = {
keys: this.keys,
@@ -692,20 +692,20 @@ export class YTextEvent extends YEvent {
/**
* @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').TextDelta<TextEmbeds>} The Delta representation of this type.
* @return {import('../utils/Delta.js').TextDelta<TextEmbeds,undefined>} The Delta representation of this type.
*
* @public
*/
getDelta (am = noAttributionsManager) {
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
return this.target.getDelta(am, { itemsToRender, retainDeletes: true })
return this.target.getContent(am, { itemsToRender, retainDeletes: true })
}
/**
* Compute the changes in the delta format.
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
*
* @type {delta.TextDelta<TextEmbeds>}
* @type {delta.TextDelta<TextEmbeds,undefined>}
*
* @public
*/
@@ -789,7 +789,7 @@ export class YText extends AbstractType {
* @type {YText<Embeds>}
*/
const text = new YText()
text.applyDelta(this.getDelta())
text.applyDelta(this.getContent())
return text
}
@@ -843,7 +843,7 @@ export class YText extends AbstractType {
/**
* Apply a {@link Delta} on this shared YText type.
*
* @param {Array<any> | delta.Delta} delta The changes to apply on this element.
* @param {Array<any> | delta.TextDelta<Embeds,undefined>} delta The changes to apply on this element.
* @param {AbstractAttributionManager} am
*
* @public
@@ -851,10 +851,7 @@ export class YText extends AbstractType {
applyDelta (delta, am = noAttributionsManager) {
if (this.doc !== null) {
transact(this.doc, transaction => {
/**
* @type {Array<any>}
*/
const deltaOps = /** @type {Array<any>} */ (/** @type {delta.Delta} */ (delta).ops instanceof Array ? /** @type {delta.Delta} */ (delta).ops : delta)
const deltaOps = /** @type {Array<any>} */ (/** @type {delta.TextDelta<any,undefined>} */ (delta).ops instanceof Array ? /** @type {delta.TextDelta<any,undefined>} */ (delta).ops : delta)
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
for (let i = 0; i < deltaOps.length; i++) {
const op = deltaOps[i]
@@ -882,14 +879,14 @@ export class YText extends AbstractType {
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').TextDelta< Embeds extends import('./AbstractType.js').AbstractType<any> ? import('./AbstractType.js').DeepContent : Embeds >} The Delta representation of this type.
* @return {import('../utils/Delta.js').TextDelta<Embeds extends import('./AbstractType.js').AbstractType<infer SubEvent> ? SubEvent : Embeds, undefined>} The Delta representation of this type.
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
return this.getDelta(am).map(d =>
return this.getContent(am).map(d =>
d instanceof delta.InsertEmbedOp && d.insert instanceof AbstractType
? new delta.InsertEmbedOp(d.insert.getDelta(am), d.attributes, d.attribution)
? new delta.InsertEmbedOp(d.insert.getContent(am), d.attributes, d.attribution)
: d
)
}
@@ -906,13 +903,13 @@ export class YText extends AbstractType {
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
* @return {import('../utils/Delta.js').TextDelta<Embeds>} The Delta representation of this type.
* @return {import('../utils/Delta.js').TextDelta<Embeds,undefined>} The Delta representation of this type.
*
* @public
*/
getDelta (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false } = {}) {
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false } = {}) {
/**
* @type {import('../utils/Delta.js').TextDelta<Embeds>}
* @type {import('../utils/Delta.js').TextDeltaBuilder<Embeds>}
*/
const d = delta.createTextDelta()
/**
@@ -1108,7 +1105,8 @@ export class YText extends AbstractType {
}
}
}
return d.done()
// @todo! fix the typings here
return /** @type {any} */ (d.done())
}
/**
@@ -1291,7 +1289,7 @@ export class YText extends AbstractType {
* @param {this} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.getDelta().equals(other.getDelta())
return this.getContent().equals(other.getContent())
}
}

View File

@@ -218,26 +218,33 @@ export class YXmlElement extends YXmlFragment {
* attribution `{ isDeleted: true, .. }`.
*
* @param {AbstractAttributionManager} am
* @return {{ nodeName: string, children: delta.ArrayDelta<Array<import('./AbstractType.js').DeepContent>>, attributes: import('./AbstractType.js').MapAttributedContent<any> }}
* @return {{ nodeName: string, children: delta.ArrayDeltaBuilder<Array<import('./AbstractType.js').DeepContent>>, attributes: import('./AbstractType.js').MapAttributedContent<any> }}
*
* @public
*/
getContentDeep (am = noAttributionsManager) {
const { children: origChildren, attributes: origAttributes } = this.getDelta(am)
const { children: origChildren, attributes: origAttributes } = this.getContent(am)
const children = origChildren.map(d => /** @type {any} */ (
(d instanceof delta.InsertArrayOp && d.insert instanceof Array)
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDeltaBuilder<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
: d
))
/**
* @todo there is a Attributes type and a DeepAttributes type.
* @type {delta.MapDelta<>}
* @type {delta.MapDeltaBuilder<any,any>}
*/
const attributes = delta.createMapDelta()
object.forEach(origAttributes, (v, key) => {
attributes[key] = Object.assign({}, v, { value: v.value instanceof AbstractType ? v.value.getContentDeep(am) : v.value })
})
return { nodeName: this.nodeName, children, attributes }
origAttributes.forEach(
null,
(insertOp, key) => {
if (insertOp.value instanceof AbstractType) {
attributes.set(key, insertOp.value.getContentDeep(am), null, insertOp.attribution)
} else {
attributes.set(key, insertOp.value, undefined, insertOp.attribution)
}
}
)
return delta.createXmlDelta(this.nodeName, children, attributes)
}
/**
@@ -251,8 +258,8 @@ export class YXmlElement extends YXmlFragment {
*
* @public
*/
getDelta (am = noAttributionsManager) {
const { children } = super.getDelta(am)
getContent (am = noAttributionsManager) {
const { children } = super.getContent(am)
const attributes = typeMapGetDelta(this, am)
return new delta.XmlDelta(this.nodeName, children, attributes)
}

View File

@@ -384,9 +384,9 @@ export class YXmlFragment extends AbstractType {
* Calculate the attributed content using the attribution manager.
*
* @param {import('../internals.js').AbstractAttributionManager} am
* @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<YXmlElement|YXmlText|YXmlHook>> }}
* @return {{ children: import('../utils/Delta.js').ArrayDeltaBuilderBuilder<Array<YXmlElement|YXmlText|YXmlHook>> }}
*/
getDelta (am = noAttributionsManager) {
getContent (am = noAttributionsManager) {
const children = typeListGetContent(this, am)
return { children }
}
@@ -395,12 +395,12 @@ export class YXmlFragment extends AbstractType {
* Calculate the attributed content using the attribution manager.
*
* @param {import('../internals.js').AbstractAttributionManager} am
* @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>> }}
* @return {{ children: import('../utils/Delta.js').ArrayDeltaBuilderBuilder<Array<import('./AbstractType.js').YXmlDeepContent>> }}
*/
getContentDeep (am) {
const { children: origChildren } = this.getDelta(am)
const { children: origChildren } = this.getContent(am)
/**
* @type {import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>>}
* @type {import('../utils/Delta.js').ArrayDeltaBuilderBuilder<Array<import('./AbstractType.js').YXmlDeepContent>>}
*/
const children = origChildren.map(d => /** @type {any} */ (
d instanceof delta.InsertArrayOp && d.insert instanceof Array

View File

@@ -40,7 +40,7 @@ export class YXmlText extends YText {
*/
clone () {
const text = new YXmlText()
text.applyDelta(this.getDelta())
text.applyDelta(this.getContent())
return text
}
@@ -68,7 +68,7 @@ export class YXmlText extends YText {
}
toString () {
return this.getDelta().ops.map(dop => {
return this.getContent().ops.map(dop => {
if (dop instanceof delta.InsertStringOp) {
const nestedNodes = []
for (const nodeName in dop.attributes) {

View File

@@ -31,21 +31,21 @@ import {
import * as error from 'lib0/error'
import { ObservableV2 } from 'lib0/observable'
import * as encoding from 'lib0/encoding'
import * as s from 'lib0/schema'
export const attributionJsonSchema = s.object({
insert: s.array(s.string).optional,
insertedAt: s.number.optional,
delete: s.array(s.string).optional,
deletedAt: s.number.optional,
attributes: s.record(s.string, s.array(s.string)).optional,
attributedAt: s.number.optional
})
/**
* @todo rename this to `insertBy`, `insertAt`, ..
*
* @typedef {Object} Attribution
* @property {Array<any>} [Attribution.insert]
* @property {number} [Attribution.insertedAt]
* @property {Array<any>} [Attribution.acceptInsert]
* @property {number} [Attribution.acceptedDeleteAt]
* @property {Array<any>} [Attribution.acceptDelete]
* @property {number} [Attribution.acceptedDeleteAt]
* @property {Array<any>} [Attribution.delete]
* @property {number} [Attribution.deletedAt]
* @property {{ [key: string]: Array<any> }} [Attribution.attributes]
* @property {number} [Attribution.attributedAt]
* @typedef {s.Unwrap<typeof attributionJsonSchema>} Attribution
*/
/**
@@ -63,18 +63,13 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => {
*/
const attribution = {}
if (deleted) {
attribution.delete = []
attribution.delete = s.array(s.string).ensure([])
} else {
attribution.insert = []
}
attrs.forEach(attr => {
switch (attr.name) {
case 'acceptDelete':
delete attribution.delete
// eslint-disable-next-line no-fallthrough
case 'acceptInsert':
delete attribution.insert
// eslint-disable-next-line no-fallthrough
// eslint-disable-next-line no-fallthrough
case 'insert':
case 'delete': {
const as = /** @type {import('../utils/Delta.js').Attribution_} */ (attribution)

View File

@@ -3,6 +3,8 @@ import * as map from 'lib0/map'
import * as fun from 'lib0/function'
import * as traits from 'lib0/traits'
import * as error from 'lib0/error'
import * as s from 'lib0/schema'
import { attributionJsonSchema } from './AttributionManager.js'
/**
* @template {any} ArrayContent
@@ -50,6 +52,13 @@ export class InsertStringOp {
this.attribution = attribution
}
/**
* @return {'insert'}
*/
get type () {
return 'insert'
}
get length () {
return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1
}
@@ -84,6 +93,13 @@ export class InsertArrayOp {
this.attribution = attribution
}
/**
* @return {'insert'}
*/
get type () {
return 'insert'
}
get length () {
return this.insert.length
}
@@ -118,6 +134,13 @@ export class InsertEmbedOp {
this.attribution = attribution
}
/**
* @return {'insertEmbed'}
*/
get type () {
return 'insertEmbed'
}
get length () {
return 1
}
@@ -145,6 +168,13 @@ export class DeleteOp {
this.delete = len
}
/**
* @return {'delete'}
*/
get type () {
return 'delete'
}
get length () {
return 0
}
@@ -176,6 +206,13 @@ export class RetainOp {
this.attribution = attribution
}
/**
* @return {'retain'}
*/
get type () {
return 'retain'
}
get length () {
return this.retain
}
@@ -208,6 +245,13 @@ export class ModifyOp {
this.modify = delta
}
/**
* @return {'modify'}
*/
get type () {
return 'modify'
}
get length () {
return 1
}
@@ -247,7 +291,7 @@ export class AbstractDelta {
/**
* @template {Delta|undefined} [Modifiers=any]
* @typedef {(TextDelta<any,Modifiers> | ArrayDelta<any,Modifiers> | MapDelta<object> | XmlDelta<string,any,any,Modifiers,Modifiers> )} Delta
* @typedef {(TextDelta<any,Modifiers> | ArrayDelta<any,Modifiers> | MapDelta<object,Modifiers> | XmlDelta<string,any,any,Modifiers,Modifiers> )} Delta
*/
/**
@@ -271,10 +315,10 @@ export class AbstractArrayDelta extends AbstractDelta {
/**
* @template {(d:TDeltaOp) => DeltaOp<any,any,any>} Mapper
* @param {Mapper} f
* @return {DeltaBuilder<Type, Mapper extends (d:TDeltaOp) => infer OP ? OP : unknown,Modifiers>}
* @return {AbstractArrayDeltaBuilder<Type, Mapper extends (d:TDeltaOp) => infer OP ? OP : unknown,Modifiers>}
*/
map (f) {
const d = /** @type {DeltaBuilder<Type,any,Modifiers>} */ (new /** @type {any} */ (this.constructor)(this.type))
const d = /** @type {AbstractArrayDeltaBuilder<Type,any,Modifiers>} */ (new /** @type {any} */ (this.constructor)(this.type))
d.ops = this.ops.map(f)
// @ts-ignore
d.lastOp = d.ops[d.ops.length - 1] ?? null
@@ -307,7 +351,7 @@ export class AbstractArrayDelta extends AbstractDelta {
* )
*
* @param {null|((d:TDeltaOp,index:number)=>void)} f
* @param {null|((insertOp: (InsertEmbedOp<any> | InsertStringOp | InsertArrayOp<any>) & TDeltaOp,index:number)=>void)} insertHandler
* @param {null|((insertOp:Exclude<TDeltaOp,RetainOp|DeleteOp|ModifyOp<any>>,index:number)=>void)} insertHandler
* @param {null|((retainOp:RetainOp,index:number)=>void)} retainHandler
* @param {null|((deleteOp:DeleteOp,index:number)=>void)} deleteHandler
* @param {null|(Modifiers extends undefined ? null : ((modifyOp:ModifyOp<Modifiers extends undefined ? never : Modifiers>,index:number)=>void))} modifyHandler
@@ -380,14 +424,26 @@ class MapInsertOp {
this.value = value
}
/**
* @return {'insert'}
*/
get type () { return 'insert' }
toJSON () {
return {
type: this.type,
value: this.value
value: this.value,
prevValue: this.prevValue,
attribution: this.attribution
}
}
/**
* @param {MapInsertOp<V>} other
*/
[traits.EqualityTraitSymbol] (other) {
return fun.equalityDeep(this.value, other.value) && fun.equalityDeep(this.prevValue, other.prevValue) && fun.equalityDeep(this.attribution, other.attribution)
}
}
/**
@@ -405,13 +461,25 @@ class MapDeleteOp {
get value () { return undefined }
/**
* @type {'delete'}
*/
get type () { return 'delete' }
toJSON () {
return {
type: 'delete'
type: this.type,
prevValue: this.prevValue,
attribution: this.attribution
}
}
/**
* @param {MapDeleteOp<V>} other
*/
[traits.EqualityTraitSymbol] (other) {
return fun.equalityDeep(this.prevValue, other.prevValue) && fun.equalityDeep(this.attribution, other.attribution)
}
}
/**
@@ -427,14 +495,24 @@ class MapModifyOp {
get value () { return undefined }
get type () { return 'insert' }
/**
* @type {'modify'}
*/
get type () { return 'modify' }
toJSON () {
return {
type: 'modify',
type: this.type,
modify: this.modify.toJSON()
}
}
/**
* @param {MapModifyOp<Modifiers>} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.modify[traits.EqualityTraitSymbol](other.modify)
}
}
/**
@@ -443,9 +521,17 @@ class MapModifyOp {
* @typedef {MapInsertOp<V> | MapDeleteOp<V> | (Modifiers extends undefined ? never : MapModifyOp<Modifiers extends undefined ? never : Modifiers>)} MapDeltaChange
*/
export const mapDeltaChangeJsonSchema = s.union(
s.object({ type: s.literal('insert'), value: s.any, prevValue: s.any.optional, attribution: attributionJsonSchema.nullable.optional }),
s.object({ type: s.literal('delete'), prevValue: s.any.optional, attribution: attributionJsonSchema.nullable.optional }),
s.object({ type: s.literal('modify'), modify: s.any })
)
export const mapDeltaJsonSchema = s.record(s.string, mapDeltaChangeJsonSchema)
/**
* @template {object} Vals
* @template {Delta|undefined} [Modifiers=undefined]
* @template {Delta|undefined} Modifiers
*/
export class MapDelta extends AbstractDelta {
constructor () {
@@ -528,7 +614,7 @@ export class MapDelta extends AbstractDelta {
}
/**
* @param {MapDelta<any>} other
* @param {MapDelta<any,any>} other
* @return {boolean}
*/
equals (other) {
@@ -536,15 +622,15 @@ export class MapDelta extends AbstractDelta {
}
/**
* @return {object}
* @return {s.Unwrap<typeof mapDeltaJsonSchema>}
*/
toJSON () {
/**
* @type {any}
* @type {s.Unwrap<typeof mapDeltaJsonSchema>}
*/
const changes = {}
this.changes.forEach((change, key) => {
changes[key] = change.toJSON()
changes[/** @type {string} */ (key)] = change.toJSON()
})
return changes
}
@@ -560,7 +646,7 @@ export class MapDelta extends AbstractDelta {
}
/**
* @param {MapDelta<any>} other
* @param {MapDelta<any,any>} other
*/
[traits.EqualityTraitSymbol] (other) {
return fun.equalityDeep(this.changes, other.changes)
@@ -585,18 +671,18 @@ export class MapDelta extends AbstractDelta {
export class XmlDelta extends AbstractDelta {
/**
* @param {NodeName} nodeName
* @param {ArrayDelta<Children,ChildModifiers>} children
* @param {ArrayDeltaBuilder<Children,ChildModifiers>} children
* @param {MapDelta<Attrs,AttrModifiers>} attributes
*/
constructor (nodeName, children = createArrayDelta(), attributes = /** @type {any} */ (createMapDelta())) {
constructor (nodeName, children, attributes) {
super()
this.nodeName = nodeName
/**
* @type {ArrayDelta<Children,ChildModifiers>}
* @type {ArrayDeltaBuilder<Children,ChildModifiers>}
*/
this.children = children
/**
* @type {Done extends 'mutable' ? MapDeltaBuilder<Attrs> : MapDelta<Attrs>}
* @type {Done extends 'mutable' ? MapDeltaBuilder<Attrs> : MapDelta<Attrs,AttrModifiers>}
*/
this.attributes = /** @type {any} */ (attributes)
}
@@ -617,12 +703,27 @@ export class XmlDelta extends AbstractDelta {
this.attributes.done()
return /** @type {any} */ (this)
}
/**
* @param {XmlDelta<any,any,any>} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.nodeName === other.nodeName && this.children[traits.EqualityTraitSymbol](other.children) && this.attributes[traits.EqualityTraitSymbol](other.attributes)
}
}
/**
* @param {string|undefined} nodeName
* @template {string|undefined} NodeName
* @template Children
* @template {object} Attrs
* @template {Delta|undefined} [ChildModifiers=undefined]
* @template {Delta|undefined} [AttrModifiers=undefined]
* @param {NodeName} nodeName
* @param {ArrayDeltaBuilder<Children,ChildModifiers>} children
* @param {MapDeltaBuilder<Attrs,AttrModifiers>} attributes
* @return {XmlDelta<NodeName,Children,Attrs,ChildModifiers, AttrModifiers>}
*/
export const createXmlDelta = (nodeName = undefined) => new XmlDelta(nodeName)
export const createXmlDelta = (nodeName, children = createArrayDelta(), attributes = /** @type {any} */ (createMapDelta())) => new XmlDelta(nodeName, children, attributes)
/**
* @template {object} Vals
@@ -691,7 +792,7 @@ const mergeAttrs = (a, b) => object.isEmpty(a) ? b : (object.isEmpty(b) ? a : ob
* @template {Delta|undefined} Modifiers
* @extends AbstractArrayDelta<Type,TDeltaOp,Modifiers>
*/
export class DeltaBuilder extends AbstractArrayDelta {
export class AbstractArrayDeltaBuilder extends AbstractArrayDelta {
/**
* @param {Type} type
*/
@@ -823,72 +924,72 @@ export class DeltaBuilder extends AbstractArrayDelta {
}
/**
* @return {this}
* @return {Type extends 'array' ? ArrayDelta<TDeltaOp,Modifiers> : (Type extends 'text' ? TextDelta<TDeltaOp,Modifiers> : AbstractArrayDelta<Type,TDeltaOp,Modifiers>)}
*/
done () {
while (this.lastOp != null && this.lastOp instanceof RetainOp && this.lastOp.attributes === null && this.lastOp.attribution === null) {
this.ops.pop()
this.lastOp = this.ops[this.ops.length - 1] ?? null
}
return this
return /** @type {any} */ (this)
}
}
/**
* @template {any} ArrayContent
* @template {Delta|undefined} Modifiers
* @extends DeltaBuilder<'array', ArrayDeltaOp<ArrayContent>,Modifiers>
* @extends AbstractArrayDeltaBuilder<'array', ArrayDeltaOp<ArrayContent>,Modifiers>
*/
export class ArrayDelta extends DeltaBuilder {
export class ArrayDeltaBuilder extends AbstractArrayDeltaBuilder {
constructor () {
super('array')
}
}
/**
* @template {any} ArrayContent
* @template {Delta|undefined} Modifiers
* @typedef {AbstractArrayDelta<'array', ArrayDeltaOp<ArrayContent>,Modifiers>} ArrayDelta
*/
/**
* @template {object} Embeds
* @template {Delta|undefined} Modifiers
* @typedef {AbstractArrayDelta<'text',TextDeltaOp<Embeds,Modifiers>,Modifiers>} TextDelta
*/
/**
* @template {object} Embeds
* @template {Delta|undefined} [Modifiers=undefined]
* @extends DeltaBuilder<'text',TextDeltaOp<Embeds,Modifiers>,Modifiers>
* @extends AbstractArrayDeltaBuilder<'text',TextDeltaOp<Embeds,Modifiers>,Modifiers>
*/
export class TextDelta extends DeltaBuilder {
export class TextDeltaBuilder extends AbstractArrayDeltaBuilder {
constructor () {
super('text')
}
}
/**
* @template {'text'|'array'|'custom'} Type
* @template {DeltaOp<any,any,Modifiers>} DeltaOps
* @template {Delta|undefined} Modifiers
* @typedef {AbstractArrayDelta<Type,DeltaOps,Modifiers>} DeltaReadonly
* @template {object} [Embeds=any]
* @template {Delta|undefined} [Modifiers=undefined]
* @return {TextDeltaBuilder<Embeds,Modifiers>}
*/
/**
* @template {object} Embeds
* @template {Delta|undefined} Modifiers
* @typedef {DeltaReadonly<'text',TextDeltaOp<Embeds,Modifiers>,Modifiers>} TextDeltaReadonly
*/
/**
* @template {object} Embeds
* @template {Delta|undefined} Modifiers
* @return {TextDelta<Embeds,Modifiers>}
*/
export const createTextDelta = () => new TextDelta()
export const createTextDelta = () => new TextDeltaBuilder()
/**
* @template [V=any]
* @template {Delta|undefined} [Modifiers=undefined]
* @return {ArrayDelta<V,Modifiers>}
* @return {ArrayDeltaBuilder<V,Modifiers>}
*/
export const createArrayDelta = () => new ArrayDelta()
export const createArrayDelta = () => new ArrayDeltaBuilder()
/**
* @template {'custom' | 'text' | 'array'} T
* @param {DeltaJson} ops
* @param {'custom' | 'text' | 'array'} type
* @param {T} type
*/
export const fromJSON = (ops, type = 'custom') => {
const d = new DeltaBuilder(type)
export const fromJSON = (ops, type) => {
const d = new AbstractArrayDeltaBuilder(type)
for (let i = 0; i < ops.length; i++) {
const op = /** @type {any} */ (ops[i])
// @ts-ignore

View File

@@ -1,5 +1,5 @@
import {
TextDelta, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
TextDeltaBuilder, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
} from '../internals.js'
import * as set from 'lib0/set'
@@ -42,7 +42,7 @@ export class YEvent {
*/
this._keys = null
/**
* @type {TextDelta<any>?}
* @type {import('./Delta.js').TextDelta<any,undefined>?}
*/
this._delta = null
/**

11
src/utils/types.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt
* |import('../index.js').Array<any>
* |import('../index.js').Map<any>
* |import('../index.js').Text<any>
* |import('../index.js').XmlElement<any>
* |import('../index.js').XmlFragment
* |import('../index.js').XmlText
* |import('../index.js').XmlHook} YValue
*/

View File

@@ -42,6 +42,7 @@
"lib0/decoding.js": "./node_modules/lib0/decoding.js",
"lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs",
"lib0/decoding": "./node_modules/lib0/decoding.js",
"lib0/diff/patience": "./node_modules/lib0/diff/patience.js",
"lib0/diff.js": "./node_modules/lib0/diff.js",
"lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs",
"lib0/diff": "./node_modules/lib0/diff.js",
@@ -157,6 +158,7 @@
"lib0/performance.js": "./node_modules/lib0/performance.js",
"lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs",
"lib0/performance": "./node_modules/lib0/performance.js",
"lib0/schema": "./node_modules/lib0/schema.js",
"@y/protocols/package.json": "./node_modules/@y/protocols/package.json",
"@y/protocols/sync.js": "./node_modules/@y/protocols/sync.js",
"@y/protocols/dist/sync.cjs": "./node_modules/@y/protocols/dist/sync.cjs",
@@ -206,6 +208,7 @@
"lib0/decoding.js": "./node_modules/lib0/decoding.js",
"lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs",
"lib0/decoding": "./node_modules/lib0/decoding.js",
"lib0/diff/patience": "./node_modules/lib0/diff/patience.js",
"lib0/diff.js": "./node_modules/lib0/diff.js",
"lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs",
"lib0/diff": "./node_modules/lib0/diff.js",
@@ -320,7 +323,8 @@
"lib0/webcrypto": "./node_modules/lib0/webcrypto.js",
"lib0/performance.js": "./node_modules/lib0/performance.js",
"lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs",
"lib0/performance": "./node_modules/lib0/performance.js"
"lib0/performance": "./node_modules/lib0/performance.js",
"lib0/schema": "./node_modules/lib0/schema.js"
}
}
}

View File

@@ -39,7 +39,7 @@ export const testAttributedEvents = _tc => {
ytext.delete(6, 5)
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getDelta(am)
const c1 = ytext.getContent(am)
t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] }))
let calledObserver = false
ytext.observe(event => {
@@ -63,7 +63,7 @@ export const testInsertionsMindingAttributedContent = _tc => {
ytext.delete(6, 5)
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getDelta(am)
const c1 = ytext.getContent(am)
t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] }))
ytext.applyDelta(delta.createTextDelta().retain(11).insert('content'), am)
t.assert(ytext.toString() === 'hello content')
@@ -81,7 +81,7 @@ export const testInsertionsIntoAttributedContent = _tc => {
ytext.insert(6, 'word')
})
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
const c1 = ytext.getDelta(am)
const c1 = ytext.getContent(am)
t.compare(c1, delta.createTextDelta().insert('hello ').insert('word', null, { insert: [] }))
ytext.applyDelta(delta.createTextDelta().retain(9).insert('l'), am)
t.assert(ytext.toString() === 'hello world')

View File

@@ -41,5 +41,5 @@ export const testTextDecodingCompatibilityV1 = _tc => {
const oldVal = [{"insert":"1306rup"},{"insert":"uj","attributes":{"italic":true,"color":"#888"}},{"insert":"ikkcjnrcpsckw1319bccgkp\n"},{"insert":"\n1131","attributes":{"bold":true}},{"insert":"1326rpcznqahopcrtd","attributes":{"italic":true}},{"insert":"3axhkthhu","attributes":{"bold":true}},{"insert":"28"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"9"},{"insert":"04ku","attributes":{"italic":true}},{"insert":"1323nucvxsqlznwlfavmpc\nu"},{"insert":"tc","attributes":{"italic":true}},{"insert":"je1318jwskjabdndrdlmjae\n1293tj\nj1292qrmf"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"k\nuf"},{"insert":"14hs","attributes":{"italic":true}},{"insert":"13dccxdyxg"},{"insert":"zc","attributes":{"italic":true,"color":"#888"}},{"insert":"apo"},{"insert":"tn","attributes":{"bold":true}},{"insert":"r"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"gn\n"},{"insert":"z","attributes":{"italic":true}},{"insert":"\n121"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"291311kk9zjznywohpx"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"cnbrcaq\n"},{"insert":"1","attributes":{"italic":true,"color":"#888"}},{"insert":"1310g"},{"insert":"ws","attributes":{"italic":true,"color":"#888"}},{"insert":"hxwych"},{"insert":"kq","attributes":{"italic":true}},{"insert":"sdru1320cohbvcrkrpjngdoc\njqic\n"},{"insert":"2","attributes":{"italic":true,"color":"#888"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"90n1297zm"},{"insert":"v1309zlgvjx","attributes":{"bold":true}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"g","attributes":{"bold":true}},{"insert":"1314pycavu","attributes":{"italic":true,"color":"#888"}},{"insert":"pkzqcj"},{"insert":"sa","attributes":{"italic":true,"color":"#888"}},{"insert":"sjy\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"xr\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"1295qfrvlyfap201312qrwt"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"b1322rnbaokorixenvp\nrxq"},{"insert":"j","attributes":{"italic":true}},{"insert":"x","attributes":{"italic":true,"color":"#888"}},{"insert":"15mziwabzkrrmscvdovao\n0","attributes":{"italic":true}},{"insert":"hx","attributes":{"italic":true,"bold":true}},{"insert":"ojeetrjhxkr13031317pfcyhksrkpkt\nuhv1","attributes":{"italic":true}},{"insert":"32","attributes":{"italic":true,"color":"#888"}},{"insert":"4rorywthq1325iodbzizxhmlibvpyrxmq\n\nganln\nqne\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}},{"insert":"dvf"},{"insert":"ac","attributes":{"bold":true}},{"insert":"1302xciwa"},{"insert":"1305rl","attributes":{"bold":true}},{"insert":"08\n"},{"insert":"eyk","attributes":{"bold":true}},{"insert":"y1321apgivydqsjfsehhezukiqtt1307tvjiejlh"},{"insert":"1316zlpkmctoqomgfthbpg","attributes":{"bold":true}},{"insert":"gv"},{"insert":"lb","attributes":{"bold":true}},{"insert":"f\nhntk\njv1uu\n"},{"insert":{"image":"https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png"}}]
const doc = new Y.Doc()
Y.applyUpdate(doc, buffer.fromBase64(oldDoc))
t.compare(doc.getText('text').getDelta().toJSON(), oldVal)
t.compare(doc.getText('text').getContent().toJSON(), oldVal)
}

View File

@@ -1,6 +1,7 @@
import * as t from 'lib0/testing'
import * as delta from '../src/utils/Delta.js'
import * as Y from 'yjs'
import * as schema from 'lib0/schema'
/**
* @param {t.TestCase} _tc
@@ -88,12 +89,11 @@ export const testMapDelta = _tc => {
.useAttribution({ delete: ['me'] })
.delete('v', 94)
.useAttribution(null)
.set('over', 'writeme', 'i existed before')
.set('over', 'andout')
.set('over', 'andout', 'i existed before')
.done()
t.compare(d.toJSON(), {
key: { type: 'insert', value: 'value', prevValue: undefined, attribution: null },
v: { type: 'delete', value: undefined, prevValue: 94, attribution: { delete: ['me'] } },
v: { type: 'delete', prevValue: 94, attribution: { delete: ['me'] } },
over: { type: 'insert', value: 'andout', prevValue: 'i existed before', attribution: null }
})
t.compare(d.origin, null)
@@ -117,7 +117,7 @@ export const testMapDelta = _tc => {
t.assert(d.get(key)?.prevValue === 94)
t.assert(change.prevValue === 94) // should know that value is number
} else if (key === 'key') {
t.assert(change.value === 'value') // should know that value is number
t.assert(change.value === 'value') // should know that value is string
} else if (key === 'over') {
t.assert(change.value === 'andout')
} else {
@@ -147,10 +147,10 @@ export const testXmlDelta = _tc => {
(op, index) => {
arr.push(op.insert, index)
},
(op, index) => {
(op, _index) => {
arr.push(op.retain)
},
(op, index) => {
(op, _index) => {
arr.push(op.delete)
}
)
@@ -159,10 +159,69 @@ export const testXmlDelta = _tc => {
console.log(x)
}
const textDeltaSchema = schema.object({
ops: schema.array(
schema.any
)
})
/**
* @param {t.TestCase} tc
* @param {t.TestCase} _tc
*/
export const testTextModifyingDelta = tc => {
const d = /** @type {delta.TextDelta<Y.Map<any>|Y.Array<any>>} */ (delta.createTextDelta()).insert('hi').insert(new Y.Map()).done()
export const testTextModifyingDelta = _tc => {
const d = /** @type {delta.TextDelta<Y.Map<any>|Y.Array<any>,undefined>} */ (delta.createTextDelta().insert('hi').insert(new Y.Map()).done())
schema.assert(d, textDeltaSchema)
console.log(d)
}
/**
* @param {t.TestCase} _tc
*/
export const testYtypeDeltaTypings = _tc => {
const ydoc = new Y.Doc({ gc: false })
{
const yarray = /** @type {Y.Array<Y.Text|number>} */ (ydoc.getArray('numbers'))
const content = yarray.getContent()
content.forEach(
op => {
schema.union(
schema.constructedBy(delta.InsertArrayOp),
schema.constructedBy(delta.RetainOp),
schema.constructedBy(delta.DeleteOp)
).ensure(op)
},
op => {
schema.constructedBy(delta.InsertArrayOp).ensure(op)
},
op => {
schema.constructedBy(delta.RetainOp).ensure(op)
},
op => {
schema.constructedBy(delta.DeleteOp).ensure(op)
}
)
const cdeep = yarray.getContentDeep()
cdeep.forEach(
op => {
schema.union(
schema.constructedBy(delta.InsertArrayOp),
schema.constructedBy(delta.RetainOp),
schema.constructedBy(delta.DeleteOp),
schema.constructedBy(delta.ModifyOp)
).ensure(op)
},
op => {
schema.constructedBy(delta.InsertArrayOp).ensure(op)
},
op => {
schema.constructedBy(delta.RetainOp).ensure(op)
},
op => {
schema.constructedBy(delta.DeleteOp).ensure(op)
},
op => {
schema.constructedBy(delta.ModifyOp).ensure(op)
}
)
}
}

View File

@@ -11,16 +11,7 @@ export const testInconsistentFormat = () => {
const content = /** @type {Y.XmlText} */ (ydoc.get('text', Y.XmlText))
content.format(0, 6, { bold: null })
content.format(6, 4, { type: 'text' })
t.compare(content.getDelta(), delta.fromJSON([
{
attributes: { type: 'text' },
insert: 'Merge Test'
},
{
attributes: { type: 'text', italic: true },
insert: ' After'
}
]))
t.compare(content.getContent(), delta.createTextDelta().insert('Merge Test', { type: 'text' }).insert(' After', { type: 'text', italic: true }))
}
const initializeYDoc = () => {
const yDoc = new Y.Doc({ gc: false })
@@ -94,11 +85,11 @@ export const testUndoText = tc => {
t.assert(text0.toString() === 'bcxyz')
// test marks
text0.format(1, 3, { bold: true })
t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
undoManager.undo()
t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'bcxyz' }]))
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'bcxyz' }]))
undoManager.redo()
t.compare(text0.getDelta(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
}
/**
@@ -694,8 +685,8 @@ export const testUndoDeleteTextFormat = _tc => {
},
{ insert: ' off the shoulder of Orion.' }
])
t.compare(text.getDelta(), expect)
t.compare(text2.getDelta(), expect)
t.compare(text.getContent(), expect)
t.compare(text2.getContent(), expect)
}
/**

View File

@@ -126,7 +126,7 @@ export const testKeyEncoding = tc => {
const update = Y.encodeStateAsUpdateV2(users[0])
Y.applyUpdateV2(users[1], update)
t.compare(text1.getDelta().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }])
t.compare(text1.getContent().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }])
compare(users)
}
@@ -330,7 +330,7 @@ export const testObfuscateUpdates = _tc => {
const omap = odoc.getMap('map')
const oarray = odoc.getArray('array')
// test ytext
const delta = /** @type {Array<any>} */ (otext.getDelta().toJSON())
const delta = /** @type {Array<any>} */ (otext.getContent().toJSON())
t.assert(delta.length === 2)
t.assert(delta[0].insert !== 'text' && delta[0].insert.length === 4)
t.assert(object.length(delta[0].attributes) === 1)

View File

@@ -515,6 +515,9 @@ export const testIteratingArrayContainingTypes = _tc => {
*/
export const testAttributedContent = _tc => {
const ydoc = new Y.Doc({ gc: false })
/**
* @type {Y.Array<number>}
*/
const yarray = ydoc.getArray()
yarray.insert(0, [1, 2])
let attributionManager = Y.noAttributionsManager
@@ -529,7 +532,7 @@ export const testAttributedContent = _tc => {
yarray.insert(1, [42])
})
const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
const attributedContent = yarray.getDelta(attributionManager)
const attributedContent = yarray.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.assert(attributedContent.equals(expectedContent))
})

View File

@@ -4,7 +4,8 @@ import {
compareIDs,
noAttributionsManager,
TwosetAttributionManager,
createIdMapFromIdSet
createIdMapFromIdSet,
mapDeltaJsonSchema
} from '../src/internals.js'
import * as t from 'lib0/testing'
import * as prng from 'lib0/prng'
@@ -630,24 +631,24 @@ export const testAttributedContent = _tc => {
})
t.group('initial value', () => {
ymap.set('test', 42)
const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } }
const attributedContent = ymap.getDelta(attributionManager)
console.log(attributedContent)
t.compare(expectedContent, attributedContent)
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: undefined, value: 42, attribution: { insert: [] } } })
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.compare(expectedContent, attributedContent.toJSON())
})
t.group('overwrite value', () => {
ymap.set('test', 'fourtytwo')
const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } }
const attributedContent = ymap.getDelta(attributionManager)
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'insert', prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } })
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent)
t.compare(expectedContent, attributedContent)
t.compare(expectedContent, attributedContent.toJSON())
})
t.group('delete value', () => {
ymap.delete('test')
const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } }
const attributedContent = ymap.getDelta(attributionManager)
const expectedContent = mapDeltaJsonSchema.ensure({ test: { type: 'delete', prevValue: 'fourtytwo', attribution: { delete: [] } } })
const attributedContent = ymap.getContent(attributionManager)
console.log(attributedContent)
t.compare(expectedContent, attributedContent)
t.compare(expectedContent, attributedContent.toJSON())
})
}

View File

@@ -232,7 +232,10 @@ export const testDeltaBug = _tc => {
}
]
ytext.applyDelta(addingList)
const result = ytext.getDelta()
const result = ytext.getContent()
/**
* @type {delta.TextDelta<any,any>}
*/
const expectedResult = delta.createTextDelta()
.insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' })
.insert('\n\n\n', { 'table-col': { width: '150' } })
@@ -1589,7 +1592,7 @@ export const testDeltaBug2 = _tc => {
}
]
ytext.applyDelta(changeEvent)
const delta = ytext.getDelta()
const delta = ytext.getContent()
t.compare(delta.ops[40].toJSON(), {
insert: '\n',
attributes: {
@@ -1640,21 +1643,21 @@ export const testBasicInsertAndDelete = tc => {
text0.insert(0, 'abc')
t.assert(text0.toString() === 'abc', 'Basic insert works')
t.compare(eventDelta, delta.fromJSON([{ insert: 'abc' }]))
t.compare(eventDelta, delta.createTextDelta().insert('abc'))
text0.delete(0, 1)
t.assert(text0.toString() === 'bc', 'Basic delete works (position 0)')
t.compare(eventDelta, delta.fromJSON([{ delete: 1 }]))
t.compare(eventDelta, delta.createTextDelta().delete(1))
text0.delete(1, 1)
t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
t.compare(eventDelta, delta.fromJSON([{ retain: 1 }, { delete: 1 }]))
t.compare(eventDelta, delta.createTextDelta().retain(1).delete(1))
users[0].transact(() => {
text0.insert(0, '1')
text0.delete(0, 1)
})
t.compare(eventDelta, delta.fromJSON([]))
t.compare(eventDelta, delta.createTextDelta())
compare(users)
}
@@ -1670,29 +1673,29 @@ export const testBasicFormat = tc => {
})
text0.insert(0, 'abc', { bold: true })
t.assert(text0.toString() === 'abc', 'Basic insert with attributes works')
t.compare(text0.getDelta(), delta.createTextDelta().insert('abc', { bold: true }).done())
t.compare(text0.getContent(), delta.createTextDelta().insert('abc', { bold: true }).done())
t.compare(eventDelta, delta.createTextDelta().insert('abc', { bold: true }))
text0.delete(0, 1)
t.assert(text0.toString() === 'bc', 'Basic delete on formatted works (position 0)')
t.compare(text0.getDelta(), delta.createTextDelta().insert('bc', { bold: true }))
t.compare(text0.getContent(), delta.createTextDelta().insert('bc', { bold: true }))
t.compare(eventDelta, delta.createTextDelta().delete(1))
text0.delete(1, 1)
t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
t.compare(text0.getDelta(), delta.createTextDelta().insert('b', { bold: true }))
t.compare(text0.getContent(), delta.createTextDelta().insert('b', { bold: true }))
t.compare(eventDelta, delta.createTextDelta().retain(1).delete(1))
text0.insert(0, 'z', { bold: true })
t.assert(text0.toString() === 'zb')
t.compare(text0.getDelta(), delta.createTextDelta().insert('zb', { bold: true }))
t.compare(text0.getContent(), delta.createTextDelta().insert('zb', { bold: true }))
t.compare(eventDelta, delta.createTextDelta().insert('z', { bold: true }))
// @ts-ignore
t.assert(text0._start.right.right.right.content.str === 'b', 'Does not insert duplicate attribute marker')
text0.insert(0, 'y')
t.assert(text0.toString() === 'yzb')
t.compare(text0.getDelta(), delta.createTextDelta().insert('y').insert('zb', { bold: true }))
t.compare(text0.getContent(), delta.createTextDelta().insert('y').insert('zb', { bold: true }))
t.compare(eventDelta, delta.createTextDelta().insert('y'))
text0.format(0, 2, { bold: null })
t.assert(text0.toString() === 'yzb')
t.compare(text0.getDelta(), delta.createTextDelta().insert('yz').insert('b', { bold: true }))
t.compare(text0.getContent(), delta.createTextDelta().insert('yz').insert('b', { bold: true }))
t.compare(eventDelta, delta.createTextDelta().retain(1).retain(1, { bold: null }))
compare(users)
}
@@ -1707,13 +1710,13 @@ export const testFalsyFormats = tc => {
delta = event.delta.toJSON()
})
text0.insert(0, 'abcde', { falsy: false })
t.compare(text0.getDelta().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }])
t.compare(text0.getContent().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }])
t.compare(delta, [{ insert: 'abcde', attributes: { falsy: false } }])
text0.format(1, 3, { falsy: true })
t.compare(text0.getDelta().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(delta, [{ retain: 1 }, { retain: 3, attributes: { falsy: true } }])
text0.format(2, 1, { falsy: false })
t.compare(text0.getDelta().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(delta, [{ retain: 2 }, { retain: 1, attributes: { falsy: false } }])
compare(users)
}
@@ -1732,7 +1735,7 @@ export const testMultilineFormat = _tc => {
{ retain: 1 }, // newline character
{ retain: 10, attributes: { bold: true } }
])
t.compare(testText.getDelta().toJSON(), [
t.compare(testText.getContent().toJSON(), [
{ insert: 'Test', attributes: { bold: true } },
{ insert: '\n' },
{ insert: 'Multi-line', attributes: { bold: true } },
@@ -1753,7 +1756,7 @@ export const testNotMergeEmptyLinesFormat = _tc => {
{ insert: '\nText' },
{ insert: '\n', attributes: { title: true } }
])
t.compare(testText.getDelta().toJSON(), [
t.compare(testText.getContent().toJSON(), [
{ insert: 'Text' },
{ insert: '\n', attributes: { title: true } },
{ insert: '\nText' },
@@ -1777,7 +1780,7 @@ export const testPreserveAttributesThroughDelete = _tc => {
{ delete: 1 },
{ retain: 1, attributes: { title: true } }
])
t.compare(testText.getDelta().toJSON(), [
t.compare(testText.getContent().toJSON(), [
{ insert: 'Text' },
{ insert: '\n', attributes: { title: true } }
])
@@ -1791,7 +1794,7 @@ export const testGetDeltaWithEmbeds = tc => {
text0.applyDelta([{
insert: { linebreak: 's' }
}])
t.compare(text0.getDelta().toJSON(), [{
t.compare(text0.getContent().toJSON(), [{
insert: { linebreak: 's' }
}])
}
@@ -1804,7 +1807,7 @@ export const testTypesAsEmbed = tc => {
text0.applyDelta([{
insert: new Y.Map([['key', 'val']])
}])
t.compare(/** @type {delta.InsertEmbedOp<any>} */ (text0.getDelta().ops[0]).insert.toJSON(), { key: 'val' })
t.compare(/** @type {delta.InsertEmbedOp<any>} */ (text0.getContent().ops[0]).insert.toJSON(), { key: 'val' })
let firedEvent = false
text1.observe(event => {
const d = event.delta
@@ -1813,7 +1816,7 @@ export const testTypesAsEmbed = tc => {
firedEvent = true
})
testConnector.flushAllMessages()
const delta = text1.getDelta().toJSON()
const delta = text1.getContent().toJSON()
t.assert(delta.length === 1)
t.compare(/** @type {any} */ (delta[0]).insert.toJSON(), { key: 'val' })
t.assert(firedEvent, 'fired the event observer containing a Type-Embed')
@@ -1847,11 +1850,11 @@ export const testSnapshot = tc => {
}, {
delete: 1
}])
const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1))
const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1))
t.compare(state1.toJSON(), [{ insert: 'abcd' }])
const state2 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot2))
const state2 = text0.getContent(createAttributionManagerFromSnapshots(snapshot2))
t.compare(state2.toJSON(), [{ insert: 'axcd' }])
const state2Diff = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON()
const state2Diff = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON()
const expected = [{ insert: 'a' }, { insert: 'x', attribution: { insert: [] } }, { insert: 'b', attribution: { delete: [] } }, { insert: 'cd' }]
t.compare(state2Diff, expected)
}
@@ -1872,8 +1875,8 @@ export const testSnapshotDeleteAfter = tc => {
}, {
insert: 'e'
}])
const state1 = text0.getDelta(createAttributionManagerFromSnapshots(snapshot1))
t.compare(state1, delta.fromJSON([{ insert: 'abcd' }]))
const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1))
t.compare(state1, delta.createTextDelta().insert('abcd'))
}
/**
@@ -1892,7 +1895,7 @@ export const testToDeltaEmbedAttributes = tc => {
const { text0 } = init(tc, { users: 1 })
text0.insert(0, 'ab', { bold: true })
text0.insertEmbed(1, { image: 'imageSrc.png' }, { width: 100 })
const delta0 = text0.getDelta().toJSON()
const delta0 = text0.getContent().toJSON()
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }])
}
@@ -1903,7 +1906,7 @@ export const testToDeltaEmbedNoAttributes = tc => {
const { text0 } = init(tc, { users: 1 })
text0.insert(0, 'ab', { bold: true })
text0.insertEmbed(1, { image: 'imageSrc.png' })
const delta0 = text0.getDelta().toJSON()
const delta0 = text0.getContent().toJSON()
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' } }, { insert: 'b', attributes: { bold: true } }], 'toDelta does not set attributes key when no attributes are present')
}
@@ -1944,7 +1947,7 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => {
})
testConnector.flushAllMessages()
/**
* @type {Array<delta.TextDelta<any>>}
* @type {Array<delta.TextDelta<any,undefined>>}
*/
const deltas = []
text0.observe(event => {
@@ -2211,9 +2214,9 @@ export const testFormattingBug = async _tc => {
{ insert: '\n', attributes: { url: 'http://docs.yjs.dev' } },
{ insert: '\n', attributes: { url: 'http://example.com' } }
]
t.compare(text1.getDelta().toJSON(), expectedResult)
t.compare(text1.getDelta().toJSON(), text2.getDelta().toJSON())
console.log(text1.getDelta().toJSON())
t.compare(text1.getContent().toJSON(), expectedResult)
t.compare(text1.getContent().toJSON(), text2.getContent().toJSON())
console.log(text1.getContent().toJSON())
}
/**
@@ -2241,8 +2244,8 @@ export const testDeleteFormatting = _tc => {
{ insert: 'on ', attributes: { bold: true } },
{ insert: 'fire off the shoulder of Orion.' }
]
t.compare(text.getDelta().toJSON(), expected)
t.compare(text2.getDelta().toJSON(), expected)
t.compare(text.getContent().toJSON(), expected)
t.compare(text2.getContent().toJSON(), expected)
}
/**
@@ -2261,14 +2264,14 @@ export const testAttributedContent = _tc => {
t.group('insert / delete / format', () => {
ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }])
const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!')
const attributedContent = ytext.getDelta(attributionManager)
const attributedContent = ytext.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.assert(attributedContent.equals(expectedContent))
})
t.group('unformat', () => {
ytext.applyDelta([{ retain: 5, attributes: { italic: null } }])
const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
const attributedContent = ytext.getDelta(attributionManager)
const attributedContent = ytext.getContent(attributionManager)
console.log(attributedContent.toJSON())
t.assert(attributedContent.equals(expectedContent))
})
@@ -2299,7 +2302,7 @@ export const testAttributedDiffing = _tc => {
// implementations is the TwosetAttributionManager
const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions)
// we render the attributed content with the attributionManager
const attributedContent = ytext.getDelta(attributionManager)
const attributedContent = ytext.getContent(attributionManager)
console.log(JSON.stringify(attributedContent.toJSON(), null, 2))
const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!')
t.assert(attributedContent.equals(expectedContent))
@@ -2583,7 +2586,7 @@ export const testAttributionManagerDefaultPerformance = tc => {
})
t.measureTime(`getContent(attributionManager) performance <executed ${M} times>`, () => {
for (let i = 0; i < M; i++) {
ytext.getDelta()
ytext.getContent()
}
})
}

View File

@@ -207,7 +207,7 @@ export const testFormattingBug = _tc => {
{ insert: 'C', attributes: { em: {}, strong: {} } }
]
yxml.applyDelta(delta)
t.compare(yxml.getDelta().toJSON(), delta)
t.compare(yxml.getContent().toJSON(), delta)
}
/**
@@ -244,10 +244,10 @@ export const testFragmentAttributedContent = _tc => {
yfragment.insert(1, [elem3])
})
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
const attributedContent = yfragment.getDelta(attributionManager)
const attributedContent = yfragment.getContent(attributionManager)
console.log(attributedContent.children.toJSON())
t.assert(attributedContent.children.equals(expectedContent))
t.compare(elem1.getDelta(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON())
t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON())
})
}
@@ -273,17 +273,17 @@ export const testElementAttributedContent = _tc => {
yelement.setAttribute('key', '42')
})
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
const attributedContent = yelement.getDelta(attributionManager)
const attributedContent = yelement.getContent(attributionManager)
console.log('children', attributedContent.children.toJSON())
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.group('test getContentDeep', () => {
const expectedContent = delta.createArrayDelta().insert(
[delta.createTextDelta().insert('hello', null, { delete: [] })],
null,
{ delete: [] }
).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }])
).insert([delta.createXmlDelta('span')])
.insert([
delta.createTextDelta().insert('world', null, { insert: [] })
], null, { insert: [] })
@@ -292,7 +292,8 @@ export const testElementAttributedContent = _tc => {
console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2))
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.compare(attributedContent.attributes, /** @type {delta.MapDeltaBuilder<any>} */ (delta.createMapDelta()).set('key', '42', undefined, { insert: [] }))
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
})
@@ -321,13 +322,13 @@ export const testElementAttributedContentViaDiffer = _tc => {
console.log('attributes', attributedContent.attributes)
t.compare(attributedContent.children.toJSON(), expectedContent.toJSON())
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.group('test getContentDeep', () => {
const expectedContent = delta.createArrayDelta().insert(
[delta.createTextDelta().insert('hello')],
null,
{ delete: [] }
).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }])
).insert([delta.createXmlDelta('span')])
.insert([
delta.createTextDelta().insert('world', null, { insert: [] })
], null, { insert: [] })
@@ -336,7 +337,7 @@ export const testElementAttributedContentViaDiffer = _tc => {
console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2))
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
ydoc.transact(() => {
@@ -348,7 +349,7 @@ export const testElementAttributedContentViaDiffer = _tc => {
[delta.createTextDelta().insert('hello')],
null,
{ delete: [] }
).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }])
).insert([delta.createXmlDelta('span')])
.insert([
delta.createTextDelta().insert('bigworld', null, { insert: [] })
], null, { insert: [] })
@@ -357,13 +358,13 @@ export const testElementAttributedContentViaDiffer = _tc => {
console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2))
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
Y.applyUpdate(ydocV1, Y.encodeStateAsUpdate(ydoc))
t.group('test getContentDeep both docs synced', () => {
t.info('expecting diffingAttributionManager to auto update itself')
const expectedContent = delta.createArrayDelta().insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }]).insert([
const expectedContent = delta.createArrayDelta().insert([delta.createXmlDelta('span')]).insert([
delta.createTextDelta().insert('bigworld')
])
const attributedContent = yelement.getContentDeep(attributionManager)
@@ -371,7 +372,7 @@ export const testElementAttributedContentViaDiffer = _tc => {
console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2))
console.log('attributes', attributedContent.attributes)
t.assert(attributedContent.children.equals(expectedContent))
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: null } })
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: null } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
}