diff --git a/README.md b/README.md
index e4ad0bea..15c1f219 100644
--- a/README.md
+++ b/README.md
@@ -743,8 +743,6 @@ or any of its children.
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
- toDOM():DocumentFragment
- Transforms this type and all children to new DOM elements.
toString():string
Get the XML serialization of all descendants.
toJSON():string
@@ -818,8 +816,6 @@ content and be actually XML compliant.
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
- toDOM():Element
- Transforms this type and all children to a new DOM element.
toString():string
Get the XML serialization of all descendants.
toJSON():string
diff --git a/package-lock.json b/package-lock.json
index d124bcc9..0d6308d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"rollup": "^4.37.0",
"standard": "^16.0.4",
"tui-jsdoc-template": "^1.2.2",
- "typescript": "^5.8.3",
+ "typescript": "^5.9.3",
"yjs": "."
},
"engines": {
@@ -5292,9 +5292,9 @@
}
},
"node_modules/typescript": {
- "version": "5.8.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
- "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
diff --git a/package.json b/package.json
index 20af3808..c40d18af 100644
--- a/package.json
+++ b/package.json
@@ -88,15 +88,15 @@
"lib0": "^0.2.114"
},
"devDependencies": {
- "@y/protocols": "^1.0.6-1",
"@types/node": "^22.14.1",
+ "@y/protocols": "^1.0.6-1",
"concurrently": "^3.6.1",
"jsdoc": "^3.6.7",
"markdownlint-cli": "^0.41.0",
"rollup": "^4.37.0",
"standard": "^16.0.4",
"tui-jsdoc-template": "^1.2.2",
- "typescript": "^5.8.3",
+ "typescript": "^5.9.3",
"yjs": "."
},
"engines": {
diff --git a/src/index.js b/src/index.js
index e4c811e2..03ceba7c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,10 +10,6 @@ export {
YXmlHook as XmlHook,
YXmlElement as XmlElement,
YXmlFragment as XmlFragment,
- YXmlEvent,
- YMapEvent,
- YArrayEvent,
- YTextEvent,
YEvent,
Item,
AbstractStruct,
@@ -74,7 +70,6 @@ export {
relativePositionToJSON,
isParentOf,
equalSnapshots,
- PermanentUserData, // @TODO experimental
tryGc,
transact,
AbstractConnector,
diff --git a/src/internals.js b/src/internals.js
index d487ce14..a67f6e83 100644
--- a/src/internals.js
+++ b/src/internals.js
@@ -8,7 +8,6 @@ export * from './utils/EventHandler.js'
export * from './utils/ID.js'
export * from './utils/isParentOf.js'
export * from './utils/logging.js'
-export * from './utils/PermanentUserData.js'
export * from './utils/RelativePosition.js'
export * from './utils/Snapshot.js'
export * from './utils/StructStore.js'
@@ -19,7 +18,6 @@ export * from './utils/YEvent.js'
export * from './utils/StructSet.js'
export * from './utils/IdMap.js'
export * from './utils/AttributionManager.js'
-export * from './utils/Delta.js'
export * from './types/AbstractType.js'
export * from './types/YArray.js'
@@ -27,7 +25,6 @@ export * from './types/YMap.js'
export * from './types/YText.js'
export * from './types/YXmlFragment.js'
export * from './types/YXmlElement.js'
-export * from './types/YXmlEvent.js'
export * from './types/YXmlHook.js'
export * from './types/YXmlText.js'
diff --git a/src/structs/ContentType.js b/src/structs/ContentType.js
index a69677d8..25ff3f67 100644
--- a/src/structs/ContentType.js
+++ b/src/structs/ContentType.js
@@ -6,13 +6,17 @@ import {
readYXmlFragment,
readYXmlHook,
readYXmlText,
- UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, AbstractType // eslint-disable-line
+ UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Transaction, Item // eslint-disable-line
} from '../internals.js'
+/**
+ * @typedef {import('../utils/types.js').YType} YType_CT
+ */
+
import * as error from 'lib0/error'
/**
- * @type {Array>}
+ * @type {Array<(decoder: UpdateDecoderV1 | UpdateDecoderV2)=>(import('../utils/types.js').YType)>}
* @private
*/
export const typeRefs = [
@@ -38,11 +42,11 @@ export const YXmlTextRefID = 6
*/
export class ContentType {
/**
- * @param {AbstractType} type
+ * @param {YType_CT} type
*/
constructor (type) {
/**
- * @type {AbstractType}
+ * @type {YType_CT}
*/
this.type = type
}
diff --git a/src/structs/Item.js b/src/structs/Item.js
index 0efc8e25..e4070304 100644
--- a/src/structs/Item.js
+++ b/src/structs/Item.js
@@ -29,6 +29,10 @@ import * as error from 'lib0/error'
import * as binary from 'lib0/binary'
import * as array from 'lib0/array'
+/**
+ * @typedef {import('../utils/types.js').YType} YType__
+ */
+
/**
* @todo This should return several items
*
@@ -68,7 +72,7 @@ export const followRedone = (store, id) => {
export const keepItem = (item, keep) => {
while (item !== null && item.keep !== keep) {
item.keep = keep
- item = /** @type {AbstractType} */ (item.parent)._item
+ item = /** @type {YType__} */ (item.parent)._item
}
}
@@ -115,7 +119,7 @@ export const splitItem = (transaction, leftItem, diff) => {
transaction._mergeStructs.push(rightItem)
// update parent._map
if (rightItem.parentSub !== null && rightItem.right === null) {
- /** @type {AbstractType} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
+ /** @type {YType__} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
}
} else {
rightItem.left = null
@@ -173,7 +177,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
if (redone !== null) {
return getItemCleanStart(transaction, redone)
}
- let parentItem = /** @type {AbstractType} */ (item.parent)._item
+ let parentItem = /** @type {YType__} */ (item.parent)._item
/**
* @type {Item|null}
*/
@@ -192,7 +196,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
parentItem = getItemCleanStart(transaction, parentItem.redone)
}
}
- const parentType = parentItem === null ? /** @type {AbstractType} */ (item.parent) : /** @type {ContentType} */ (parentItem.content).type
+ /**
+ * @type {YType__}
+ */
+ const parentType = /** @type {YType__} */ (parentItem === null ? item.parent : /** @type {ContentType} */ (parentItem.content).type)
if (item.parentSub === null) {
// Is an array item. Insert at the old position
@@ -205,10 +212,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
*/
let leftTrace = left
// trace redone until parent matches
- while (leftTrace !== null && /** @type {AbstractType} */ (leftTrace.parent)._item !== parentItem) {
+ while (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item !== parentItem) {
leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
}
- if (leftTrace !== null && /** @type {AbstractType} */ (leftTrace.parent)._item === parentItem) {
+ if (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item === parentItem) {
left = leftTrace
break
}
@@ -220,10 +227,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
*/
let rightTrace = right
// trace redone until parent matches
- while (rightTrace !== null && /** @type {AbstractType} */ (rightTrace.parent)._item !== parentItem) {
+ while (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item !== parentItem) {
rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
}
- if (rightTrace !== null && /** @type {AbstractType} */ (rightTrace.parent)._item === parentItem) {
+ if (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item === parentItem) {
right = rightTrace
break
}
@@ -275,7 +282,7 @@ export class Item extends AbstractStruct {
* @param {ID | null} origin
* @param {Item | null} right
* @param {ID | null} rightOrigin
- * @param {AbstractType|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
+ * @param {YType__|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
* @param {string | null} parentSub
* @param {AbstractContent} content
*/
@@ -302,7 +309,7 @@ export class Item extends AbstractStruct {
*/
this.rightOrigin = rightOrigin
/**
- * @type {AbstractType|ID|null}
+ * @type {YType__|ID|null}
*/
this.parent = parent
/**
@@ -541,7 +548,7 @@ export class Item extends AbstractStruct {
addStruct(transaction.doc.store, this)
this.content.integrate(transaction, this)
// add parent to transaction.changed
- addChangedTypeToTransaction(transaction, /** @type {AbstractType} */ (this.parent), this.parentSub)
+ addChangedTypeToTransaction(transaction, /** @type {import('../utils/types.js').YType} */ (this.parent), this.parentSub)
if ((/** @type {AbstractType} */ (this.parent)._item !== null && /** @type {AbstractType} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
// delete if parent is deleted or if this is not the current attribute value of parent
this.delete(transaction)
@@ -635,7 +642,7 @@ export class Item extends AbstractStruct {
*/
delete (transaction) {
if (!this.deleted) {
- const parent = /** @type {AbstractType} */ (this.parent)
+ const parent = /** @type {import('../utils/types.js').YType} */ (this.parent)
// adjust the length of parent
if (this.countable && this.parentSub === null) {
parent._length -= this.length
diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js
index 282704b9..bce269c6 100644
--- a/src/types/AbstractType.js
+++ b/src/types/AbstractType.js
@@ -8,18 +8,32 @@ import {
ContentType,
createID,
ContentAny,
+ ContentFormat,
ContentBinary,
+ ContentJSON,
+ ContentDeleted,
+ ContentString,
+ ContentEmbed,
getItemCleanStart,
+ noAttributionsManager,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager, // eslint-disable-line
} from '../internals.js'
-import * as delta from '../utils/Delta.js'
+import * as delta from 'lib0/delta'
import * as array from 'lib0/array'
import * as map from 'lib0/map'
import * as iterator from 'lib0/iterator'
import * as error from 'lib0/error'
import * as math from 'lib0/math'
import * as log from 'lib0/logging'
+import * as object from 'lib0/object'
+
+/**
+ * @typedef {import('../utils/types.js').YType} YType_
+ */
+/**
+ * @typedef {import('../utils/types.js').YValue} _YValue
+ */
/**
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
@@ -98,7 +112,7 @@ const markPosition = (searchMarker, p, index) => {
*
* This function always returns a refreshed marker (updated timestamp)
*
- * @param {AbstractType} yarray
+ * @param {import('../utils/types.js').YType} yarray
* @param {number} index
*/
export const findMarker = (yarray, index) => {
@@ -219,7 +233,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
/**
* Accumulate all (list) children of a type and return them as an Array.
*
- * @param {AbstractType} t
+ * @param {AbstractType} t
* @return {Array- }
*/
export const getTypeChildren = t => {
@@ -237,10 +251,9 @@ export const getTypeChildren = t => {
* Call event listeners with an event. This will also add an event to all
* parents (for `.observeDeep` handlers).
*
- * @template EventType
- * @param {AbstractType} type
+ * @param {import('../utils/types.js').YType} type
* @param {Transaction} transaction
- * @param {EventType} event
+ * @param {YEvent} event
*/
export const callTypeObservers = (type, transaction, event) => {
const changedType = type
@@ -251,17 +264,15 @@ export const callTypeObservers = (type, transaction, event) => {
if (type._item === null) {
break
}
- type = /** @type {AbstractType} */ (type._item.parent)
+ type = /** @type {import('../utils/types.js').YType} */ (type._item.parent)
}
- callEventHandlerListeners(changedType._eH, event, transaction)
+ callEventHandlerListeners(/** @type {any} */ (changedType._eH), event, transaction)
}
/**
* Abstract Yjs Type class
- *
- * @template EventType
- * @template {import('../utils/Delta.js').Delta} [EventDelta=any]
- * @template {import('../utils/Delta.js').Delta} [EventDeltaDeep=any]
+ * @template {delta.Delta} [EventDelta=delta.Delta]
+ * @template {YType_} [Self=any]
*/
export class AbstractType {
constructor () {
@@ -284,7 +295,7 @@ export class AbstractType {
this._length = 0
/**
* Event handlers
- * @type {EventHandler}
+ * @type {EventHandler,Transaction>}
*/
this._eH = createEventHandler()
/**
@@ -299,10 +310,18 @@ export class AbstractType {
}
/**
- * @return {AbstractType|null}
+ * Returns a fresh delta that can be used to change this YType.
+ * @type {EventDelta}
+ */
+ get change () {
+ return /** @type {any} */ (delta.create())
+ }
+
+ /**
+ * @return {import('../utils/types.js').YType|null}
*/
get parent () {
- return this._item ? /** @type {AbstractType} */ (this._item.parent) : null
+ return /** @type {import('../utils/types.js').YType} */ (this._item ? this._item.parent : null)
}
/**
@@ -321,10 +340,11 @@ export class AbstractType {
}
/**
- * @return {AbstractType}
+ * @return {Self}
*/
_copy () {
- throw error.methodUnimplemented()
+ // @ts-ignore
+ return new this.constructor()
}
/**
@@ -332,9 +352,10 @@ export class AbstractType {
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
- * @return {AbstractType}
+ * @return {Self}
*/
clone () {
+ // @todo remove this method from othern types by doing `_copy().apply(this.getContent())`
throw error.methodUnimplemented()
}
@@ -370,7 +391,7 @@ export class AbstractType {
/**
* Observe all events that are created on this type.
*
- * @param {function(EventType, Transaction):void} f Observer function
+ * @param {(target: YEvent, tr: Transaction) => void} f Observer function
*/
observe (f) {
addEventHandlerListener(this._eH, f)
@@ -388,7 +409,7 @@ export class AbstractType {
/**
* Unregister an observer function.
*
- * @param {function(EventType,Transaction):void} f Observer function
+ * @param {(type:YEvent,tr:Transaction)=>void} f Observer function
*/
unobserve (f) {
removeEventHandlerListener(this._eH, f)
@@ -410,24 +431,284 @@ export class AbstractType {
toJSON () {}
/**
- * @param {AbstractAttributionManager} _am
- * @return {EventDelta}
+ * Render the difference to another ydoc (which can be empty) and highlight the differences with
+ * attributions.
+ *
+ * Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
+ * attribution `{ isDeleted: true, .. }`.
+ *
+ * @param {AbstractAttributionManager} am
+ * @param {Object} [opts]
+ * @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?} [opts.renderAttrs] - if true, retain rendered+attributed deletes only
+ * @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only
+ * @return {EventDelta} The Delta representation of this type.
+ *
+ * @public
*/
- getContent (_am) {
- error.methodUnimplemented()
+ getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true } = {}) {
+ /**
+ * @type {EventDelta}
+ */
+ const d = /** @type {any} */ (delta.create())
+ typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am)
+ if (renderChildren) {
+ /**
+ * @type {delta.FormattingAttributes}
+ */
+ let currentAttributes = {} // saves all current attributes for insert
+ let usingCurrentAttributes = false
+ /**
+ * @type {delta.FormattingAttributes}
+ */
+ let changedAttributes = {} // saves changed attributes for retain
+ let usingChangedAttributes = false
+ /**
+ * Logic for formatting attribute attribution
+ * Everything that comes after an formatting attribute is formatted by the user that created it.
+ * Two exceptions:
+ * - the user resets formatting to the previously known formatting that is not attributed
+ * - the user deletes a formatting attribute and hence restores the previously known formatting
+ * that is not attributed.
+ * @type {delta.FormattingAttributes}
+ */
+ const previousUnattributedAttributes = {} // contains previously known unattributed formatting
+ /**
+ * @type {delta.FormattingAttributes}
+ */
+ const previousAttributes = {} // The value before changes
+ /**
+ * @type {Array>}
+ */
+ const cs = []
+ for (let item = this._start; item !== null; cs.length = 0) {
+ if (itemsToRender != null) {
+ for (; item !== null && cs.length < 50; item = item.right) {
+ const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
+ 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 = itemContent.splice(idrange.len)
+ }
+ am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
+ }
+ }
+ } else {
+ for (; item !== null && cs.length < 50; item = item.right) {
+ am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
+ }
+ }
+ for (let i = 0; i < cs.length; i++) {
+ const c = cs[i]
+ // render (attributed) content even if it was deleted
+ const renderContent = c.render && (!c.deleted || c.attrs != null)
+ // content that was just deleted. It is not rendered as an insertion, because it doesn't
+ // have any attributes.
+ 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 || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
+ switch (c.content.constructor) {
+ case ContentDeleted: {
+ if (renderDelete) d.delete(c.content.getLength())
+ break
+ }
+ case ContentString:
+ if (renderContent) {
+ d.usedAttributes = currentAttributes
+ usingCurrentAttributes = true
+ if (c.deleted ? retainDeletes : retainInserts) {
+ d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {})
+ } else {
+ d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
+ }
+ } else if (renderDelete) {
+ d.delete(c.content.getLength())
+ } else if (retainContent) {
+ d.usedAttributes = changedAttributes
+ usingChangedAttributes = true
+ d.retain(c.content.getLength())
+ }
+ break
+ case ContentEmbed:
+ case ContentAny:
+ case ContentJSON:
+ case ContentType:
+ case ContentBinary:
+ if (renderContent) {
+ d.usedAttributes = currentAttributes
+ usingCurrentAttributes = true
+ if (c.deleted ? retainDeletes : retainInserts) {
+ d.retain(c.content.getLength(), null, attribution ?? {})
+ } else {
+ d.insert(c.content.getContent(), null, attribution)
+ }
+ } else if (renderDelete) {
+ d.delete(1)
+ } else if (retainContent) {
+ d.usedAttributes = changedAttributes
+ usingChangedAttributes = true
+ d.retain(1)
+ }
+ break
+ case ContentFormat: {
+ const { key, value } = /** @type {ContentFormat} */ (c.content)
+ const currAttrVal = currentAttributes[key] ?? null
+ if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) {
+ previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal
+ }
+ // @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
+ // # Update Attributes
+ if (renderContent || renderDelete) {
+ // create fresh references
+ if (usingCurrentAttributes) {
+ currentAttributes = object.assign({}, currentAttributes)
+ usingCurrentAttributes = false
+ }
+ if (usingChangedAttributes) {
+ usingChangedAttributes = false
+ changedAttributes = object.assign({}, changedAttributes)
+ }
+ }
+ if (renderContent || renderDelete) {
+ if (c.deleted) {
+ // content was deleted, but is possibly attributed
+ if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed
+ if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
+ delete changedAttributes[key]
+ } else {
+ changedAttributes[key] = currAttrVal
+ }
+ // current attributes doesn't change
+ previousAttributes[key] = value
+ }
+ } else { // !c.deleted
+ // content was inserted, and is possibly attributed
+ if (equalAttrs(value, currAttrVal)) {
+ // item.delete(transaction)
+ } else if (equalAttrs(value, previousAttributes[key] ?? null)) {
+ delete changedAttributes[key]
+ } else {
+ changedAttributes[key] = value
+ }
+ if (value == null) {
+ delete currentAttributes[key]
+ } else {
+ currentAttributes[key] = value
+ }
+ }
+ } else if (retainContent && !c.deleted) {
+ // fresh reference to currentAttributes only
+ if (usingCurrentAttributes) {
+ currentAttributes = object.assign({}, currentAttributes)
+ usingCurrentAttributes = false
+ }
+ 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
+ }
+ // # Update Attributions
+ if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) {
+ /**
+ * @type {import('../utils/AttributionManager.js').Attribution}
+ */
+ const formattingAttribution = object.assign({}, d.usedAttribution)
+ const changedAttributedAttributes = /** @type {{ [key: string]: Array }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {}))
+ if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
+ // an unattributed formatting attribute was found or an attributed formatting
+ // attribute was found that resets to the previous status
+ delete changedAttributedAttributes[key]
+ delete previousUnattributedAttributes[key]
+ } else {
+ const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
+ by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
+ const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
+ if (attributedAt) formattingAttribution.attributedAt = attributedAt
+ }
+ if (object.isEmpty(changedAttributedAttributes)) {
+ d.useAttribution(null)
+ } else if (attribution != null) {
+ const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
+ if (attributedAt != null) formattingAttribution.attributedAt = attributedAt
+ d.useAttribution(formattingAttribution)
+ }
+ }
+ break
+ }
+ }
+ }
+ }
+ }
+ return d
}
/**
- * @param {AbstractAttributionManager} _am
- * @return {EventDeltaDeep}
+ * Render the difference to another ydoc (which can be empty) and highlight the differences with
+ * attributions.
+ *
+ * @param {AbstractAttributionManager} am
+ * @return {ToDeepEventDelta}
*/
- getContentDeep (_am) {
- error.methodUnimplemented()
+ getContentDeep (am = noAttributionsManager) {
+ const d = this.getContent(am)
+ d.children.forEach(op => {
+ if (op instanceof delta.InsertOp) {
+ op.insert = /** @type {any} */ (op.insert.map(ins =>
+ ins instanceof AbstractType
+ // @ts-ignore
+ ? ins.getContentDeep(am)
+ : ins)
+ )
+ }
+ })
+ d.attrs.forEach((op) => {
+ if (delta.$insertOp.check(op) && op.value instanceof AbstractType) {
+ op.value = op.value.getContentDeep(am)
+ }
+ })
+ return /** @type {any} */ (d)
}
}
/**
- * @param {AbstractType} type
+ * @param {any} a
+ * @param {any} b
+ * @return {boolean}
+ */
+export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
+
+/**
+ * @template {delta.Delta} D
+ * @typedef {D extends delta.Delta
+ * ? delta.Delta<
+ * N,
+ * { [K in keyof Attrs]: TypeToDelta },
+ * TypeToDelta,
+ * Text
+ * >
+ * : D
+ * } ToDeepEventDelta
+ */
+
+/**
+ * @template {any} T
+ * @typedef {(Extract> extends AbstractType ? (unknown extends D ? never : ToDeepEventDelta) : never) | Exclude>} TypeToDelta
+ */
+
+/**
+ * @param {AbstractType} type
* @param {number} start
* @param {number} end
* @return {Array}
@@ -465,7 +746,7 @@ export const typeListSlice = (type, start, end) => {
}
/**
- * @param {AbstractType} type
+ * @param {import('../utils/types.js').YType} type
* @return {Array}
*
* @private
@@ -488,23 +769,23 @@ export const typeListToArray = type => {
}
/**
+ * @todo this can be removed as this can be replaced by a generic function
* Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions.
*
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
- * @template {delta.ArrayDelta} TypeDelta
- * @param {AbstractType} type
+ * @template {delta.Delta} TypeDelta
+ * @param {TypeDelta} d
+ * @param {import('../utils/types.js').YType} type
* @param {import('../internals.js').AbstractAttributionManager} am
- * @return {TypeDelta}
*
* @private
* @function
*/
-export const typeListGetContent = (type, am) => {
+export const typeListGetContent = (d, type, am) => {
type.doc ?? warnPrematureAccess()
- const d = delta.createArrayDelta()
/**
* @type {Array>}
*/
@@ -526,11 +807,10 @@ export const typeListGetContent = (type, am) => {
}
}
}
- return /** @type {TypeDelta} */ (d.done())
}
/**
- * @param {AbstractType} type
+ * @param {AbstractType} type
* @param {Snapshot} snapshot
* @return {Array}
*
@@ -555,7 +835,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
/**
* Executes a provided function on once on every element of this YArray.
*
- * @param {AbstractType} type
+ * @param {AbstractType} type
* @param {function(any,number,any):void} f A function to execute on every element of this YArray.
*
* @private
@@ -578,8 +858,8 @@ export const typeListForEach = (type, f) => {
/**
* @template C,R
- * @param {AbstractType} type
- * @param {function(C,number,AbstractType):R} f
+ * @param {AbstractType} type
+ * @param {function(C,number,AbstractType):R} f
* @return {Array}
*
* @private
@@ -597,7 +877,7 @@ export const typeListMap = (type, f) => {
}
/**
- * @param {AbstractType} type
+ * @param {AbstractType} type
* @return {IterableIterator}
*
* @private
@@ -649,8 +929,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} type
- * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray.
+ * @param {AbstractType} type
+ * @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray.
* @param {Snapshot} snapshot
*
* @private
@@ -671,7 +951,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
}
/**
- * @param {AbstractType} type
+ * @param {import('../utils/types.js').YType} type
* @param {number} index
* @return {any}
*
@@ -698,9 +978,9 @@ export const typeListGet = (type, index) => {
/**
* @param {Transaction} transaction
- * @param {AbstractType} parent
+ * @param {YType_} parent
* @param {Item?} referenceItem
- * @param {Array