mirror of
https://github.com/yjs/yjs.git
synced 2025-12-14 18:57:45 +01:00
fixed all tests & proper diff /w attribution in nested deltas
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "14.0.0-14",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.115-4"
|
||||
"lib0": "^0.2.115-6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
@@ -3479,9 +3479,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.115-4",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-4.tgz",
|
||||
"integrity": "sha512-6/oPO1T3Hsl7YAIuCga+iGpIMwY3/6osNCZbMNkBGIdHrhEm+fdW2I4x0UNNL52BvyFTGJslJimj/iOJ+NqABA==",
|
||||
"version": "0.2.115-6",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-6.tgz",
|
||||
"integrity": "sha512-Xv2aEEvDYOzWiJEYZy5rM4WpKb1ujJYe70Wds/kDpxEP5wMbS/zWHFnveekx1aG3o8anq8FGrb9CCenGWA1BYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
},
|
||||
"homepage": "https://docs.yjs.dev",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.115-4"
|
||||
"lib0": "^0.2.115-6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
|
||||
@@ -464,22 +464,22 @@ export class 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
|
||||
* @param {Set<string>?} [opts.renderAttrs] - set of attrs to render. if null, render all attributes
|
||||
* @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only
|
||||
* @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
|
||||
* @param {Set<import('../utils/types.js').YType>|Map<import('../utils/types.js').YType,any>|null} [opts.modified] - set of types that should be rendered as modified children
|
||||
* @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.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am = noAttributionsManager, opts = {}) {
|
||||
const { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null, modified = null, deep = false } = 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))
|
||||
/**
|
||||
* @type {EventDelta extends delta.Delta<infer N,infer Attrs,infer Children,infer Text,any> ? delta.DeltaBuilder<N,Attrs,Children,Text,any> : never}
|
||||
*/
|
||||
const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null))
|
||||
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender)
|
||||
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender, opts)
|
||||
if (renderChildren) {
|
||||
/**
|
||||
* @type {delta.FormattingAttributes}
|
||||
@@ -571,7 +571,7 @@ export class AbstractType {
|
||||
if (c.deleted ? retainDeletes : retainInserts) {
|
||||
d.retain(c.content.getLength(), null, attribution ?? {})
|
||||
} else if (deep && c.content.constructor === ContentType) {
|
||||
d.insert([/** @type {any} */(c.content).type.getContent(am, { ...opts, renderChildren: true, renderAttrs: null })], null, attribution)
|
||||
d.insert([/** @type {any} */(c.content).type.getContent(am, opts)], null, attribution)
|
||||
} else {
|
||||
d.insert(c.content.getContent(), null, attribution)
|
||||
}
|
||||
@@ -1296,6 +1296,8 @@ export const typeMapGetAll = (parent) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo move this to getContent/getDelta
|
||||
*
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
*
|
||||
@@ -1305,17 +1307,18 @@ export const typeMapGetAll = (parent) => {
|
||||
* @template {delta.DeltaBuilder<any,any,any,any>} TypeDelta
|
||||
* @param {TypeDelta} d
|
||||
* @param {YType_} parent
|
||||
* @param {Set<string>?} attrsToRender
|
||||
* @param {Set<string|null>?} attrsToRender
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @param {boolean} deep
|
||||
* @param {Set<import('../utils/types.js').YType>|Map<import('../utils/types.js').YType,any>|null} [modified] - set of types that should be rendered as modified children
|
||||
* @param {import('../utils/IdSet.js').IdSet?} [deletedItems]
|
||||
* @param {import('../utils/IdSet.js').IdSet?} [itemsToRender]
|
||||
* @param {any} [opts]
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender) => {
|
||||
export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender, opts) => {
|
||||
// @todo support modified ops!
|
||||
/**
|
||||
* @param {Item} item
|
||||
@@ -1334,6 +1337,8 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de
|
||||
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
|
||||
d.unset(key, attribution, c)
|
||||
}
|
||||
} else if (deep && c instanceof AbstractType && modified?.has(c)) {
|
||||
d.update(key, c.getContent(am, opts))
|
||||
} else {
|
||||
// find prev content
|
||||
let prevContentItem = item
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
AbstractAttributionManager, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as map from 'lib0/map'
|
||||
import * as delta from 'lib0/delta' // eslint-disable-line
|
||||
import * as set from 'lib0/set'
|
||||
|
||||
/**
|
||||
* @typedef {import('./types.js').YType} _YType
|
||||
@@ -121,8 +123,33 @@ export class YEvent {
|
||||
*/
|
||||
getDelta (am = noAttributionsManager, { deep } = {}) {
|
||||
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
|
||||
const modified = deep ? this.transaction.changedParentTypes : null
|
||||
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: deep || this.childListChanged, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
|
||||
/**
|
||||
* @todo this should be done only one in the transaction step
|
||||
*
|
||||
* @type {Map<import('./types.js').YType,Set<string|null>>|null}
|
||||
*/
|
||||
let modified = this.transaction.changed
|
||||
if (deep) {
|
||||
// need to add deep changes to copy of modified
|
||||
const dchanged = new Map()
|
||||
modified.forEach((attrs, type) => {
|
||||
dchanged.set(type, new Set(attrs))
|
||||
})
|
||||
for (let m of modified.keys()) {
|
||||
while (m._item != null) {
|
||||
const item = m._item
|
||||
const ms = map.setIfUndefined(dchanged, item?.parent, set.create)
|
||||
if (item && !ms.has(item.parentSub)) {
|
||||
ms.add(item.parentSub)
|
||||
m = /** @type {any} */ (item.parent)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
modified = dchanged
|
||||
}
|
||||
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,7 @@ export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff(
|
||||
if (typeConf) {
|
||||
// @ts-ignore
|
||||
const shareDelta = type.getContent(am, {
|
||||
itemsToRender, retainDeletes: true, renderAttrs: /** @type {Set<string>} */ (changedTypes.get(type)), renderChildren: typeConf.has(null), deletedItems: deletesOnly, modified: changedTypes, deep: true
|
||||
itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true
|
||||
})
|
||||
d.update(typename, shareDelta)
|
||||
}
|
||||
|
||||
12
test.html
12
test.html
@@ -35,7 +35,7 @@
|
||||
"lib0/conditions": "./node_modules/lib0/conditions.js",
|
||||
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
|
||||
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
|
||||
"lib0/delta": "./node_modules/lib0/delta/d2.js",
|
||||
"lib0/delta": "./node_modules/lib0/delta/delta.js",
|
||||
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
|
||||
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
|
||||
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
|
||||
@@ -137,9 +137,7 @@
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/traits.js": "./node_modules/lib0/traits.js",
|
||||
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
|
||||
"lib0/traits": "./node_modules/lib0/traits.js",
|
||||
"lib0/traits": "./node_modules/lib0/trait/traits.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
@@ -202,7 +200,7 @@
|
||||
"lib0/conditions": "./node_modules/lib0/conditions.js",
|
||||
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
|
||||
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
|
||||
"lib0/delta": "./node_modules/lib0/delta/d2.js",
|
||||
"lib0/delta": "./node_modules/lib0/delta/delta.js",
|
||||
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
|
||||
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
|
||||
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
|
||||
@@ -304,9 +302,7 @@
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/traits.js": "./node_modules/lib0/traits.js",
|
||||
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
|
||||
"lib0/traits": "./node_modules/lib0/traits.js",
|
||||
"lib0/traits": "./node_modules/lib0/trait/traits.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
|
||||
@@ -100,11 +100,10 @@ export const testYdocDiff = () => {
|
||||
ydocUpdated.getMap('map').get('nested').insert(0, [1])
|
||||
// @todo add custom attribution
|
||||
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'))
|
||||
.update('array', delta.create().retain(1).insert(['x']))
|
||||
.update('map', delta.create().set('newk', 42).update('nested', delta.create().insert([1])))
|
||||
.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: [] })))
|
||||
)
|
||||
console.log(d.toJSON())
|
||||
debugger
|
||||
}
|
||||
|
||||
@@ -37,6 +37,37 @@ export const testIterators = _tc => {
|
||||
console.log(vals, entries, keys)
|
||||
}
|
||||
|
||||
export const testNestedMapEvent = () => {
|
||||
const ydoc = new Y.Doc()
|
||||
const ymap = ydoc.getMap()
|
||||
const ymapNested = ymap.set('nested', new Y.Map())
|
||||
let called = 0
|
||||
ymap.observeDeep((events, tr) => {
|
||||
const event = events.find(event => event.target === ymap) || new Y.YEvent(ymap, tr, new Set())
|
||||
const d = event.deltaDeep
|
||||
called++
|
||||
t.compare(d, delta.create().update('nested', delta.create().set('k', 'v')))
|
||||
})
|
||||
ymapNested.set('k', 'v')
|
||||
t.assert(called === 1)
|
||||
}
|
||||
|
||||
export const testNestedMapEvent2 = () => {
|
||||
const ydoc = new Y.Doc()
|
||||
const yarr = ydoc.getArray()
|
||||
const ymapNested = new Y.Map()
|
||||
yarr.insert(0, [ymapNested])
|
||||
let called = 0
|
||||
yarr.observeDeep((events, tr) => {
|
||||
const event = events.find(event => event.target === yarr) || new Y.YEvent(yarr, tr, new Set())
|
||||
const d = event.deltaDeep
|
||||
called++
|
||||
t.compare(d, delta.create().modify(delta.create().set('k', 'v')))
|
||||
})
|
||||
ymapNested.set('k', 'v')
|
||||
t.assert(called === 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing event changes after transaction should result in an error. See yjs#539
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user