draft of YWeakLink type
This commit is contained in:
@@ -10,6 +10,8 @@ export {
|
||||
YXmlHook as XmlHook,
|
||||
YXmlElement as XmlElement,
|
||||
YXmlFragment as XmlFragment,
|
||||
YWeakLink as WeakLink,
|
||||
YWeakLinkEvent,
|
||||
YXmlEvent,
|
||||
YMapEvent,
|
||||
YArrayEvent,
|
||||
|
||||
@@ -27,6 +27,7 @@ export * from './types/YXmlElement.js'
|
||||
export * from './types/YXmlEvent.js'
|
||||
export * from './types/YXmlHook.js'
|
||||
export * from './types/YXmlText.js'
|
||||
export * from './types/YWeakLink.js'
|
||||
|
||||
export * from './structs/AbstractStruct.js'
|
||||
export * from './structs/GC.js'
|
||||
|
||||
@@ -6,8 +6,11 @@ import {
|
||||
readYXmlElement,
|
||||
readYXmlFragment,
|
||||
readYXmlHook,
|
||||
readYXmlText,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line
|
||||
readYXmlText,
|
||||
readYWeakLink,
|
||||
unlinkFrom,
|
||||
YWeakLink,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType, ID, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as error from 'lib0/error'
|
||||
@@ -33,6 +36,7 @@ export const YXmlElementRefID = 3
|
||||
export const YXmlFragmentRefID = 4
|
||||
export const YXmlHookRefID = 5
|
||||
export const YXmlTextRefID = 6
|
||||
export const YWeakLinkRefID = 7
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -104,6 +108,22 @@ export class ContentType {
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
delete (transaction) {
|
||||
if (this.type.constructor === YWeakLink) {
|
||||
// when removing weak links, remove references to them
|
||||
// from type they're pointing to
|
||||
const type = /** @type {YWeakLink<any>} */ (this.type)
|
||||
const end = /** @type {ID} */ (type._quoteEnd.item)
|
||||
for (let item = type._firstItem; item !== null; item = item.right) {
|
||||
if (item.linked) {
|
||||
unlinkFrom(transaction, item, type)
|
||||
}
|
||||
const lastId = item.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
}
|
||||
type._firstItem = null
|
||||
}
|
||||
let item = this.type._start
|
||||
while (item !== null) {
|
||||
if (!item.deleted) {
|
||||
|
||||
+84
-4
@@ -23,7 +23,9 @@ import {
|
||||
readContentType,
|
||||
addChangedTypeToTransaction,
|
||||
isDeleted,
|
||||
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
|
||||
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
|
||||
YWeakLink,
|
||||
joinLinkedRange
|
||||
} from '../internals.js'
|
||||
|
||||
import * as error from 'lib0/error'
|
||||
@@ -105,6 +107,14 @@ export const splitItem = (transaction, leftItem, diff) => {
|
||||
if (leftItem.redone !== null) {
|
||||
rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff)
|
||||
}
|
||||
if (leftItem.linked) {
|
||||
rightItem.linked = true
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
const linkedBy = allLinks.get(leftItem)
|
||||
if (linkedBy !== undefined) {
|
||||
allLinks.set(rightItem, new Set(linkedBy))
|
||||
}
|
||||
}
|
||||
// update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
|
||||
leftItem.right = rightItem
|
||||
// update right
|
||||
@@ -304,11 +314,28 @@ export class Item extends AbstractStruct {
|
||||
* bit2: countable
|
||||
* bit3: deleted
|
||||
* bit4: mark - mark node as fast-search-marker
|
||||
* bit9: linked - this item is linked by Weak Link references
|
||||
* @type {number} byte
|
||||
*/
|
||||
this.info = this.content.isCountable() ? binary.BIT2 : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to mark the item as linked by weak link references.
|
||||
* Reference dependencies are being kept in StructStore.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
set linked (isLinked) {
|
||||
if (((this.info & binary.BIT9) > 0) !== isLinked) {
|
||||
this.info ^= binary.BIT9
|
||||
}
|
||||
}
|
||||
|
||||
get linked () {
|
||||
return (this.info & binary.BIT9) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to mark the item as an indexed fast-search marker
|
||||
*
|
||||
@@ -376,6 +403,20 @@ export class Item extends AbstractStruct {
|
||||
if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) {
|
||||
return this.parent.client
|
||||
}
|
||||
|
||||
if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
|
||||
// make sure that linked content is integrated first
|
||||
const content = /** @type {ContentType} */ (this.content)
|
||||
const link = /** @type {YWeakLink<any>} */ (content.type)
|
||||
const start = link._quoteStart.item
|
||||
if (start !== null && start.clock >= getState(store, start.client)) {
|
||||
return start.client
|
||||
}
|
||||
const end = link._quoteEnd.item
|
||||
if (end !== null && end.clock >= getState(store, end.client)) {
|
||||
return end.client
|
||||
}
|
||||
}
|
||||
|
||||
// We have all missing ids, now find the items
|
||||
|
||||
@@ -508,18 +549,43 @@ export class Item extends AbstractStruct {
|
||||
// set as current parent value if right === null and this is parentSub
|
||||
/** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
|
||||
if (this.left !== null) {
|
||||
// move links from block we're overriding
|
||||
this.linked = this.left.linked
|
||||
this.left.linked = false
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
const links = allLinks.get(this.left)
|
||||
if (links !== undefined) {
|
||||
allLinks.set(this, links)
|
||||
// since left is being deleted, it will remove
|
||||
// its links from store.linkedBy anyway
|
||||
}
|
||||
// this is the current attribute value of parent. delete right
|
||||
this.left.delete(transaction)
|
||||
}
|
||||
}
|
||||
// adjust length of parent
|
||||
if (this.parentSub === null && this.countable && !this.deleted) {
|
||||
/** @type {AbstractType<any>} */ (this.parent)._length += this.length
|
||||
if (this.parentSub === null && !this.deleted) {
|
||||
if (this.countable) {
|
||||
// adjust length of parent
|
||||
/** @type {AbstractType<any>} */ (this.parent)._length += this.length
|
||||
}
|
||||
if (this.left && this.left.linked && this.right && this.right.linked) {
|
||||
// this item exists within a quoted range
|
||||
joinLinkedRange(transaction, this)
|
||||
}
|
||||
}
|
||||
addStruct(transaction.doc.store, this)
|
||||
this.content.integrate(transaction, this)
|
||||
// add parent to transaction.changed
|
||||
addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
|
||||
if (this.linked) {
|
||||
// notify links about changes
|
||||
const linkedBy = transaction.doc.store.linkedBy.get(this)
|
||||
if (linkedBy !== undefined) {
|
||||
for (const link of linkedBy) {
|
||||
addChangedTypeToTransaction(transaction, link, this.parentSub)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (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)
|
||||
@@ -577,6 +643,7 @@ export class Item extends AbstractStruct {
|
||||
this.deleted === right.deleted &&
|
||||
this.redone === null &&
|
||||
right.redone === null &&
|
||||
!this.linked && !right.linked && // linked items cannot be merged
|
||||
this.content.constructor === right.content.constructor &&
|
||||
this.content.mergeWith(right.content)
|
||||
) {
|
||||
@@ -622,6 +689,19 @@ export class Item extends AbstractStruct {
|
||||
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
|
||||
addChangedTypeToTransaction(transaction, parent, this.parentSub)
|
||||
this.content.delete(transaction)
|
||||
|
||||
if (this.linked) {
|
||||
// notify links that current element has been removed
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
const linkedBy = allLinks.get(this)
|
||||
if (linkedBy !== undefined) {
|
||||
for (const link of linkedBy) {
|
||||
addChangedTypeToTransaction(transaction, link, this.parentSub)
|
||||
}
|
||||
allLinks.delete(this)
|
||||
}
|
||||
this.linked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ContentAny,
|
||||
ContentBinary,
|
||||
getItemCleanStart,
|
||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line
|
||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, YWeakLink, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as map from 'lib0/map'
|
||||
@@ -233,8 +233,9 @@ export const getTypeChildren = t => {
|
||||
* @param {AbstractType<EventType>} type
|
||||
* @param {Transaction} transaction
|
||||
* @param {EventType} event
|
||||
* @param {Set<YWeakLink<any>>|null} visitedLinks
|
||||
*/
|
||||
export const callTypeObservers = (type, transaction, event) => {
|
||||
export const callTypeObservers = (type, transaction, event, visitedLinks = null) => {
|
||||
const changedType = type
|
||||
const changedParentTypes = transaction.changedParentTypes
|
||||
while (true) {
|
||||
@@ -242,6 +243,18 @@ export const callTypeObservers = (type, transaction, event) => {
|
||||
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
||||
if (type._item === null) {
|
||||
break
|
||||
} else if (type._item.linked) {
|
||||
const linkedBy = transaction.doc.store.linkedBy.get(type._item)
|
||||
if (linkedBy !== undefined) {
|
||||
for (let link of linkedBy) {
|
||||
if (visitedLinks === null || !visitedLinks.has(link)) {
|
||||
visitedLinks = visitedLinks !== null ? visitedLinks : new Set()
|
||||
visitedLinks.add(link)
|
||||
// recursive call
|
||||
callTypeObservers(link, transaction, /** @type {any} */ (event), visitedLinks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type = /** @type {AbstractType<any>} */ (type._item.parent)
|
||||
}
|
||||
|
||||
+21
-1
@@ -16,7 +16,8 @@ import {
|
||||
YArrayRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
arrayWeakLink,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, YWeakLink, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
import { typeListSlice } from './AbstractType.js'
|
||||
|
||||
@@ -200,6 +201,25 @@ export class YArray extends AbstractType {
|
||||
get (index) {
|
||||
return typeListGet(this, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the weak link that allows to refer and observe live changes of contents of an YArray.
|
||||
* It points at a consecutive range of elements, starting at give `index` and spanning over provided
|
||||
* length of elements.
|
||||
*
|
||||
* @param {number} index The index of the element to return from the YArray
|
||||
* @param {number} length The number of elements to include in returned weak link reference.
|
||||
* @return {YWeakLink<T>}
|
||||
*/
|
||||
quote (index, length = 1) {
|
||||
if (this.doc !== null) {
|
||||
return transact(this.doc, transaction => {
|
||||
return arrayWeakLink(transaction, this, index, length)
|
||||
})
|
||||
} else {
|
||||
throw new Error('cannot quote an YArray that has not been integrated into YDoc')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this YArray to a JavaScript Array.
|
||||
|
||||
+12
-1
@@ -14,7 +14,8 @@ import {
|
||||
YMapRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
mapWeakLink,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, YWeakLink, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as iterator from 'lib0/iterator'
|
||||
@@ -232,6 +233,16 @@ export class YMap extends AbstractType {
|
||||
get (key) {
|
||||
return /** @type {any} */ (typeMapGet(this, key))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a weak reference link to another element stored in the same document.
|
||||
*
|
||||
* @param {string} key
|
||||
* @return {YWeakLink<MapType>|undefined}
|
||||
*/
|
||||
link (key) {
|
||||
return mapWeakLink(this, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the specified key exists or not.
|
||||
|
||||
+168
-102
@@ -27,7 +27,8 @@ import {
|
||||
typeMapGetAll,
|
||||
updateMarkerChanges,
|
||||
ContentType,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction, YWeakLink, // eslint-disable-line
|
||||
quoteText
|
||||
} from '../internals.js'
|
||||
|
||||
import * as object from 'lib0/object'
|
||||
@@ -1000,107 +1001,7 @@ export class YText extends AbstractType {
|
||||
* @public
|
||||
*/
|
||||
toDelta (snapshot, prevSnapshot, computeYChange) {
|
||||
/**
|
||||
* @type{Array<any>}
|
||||
*/
|
||||
const ops = []
|
||||
const currentAttributes = new Map()
|
||||
const doc = /** @type {Doc} */ (this.doc)
|
||||
let str = ''
|
||||
let n = this._start
|
||||
function packStr () {
|
||||
if (str.length > 0) {
|
||||
// pack str with attributes to ops
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const attributes = {}
|
||||
let addAttributes = false
|
||||
currentAttributes.forEach((value, key) => {
|
||||
addAttributes = true
|
||||
attributes[key] = value
|
||||
})
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = { insert: str }
|
||||
if (addAttributes) {
|
||||
op.attributes = attributes
|
||||
}
|
||||
ops.push(op)
|
||||
str = ''
|
||||
}
|
||||
}
|
||||
const computeDelta = () => {
|
||||
while (n !== null) {
|
||||
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
|
||||
switch (n.content.constructor) {
|
||||
case ContentString: {
|
||||
const cur = currentAttributes.get('ychange')
|
||||
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
||||
if (cur === undefined || cur.user !== n.id.client || cur.type !== 'removed') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' })
|
||||
}
|
||||
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
||||
if (cur === undefined || cur.user !== n.id.client || cur.type !== 'added') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' })
|
||||
}
|
||||
} else if (cur !== undefined) {
|
||||
packStr()
|
||||
currentAttributes.delete('ychange')
|
||||
}
|
||||
str += /** @type {ContentString} */ (n.content).str
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
packStr()
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = {
|
||||
insert: n.content.getContent()[0]
|
||||
}
|
||||
if (currentAttributes.size > 0) {
|
||||
const attrs = /** @type {Object<string,any>} */ ({})
|
||||
op.attributes = attrs
|
||||
currentAttributes.forEach((value, key) => {
|
||||
attrs[key] = value
|
||||
})
|
||||
}
|
||||
ops.push(op)
|
||||
break
|
||||
}
|
||||
case ContentFormat:
|
||||
if (isVisible(n, snapshot)) {
|
||||
packStr()
|
||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
packStr()
|
||||
}
|
||||
if (snapshot || prevSnapshot) {
|
||||
// snapshots are merged again after the transaction, so we need to keep the
|
||||
// transaction alive until we are done
|
||||
transact(doc, transaction => {
|
||||
if (snapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, snapshot)
|
||||
}
|
||||
if (prevSnapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, prevSnapshot)
|
||||
}
|
||||
computeDelta()
|
||||
}, 'cleanup')
|
||||
} else {
|
||||
computeDelta()
|
||||
}
|
||||
return ops
|
||||
return rangeDelta(this, null, null, snapshot, prevSnapshot, computeYChange)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1155,6 +1056,31 @@ export class YText extends AbstractType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a WeakLink representing a dynamic quotation of a range of elements.
|
||||
*
|
||||
* In case when quotation happens in a middle of formatting range, formatting
|
||||
* attributes will be split into before|within|after eg. quoting fragment of
|
||||
* `<i>hello world</i>` could result in `<i>he</i>"<i>llo wo</i>"<i>rld</i>`
|
||||
* where `"<i>llo wo</i>"` represents quoted range.
|
||||
*
|
||||
* @param {number} index The index where quoted range should start
|
||||
* @param {number} length Number of quoted elements
|
||||
* @return {YWeakLink<string>}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
quote (index, length) {
|
||||
const y = this.doc
|
||||
if (y !== null) {
|
||||
return transact(y, transaction => {
|
||||
const pos = findPosition(transaction, this, index)
|
||||
return quoteText(transaction, this, pos, length)
|
||||
})
|
||||
}
|
||||
throw new Error('Quoted text was not integrated into Doc')
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes text starting from an index.
|
||||
*
|
||||
@@ -1280,6 +1206,146 @@ export class YText extends AbstractType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a delta representation that happens between `start` and `end` ranges (both sides inclusive).
|
||||
*
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {ID|null} start
|
||||
* @param {ID|null} end
|
||||
* @param {Snapshot|undefined} snapshot
|
||||
* @param {Snapshot|undefined} prevSnapshot
|
||||
* @param {(function('removed' | 'added', ID):any)|undefined} computeYChange
|
||||
* @returns {any} The Delta representation of this type.
|
||||
*/
|
||||
export const rangeDelta = (parent, start, end, snapshot, prevSnapshot, computeYChange) => {
|
||||
/**
|
||||
* @type{Array<any>}
|
||||
*/
|
||||
const ops = []
|
||||
const currentAttributes = new Map()
|
||||
const doc = /** @type {Doc} */ (parent.doc)
|
||||
let str = ''
|
||||
let n = parent._start
|
||||
function packStr () {
|
||||
if (str.length > 0) {
|
||||
// pack str with attributes to ops
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const attributes = {}
|
||||
let addAttributes = false
|
||||
currentAttributes.forEach((value, key) => {
|
||||
addAttributes = true
|
||||
attributes[key] = value
|
||||
})
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = { insert: str }
|
||||
if (addAttributes) {
|
||||
op.attributes = attributes
|
||||
}
|
||||
ops.push(op)
|
||||
str = ''
|
||||
}
|
||||
}
|
||||
const computeDelta = () => {
|
||||
// startOffset represents offset at current block from which we're intersted in picking string
|
||||
// if it's -1 it means, we're out of scope and we should break at this point
|
||||
let startOffset = start === null ? 0 : -1
|
||||
// eslint-disable-next-line no-labels
|
||||
loop: while (n !== null) {
|
||||
if (startOffset < 0 && start !== null) {
|
||||
if (start.client === n.id.client && start.clock >= n.id.clock && start.clock < n.id.clock + n.length) {
|
||||
startOffset = start.clock - n.id.clock
|
||||
}
|
||||
}
|
||||
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
|
||||
switch (n.content.constructor) {
|
||||
case ContentString: {
|
||||
const cur = currentAttributes.get('ychange')
|
||||
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
||||
if (cur === undefined || cur.user !== n.id.client || cur.type !== 'removed') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' })
|
||||
}
|
||||
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
||||
if (cur === undefined || cur.user !== n.id.client || cur.type !== 'added') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' })
|
||||
}
|
||||
} else if (cur !== undefined) {
|
||||
packStr()
|
||||
currentAttributes.delete('ychange')
|
||||
}
|
||||
const s = /** @type {ContentString} */ (n.content).str
|
||||
if (startOffset > 0) {
|
||||
str += s.slice(startOffset)
|
||||
startOffset = 0
|
||||
} else if (end !== null && end.client === n.id.client && end.clock >= n.id.clock && end.clock < n.id.clock + n.length) {
|
||||
// we reached the end or range
|
||||
const endOffset = n.id.clock + n.length - end.clock - 1
|
||||
str += s.slice(0, s.length + endOffset) // scope is negative
|
||||
packStr()
|
||||
// eslint-disable-next-line no-labels
|
||||
break loop
|
||||
} else if (startOffset === 0) {
|
||||
str += s
|
||||
}
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
packStr()
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = {
|
||||
insert: n.content.getContent()[0]
|
||||
}
|
||||
if (currentAttributes.size > 0) {
|
||||
const attrs = /** @type {Object<string,any>} */ ({})
|
||||
op.attributes = attrs
|
||||
currentAttributes.forEach((value, key) => {
|
||||
attrs[key] = value
|
||||
})
|
||||
}
|
||||
ops.push(op)
|
||||
break
|
||||
}
|
||||
case ContentFormat:
|
||||
if (isVisible(n, snapshot)) {
|
||||
packStr()
|
||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (end !== null && end.client === n.id.client && end.clock >= n.id.clock && end.clock < n.id.clock + n.length) {
|
||||
// block may not passed visibility check, but we still need to verify boundaries
|
||||
break
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
packStr()
|
||||
}
|
||||
if (snapshot || prevSnapshot) {
|
||||
// snapshots are merged again after the transaction, so we need to keep the
|
||||
// transaction alive until we are done
|
||||
transact(doc, transaction => {
|
||||
if (snapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, snapshot)
|
||||
}
|
||||
if (prevSnapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, prevSnapshot)
|
||||
}
|
||||
computeDelta()
|
||||
}, 'cleanup')
|
||||
} else {
|
||||
computeDelta()
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {YText}
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
import { decoding, encoding, error } from 'lib0'
|
||||
import * as map from 'lib0/map'
|
||||
import * as set from 'lib0/set'
|
||||
import {
|
||||
YEvent, AbstractType,
|
||||
transact,
|
||||
getItemCleanEnd,
|
||||
createID,
|
||||
getItemCleanStart,
|
||||
callTypeObservers,
|
||||
YWeakLinkRefID,
|
||||
writeID,
|
||||
readID,
|
||||
RelativePosition,
|
||||
ContentString,
|
||||
rangeDelta,
|
||||
formatXmlString,
|
||||
YText,
|
||||
YXmlText,
|
||||
Transaction, Item, Doc, ID, Snapshot, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ItemTextListPosition // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
/**
|
||||
* @template T extends AbstractType<any>
|
||||
* @extends YEvent<any>
|
||||
* Event that describes the changes on a YMap.
|
||||
*/
|
||||
export class YWeakLinkEvent extends YEvent {
|
||||
/**
|
||||
* @param {YWeakLink<T>} ylink The YWeakLink to which this event was propagated to.
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
// eslint-disable-next-line no-useless-constructor
|
||||
constructor (ylink, transaction) {
|
||||
super(ylink, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends AbstractType<YWeakLinkEvent<T>>
|
||||
*
|
||||
* Weak link to another value stored somewhere in the document.
|
||||
*/
|
||||
export class YWeakLink extends AbstractType {
|
||||
/**
|
||||
* @param {RelativePosition} start
|
||||
* @param {RelativePosition} end
|
||||
* @param {Item|null} firstItem
|
||||
*/
|
||||
constructor (start, end, firstItem) {
|
||||
super()
|
||||
/** @type {RelativePosition} */
|
||||
this._quoteStart = start
|
||||
/** @type {RelativePosition} */
|
||||
this._quoteEnd = end
|
||||
this._firstItem = firstItem
|
||||
}
|
||||
|
||||
/**
|
||||
* Position descriptor of the start of a quoted range.
|
||||
*
|
||||
* @returns {RelativePosition}
|
||||
*/
|
||||
get quoteStart () {
|
||||
return this._quoteStart
|
||||
}
|
||||
|
||||
/**
|
||||
* Position descriptor of the end of a quoted range.
|
||||
*
|
||||
* @returns {RelativePosition}
|
||||
*/
|
||||
get quoteEnd () {
|
||||
return this._quoteEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current link contains only a single element.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isSingle () {
|
||||
return this._quoteStart.item === this._quoteEnd.item
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to an underlying value existing somewhere on in the document.
|
||||
*
|
||||
* @return {T|undefined}
|
||||
*/
|
||||
deref () {
|
||||
if (this._firstItem !== null) {
|
||||
let item = this._firstItem
|
||||
if (item.parentSub !== null) {
|
||||
while (item.right !== null) {
|
||||
item = item.right
|
||||
}
|
||||
// we don't support quotations over maps
|
||||
this._firstItem = item
|
||||
}
|
||||
if (!this._firstItem.deleted) {
|
||||
return this._firstItem.content.getContent()[0]
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of references to all elements quoted by current weak link.
|
||||
*
|
||||
* @return {Array<any>}
|
||||
*/
|
||||
unquote () {
|
||||
let result = /** @type {Array<any>} */ ([])
|
||||
let item = this._firstItem
|
||||
const end = /** @type {ID} */ (this._quoteEnd.item)
|
||||
// TODO: moved elements
|
||||
while (item !== null) {
|
||||
if (!item.deleted) {
|
||||
result = result.concat(item.content.getContent())
|
||||
}
|
||||
const lastId = item.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
item = item.right
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrate this type into the Yjs instance.
|
||||
*
|
||||
* * Save this struct in the os
|
||||
* * This type is sent to other client
|
||||
* * Observer functions are fired
|
||||
*
|
||||
* @param {Doc} y The Yjs instance
|
||||
* @param {Item|null} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
if (item !== null) {
|
||||
transact(y, (transaction) => {
|
||||
// link may refer to a single element in multi-element block
|
||||
// in such case we need to cut of the linked element into a
|
||||
// separate block
|
||||
let firstItem = this._firstItem !== null ? this._firstItem : getItemCleanStart(transaction, /** @type {ID} */ (this._quoteStart.item))
|
||||
getItemCleanEnd(transaction, y.store, /** @type {ID} */(this._quoteEnd.item))
|
||||
if (firstItem.parentSub !== null) {
|
||||
// for maps, advance to most recent item
|
||||
while (firstItem.right !== null) {
|
||||
firstItem = firstItem.right
|
||||
}
|
||||
}
|
||||
this._firstItem = firstItem
|
||||
|
||||
/** @type {Item|null} */
|
||||
let item = firstItem
|
||||
const end = /** @type {ID} */ (this._quoteEnd.item)
|
||||
for (;item !== null; item = item.right) {
|
||||
createLink(transaction, item, this)
|
||||
const lastId = item.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {YWeakLink<T>}
|
||||
*/
|
||||
_copy () {
|
||||
return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {YWeakLink<T>}
|
||||
*/
|
||||
clone () {
|
||||
return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates YWeakLinkEvent and calls observers.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
super._callObserver(transaction, parentSubs)
|
||||
callTypeObservers(this, transaction, new YWeakLinkEvent(this, transaction))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
||||
*/
|
||||
_write (encoder) {
|
||||
encoder.writeTypeRef(YWeakLinkRefID)
|
||||
const isSingle = this.isSingle
|
||||
const info = (isSingle ? 0 : 1) | (this._quoteStart.assoc >= 0 ? 2 : 0) | (this._quoteEnd.assoc >= 0 ? 4 : 0)
|
||||
encoding.writeUint8(encoder.restEncoder, info)
|
||||
writeID(encoder.restEncoder, /** @type {ID} */ (this._quoteStart.item))
|
||||
if (!isSingle) {
|
||||
writeID(encoder.restEncoder, /** @type {ID} */ (this._quoteEnd.item))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unformatted string representation of this quoted text range.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toString () {
|
||||
if (this._firstItem !== null) {
|
||||
switch (/** @type {AbstractType<any>} */ (this._firstItem.parent).constructor) {
|
||||
case YText: {
|
||||
let str = ''
|
||||
/**
|
||||
* @type {Item|null}
|
||||
*/
|
||||
let n = this._firstItem
|
||||
const end = /** @type {ID} */ (this._quoteEnd.item)
|
||||
while (n !== null) {
|
||||
if (!n.deleted && n.countable && n.content.constructor === ContentString) {
|
||||
str += /** @type {ContentString} */ (n.content).str
|
||||
}
|
||||
const lastId = n.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
return str
|
||||
}
|
||||
case YXmlText:
|
||||
return this.toDelta().map(delta => formatXmlString(delta)).join('')
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Delta representation of quoted part of underlying text type.
|
||||
*
|
||||
* @param {Snapshot|undefined} [snapshot]
|
||||
* @param {Snapshot|undefined} [prevSnapshot]
|
||||
* @param {function('removed' | 'added', ID):any} [computeYChange]
|
||||
* @returns {Array<any>}
|
||||
*/
|
||||
toDelta (snapshot, prevSnapshot, computeYChange) {
|
||||
if (this._firstItem !== null && this._quoteStart.item !== null && this._quoteEnd.item !== null) {
|
||||
const parent = /** @type {AbstractType<any>} */ (this._firstItem.parent)
|
||||
return rangeDelta(parent, this._quoteStart.item, this._quoteEnd.item, snapshot, prevSnapshot, computeYChange)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||
* @return {YWeakLink<any>}
|
||||
*/
|
||||
export const readYWeakLink = decoder => {
|
||||
const info = decoding.readUint8(decoder.restDecoder)
|
||||
const isSingle = (info & 1) !== 1
|
||||
const startAssoc = (info & 2) === 2 ? 0 : -1
|
||||
const endAssoc = (info & 4) === 4 ? 0 : -1
|
||||
const startID = readID(decoder.restDecoder)
|
||||
const start = new RelativePosition(null, null, startID, startAssoc)
|
||||
const end = new RelativePosition(null, null, isSingle ? startID : readID(decoder.restDecoder), endAssoc)
|
||||
return new YWeakLink(start, end, null)
|
||||
}
|
||||
|
||||
const invalidQuotedRange = error.create('Invalid quoted range length.')
|
||||
|
||||
/**
|
||||
* Returns a {WeakLink} to an YArray element at given index.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {number} index
|
||||
* @return {YWeakLink<any>}
|
||||
*/
|
||||
export const arrayWeakLink = (transaction, parent, index, length = 1) => {
|
||||
if (length <= 0) {
|
||||
throw invalidQuotedRange
|
||||
}
|
||||
let startItem = parent._start
|
||||
for (;startItem !== null; startItem = startItem.right) {
|
||||
if (!startItem.deleted && startItem.countable) {
|
||||
if (index < startItem.length) {
|
||||
if (index > 0) {
|
||||
startItem = getItemCleanStart(transaction, createID(startItem.id.client, startItem.id.clock + index))
|
||||
}
|
||||
break
|
||||
}
|
||||
index -= startItem.length
|
||||
}
|
||||
}
|
||||
let endItem = startItem
|
||||
let remaining = length
|
||||
for (;endItem !== null; endItem = endItem.right) {
|
||||
if (!endItem.deleted && endItem.countable) {
|
||||
if (remaining > endItem.length) {
|
||||
remaining -= endItem.length
|
||||
} else {
|
||||
endItem = getItemCleanEnd(transaction, transaction.doc.store, createID(endItem.id.client, endItem.id.clock + remaining - 1))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startItem !== null && endItem !== null) {
|
||||
const start = new RelativePosition(null, null, startItem.id, 0)
|
||||
const end = new RelativePosition(null, null, endItem.lastId, -1)
|
||||
const link = new YWeakLink(start, end, startItem)
|
||||
if (parent.doc !== null) {
|
||||
transact(parent.doc, (transaction) => {
|
||||
const end = /** @type {ID} */ (link._quoteEnd.item)
|
||||
for (let item = link._firstItem; item !== null; item = item = item.right) {
|
||||
createLink(transaction, item, link)
|
||||
const lastId = item.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return link
|
||||
}
|
||||
throw invalidQuotedRange
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {WeakLink} to an YMap element at given key.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {ItemTextListPosition} pos
|
||||
* @param {number} length
|
||||
* @return {YWeakLink<string>}
|
||||
*/
|
||||
export const quoteText = (transaction, parent, pos, length) => {
|
||||
if (pos.right !== null) {
|
||||
const startItem = pos.right
|
||||
const endIndex = pos.index + length
|
||||
while (pos.index < endIndex) {
|
||||
pos.forward()
|
||||
}
|
||||
if (pos.left !== null) {
|
||||
let endItem = pos.left
|
||||
if (pos.index > endIndex) {
|
||||
const overflow = pos.index - endIndex
|
||||
endItem = getItemCleanEnd(transaction, transaction.doc.store, createID(endItem.id.client, endItem.id.clock + endItem.length - overflow - 1))
|
||||
}
|
||||
const start = new RelativePosition(null, null, startItem.id, 0)
|
||||
const end = new RelativePosition(null, null, endItem.lastId, -1)
|
||||
const link = new YWeakLink(start, end, startItem)
|
||||
if (parent.doc !== null) {
|
||||
transact(parent.doc, (transaction) => {
|
||||
const end = /** @type {ID} */ (link._quoteEnd.item)
|
||||
for (let item = link._firstItem; item !== null; item = item = item.right) {
|
||||
createLink(transaction, item, link)
|
||||
const lastId = item.lastId
|
||||
if (lastId.client === end.client && lastId.clock === end.clock) {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
||||
throw invalidQuotedRange
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {WeakLink} to an YMap element at given key.
|
||||
*
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {string} key
|
||||
* @return {YWeakLink<any>|undefined}
|
||||
*/
|
||||
export const mapWeakLink = (parent, key) => {
|
||||
const item = parent._map.get(key)
|
||||
if (item !== undefined) {
|
||||
const start = new RelativePosition(null, null, item.id, 0)
|
||||
const end = new RelativePosition(null, null, item.id, -1)
|
||||
const link = new YWeakLink(start, end, item)
|
||||
if (parent.doc !== null) {
|
||||
transact(parent.doc, (transaction) => {
|
||||
createLink(transaction, item, link)
|
||||
})
|
||||
}
|
||||
return link
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a link between source and weak link reference.
|
||||
* It assumes that source has already been split if necessary.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Item} source
|
||||
* @param {YWeakLink<any>} linkRef
|
||||
*/
|
||||
export const createLink = (transaction, source, linkRef) => {
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
map.setIfUndefined(allLinks, source, set.create).add(linkRef)
|
||||
source.linked = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the link between source and a weak link reference.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Item} source
|
||||
* @param {YWeakLink<any>} linkRef
|
||||
*/
|
||||
export const unlinkFrom = (transaction, source, linkRef) => {
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
const linkedBy = allLinks.get(source)
|
||||
if (linkedBy !== undefined) {
|
||||
linkedBy.delete(linkRef)
|
||||
if (linkedBy.size === 0) {
|
||||
allLinks.delete(source)
|
||||
source.linked = false
|
||||
if (source.countable) {
|
||||
// since linked property is blocking items from merging,
|
||||
// it may turn out that source item can be merged now
|
||||
transaction._mergeStructs.push(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebinds linkedBy links pointed between neighbours of a current item.
|
||||
* This method expects that current item has both left and right neighbours.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Item} item
|
||||
*/
|
||||
export const joinLinkedRange = (transaction, item) => {
|
||||
item.linked = true
|
||||
const allLinks = transaction.doc.store.linkedBy
|
||||
const leftLinks = allLinks.get(/** @type {Item} */ (item.left))
|
||||
const rightLinks = allLinks.get(/** @type {Item} */ (item.right))
|
||||
if (leftLinks && rightLinks) {
|
||||
const common = new Set()
|
||||
for (const link of leftLinks) {
|
||||
if (rightLinks.has(link)) {
|
||||
common.add(link)
|
||||
}
|
||||
}
|
||||
if (common.size !== 0) {
|
||||
allLinks.set(item, common)
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-30
@@ -64,36 +64,7 @@ export class YXmlText extends YText {
|
||||
|
||||
toString () {
|
||||
// @ts-ignore
|
||||
return this.toDelta().map(delta => {
|
||||
const nestedNodes = []
|
||||
for (const nodeName in delta.attributes) {
|
||||
const attrs = []
|
||||
for (const key in delta.attributes[nodeName]) {
|
||||
attrs.push({ key, value: delta.attributes[nodeName][key] })
|
||||
}
|
||||
// sort attributes to get a unique order
|
||||
attrs.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||
nestedNodes.push({ nodeName, attrs })
|
||||
}
|
||||
// sort node order to get a unique order
|
||||
nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1)
|
||||
// now convert to dom string
|
||||
let str = ''
|
||||
for (let i = 0; i < nestedNodes.length; i++) {
|
||||
const node = nestedNodes[i]
|
||||
str += `<${node.nodeName}`
|
||||
for (let j = 0; j < node.attrs.length; j++) {
|
||||
const attr = node.attrs[j]
|
||||
str += ` ${attr.key}="${attr.value}"`
|
||||
}
|
||||
str += '>'
|
||||
}
|
||||
str += delta.insert
|
||||
for (let i = nestedNodes.length - 1; i >= 0; i--) {
|
||||
str += `</${nestedNodes[i].nodeName}>`
|
||||
}
|
||||
return str
|
||||
}).join('')
|
||||
return this.toDelta().map(delta => formatXmlString(delta)).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,6 +82,43 @@ export class YXmlText extends YText {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats individual delta segment provided by `Text.toDelta` into XML-formatted string.
|
||||
*
|
||||
* @param {any} delta
|
||||
* @returns {string}
|
||||
*/
|
||||
export const formatXmlString = (delta) => {
|
||||
const nestedNodes = []
|
||||
for (const nodeName in delta.attributes) {
|
||||
const attrs = []
|
||||
for (const key in delta.attributes[nodeName]) {
|
||||
attrs.push({ key, value: delta.attributes[nodeName][key] })
|
||||
}
|
||||
// sort attributes to get a unique order
|
||||
attrs.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||
nestedNodes.push({ nodeName, attrs })
|
||||
}
|
||||
// sort node order to get a unique order
|
||||
nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1)
|
||||
// now convert to dom string
|
||||
let str = ''
|
||||
for (let i = 0; i < nestedNodes.length; i++) {
|
||||
const node = nestedNodes[i]
|
||||
str += `<${node.nodeName}`
|
||||
for (let j = 0; j < node.attrs.length; j++) {
|
||||
const attr = node.attrs[j]
|
||||
str += ` ${attr.key}="${attr.value}"`
|
||||
}
|
||||
str += '>'
|
||||
}
|
||||
str += delta.insert
|
||||
for (let i = nestedNodes.length - 1; i >= 0; i--) {
|
||||
str += `</${nestedNodes[i].nodeName}>`
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||
* @return {YXmlText}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {
|
||||
GC,
|
||||
splitItem,
|
||||
Transaction, ID, Item, DSDecoderV2 // eslint-disable-line
|
||||
Transaction, ID, Item, DSDecoderV2, YWeakLink // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as math from 'lib0/math'
|
||||
@@ -14,6 +14,13 @@ export class StructStore {
|
||||
* @type {Map<number,Array<GC|Item>>}
|
||||
*/
|
||||
this.clients = new Map()
|
||||
/**
|
||||
* If this item was referenced by other weak links, here we keep the references
|
||||
* to these weak refs.
|
||||
*
|
||||
* @type {Map<Item, Set<YWeakLink<any>>>}
|
||||
*/
|
||||
this.linkedBy = new Map()
|
||||
/**
|
||||
* @type {null | { missing: Map<number, number>, update: Uint8Array }}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user