more attribution fixes for y-quill

This commit is contained in:
Kevin Jahns
2025-05-24 12:56:37 +02:00
parent 34b90fcdd6
commit 90514dd51b
4 changed files with 38 additions and 32 deletions

View File

@@ -518,7 +518,7 @@ export const typeListGetContent = (type, am) => {
}
for (let i = 0; i < cs.length; i++) {
const c = cs[i]
const attribution = createAttributionFromAttributionItems(c.attrs, c.deleted).attribution
const attribution = createAttributionFromAttributionItems(c.attrs, c.deleted)
if (c.content.isCountable()) {
if (c.render || attribution != null) {
d.insert(c.content.getContent(), null, attribution)
@@ -1014,7 +1014,7 @@ export const typeMapGetContent = (parent, am) => {
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 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)
if (deleted) {
mapcontent[key] = { prevValue: c, value: undefined, attribution }
} else {

View File

@@ -27,7 +27,8 @@ import {
noAttributionsManager, AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, Transaction, // eslint-disable-line
createAttributionFromAttributionItems,
mergeIdSets,
diffIdSet
diffIdSet,
ContentDeleted
} from '../internals.js'
import * as delta from '../utils/Delta.js'
@@ -900,12 +901,12 @@ export class YText extends AbstractType {
if (itemsToRender != null) {
for (; item !== null && cs.length < 50; item = item.right) {
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
const itemContent = rslice.length > 1 ? item.content.copy() : item.content
let itemContent = rslice.length > 1 ? item.content.copy() : item.content
for (let ir = 0; ir < rslice.length; ir++) {
const idrange = rslice[ir]
const content = itemContent
if (ir !== rslice.length - 1) {
itemContent.splice(idrange.len)
itemContent = itemContent.splice(idrange.len)
}
am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
}
@@ -924,15 +925,19 @@ export class YText extends AbstractType {
const renderDelete = c.render && c.deleted
// existing content that should be retained, only adding changed attributes
const retainContent = !c.render && (!c.deleted || c.attrs != null)
const attribution = renderContent ? createAttributionFromAttributionItems(c.attrs, c.deleted).attribution : null
const attribution = renderContent ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
switch (c.content.constructor) {
case ContentDeleted: {
if (renderDelete) d.delete(c.content.getLength())
break
}
case ContentType:
case ContentEmbed:
if (renderContent) {
d.usedAttributes = currentAttributes
usingCurrentAttributes = true
if (c.deleted ? retainDeletes : retainInserts) {
d.retain(c.content.getLength(), null, attribution)
d.retain(c.content.getLength(), null, attribution ?? {})
} else {
d.insert(c.content.getContent()[0], null, attribution)
}
@@ -949,7 +954,7 @@ export class YText extends AbstractType {
d.usedAttributes = currentAttributes
usingCurrentAttributes = true
if (c.deleted ? retainDeletes : retainInserts) {
d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution)
d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? null)
} else {
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
}

View File

@@ -13,18 +13,23 @@ import {
mergeIdMaps,
createID,
mergeIdSets,
IdSet, Item, Snapshot, Doc, AbstractContent, IdMap // eslint-disable-line
IdSet, Item, Snapshot, Doc, AbstractContent, IdMap, // eslint-disable-line
intersectSets
} from '../internals.js'
import * as error from 'lib0/error'
import { ObservableV2 } from 'lib0/observable'
/**
* @todo rename this to `insertBy`, `insertAt`, ..
*
* @typedef {Object} Attribution
* @property {Array<any>} [Attribution.insert]
* @property {number} [Attribution.insertedAt]
* @property {Array<any>} [Attribution.suggest]
* @property {number} [Attribution.suggestedAt]
* @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]
@@ -35,16 +40,15 @@ import { ObservableV2 } from 'lib0/observable'
* @todo SHOULD NOT RETURN AN OBJECT!
* @param {Array<import('./IdMap.js').AttributionItem<any>>?} attrs
* @param {boolean} deleted - whether the attributed item is deleted
* @return {{ attribution: Attribution?, retainOnly: boolean }}
* @return {Attribution?}
*/
export const createAttributionFromAttributionItems = (attrs, deleted) => {
if (attrs == null) return null
/**
* @type {Attribution?}
* @type {Attribution}
*/
let attribution = null
let retainOnly = false
const attribution = {}
if (attrs != null) {
attribution = {}
if (deleted) {
attribution.delete = []
} else {
@@ -52,12 +56,14 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => {
}
attrs.forEach(attr => {
switch (attr.name) {
case 'retain':
retainOnly = true
break
case 'acceptDelete':
delete attribution.delete
// eslint-disable-next-line no-fallthrough
case 'acceptInsert':
delete attribution.insert
// eslint-disable-next-line no-fallthrough
case 'insert':
case 'delete':
case 'suggest': {
case 'delete': {
const as = /** @type {import('../utils/Delta.js').Attribution} */ (attribution)
const ls = as[attr.name] = as[attr.name] ?? []
ls.push(attr.val)
@@ -71,7 +77,7 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => {
}
})
}
return { attribution, retainOnly }
return attribution
}
/**
@@ -243,13 +249,8 @@ export class DiffAttributionManager extends ObservableV2 {
this._prevBOH = prevDoc.on('beforeObserverCalls', tr => {
insertIntoIdSet(_prevDocInserts, tr.insertSet)
insertIntoIdSet(prevDocDeletes, tr.deleteSet)
if (tr.insertSet.clients.size < 2) {
tr.insertSet.forEach((idrange, client) => {
this.inserts.delete(client, idrange.clock, idrange.len)
})
} else {
this.inserts = diffIdMap(this.inserts, tr.insertSet)
}
insertIntoIdMap(this.inserts, createIdMapFromIdSet(intersectSets(tr.insertSet, this.inserts), [createAttributionItem('acceptInsert', 'unknown')]))
// insertIntoIdMap(this.deletes, createIdMapFromIdSet(intersectSets(tr.deleteSet, this.deletes), [createAttributionItem('acceptDelete', 'unknown')]))
if (tr.deleteSet.clients.size < 2) {
tr.deleteSet.forEach((attrRange, client) => {
this.deletes.delete(client, attrRange.clock, attrRange.len)

View File

@@ -364,14 +364,14 @@ export const testElementAttributedContentViaDiffer = _tc => {
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([
delta.createTextDelta().insert('bigworld')
])
delta.createTextDelta().insert('bigworld', null, { acceptInsert: ['unknown'] })
], null, { acceptInsert: ['unknown'] })
const attributedContent = yelement.getContentDeep(attributionManager)
console.log('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
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, { key: { prevValue: undefined, value: '42', attribution: { acceptInsert: ['unknown'] } } })
t.assert(attributedContent.nodeName === 'UNDEFINED')
})
}