mirror of
https://github.com/yjs/yjs.git
synced 2025-12-16 11:47:46 +01:00
events can be computed with attributions
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -640,47 +640,55 @@ 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) {
|
||||
const ydoc = /** @type {Doc} */ (this.target.doc)
|
||||
const d = this._delta = delta.createTextDelta()
|
||||
transact(ydoc, transaction => {
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let currentAttributes = {} // saves all current attributes for insert
|
||||
let usingCurrentAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let changedAttributes = {} // saves changed attributes for retain
|
||||
let usingChangedAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
const previousAttributes = {} // The value before changes
|
||||
const tr = this.transaction
|
||||
let item = this.target._start
|
||||
while (item !== null) {
|
||||
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) {
|
||||
getDelta (am = noAttributionsManager) {
|
||||
const ydoc = /** @type {Doc} */ (this.target.doc)
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').TextDelta<TextEmbeds>}
|
||||
*/
|
||||
const d = delta.createTextDelta()
|
||||
transact(ydoc, transaction => {
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let currentAttributes = {} // saves all current attributes for insert
|
||||
let usingCurrentAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let changedAttributes = {} // saves changed attributes for retain
|
||||
let usingChangedAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
const previousAttributes = {} // The value before changes
|
||||
const tr = this.transaction
|
||||
|
||||
/**
|
||||
* @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)
|
||||
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 /** @type {any} */ (this._delta)
|
||||
}
|
||||
})
|
||||
return d.done()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>}
|
||||
|
||||
Reference in New Issue
Block a user