events can be computed with attributions

This commit is contained in:
Kevin Jahns
2025-05-08 15:18:18 +02:00
parent fc620617df
commit 62422544bc
4 changed files with 91 additions and 57 deletions

View File

@@ -514,7 +514,7 @@ export const typeListGetContent = (type, am) => {
for (let item = type._start; item !== null; cs.length = 0) {
// populate cs
for (; item !== null && cs.length < 50; item = item.right) {
am.readContent(cs, item)
am.readContent(cs, item, false)
}
for (let i = 0; i < cs.length; i++) {
const { content, deleted, attrs } = cs[i]
@@ -1005,7 +1005,7 @@ export const typeMapGetContent = (parent, am) => {
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
const cs = []
am.readContent(cs, item)
am.readContent(cs, item, false)
const { deleted, attrs, content } = cs[cs.length - 1]
const c = array.last(content.getContent())
const attribution = createAttributionFromAttributionItems(attrs, deleted)
@@ -1021,7 +1021,7 @@ export const typeMapGetContent = (parent, am) => {
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
const tmpcs = []
am.readContent(tmpcs, prevItem)
am.readContent(tmpcs, prevItem, false)
cs = tmpcs.concat(cs)
if (cs.length === 0 || cs[0].attrs == null) {
cs.splice(0, cs.findIndex(c => c.attrs != null))

View File

@@ -640,17 +640,17 @@ export class YTextEvent extends YEvent {
}
/**
* Compute the changes in the delta format.
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
*
* @type {delta.TextDelta<TextEmbeds>}
* @param {AbstractAttributionManager} am
* @return {import('../utils/Delta.js').TextDelta<TextEmbeds>} The Delta representation of this type.
*
* @public
*/
get delta () {
if (this._delta === null) {
getDelta (am = noAttributionsManager) {
const ydoc = /** @type {Doc} */ (this.target.doc)
const d = this._delta = delta.createTextDelta()
/**
* @type {import('../utils/Delta.js').TextDelta<TextEmbeds>}
*/
const d = delta.createTextDelta()
transact(ydoc, transaction => {
/**
* @type {import('../utils/Delta.js').FormattingAttributes}
@@ -667,20 +667,28 @@ export class YTextEvent extends YEvent {
*/
const previousAttributes = {} // The value before changes
const tr = this.transaction
let item = this.target._start
while (item !== null) {
/**
* @type {Array<import('../internals.js').AttributedContent<any>>}
*/
const cs = []
for (let item = this.target._start; item !== null; cs.length = 0, item = item.right) {
const freshDelete = item.deleted && tr.deleteSet.hasId(item.id) && !tr.insertSet.hasId(item.id)
const freshInsert = !item.deleted && tr.insertSet.hasId(item.id)
switch (item.content.constructor) {
am.readContent(cs, item, freshDelete) // do item.right after calling this
for (let i = 0; i < cs.length; i++) {
const c = cs[i]
const attribution = createAttributionFromAttributionItems(c.attrs, c.deleted)
switch (c.content.constructor) {
case ContentType:
case ContentEmbed:
if (freshInsert) {
d.usedAttributes = currentAttributes
usingCurrentAttributes = true
d.insert(item.content.getContent()[0])
d.insert(c.content.getContent()[0], null, attribution)
} else if (freshDelete) {
d.delete(1)
} else if (!item.deleted) {
} else if (!c.deleted) {
d.usedAttributes = changedAttributes
usingChangedAttributes = true
d.retain(1)
@@ -690,17 +698,17 @@ export class YTextEvent extends YEvent {
if (freshInsert) {
d.usedAttributes = currentAttributes
usingCurrentAttributes = true
d.insert(/** @type {ContentString} */ (item.content).str)
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
} else if (freshDelete) {
d.delete(item.length)
} else if (!item.deleted) {
d.delete(c.content.getLength())
} else if (!c.deleted) {
d.usedAttributes = changedAttributes
usingChangedAttributes = true
d.retain(item.length)
d.retain(c.content.getLength())
}
break
case ContentFormat: {
const { key, value } = /** @type {ContentFormat} */ (item.content)
const { key, value } = /** @type {ContentFormat} */ (c.content)
const currAttrVal = currentAttributes[key] ?? null
if (freshDelete || freshInsert) {
// create fresh references
@@ -727,7 +735,7 @@ export class YTextEvent extends YEvent {
changedAttributes[key] = currAttrVal
currentAttributes[key] = currAttrVal
previousAttributes[key] = value
} else if (!item.deleted) {
} else if (!c.deleted) {
// fresh reference to currentAttributes only
if (usingCurrentAttributes) {
currentAttributes = object.assign({}, currentAttributes)
@@ -739,12 +747,22 @@ export class YTextEvent extends YEvent {
break
}
}
item = item.right
}
}
})
d.done()
return d.done()
}
return /** @type {any} */ (this._delta)
/**
* Compute the changes in the delta format.
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
*
* @type {delta.TextDelta<TextEmbeds>}
*
* @public
*/
get delta () {
return this._delta ?? (this._delta = this.getDelta())
}
}
@@ -961,7 +979,7 @@ export class YText extends AbstractType {
for (let item = this._start; item !== null; cs.length = 0) {
// populate cs
for (; item !== null && cs.length < 50; item = item.right) {
am.readContent(cs, item)
am.readContent(cs, item, false)
}
for (let i = 0; i < cs.length; i++) {
const { content, deleted, attrs } = cs[i]

View File

@@ -89,8 +89,9 @@ export class AbstractAttributionManager {
/**
* @param {Array<AttributedContent<any>>} _contents
* @param {Item} _item
* @param {boolean} _forceRead read content even if it is unattributed and deleted
*/
readContent (_contents, _item) {
readContent (_contents, _item, _forceRead) {
error.methodUnimplemented()
}
@@ -115,8 +116,9 @@ export class TwosetAttributionManager {
/**
* @param {Array<AttributedContent<any>>} contents
* @param {Item} item
* @param {boolean} forceRead read content even if it is unattributed and deleted
*/
readContent (contents, item) {
readContent (contents, item, forceRead) {
const deleted = item.deleted
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
let content = slice.length === 1 ? item.content : item.content.copy()
@@ -125,7 +127,7 @@ export class TwosetAttributionManager {
if (s.len < c.getLength()) {
content = c.splice(s.len)
}
if (!deleted || s.attrs != null) {
if (!deleted || s.attrs != null || forceRead) {
contents.push(new AttributedContent(c, deleted, s.attrs))
}
})
@@ -143,9 +145,10 @@ export class NoAttributionsManager {
/**
* @param {Array<AttributedContent<any>>} contents
* @param {Item} item
* @param {boolean} forceRead read content even if it is unattributed and deleted
*/
readContent (contents, item) {
if (!item.deleted) {
readContent (contents, item, forceRead) {
if (!item.deleted || forceRead) {
contents.push(new AttributedContent(item.content, false, null))
}
}
@@ -214,8 +217,9 @@ export class DiffAttributionManager {
/**
* @param {Array<AttributedContent<any>>} contents
* @param {Item} item
* @param {boolean} forceRead read content even if it is unattributed and deleted
*/
readContent (contents, item) {
readContent (contents, item, forceRead) {
const deleted = item.deleted || /** @type {any} */ (item.parent).doc !== this._nextDoc
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
let content = slice.length === 1 ? item.content : item.content.copy()
@@ -238,7 +242,7 @@ export class DiffAttributionManager {
if (s.len < c.getLength()) {
content = c.splice(s.len)
}
if (!deleted || s.attrs != null) {
if (!deleted || s.attrs != null || forceRead) {
contents.push(new AttributedContent(c, deleted, s.attrs))
}
})
@@ -282,8 +286,9 @@ export class SnapshotAttributionManager {
/**
* @param {Array<AttributedContent<any>>} contents
* @param {Item} item
* @param {boolean} forceRead read content even if it is unattributed and deleted
*/
readContent (contents, item) {
readContent (contents, item, forceRead) {
if ((this.nextSnapshot.sv.get(item.id.client) ?? 0) <= item.id.clock) return // future item that should not be displayed
const slice = this.attrs.slice(item.id, item.length)
let content = slice.length === 1 ? item.content : item.content.copy()
@@ -295,7 +300,7 @@ export class SnapshotAttributionManager {
content = c.splice(s.len)
}
if (nonExistend) return
if (!deleted || (s.attrs != null && s.attrs.length > 0)) {
if (!deleted || forceRead || (s.attrs != null && s.attrs.length > 0)) {
let attrsWithoutChange = s.attrs?.filter(attr => attr.name !== 'change') ?? null
if (s.attrs?.length === 0) {
attrsWithoutChange = null

View File

@@ -410,7 +410,7 @@ export class DeltaBuilder extends AbstractDelta {
}
/**
* @return {AbstractDelta<Type,TDeltaOp>}
* @return {this}
*/
done () {
while (this.lastOp != null && this.lastOp instanceof RetainOp && this.lastOp.attributes === null) {
@@ -432,7 +432,7 @@ export class ArrayDelta extends DeltaBuilder {
}
/**
* @template {{ [key:string]: any }} Embeds
* @template {object} Embeds
* @extends DeltaBuilder<'text',TextDeltaOp<Embeds>>
*/
export class TextDelta extends DeltaBuilder {
@@ -441,6 +441,17 @@ export class TextDelta extends DeltaBuilder {
}
}
/**
* @template {'text'|'array'|'custom'} Type
* @template {DeltaOp<any,any>} DeltaOps
* @typedef {AbstractDelta<Type, DeltaOps>} DeltaReadonly
*/
/**
* @template {object} Embeds
* @typedef {DeltaReadonly<'text',TextDeltaOp<Embeds>>} TextDeltaReadonly
*/
/**
* @template {object} Embeds
* @return {TextDelta<Embeds>}