mirror of
https://github.com/yjs/yjs.git
synced 2025-12-16 19:57:45 +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) {
|
for (let item = type._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)
|
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]
|
||||||
@@ -1005,7 +1005,7 @@ export const typeMapGetContent = (parent, am) => {
|
|||||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||||
*/
|
*/
|
||||||
const cs = []
|
const cs = []
|
||||||
am.readContent(cs, item)
|
am.readContent(cs, item, false)
|
||||||
const { deleted, attrs, content } = cs[cs.length - 1]
|
const { deleted, attrs, content } = cs[cs.length - 1]
|
||||||
const c = array.last(content.getContent())
|
const c = array.last(content.getContent())
|
||||||
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
||||||
@@ -1021,7 +1021,7 @@ export const typeMapGetContent = (parent, am) => {
|
|||||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||||
*/
|
*/
|
||||||
const tmpcs = []
|
const tmpcs = []
|
||||||
am.readContent(tmpcs, prevItem)
|
am.readContent(tmpcs, prevItem, false)
|
||||||
cs = tmpcs.concat(cs)
|
cs = tmpcs.concat(cs)
|
||||||
if (cs.length === 0 || cs[0].attrs == null) {
|
if (cs.length === 0 || cs[0].attrs == null) {
|
||||||
cs.splice(0, cs.findIndex(c => c.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.
|
* @param {AbstractAttributionManager} am
|
||||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
* @return {import('../utils/Delta.js').TextDelta<TextEmbeds>} The Delta representation of this type.
|
||||||
*
|
|
||||||
* @type {delta.TextDelta<TextEmbeds>}
|
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
get delta () {
|
getDelta (am = noAttributionsManager) {
|
||||||
if (this._delta === null) {
|
const ydoc = /** @type {Doc} */ (this.target.doc)
|
||||||
const ydoc = /** @type {Doc} */ (this.target.doc)
|
/**
|
||||||
const d = this._delta = delta.createTextDelta()
|
* @type {import('../utils/Delta.js').TextDelta<TextEmbeds>}
|
||||||
transact(ydoc, transaction => {
|
*/
|
||||||
/**
|
const d = delta.createTextDelta()
|
||||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
transact(ydoc, transaction => {
|
||||||
*/
|
/**
|
||||||
let currentAttributes = {} // saves all current attributes for insert
|
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||||
let usingCurrentAttributes = false
|
*/
|
||||||
/**
|
let currentAttributes = {} // saves all current attributes for insert
|
||||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
let usingCurrentAttributes = false
|
||||||
*/
|
/**
|
||||||
let changedAttributes = {} // saves changed attributes for retain
|
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||||
let usingChangedAttributes = false
|
*/
|
||||||
/**
|
let changedAttributes = {} // saves changed attributes for retain
|
||||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
let usingChangedAttributes = false
|
||||||
*/
|
/**
|
||||||
const previousAttributes = {} // The value before changes
|
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||||
const tr = this.transaction
|
*/
|
||||||
let item = this.target._start
|
const previousAttributes = {} // The value before changes
|
||||||
while (item !== null) {
|
const tr = this.transaction
|
||||||
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) {
|
* @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 ContentType:
|
||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
if (freshInsert) {
|
if (freshInsert) {
|
||||||
d.usedAttributes = currentAttributes
|
d.usedAttributes = currentAttributes
|
||||||
usingCurrentAttributes = true
|
usingCurrentAttributes = true
|
||||||
d.insert(item.content.getContent()[0])
|
d.insert(c.content.getContent()[0], null, attribution)
|
||||||
} else if (freshDelete) {
|
} else if (freshDelete) {
|
||||||
d.delete(1)
|
d.delete(1)
|
||||||
} else if (!item.deleted) {
|
} else if (!c.deleted) {
|
||||||
d.usedAttributes = changedAttributes
|
d.usedAttributes = changedAttributes
|
||||||
usingChangedAttributes = true
|
usingChangedAttributes = true
|
||||||
d.retain(1)
|
d.retain(1)
|
||||||
@@ -690,17 +698,17 @@ export class YTextEvent extends YEvent {
|
|||||||
if (freshInsert) {
|
if (freshInsert) {
|
||||||
d.usedAttributes = currentAttributes
|
d.usedAttributes = currentAttributes
|
||||||
usingCurrentAttributes = true
|
usingCurrentAttributes = true
|
||||||
d.insert(/** @type {ContentString} */ (item.content).str)
|
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
|
||||||
} else if (freshDelete) {
|
} else if (freshDelete) {
|
||||||
d.delete(item.length)
|
d.delete(c.content.getLength())
|
||||||
} else if (!item.deleted) {
|
} else if (!c.deleted) {
|
||||||
d.usedAttributes = changedAttributes
|
d.usedAttributes = changedAttributes
|
||||||
usingChangedAttributes = true
|
usingChangedAttributes = true
|
||||||
d.retain(item.length)
|
d.retain(c.content.getLength())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case ContentFormat: {
|
case ContentFormat: {
|
||||||
const { key, value } = /** @type {ContentFormat} */ (item.content)
|
const { key, value } = /** @type {ContentFormat} */ (c.content)
|
||||||
const currAttrVal = currentAttributes[key] ?? null
|
const currAttrVal = currentAttributes[key] ?? null
|
||||||
if (freshDelete || freshInsert) {
|
if (freshDelete || freshInsert) {
|
||||||
// create fresh references
|
// create fresh references
|
||||||
@@ -727,7 +735,7 @@ export class YTextEvent extends YEvent {
|
|||||||
changedAttributes[key] = currAttrVal
|
changedAttributes[key] = currAttrVal
|
||||||
currentAttributes[key] = currAttrVal
|
currentAttributes[key] = currAttrVal
|
||||||
previousAttributes[key] = value
|
previousAttributes[key] = value
|
||||||
} else if (!item.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)
|
||||||
@@ -739,12 +747,22 @@ export class YTextEvent extends YEvent {
|
|||||||
break
|
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) {
|
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)
|
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]
|
||||||
|
|||||||
@@ -89,8 +89,9 @@ export class AbstractAttributionManager {
|
|||||||
/**
|
/**
|
||||||
* @param {Array<AttributedContent<any>>} _contents
|
* @param {Array<AttributedContent<any>>} _contents
|
||||||
* @param {Item} _item
|
* @param {Item} _item
|
||||||
|
* @param {boolean} _forceRead read content even if it is unattributed and deleted
|
||||||
*/
|
*/
|
||||||
readContent (_contents, _item) {
|
readContent (_contents, _item, _forceRead) {
|
||||||
error.methodUnimplemented()
|
error.methodUnimplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +116,9 @@ export class TwosetAttributionManager {
|
|||||||
/**
|
/**
|
||||||
* @param {Array<AttributedContent<any>>} contents
|
* @param {Array<AttributedContent<any>>} contents
|
||||||
* @param {Item} item
|
* @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 deleted = item.deleted
|
||||||
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
|
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
|
||||||
let content = slice.length === 1 ? item.content : item.content.copy()
|
let content = slice.length === 1 ? item.content : item.content.copy()
|
||||||
@@ -125,7 +127,7 @@ export class TwosetAttributionManager {
|
|||||||
if (s.len < c.getLength()) {
|
if (s.len < c.getLength()) {
|
||||||
content = c.splice(s.len)
|
content = c.splice(s.len)
|
||||||
}
|
}
|
||||||
if (!deleted || s.attrs != null) {
|
if (!deleted || s.attrs != null || forceRead) {
|
||||||
contents.push(new AttributedContent(c, deleted, s.attrs))
|
contents.push(new AttributedContent(c, deleted, s.attrs))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -143,9 +145,10 @@ export class NoAttributionsManager {
|
|||||||
/**
|
/**
|
||||||
* @param {Array<AttributedContent<any>>} contents
|
* @param {Array<AttributedContent<any>>} contents
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
|
* @param {boolean} forceRead read content even if it is unattributed and deleted
|
||||||
*/
|
*/
|
||||||
readContent (contents, item) {
|
readContent (contents, item, forceRead) {
|
||||||
if (!item.deleted) {
|
if (!item.deleted || forceRead) {
|
||||||
contents.push(new AttributedContent(item.content, false, null))
|
contents.push(new AttributedContent(item.content, false, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,8 +217,9 @@ export class DiffAttributionManager {
|
|||||||
/**
|
/**
|
||||||
* @param {Array<AttributedContent<any>>} contents
|
* @param {Array<AttributedContent<any>>} contents
|
||||||
* @param {Item} item
|
* @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 deleted = item.deleted || /** @type {any} */ (item.parent).doc !== this._nextDoc
|
||||||
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
|
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
|
||||||
let content = slice.length === 1 ? item.content : item.content.copy()
|
let content = slice.length === 1 ? item.content : item.content.copy()
|
||||||
@@ -238,7 +242,7 @@ export class DiffAttributionManager {
|
|||||||
if (s.len < c.getLength()) {
|
if (s.len < c.getLength()) {
|
||||||
content = c.splice(s.len)
|
content = c.splice(s.len)
|
||||||
}
|
}
|
||||||
if (!deleted || s.attrs != null) {
|
if (!deleted || s.attrs != null || forceRead) {
|
||||||
contents.push(new AttributedContent(c, deleted, s.attrs))
|
contents.push(new AttributedContent(c, deleted, s.attrs))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -282,8 +286,9 @@ export class SnapshotAttributionManager {
|
|||||||
/**
|
/**
|
||||||
* @param {Array<AttributedContent<any>>} contents
|
* @param {Array<AttributedContent<any>>} contents
|
||||||
* @param {Item} item
|
* @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
|
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)
|
const slice = this.attrs.slice(item.id, item.length)
|
||||||
let content = slice.length === 1 ? item.content : item.content.copy()
|
let content = slice.length === 1 ? item.content : item.content.copy()
|
||||||
@@ -295,7 +300,7 @@ export class SnapshotAttributionManager {
|
|||||||
content = c.splice(s.len)
|
content = c.splice(s.len)
|
||||||
}
|
}
|
||||||
if (nonExistend) return
|
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
|
let attrsWithoutChange = s.attrs?.filter(attr => attr.name !== 'change') ?? null
|
||||||
if (s.attrs?.length === 0) {
|
if (s.attrs?.length === 0) {
|
||||||
attrsWithoutChange = null
|
attrsWithoutChange = null
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ export class DeltaBuilder extends AbstractDelta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {AbstractDelta<Type,TDeltaOp>}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
done () {
|
done () {
|
||||||
while (this.lastOp != null && this.lastOp instanceof RetainOp && this.lastOp.attributes === null) {
|
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>>
|
* @extends DeltaBuilder<'text',TextDeltaOp<Embeds>>
|
||||||
*/
|
*/
|
||||||
export class TextDelta extends DeltaBuilder {
|
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
|
* @template {object} Embeds
|
||||||
* @return {TextDelta<Embeds>}
|
* @return {TextDelta<Embeds>}
|
||||||
|
|||||||
Reference in New Issue
Block a user