fixed several v2 issues

This commit is contained in:
Kevin Jahns
2025-10-22 23:25:30 +02:00
parent c9829b0993
commit 1ce8154d64
4 changed files with 112 additions and 125 deletions

View File

@@ -16,7 +16,12 @@ import {
ContentEmbed, ContentEmbed,
getItemCleanStart, getItemCleanStart,
noAttributionsManager, noAttributionsManager,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager, // eslint-disable-line transact,
ItemTextListPosition,
insertText,
deleteText,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager,
YXmlElement, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as delta from 'lib0/delta' import * as delta from 'lib0/delta'
@@ -178,7 +183,7 @@ export const findMarker = (yarray, index) => {
// window.lengths.push(marker.index - pindex) // window.lengths.push(marker.index - pindex)
// console.log('distance', marker.index - pindex, 'len', p && p.parent.length) // console.log('distance', marker.index - pindex, 'len', p && p.parent.length)
// } // }
if (marker !== null && math.abs(marker.index - pindex) < /** @type {YText|YArray<any>} */ (p.parent).length / maxSearchMarker) { if (marker !== null && math.abs(marker.index - pindex) < /** @type {any} */ (p.parent).length / maxSearchMarker) {
// adjust existing marker // adjust existing marker
overwriteMarker(marker, p, pindex) overwriteMarker(marker, p, pindex)
return marker return marker
@@ -307,6 +312,10 @@ export class AbstractType {
* @type {null | Array<ArraySearchMarker>} * @type {null | Array<ArraySearchMarker>}
*/ */
this._searchMarker = null this._searchMarker = null
/**
* @type {EventDelta?}
*/
this._prelim = null
} }
/** /**
@@ -337,6 +346,10 @@ export class AbstractType {
_integrate (y, item) { _integrate (y, item) {
this.doc = y this.doc = y
this._item = item this._item = item
if (this._prelim) {
this.applyDelta(this._prelim)
this._prelim = null
}
} }
/** /**
@@ -437,6 +450,8 @@ export class AbstractType {
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`. * attribution `{ isDeleted: true, .. }`.
* *
* @template {boolean} [Deep=false]
*
* @param {AbstractAttributionManager} am * @param {AbstractAttributionManager} am
* @param {Object} [opts] * @param {Object} [opts]
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender] * @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
@@ -445,16 +460,19 @@ export class AbstractType {
* @param {Set<string>?} [opts.renderAttrs] - set of attrs to render. if null, render all attributes * @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 {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 {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
* @return {EventDelta} The Delta representation of this type. * @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 {Deep} [opts.deep] - render child types as delta
* @return {Deep extends true ? ToDeepEventDelta<EventDelta> : EventDelta} The Delta representation of this type.
* *
* @public * @public
*/ */
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null } = {}) { getContent (am = noAttributionsManager, opts = {}) {
const { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null, modified = null, deep = false } = opts
/** /**
* @type {EventDelta} * @type {EventDelta}
*/ */
const d = /** @type {any} */ (delta.create()) const d = /** @type {any} */ (delta.create(this.nodeName || null))
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deletedItems, itemsToRender) typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender)
if (renderChildren) { if (renderChildren) {
/** /**
* @type {delta.FormattingAttributes} * @type {delta.FormattingAttributes}
@@ -545,15 +563,21 @@ export class AbstractType {
usingCurrentAttributes = true usingCurrentAttributes = true
if (c.deleted ? retainDeletes : retainInserts) { if (c.deleted ? retainDeletes : retainInserts) {
d.retain(c.content.getLength(), null, attribution ?? {}) d.retain(c.content.getLength(), null, attribution ?? {})
} else if (deep && c.content.constructor === ContentType) {
d.insert([/** @type {any} */ (c.content).type.getContent(am, opts)], null, attribution)
} else { } else {
d.insert(c.content.getContent(), null, attribution) d.insert(c.content.getContent(), null, attribution)
} }
} else if (renderDelete) { } else if (renderDelete) {
d.delete(1) d.delete(1)
} else if (retainContent) { } else if (retainContent) {
d.usedAttributes = changedAttributes if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
usingChangedAttributes = true d.modify(/** @type {any} */ (c.content).type.getContent(am, opts))
d.retain(1) } else {
d.usedAttributes = changedAttributes
usingChangedAttributes = true
d.retain(1)
}
} }
break break
case ContentFormat: { case ContentFormat: {
@@ -663,23 +687,52 @@ export class AbstractType {
* @return {ToDeepEventDelta<EventDelta>} * @return {ToDeepEventDelta<EventDelta>}
*/ */
getContentDeep (am = noAttributionsManager) { getContentDeep (am = noAttributionsManager) {
const d = this.getContent(am) return /** @type {any} */ (this.getContent(am, { deep: true }))
d.children.forEach(op => { }
if (op instanceof delta.InsertOp) {
op.insert = /** @type {any} */ (op.insert.map(ins => /**
ins instanceof AbstractType * Apply a {@link Delta} on this shared type.
// @ts-ignore *
? ins.getContentDeep(am) * @param {delta.Delta<any,any,any,any,any>} d The changes to apply on this element.
: ins) * @param {AbstractAttributionManager} am
) *
} * @public
}) */
d.attrs.forEach((op) => { applyDelta (d, am = noAttributionsManager) {
if (delta.$insertOp.check(op) && op.value instanceof AbstractType) { if (this.doc == null)
op.value = op.value.getContentDeep(am) (this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d)
} else {
}) // @todo this was moved here from ytext. Make this more generic
return /** @type {any} */ (d.done()) transact(this.doc, transaction => {
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
for (const op of d.children) {
if (delta.$textOp.check(op)) {
insertText(transaction, this, currPos, op.insert, op.format || {})
} else if (delta.$insertOp.check(op)) {
for (let i = 0; i < op.insert.length; i++) {
let ins = op.insert[i]
if (delta.$deltaAny.check(ins)) {
if (ins.name != null) {
const t = new YXmlElement(ins.name)
t.applyDelta(ins)
ins = t
} else {
error.unexpectedCase()
}
}
insertText(transaction, this, currPos, ins, op.format || {})
}
} else if (delta.$retainOp.check(op)) {
currPos.formatText(transaction, this, op.retain, op.format || {})
} else if (delta.$deleteOp.check(op)) {
deleteText(transaction, currPos, op.delete)
} else if (delta.$modifyOp.check(op)) {
/** @type {ContentType} */ (currPos.right?.content).type.applyDelta(op.modify)
currPos.formatText(transaction, this, 1, op.format || {})
}
}
})
}
} }
} }
@@ -1230,13 +1283,16 @@ export const typeMapGetAll = (parent) => {
* @param {YType_} parent * @param {YType_} parent
* @param {Set<string>?} attrsToRender * @param {Set<string>?} attrsToRender
* @param {import('../internals.js').AbstractAttributionManager} am * @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?} [deletedItems]
* @param {import('../utils/IdSet.js').IdSet?} [itemsToRender] * @param {import('../utils/IdSet.js').IdSet?} [itemsToRender]
* *
* @private * @private
* @function * @function
*/ */
export const typeMapGetDelta = (d, parent, attrsToRender, am, deletedItems, itemsToRender) => { export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender) => {
// @todo support modified ops!
/** /**
* @param {Item} item * @param {Item} item
* @param {string} key * @param {string} key
@@ -1248,8 +1304,8 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deletedItems, item
const cs = [] const cs = []
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1) am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
const { deleted, attrs, content } = cs[cs.length - 1] const { deleted, attrs, content } = cs[cs.length - 1]
const c = array.last(content.getContent())
const attribution = createAttributionFromAttributionItems(attrs, deleted) const attribution = createAttributionFromAttributionItems(attrs, deleted)
let c = array.last(content.getContent())
if (deleted) { if (deleted) {
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) { if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
d.unset(key, attribution, c) d.unset(key, attribution, c)
@@ -1262,6 +1318,9 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deletedItems, item
// nop // nop
} }
const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined const prevValue = (prevContentItem !== item && itemsToRender?.hasId(prevContentItem.lastId)) ? array.last(prevContentItem.content.getContent()) : undefined
if (deep && c instanceof AbstractType) {
c = c.getContent(am)
}
d.set(key, c, attribution, prevValue) d.set(key, c, attribution, prevValue)
} }
} }

View File

@@ -337,7 +337,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
* @private * @private
* @function * @function
**/ **/
const insertText = (transaction, parent, currPos, text, attributes) => { export const insertText = (transaction, parent, currPos, text, attributes) => {
currPos.currentAttributes.forEach((_val, key) => { currPos.currentAttributes.forEach((_val, key) => {
if (attributes[key] === undefined) { if (attributes[key] === undefined) {
attributes[key] = null attributes[key] = null
@@ -546,7 +546,7 @@ export const cleanupYTextAfterTransaction = transaction => {
* @private * @private
* @function * @function
*/ */
const deleteText = (transaction, currPos, length) => { export const deleteText = (transaction, currPos, length) => {
const startLength = length const startLength = length
const startAttrs = map.copy(currPos.currentAttributes) const startAttrs = map.copy(currPos.currentAttributes)
const start = currPos.right const start = currPos.right
@@ -745,37 +745,6 @@ export class YText extends AbstractType {
return this.toString() return this.toString()
} }
/**
* Apply a {@link Delta} on this shared YText type.
*
* @param {delta.TextDelta<Embeds>} d The changes to apply on this element.
* @param {AbstractAttributionManager} am
*
* @public
*/
applyDelta (d, am = noAttributionsManager) {
if (this.doc !== null) {
transact(this.doc, transaction => {
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
for (const op of d.children) {
if (delta.$textOp.check(op)) {
insertText(transaction, this, currPos, op.insert, op.format || {})
} else if (delta.$insertOp.check(op)) {
for (let i = 0; i < op.insert.length; i++) {
insertText(transaction, this, currPos, op.insert[i], op.format || {})
}
} else if (delta.$retainOp.check(op)) {
currPos.formatText(transaction, this, op.retain, op.format || {})
} else if (delta.$deleteOp.check(op)) {
deleteText(transaction, currPos, op.delete)
}
}
})
} else {
/** @type {Array<function>} */ (this._pending).push(() => this.applyDelta(d))
}
}
/** /**
* Insert text at a given index. * Insert text at a given index.
* *

View File

@@ -59,6 +59,7 @@ export class YXmlFragment extends AbstractType {
constructor () { constructor () {
super() super()
/** /**
* @todo remove _prelimContent
* @type {Array<any>|null} * @type {Array<any>|null}
*/ */
this._prelimContent = [] this._prelimContent = []

View File

@@ -41,14 +41,14 @@ export class YEvent {
* @type {Transaction} * @type {Transaction}
*/ */
this.transaction = transaction this.transaction = transaction
/**
* @type {null | Map<string, { action: 'add' | 'update' | 'delete', oldValue: any }>}
*/
this._keys = null
/** /**
* @type {(Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>)|null} * @type {(Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>)|null}
*/ */
this._delta = null this._delta = null
/**
* @type {(Target extends AbstractType<infer D,any> ? import('../internals.js').ToDeepEventDelta<D> : delta.Delta<any,any,any,any,any>)|null}
*/
this._deltaDeep = null
/** /**
* @type {Array<string|number>|null} * @type {Array<string|number>|null}
*/ */
@@ -102,63 +102,6 @@ export class YEvent {
return this.transaction.deleteSet.hasId(struct.id) return this.transaction.deleteSet.hasId(struct.id)
} }
/**
* @type {Map<string, { action: 'add' | 'update' | 'delete', oldValue: any }>}
*/
get keys () {
if (this._keys === null) {
if (this.transaction.doc._transactionCleanups.length === 0) {
throw error.create(errorComputeChanges)
}
const keys = new Map()
const target = this.target
// @ts-ignore
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
changed.forEach(key => {
if (key !== null) {
const item = /** @type {Item} */ (target._map.get(key))
/**
* @type {'delete' | 'add' | 'update'}
*/
let action
let oldValue
if (this.adds(item)) {
let prev = item.left
while (prev !== null && this.adds(prev)) {
prev = prev.left
}
if (this.deletes(item)) {
if (prev !== null && this.deletes(prev)) {
action = 'delete'
oldValue = array.last(prev.content.getContent())
} else {
return
}
} else {
if (prev !== null && this.deletes(prev)) {
action = 'update'
oldValue = array.last(prev.content.getContent())
} else {
action = 'add'
oldValue = undefined
}
}
} else {
if (this.deletes(item)) {
action = 'delete'
oldValue = array.last(/** @type {Item} */ item.content.getContent())
} else {
return // nop
}
}
keys.set(key, { action, oldValue })
}
})
this._keys = keys
}
return this._keys
}
/** /**
* Check if a struct is added by this event. * Check if a struct is added by this event.
* *
@@ -172,14 +115,18 @@ export class YEvent {
} }
/** /**
* @template {boolean} [Deep=false]
* @param {AbstractAttributionManager} am * @param {AbstractAttributionManager} am
* @return {Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any>} The Delta representation of this type. * @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.
* *
* @public * @public
*/ */
getDelta (am = noAttributionsManager) { getDelta (am = noAttributionsManager, { deep } = {}) {
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)]) const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: this.childListChanged, deletedItems: this.transaction.deleteSet })) 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 }))
} }
/** /**
@@ -192,6 +139,17 @@ export class YEvent {
get delta () { get delta () {
return /** @type {any} */ (this._delta ?? (this._delta = this.getDelta())) return /** @type {any} */ (this._delta ?? (this._delta = this.getDelta()))
} }
/**
* 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.
* @public
*/
get deltaDeep () {
return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = this.getDelta(noAttributionsManager, { deep: true })))
}
} }
/** /**