only have a single getDelta implementation for events and retrieving content

This commit is contained in:
Kevin Jahns
2025-05-19 17:52:59 +02:00
parent 7d5d6b840f
commit e1ef2210d9
3 changed files with 140 additions and 90 deletions

View File

@@ -29,19 +29,19 @@
"exports": { "exports": {
".": { ".": {
"types": "./dist/src/index.d.ts", "types": "./dist/src/index.d.ts",
"module": "./dist/yjs.mjs", "module": "./src/index.js",
"require": "./dist/yjs.cjs", "require": "./dist/yjs.cjs",
"import": "./src/index.js" "import": "./src/index.js"
}, },
"./internals": { "./internals": {
"types": "./dist/src/internals.d.ts", "types": "./dist/src/internals.d.ts",
"module": "./dist/internals.mjs", "module": "./src/internals.js",
"require": "./dist/internals.cjs", "require": "./dist/internals.cjs",
"import": "./src/internals.js" "import": "./src/internals.js"
}, },
"./testHelper": { "./testHelper": {
"types": "./dist/testHelper.d.ts", "types": "./dist/testHelper.d.ts",
"module": "./dist/testHelper.mjs", "module": "./tests/testHelper.js",
"require": "./dist/testHelper.cjs", "require": "./dist/testHelper.cjs",
"import": "./tests/testHelper.js" "import": "./tests/testHelper.js"
}, },

View File

@@ -33,6 +33,7 @@ import {
import * as delta from '../utils/Delta.js' import * as delta from '../utils/Delta.js'
import * as traits from 'lib0/traits'
import * as object from 'lib0/object' import * as object from 'lib0/object'
import * as map from 'lib0/map' import * as map from 'lib0/map'
import * as error from 'lib0/error' import * as error from 'lib0/error'
@@ -649,6 +650,18 @@ export class YTextEvent extends YEvent {
* @public * @public
*/ */
getDelta (am = noAttributionsManager) { getDelta (am = noAttributionsManager) {
const whatToWatch = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
const genericDelta = this.target.getDelta(am, whatToWatch)
return genericDelta;
/*
if (!d.equals(genericDelta)) {
console.log(d.toJSON())
console.log(genericDelta.toJSON())
debugger
const d2 = this.target.getDelta(am, whatToWatch)
throw new Error('should match', d2)
}
return d
const ydoc = /** @type {Doc} */ (this.target.doc) const ydoc = /** @type {Doc} */ (this.target.doc)
/** /**
* @type {import('../utils/Delta.js').TextDelta<TextEmbeds>} * @type {import('../utils/Delta.js').TextDelta<TextEmbeds>}
@@ -729,23 +742,42 @@ export class YTextEvent extends YEvent {
if (equalAttrs(value, currAttrVal)) { if (equalAttrs(value, currAttrVal)) {
item.delete(transaction) item.delete(transaction)
} else if (equalAttrs(value, previousAttributes[key] ?? null)) { } else if (equalAttrs(value, previousAttributes[key] ?? null)) {
delete currentAttributes[key]
delete changedAttributes[key] delete changedAttributes[key]
} else { } else {
currentAttributes[key] = value
changedAttributes[key] = value changedAttributes[key] = value
} }
if (value == null) {
delete currentAttributes[key]
} else {
currentAttributes[key] = value
}
} else if (freshDelete) { } else if (freshDelete) {
changedAttributes[key] = currAttrVal if (equalAttrs(value, currAttrVal)) {
currentAttributes[key] = currAttrVal // nop
} else if (equalAttrs(currAttrVal, previousAttributes[key] ?? null)) {
delete changedAttributes[key]
} else {
changedAttributes[key] = currAttrVal
}
// current attributes doesn't change
previousAttributes[key] = value previousAttributes[key] = value
} else if (!c.deleted) { } else if (!c.deleted) {
// fresh reference to currentAttributes only // fresh reference to currentAttributes only
if (usingCurrentAttributes) { if (usingCurrentAttributes) {
currentAttributes = object.assign({}, currentAttributes) currentAttributes = object.assign({}, currentAttributes)
usingCurrentAttributes = false usingCurrentAttributes = false
} }
currentAttributes[key] = value if (usingChangedAttributes && changedAttributes[key] !== undefined) {
usingChangedAttributes = false
changedAttributes = object.assign({}, changedAttributes)
}
if (value == null) {
delete currentAttributes[key]
} else {
currentAttributes[key] = value
}
delete changedAttributes[key]
previousAttributes[key] = value previousAttributes[key] = value
} }
// # Update Attributions // # Update Attributions
@@ -788,6 +820,7 @@ export class YTextEvent extends YEvent {
// throw new Error('should match', d2) // throw new Error('should match', d2)
// } // }
// return d // return d
// */
} }
/** /**
@@ -1005,81 +1038,81 @@ export class YText extends AbstractType {
*/ */
getContent (am = noAttributionsManager) { getContent (am = noAttributionsManager) {
return this.getDelta(am) return this.getDelta(am)
this.doc ?? warnPrematureAccess() // this.doc ?? warnPrematureAccess()
/** // /**
* @type {delta.TextDelta<Embeds>} // * @type {delta.TextDelta<Embeds>}
*/ // */
const d = delta.createTextDelta() // const d = delta.createTextDelta()
/** // /**
* @type {Array<import('../internals.js').AttributedContent<any>>} // * @type {Array<import('../internals.js').AttributedContent<any>>}
*/ // */
const cs = [] // const cs = []
for (let item = this._start; item !== null; cs.length = 0) { // for (let item = this._start; item !== null; cs.length = 0) {
// populate cs // // populate cs
for (; item !== null && cs.length < 50; item = item.right) { // for (; item !== null && cs.length < 50; item = item.right) {
am.readContent(cs, item, false) // am.readContent(cs, item, false)
} // }
for (let i = 0; i < cs.length; i++) { // for (let i = 0; i < cs.length; i++) {
const { content, deleted, attrs } = cs[i] // const { content, deleted, attrs } = cs[i]
const { attribution, retainOnly } = createAttributionFromAttributionItems(attrs, deleted) // const { attribution, retainOnly } = createAttributionFromAttributionItems(attrs, deleted)
switch (content.constructor) { // switch (content.constructor) {
case ContentString: { // case ContentString: {
if (retainOnly) { // if (retainOnly) {
d.retain(content.getLength(), null, attribution) // d.retain(content.getLength(), null, attribution)
} else { // } else {
d.insert(/** @type {ContentString} */ (content).str, null, attribution) // d.insert(/** @type {ContentString} */ (content).str, null, attribution)
} // }
break // break
} // }
case ContentType: // case ContentType:
case ContentEmbed: { // case ContentEmbed: {
if (retainOnly) { // if (retainOnly) {
d.retain(content.getLength(), null, attribution) // d.retain(content.getLength(), null, attribution)
} else { // } else {
d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attribution) // d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attribution)
} // }
break // break
} // }
case ContentFormat: { // case ContentFormat: {
const contentFormat = /** @type {ContentFormat} */ (content) // const contentFormat = /** @type {ContentFormat} */ (content)
if (attribution != null) { // if (attribution != null) {
/** // /**
* @type {import('../utils/Delta.js').Attribution} // * @type {import('../utils/Delta.js').Attribution}
*/ // */
const formattingAttribution = object.assign({}, d.usedAttribution) // const formattingAttribution = object.assign({}, d.usedAttribution)
const attributesChanged = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {})) // const attributesChanged = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {}))
if (contentFormat.value === null) { // if (contentFormat.value === null) {
delete attributesChanged[contentFormat.key] // delete attributesChanged[contentFormat.key]
} else { // } else {
const by = attributesChanged[contentFormat.key] = attributesChanged[contentFormat.key]?.slice() ?? [] // const by = attributesChanged[contentFormat.key] = attributesChanged[contentFormat.key]?.slice() ?? []
by.push(...((deleted ? attribution.delete : attribution.insert) ?? [])) // by.push(...((deleted ? attribution.delete : attribution.insert) ?? []))
const attributedAt = (deleted ? attribution.deletedAt : attribution.insertedAt) // const attributedAt = (deleted ? attribution.deletedAt : attribution.insertedAt)
if (attributedAt) formattingAttribution.attributedAt = attributedAt // if (attributedAt) formattingAttribution.attributedAt = attributedAt
} // }
if (object.isEmpty(attributesChanged)) { // if (object.isEmpty(attributesChanged)) {
d.useAttribution(null) // d.useAttribution(null)
} else { // } else {
const attributedAt = (deleted ? attribution.deletedAt : attribution.insertedAt) // const attributedAt = (deleted ? attribution.deletedAt : attribution.insertedAt)
if (attributedAt != null) formattingAttribution.attributedAt = attributedAt // if (attributedAt != null) formattingAttribution.attributedAt = attributedAt
d.useAttribution(formattingAttribution) // d.useAttribution(formattingAttribution)
} // }
} // }
if (!deleted) { // if (!deleted) {
const currAttrs = d.usedAttributes // const currAttrs = d.usedAttributes
if (contentFormat.value == null) { // if (contentFormat.value == null) {
const nextAttrs = object.assign({}, currAttrs) // const nextAttrs = object.assign({}, currAttrs)
delete nextAttrs[contentFormat.key] // delete nextAttrs[contentFormat.key]
d.useAttributes(nextAttrs) // d.useAttributes(nextAttrs)
} else { // } else {
d.useAttributes(object.assign({}, currAttrs, { [contentFormat.key]: contentFormat.value })) // d.useAttributes(object.assign({}, currAttrs, { [contentFormat.key]: contentFormat.value }))
} // }
} // }
break // break
} // }
} // }
} // }
} // }
return d // return d
} }
/** /**
@@ -1169,6 +1202,7 @@ export class YText extends AbstractType {
case ContentFormat: { case ContentFormat: {
const { key, value } = /** @type {ContentFormat} */ (c.content) const { key, value } = /** @type {ContentFormat} */ (c.content)
const currAttrVal = currentAttributes[key] ?? null const currAttrVal = currentAttributes[key] ?? null
// @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
// # Update Attributes // # Update Attributes
if (renderDelete || renderInsert) { if (renderDelete || renderInsert) {
// create fresh references // create fresh references
@@ -1185,20 +1219,24 @@ export class YText extends AbstractType {
if (equalAttrs(value, currAttrVal)) { if (equalAttrs(value, currAttrVal)) {
// item.delete(transaction) // item.delete(transaction)
} else if (equalAttrs(value, previousAttributes[key] ?? null)) { } else if (equalAttrs(value, previousAttributes[key] ?? null)) {
delete currentAttributes[key]
delete changedAttributes[key] delete changedAttributes[key]
} else { } else {
currentAttributes[key] = value
changedAttributes[key] = value changedAttributes[key] = value
} }
if (value == null) {
delete currentAttributes[key]
} else {
currentAttributes[key] = value
}
} else if (renderDelete) { } else if (renderDelete) {
if (equalAttrs(value, currAttrVal)) { if (equalAttrs(value, currAttrVal)) {
// nop
} else if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
delete changedAttributes[key] delete changedAttributes[key]
delete currentAttributes[key]
} else { } else {
changedAttributes[key] = currAttrVal changedAttributes[key] = currAttrVal
currentAttributes[key] = currAttrVal
} }
// current attributes doesn't change
previousAttributes[key] = value previousAttributes[key] = value
} else if (!c.deleted) { } else if (!c.deleted) {
// fresh reference to currentAttributes only // fresh reference to currentAttributes only
@@ -1206,11 +1244,16 @@ export class YText extends AbstractType {
currentAttributes = object.assign({}, currentAttributes) currentAttributes = object.assign({}, currentAttributes)
usingCurrentAttributes = false usingCurrentAttributes = false
} }
if (equalAttrs(value, previousAttributes[key] ?? null)) { if (usingChangedAttributes && changedAttributes[key] !== undefined) {
usingChangedAttributes = false
changedAttributes = object.assign({}, changedAttributes)
}
if (value == null) {
delete currentAttributes[key] delete currentAttributes[key]
} else { } else {
currentAttributes[key] = value currentAttributes[key] = value
} }
delete changedAttributes[key]
previousAttributes[key] = value previousAttributes[key] = value
} }
// # Update Attributions // # Update Attributions
@@ -1419,6 +1462,13 @@ export class YText extends AbstractType {
_write (encoder) { _write (encoder) {
encoder.writeTypeRef(YTextRefID) encoder.writeTypeRef(YTextRefID)
} }
/**
* @param {this} other
*/
[traits.EqualityTraitSymbol] (other) {
return this.getContent().equals(other.getContent())
}
} }
/** /**

View File

@@ -1621,7 +1621,7 @@ export const testDeltaAfterConcurrentFormatting = tc => {
} }
}) })
testConnector.flushAllMessages() testConnector.flushAllMessages()
t.compare(deltas, [[{ retain: 3, attributes: { bold: true } }, { retain: 2, attributes: { bold: null } }]]) t.compare(deltas, [[{ retain: 2, attributes: { bold: true } }, { retain: 1 }, { retain: 1, attributes: { bold: null } }]])
} }
/** /**
@@ -2267,7 +2267,7 @@ export const testAttributedContent = _tc => {
}) })
t.group('unformat', () => { t.group('unformat', () => {
ytext.applyDelta([{ retain: 5, attributes: { italic: null } }]) ytext.applyDelta([{ retain: 5, attributes: { italic: null } }])
const expectedContent = delta.createTextDelta().insert('Hell', { italic: null }, { attributes: { italic: [] } }).insert('o attributions!') const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
const attributedContent = ytext.getContent(attributionManager) const attributedContent = ytext.getContent(attributionManager)
console.log(attributedContent.toJSON()) console.log(attributedContent.toJSON())
t.assert(attributedContent.equals(expectedContent)) t.assert(attributedContent.equals(expectedContent))