fixed YArray
This commit is contained in:
parent
d9ab593b07
commit
ff981a8697
@ -6,7 +6,7 @@
|
||||
"module": "./dist/yjs.mjs'",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"test": "npm run dist && node ./dist/tests.js --repitition-time 50",
|
||||
"test": "npm run dist && node ./dist/tests.js --repitition-time 50 --production",
|
||||
"test-exhaustive": "npm run lint && npm run dist && node ./dist/tests.js --repitition-time 10000",
|
||||
"dist": "rm -rf dist examples/build && rollup -c",
|
||||
"watch": "rollup -wc",
|
||||
|
@ -2,63 +2,29 @@
|
||||
* @module structs
|
||||
*/
|
||||
|
||||
import { readID, createID, writeID, writeNullID, ID, createNextID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { Delete } from '../Delete.js'
|
||||
import { writeStructToTransaction } from '../utils/structEncoding.js'
|
||||
import { readID, createID, writeID, writeNullID, ID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { GC } from './GC.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { ItemType } from './ItemType.js' // eslint-disable-line
|
||||
import { AbstractType } from '../types/AbstractType.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import { Transaction, nextID } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import * as maplib from 'lib0/map.js'
|
||||
import * as set from 'lib0/set.js'
|
||||
import * as binary from 'lib0/binary.js'
|
||||
import { AbstractRef, AbstractStruct } from './AbstractStruct.js' // eslint-disable-line
|
||||
import * as error from 'lib0/error.js'
|
||||
|
||||
/**
|
||||
* Stringify an item id.
|
||||
*
|
||||
* @param { ID } id
|
||||
* @return {string}
|
||||
*/
|
||||
export const stringifyID = id => `(${id.client},${id.clock})`
|
||||
|
||||
/**
|
||||
* Stringify an item as ID. HHere, an item could also be a Yjs instance (e.g. item._parent).
|
||||
*
|
||||
* @param {AbstractItem | null} item
|
||||
* @return {string}
|
||||
*/
|
||||
export const stringifyItemID = item =>
|
||||
item === null ? '()' : (item.id != null ? stringifyID(item.id) : 'y')
|
||||
|
||||
/**
|
||||
* Helper utility to convert an item to a readable format.
|
||||
*
|
||||
* @param {String} name The name of the item class (YText, ItemString, ..).
|
||||
* @param {AbstractItem} item The item instance.
|
||||
* @param {String} [append] Additional information to append to the returned
|
||||
* string.
|
||||
* @return {String} A readable string that represents the item object.
|
||||
*
|
||||
*/
|
||||
export const logItemHelper = (name, item, append) => {
|
||||
const left = item.left !== null ? stringifyID(item.left.lastId) : '()'
|
||||
const origin = item.origin !== null ? stringifyID(item.origin.lastId) : '()'
|
||||
return `${name}(id:${stringifyItemID(item)},left:${left},origin:${origin},right:${stringifyItemID(item.right)},parent:${stringifyItemID(item.parent)},parentSub:${item.parentSub}${append !== undefined ? ' - ' + append : ''})`
|
||||
}
|
||||
import { replaceStruct, addStruct } from '../utils/StructStore.js'
|
||||
|
||||
/**
|
||||
* Split leftItem into two items
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractItem} leftItem
|
||||
* @param {Y} y
|
||||
* @param {number} diff
|
||||
* @return {any}
|
||||
* @return {AbstractItem}
|
||||
*/
|
||||
export const splitItem = (leftItem, diff) => {
|
||||
export const splitItem = (transaction, leftItem, diff) => {
|
||||
const id = leftItem.id
|
||||
// create rightItem
|
||||
const rightItem = leftItem.copy(createID(id.client, id.clock + diff), leftItem, leftItem.rightOrigin, leftItem.parent, leftItem.parentSub)
|
||||
@ -85,6 +51,13 @@ export const splitItem = (leftItem, diff) => {
|
||||
foundOrigins.add(o)
|
||||
o = o.right
|
||||
}
|
||||
const right = leftItem.splitAt(diff)
|
||||
if (transaction.added.has(leftItem)) {
|
||||
transaction.added.add(right)
|
||||
} else if (transaction.deleted.has(leftItem)) {
|
||||
transaction.deleted.add(right)
|
||||
}
|
||||
return rightItem
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +79,7 @@ export class AbstractItem extends AbstractStruct {
|
||||
parent = right.parent
|
||||
parentSub = right.parentSub
|
||||
} else if (parent === null) {
|
||||
error.throwUnexpectedCase()
|
||||
throw error.unexpectedCase()
|
||||
}
|
||||
super(id)
|
||||
/**
|
||||
@ -163,7 +136,6 @@ export class AbstractItem extends AbstractStruct {
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
integrate (transaction) {
|
||||
const y = transaction.y
|
||||
const id = this.id
|
||||
const parent = this.parent
|
||||
const parentSub = this.parentSub
|
||||
@ -171,12 +143,6 @@ export class AbstractItem extends AbstractStruct {
|
||||
const left = this.left
|
||||
const right = this.right
|
||||
// integrate
|
||||
const parentType = parent !== null ? parent.type : maplib.setTfUndefined(y.share, parentSub, () => new AbstractType())
|
||||
if (y.ss.getState(id.client) !== id.clock) {
|
||||
throw new Error('Expected other operation')
|
||||
}
|
||||
y.ss.setState(id.client, id.clock + length)
|
||||
transaction.added.add(this)
|
||||
/*
|
||||
# $this has to find a unique position between origin and the next known character
|
||||
# case 1: $origin equals $o.origin: the $creator parameter decides if left or right
|
||||
@ -200,10 +166,10 @@ export class AbstractItem extends AbstractStruct {
|
||||
// set o to the first conflicting item
|
||||
if (left !== null) {
|
||||
o = left.right
|
||||
} else if (this.parentSub !== null) {
|
||||
o = parentType._map.get(parentSub) || null
|
||||
} else if (parentSub !== null) {
|
||||
o = parent._map.get(parentSub) || null
|
||||
} else {
|
||||
o = parentType._start
|
||||
o = parent._start
|
||||
}
|
||||
const conflictingItems = new Set()
|
||||
const itemsBeforeOrigin = new Set()
|
||||
@ -244,31 +210,31 @@ export class AbstractItem extends AbstractStruct {
|
||||
} else {
|
||||
let r
|
||||
if (parentSub !== null) {
|
||||
const pmap = parentType._map
|
||||
const pmap = parent._map
|
||||
r = pmap.get(parentSub) || null
|
||||
pmap.set(parentSub, this)
|
||||
} else {
|
||||
r = parentType._start
|
||||
parentType._start = this
|
||||
r = parent._start
|
||||
parent._start = this
|
||||
}
|
||||
this.right = r
|
||||
if (r !== null) {
|
||||
r._left = this
|
||||
r.left = this
|
||||
}
|
||||
}
|
||||
// adjust the length of parent
|
||||
if (parentSub === null && this.countable) {
|
||||
parentType._length += length
|
||||
parent._length += length
|
||||
}
|
||||
if (parent !== null && parent.deleted) {
|
||||
addStruct(transaction.y.store, this)
|
||||
if (parent !== null) {
|
||||
maplib.setIfUndefined(transaction.changed, parent, set.create).add(parentSub)
|
||||
}
|
||||
transaction.added.add(this)
|
||||
// @ts-ignore
|
||||
if (parent._item.deleted) {
|
||||
this.delete(transaction, false, true)
|
||||
}
|
||||
y.os.put(this)
|
||||
if (parent !== null) {
|
||||
maplib.setTfUndefined(transaction.changed, parent, set.create).add(parentSub)
|
||||
}
|
||||
|
||||
writeStructToTransaction(y._transaction, this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,7 +243,7 @@ export class AbstractItem extends AbstractStruct {
|
||||
*/
|
||||
get next () {
|
||||
let n = this.right
|
||||
while (n !== null && n._deleted) {
|
||||
while (n !== null && n.deleted) {
|
||||
n = n.right
|
||||
}
|
||||
return n
|
||||
@ -289,7 +255,7 @@ export class AbstractItem extends AbstractStruct {
|
||||
*/
|
||||
get prev () {
|
||||
let n = this.left
|
||||
while (n !== null && n._deleted) {
|
||||
while (n !== null && n.deleted) {
|
||||
n = n.left
|
||||
}
|
||||
return n
|
||||
@ -301,7 +267,7 @@ export class AbstractItem extends AbstractStruct {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem|null} left
|
||||
* @param {AbstractItem|null} right
|
||||
* @param {ItemType|null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string|null} parentSub
|
||||
* @return {AbstractItem}
|
||||
*/
|
||||
@ -312,12 +278,12 @@ export class AbstractItem extends AbstractStruct {
|
||||
/**
|
||||
* Redoes the effect of this operation.
|
||||
*
|
||||
* @param {Y} y The Yjs instance.
|
||||
* @param {Transaction} transaction The Yjs instance.
|
||||
* @param {Set<AbstractItem>} redoitems
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
redo (y, redoitems) {
|
||||
redo (transaction, redoitems) {
|
||||
if (this.redone !== null) {
|
||||
return this.redone
|
||||
}
|
||||
@ -337,12 +303,11 @@ export class AbstractItem extends AbstractStruct {
|
||||
// Is a map item. Insert at the start
|
||||
left = null
|
||||
right = parent.type._map.get(this.parentSub)
|
||||
right._delete(y)
|
||||
}
|
||||
// make sure that parent is redone
|
||||
if (parent._deleted === true && parent.redone === null) {
|
||||
// try to undo parent if it will be undone anyway
|
||||
if (!redoitems.has(parent) || !parent.redo(y, redoitems)) {
|
||||
if (!redoitems.has(parent) || !parent.redo(transaction, redoitems)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -365,7 +330,8 @@ export class AbstractItem extends AbstractStruct {
|
||||
right = right._right
|
||||
}
|
||||
}
|
||||
this.redone = this.copy(createNextID(y), left, right, parent, this.parentSub)
|
||||
this.redone = this.copy(nextID(transaction), left, right, parent, this.parentSub)
|
||||
this.redone.integrate(transaction)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -401,6 +367,8 @@ export class AbstractItem extends AbstractStruct {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call directly. Always split via StructStore!
|
||||
*
|
||||
* Splits this Item so that another Item can be inserted in-between.
|
||||
* This must be overwritten if _length > 1
|
||||
* Returns right part after split
|
||||
@ -411,10 +379,11 @@ export class AbstractItem extends AbstractStruct {
|
||||
*
|
||||
* This method should only be cally by StructStore.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {number} diff
|
||||
* @return {AbstractItem}
|
||||
*/
|
||||
splitAt (diff) {
|
||||
splitAt (transaction, diff) {
|
||||
throw new Error('unimplemented')
|
||||
}
|
||||
|
||||
@ -430,32 +399,13 @@ export class AbstractItem extends AbstractStruct {
|
||||
*/
|
||||
delete (transaction, createDelete = true, gcChildren) {
|
||||
if (!this.deleted) {
|
||||
const y = transaction.y
|
||||
const parent = this.parent
|
||||
const len = this.length
|
||||
// adjust the length of parent
|
||||
if (this.countable && this.parentSub === null) {
|
||||
if (parent !== null) {
|
||||
// parent is y
|
||||
y.get(this.)
|
||||
|
||||
} else {
|
||||
transaction.y.get(this.parentSub)
|
||||
}
|
||||
}
|
||||
if (parent.length !== undefined && this.countable) {
|
||||
parent.length -= len
|
||||
}
|
||||
this._deleted = true
|
||||
y.ds.mark(this.id, this.length, false)
|
||||
let del = new Delete(this.id, len)
|
||||
if (createDelete) {
|
||||
// broadcast and persists Delete
|
||||
del.integrate(y, true)
|
||||
}
|
||||
if (parent !== null) {
|
||||
maplib.setTfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
|
||||
parent._length -= this.length
|
||||
}
|
||||
this.deleted = true
|
||||
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
|
||||
transaction.deleted.add(this)
|
||||
}
|
||||
}
|
||||
@ -469,13 +419,14 @@ export class AbstractItem extends AbstractStruct {
|
||||
* @param {Y} y
|
||||
*/
|
||||
gc (y) {
|
||||
if (this.id !== null) {
|
||||
y.os.replace(this, new GC(this.id, this.length))
|
||||
}
|
||||
replaceStruct(y.store, this, new GC(this.id, this.length))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Array<any>}
|
||||
*/
|
||||
getContent () {
|
||||
throw new Error('Must implement') // TODO: create function in lib0
|
||||
throw error.methodUnimplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -502,11 +453,28 @@ export class AbstractItem extends AbstractStruct {
|
||||
writeID(encoder, this.rightOrigin.id)
|
||||
}
|
||||
if (this.origin === null && this.rightOrigin === null) {
|
||||
if (this.parent === null) {
|
||||
const parent = this.parent
|
||||
if (parent._item === null) {
|
||||
// parent type on y._map
|
||||
// find the correct key
|
||||
// @ts-ignore we know that y exists
|
||||
const map = parent._y.share
|
||||
let ykey = null
|
||||
for (const [key, type] of map) {
|
||||
if (type === parent) {
|
||||
ykey = key
|
||||
break
|
||||
}
|
||||
}
|
||||
if (ykey === null) {
|
||||
throw error.unexpectedCase()
|
||||
}
|
||||
writeNullID(encoder)
|
||||
encoding.writeVarString(encoder, ykey)
|
||||
} else {
|
||||
// neither origin nor right is defined
|
||||
writeID(encoder, this.parent.id)
|
||||
// @ts-ignore _item is defined because parent is integrated
|
||||
writeID(encoder, parent._item.id)
|
||||
}
|
||||
if (this.parentSub !== null) {
|
||||
encoding.writeVarString(encoder, this.parentSub)
|
||||
@ -524,7 +492,7 @@ export class AbstractItemRef extends AbstractRef {
|
||||
super()
|
||||
const id = readID(decoder)
|
||||
if (id === null) {
|
||||
throw new Error('id must not be null')
|
||||
throw error.unexpectedCase()
|
||||
}
|
||||
/**
|
||||
* The uniqe identifier of this type.
|
||||
@ -547,6 +515,13 @@ export class AbstractItemRef extends AbstractRef {
|
||||
* @type {ID | null}
|
||||
*/
|
||||
this.parent = canCopyParentInfo ? readID(decoder) : null
|
||||
/**
|
||||
* If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
|
||||
* and we read the next string as parentYKey.
|
||||
* It indicates how we store/retrieve parent from `y.share`
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.parentYKey = canCopyParentInfo && this.parent === null ? decoding.readVarString(decoder) : null
|
||||
/**
|
||||
* If the parent refers to this item with some kind of key (e.g. YMap, the
|
||||
* key is specified here. The key is then used to refer to the list in which
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { ID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { ID, createID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
||||
import * as error from 'lib0/error.js'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export class AbstractStruct {
|
||||
@ -19,7 +21,15 @@ export class AbstractStruct {
|
||||
* @type {number}
|
||||
*/
|
||||
get length () {
|
||||
throw new Error('unimplemented')
|
||||
throw error.methodUnimplemented()
|
||||
}
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||
* @param {number} encodingRef
|
||||
* @private
|
||||
*/
|
||||
write (encoder, encodingRef) {
|
||||
throw error.methodUnimplemented()
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,5 +44,7 @@ export class AbstractRef {
|
||||
* @param {Transaction} transaction
|
||||
* @return {AbstractStruct}
|
||||
*/
|
||||
toStruct (transaction) { throw new Error('Must be defined') }
|
||||
toStruct (transaction) {
|
||||
throw error.methodUnimplemented()
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ export class GC extends AbstractStruct {
|
||||
this.length = length
|
||||
}
|
||||
|
||||
get deleted () {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
*/
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
// TODO: ItemBinary should be able to merge with right (similar to other items). Or the other items (ItemJSON) should not be able to merge - extra byte + consistency
|
||||
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef } from './AbstractItem.js'
|
||||
import { AbstractItem, AbstractItemRef } from './AbstractItem.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { ID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { ItemType } from './ItemType.js' // eslint-disable-line
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import { getItemCleanEnd, getItemCleanStart, getItemType } from '../utils/StructStore.js'
|
||||
@ -20,7 +20,7 @@ export class ItemBinary extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {ArrayBuffer} content
|
||||
*/
|
||||
@ -28,25 +28,19 @@ export class ItemBinary extends AbstractItem {
|
||||
super(id, left, right, parent, parentSub)
|
||||
this.content = content
|
||||
}
|
||||
getContent () {
|
||||
return [this.content]
|
||||
}
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemBinary(id, left, right, parent, parentSub, this.content)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemBinary', this)
|
||||
}
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
*/
|
||||
@ -73,12 +67,14 @@ export class ItemBinaryRef extends AbstractItemRef {
|
||||
* @return {ItemBinary}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemBinary(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
|
||||
this.parentSub,
|
||||
this.content
|
||||
)
|
||||
|
@ -4,53 +4,45 @@
|
||||
|
||||
// TODO: ItemBinary should be able to merge with right (similar to other items). Or the other items (ItemJSON) should not be able to merge - extra byte + consistency
|
||||
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef } from './AbstractItem.js'
|
||||
import { AbstractItem, AbstractItemRef } from './AbstractItem.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { ID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { ItemType } from './ItemType.js' // eslint-disable-line
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { getItemCleanEnd, getItemCleanStart, getItemType } from '../utils/StructStore.js'
|
||||
|
||||
export const structDeletedRefNumber = 2
|
||||
|
||||
export class ItemBinary extends AbstractItem {
|
||||
export class ItemDeleted extends AbstractItem {
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {ArrayBuffer} content
|
||||
* @param {number} length
|
||||
*/
|
||||
constructor (id, left, right, parent, parentSub, content) {
|
||||
constructor (id, left, right, parent, parentSub, length) {
|
||||
super(id, left, right, parent, parentSub)
|
||||
this.content = content
|
||||
this.length = length
|
||||
}
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemBinary(id, left, right, parent, parentSub, this.content)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemBinary', this)
|
||||
return new ItemDeleted(id, left, right, parent, parentSub, this.length)
|
||||
}
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
*/
|
||||
write (encoder) {
|
||||
super.write(encoder, structDeletedRefNumber)
|
||||
encoding.writePayload(encoder, this.content)
|
||||
encoding.writeVarUint(encoder, this.length)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,22 +54,25 @@ export class ItemDeletedRef extends AbstractItemRef {
|
||||
constructor (decoder, info) {
|
||||
super(decoder, info)
|
||||
/**
|
||||
* @type {ArrayBuffer}
|
||||
* @type {number}
|
||||
*/
|
||||
this.content = decoding.readPayload(decoder)
|
||||
this.length = decoding.readVarUint(decoder)
|
||||
}
|
||||
/**
|
||||
* @param {Y} y
|
||||
* @return {ItemBinary}
|
||||
* @param {Transaction} transaction
|
||||
* @return {ItemDeleted}
|
||||
*/
|
||||
toStruct (y) {
|
||||
return new ItemBinary(
|
||||
toStruct (transaction) {
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemDeleted(
|
||||
this.id,
|
||||
this.left === null ? null : y.os.getItemCleanEnd(this.left),
|
||||
this.right === null ? null : y.os.getItemCleanStart(this.right),
|
||||
this.parent === null ? null : y.os.getItem(this.parent),
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
|
||||
this.parentSub,
|
||||
this.content
|
||||
this.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
* @module structs
|
||||
*/
|
||||
|
||||
import { AbstractItem, AbstractItemRef, logItemHelper } from './AbstractItem.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { AbstractItem, AbstractItemRef } from './AbstractItem.js'
|
||||
import { ItemType } from './ItemType.js' // eslint-disable-line
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
@ -18,7 +19,7 @@ export class ItemEmbed extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {Object} embed
|
||||
*/
|
||||
@ -30,28 +31,12 @@ export class ItemEmbed extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemEmbed(id, left, right, parent, parentSub, this.embed)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemEmbed', this)
|
||||
}
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get _length () {
|
||||
return 1
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
*/
|
||||
@ -78,12 +63,14 @@ export class ItemEmbedRef extends AbstractItemRef {
|
||||
* @return {ItemEmbed}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemEmbed(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent),
|
||||
this.parentSub,
|
||||
this.embed
|
||||
)
|
||||
|
@ -2,7 +2,8 @@
|
||||
* @module structs
|
||||
*/
|
||||
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef } from './AbstractItem.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { AbstractItem, AbstractItemRef } from './AbstractItem.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
@ -18,7 +19,7 @@ export class ItemFormat extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
@ -32,25 +33,13 @@ export class ItemFormat extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemFormat(id, left, right, parent, parentSub, this.key, this.value)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemFormat', this, `key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)}`)
|
||||
}
|
||||
get _length () {
|
||||
return 1
|
||||
}
|
||||
get _countable () {
|
||||
get countable () {
|
||||
return false
|
||||
}
|
||||
/**
|
||||
@ -81,12 +70,14 @@ export class ItemFormatRef extends AbstractItemRef {
|
||||
* @return {ItemFormat}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemFormat(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent),
|
||||
this.parentSub,
|
||||
this.key,
|
||||
this.value
|
||||
|
@ -2,7 +2,8 @@
|
||||
* @module structs
|
||||
*/
|
||||
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef, splitItem } from './AbstractItem.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { AbstractItem, AbstractItemRef, splitItem } from './AbstractItem.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
@ -18,7 +19,7 @@ export class ItemJSON extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {Array<any>} content
|
||||
*/
|
||||
@ -30,32 +31,28 @@ export class ItemJSON extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemJSON(id, left, right, parent, parentSub, this.content)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemJSON', this, `content:${JSON.stringify(this.content)}`)
|
||||
}
|
||||
get length () {
|
||||
return this.content.length
|
||||
}
|
||||
getContent () {
|
||||
return this.content
|
||||
}
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {number} diff
|
||||
*/
|
||||
splitAt (diff) {
|
||||
splitAt (transaction, diff) {
|
||||
/**
|
||||
* @type {ItemJSON}
|
||||
*/
|
||||
const right = splitItem(this, diff)
|
||||
// @ts-ignore
|
||||
const right = splitItem(transaction, this, diff)
|
||||
right.content = this.content.splice(diff)
|
||||
return right
|
||||
}
|
||||
@ -97,12 +94,14 @@ export class ItemJSONRef extends AbstractItemRef {
|
||||
* @return {ItemJSON}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemJSON(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent),
|
||||
this.parentSub,
|
||||
this.content
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @module structs
|
||||
*/
|
||||
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef, splitItem } from './AbstractItem.js'
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { AbstractItem, AbstractItemRef, splitItem } from './AbstractItem.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
@ -18,48 +18,46 @@ export class ItemString extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {string} string
|
||||
*/
|
||||
constructor (id, left, right, parent, parentSub, string) {
|
||||
super(id, left, right, parent, parentSub)
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.string = string
|
||||
}
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemString(id, left, right, parent, parentSub, this.string)
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemString', this, `content:"${this.string}"`)
|
||||
getContent () {
|
||||
return this.string.split('')
|
||||
}
|
||||
get length () {
|
||||
return this.string.length
|
||||
}
|
||||
splitAt (y, diff) {
|
||||
if (diff === 0) {
|
||||
return this
|
||||
} else if (diff >= this.string.length) {
|
||||
return this.right
|
||||
}
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {number} diff
|
||||
* @return {ItemString}
|
||||
*/
|
||||
splitAt (transaction, diff) {
|
||||
/**
|
||||
* @type {ItemString}
|
||||
*/
|
||||
const right = splitItem(this, y, diff)
|
||||
// @ts-ignore
|
||||
const right = splitItem(transaction, this, diff)
|
||||
right.string = this.string.slice(diff)
|
||||
right.string = this.string.slice(0, diff)
|
||||
this.string = this.string.slice(0, diff)
|
||||
return right
|
||||
}
|
||||
/**
|
||||
@ -88,12 +86,14 @@ export class ItemStringRef extends AbstractItemRef {
|
||||
* @return {ItemString}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemString(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent),
|
||||
this.parentSub,
|
||||
this.string
|
||||
)
|
||||
|
@ -6,9 +6,8 @@
|
||||
|
||||
import { ID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import { AbstractType } from '../types/AbstractType.js' // eslint-disable-line
|
||||
import { AbstractItem, logItemHelper, AbstractItemRef } from './AbstractItem.js'
|
||||
import { AbstractItem, AbstractItemRef } from './AbstractItem.js'
|
||||
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { readYArray } from '../types/YArray.js'
|
||||
@ -20,12 +19,14 @@ import { readYXmlText } from '../types/YXmlText.js'
|
||||
import { getItemCleanEnd, getItemCleanStart, getItemType } from '../utils/StructStore.js'
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
|
||||
|
||||
/**
|
||||
* @param {Y} y
|
||||
* @param {AbstractItem | null} item
|
||||
*/
|
||||
const gcChildren = (y, item) => {
|
||||
while (item !== null) {
|
||||
item._delete(y, false, true)
|
||||
item._gc(y)
|
||||
item = item._right
|
||||
item.gc(y)
|
||||
item = item.right
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ export class ItemType extends AbstractItem {
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @param {AbstractType} type
|
||||
*/
|
||||
@ -57,26 +58,20 @@ export class ItemType extends AbstractItem {
|
||||
super(id, left, right, parent, parentSub)
|
||||
this.type = type
|
||||
}
|
||||
getContent () {
|
||||
return [this.type]
|
||||
}
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @param {AbstractItem | null} left
|
||||
* @param {AbstractItem | null} right
|
||||
* @param {ItemType | null} parent
|
||||
* @param {AbstractType} parent
|
||||
* @param {string | null} parentSub
|
||||
* @return {AbstractItem} TODO, returns itemtype
|
||||
*/
|
||||
copy (id, left, right, parent, parentSub) {
|
||||
return new ItemType(id, left, right, parent, parentSub, this.type._copy())
|
||||
}
|
||||
/**
|
||||
* Transform this Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
logString () {
|
||||
return logItemHelper('ItemType', this)
|
||||
}
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
*/
|
||||
@ -97,20 +92,20 @@ export class ItemType extends AbstractItem {
|
||||
delete (transaction, createDelete, gcChildren = transaction.y.gcEnabled) {
|
||||
const y = transaction.y
|
||||
super.delete(transaction, createDelete, gcChildren)
|
||||
transaction.changed.delete(this)
|
||||
transaction.changed.delete(this.type)
|
||||
// delete map types
|
||||
for (let value of this.type._map.values()) {
|
||||
if (!value._deleted) {
|
||||
value._delete(y, false, gcChildren)
|
||||
if (!value.deleted) {
|
||||
value.delete(transaction, false, gcChildren)
|
||||
}
|
||||
}
|
||||
// delete array types
|
||||
let t = this.type._start
|
||||
while (t !== null) {
|
||||
if (!t._deleted) {
|
||||
t._delete(y, false, gcChildren)
|
||||
if (!t.deleted) {
|
||||
t.delete(transaction, false, gcChildren)
|
||||
}
|
||||
t = t._right
|
||||
t = t.right
|
||||
}
|
||||
if (gcChildren) {
|
||||
this.gcChildren(y)
|
||||
@ -156,12 +151,14 @@ export class ItemBinaryRef extends AbstractItemRef {
|
||||
* @return {ItemType}
|
||||
*/
|
||||
toStruct (transaction) {
|
||||
const store = transaction.y.store
|
||||
const y = transaction.y
|
||||
const store = y.store
|
||||
return new ItemType(
|
||||
this.id,
|
||||
this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
|
||||
this.right === null ? null : getItemCleanStart(store, transaction, this.right),
|
||||
this.parent === null ? null : getItemType(store, this.parent),
|
||||
// @ts-ignore
|
||||
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent),
|
||||
this.parentSub,
|
||||
this.type
|
||||
)
|
||||
|
@ -9,6 +9,12 @@ import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { Encoder } from 'lib0/encoding.js' // eslint-disable-line
|
||||
import { Transaction, nextID } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import * as map from 'lib0/map.js'
|
||||
import { isVisible, Snapshot } from '../utils/Snapshot.js' // eslint-disable-line
|
||||
import { ItemJSON } from '../structs/ItemJSON.js'
|
||||
import { ItemBinary } from '../structs/ItemBinary.js'
|
||||
import { ID, createID } from '../utils/ID.js' // eslint-disable-line
|
||||
import { getItemCleanStart } from '../utils/StructStore.js'
|
||||
|
||||
/**
|
||||
* Restructure children as if they were inserted one after another
|
||||
@ -118,10 +124,13 @@ export class AbstractType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates YArray Event and calls observers.
|
||||
* Creates YEvent and calls observers.
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
_callObserver (transaction, parentSubs) {
|
||||
this._callEventHandler(transaction, new YEvent(this))
|
||||
}
|
||||
|
||||
@ -129,38 +138,23 @@ export class AbstractType {
|
||||
* Call event listeners with an event. This will also add an event to all
|
||||
* parents (for `.observeDeep` handlers).
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {any} event
|
||||
*/
|
||||
_callEventHandler (transaction, event) {
|
||||
const changedParentTypes = transaction.changedParentTypes
|
||||
this._eventHandler.callEventListeners(transaction, event)
|
||||
/**
|
||||
* @type {any}
|
||||
* @type {AbstractType}
|
||||
*/
|
||||
let type = this
|
||||
while (type !== this._y) {
|
||||
let events = changedParentTypes.get(type)
|
||||
if (events === undefined) {
|
||||
events = []
|
||||
changedParentTypes.set(type, events)
|
||||
while (true) {
|
||||
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
||||
if (type._item === null) {
|
||||
break
|
||||
}
|
||||
events.push(event)
|
||||
type = type._parent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to transact if the y instance is available.
|
||||
*
|
||||
* TODO: Currently event handlers are not thrown when a type is not registered
|
||||
* with a Yjs instance.
|
||||
* @private
|
||||
*/
|
||||
_transact (f) {
|
||||
const y = this._y
|
||||
if (y !== null) {
|
||||
y.transact(f)
|
||||
} else {
|
||||
f(y)
|
||||
type = type._item.parent
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,8 +205,19 @@ export class AbstractType {
|
||||
* @param {AbstractType} type
|
||||
* @return {Array<any>}
|
||||
*/
|
||||
export const typeToArray = type => {
|
||||
|
||||
export const typeArrayToArray = type => {
|
||||
const cs = []
|
||||
let n = type._start
|
||||
while (n !== null) {
|
||||
if (n.countable && !n.deleted) {
|
||||
const c = n.getContent()
|
||||
for (let i = 0; i < c.length; i++) {
|
||||
cs.push(c[i])
|
||||
}
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
/**
|
||||
@ -220,18 +225,176 @@ export const typeToArray = type => {
|
||||
*
|
||||
* @param {AbstractType} type
|
||||
* @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray.
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
*/
|
||||
export const typeForEach = (type, f, snapshot) => {
|
||||
export const typeArrayForEach = (type, f) => {
|
||||
let index = 0
|
||||
let n = type._start
|
||||
while (n !== null) {
|
||||
if (isVisible(n, snapshot) && n._countable) {
|
||||
if (n.countable && !n.deleted) {
|
||||
const c = n.getContent()
|
||||
for (let i = 0; i < c.length; i++) {
|
||||
f(c[i], index++, type)
|
||||
}
|
||||
}
|
||||
n = n._right
|
||||
n = n.right
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType} type
|
||||
*/
|
||||
export const typeArrayCreateIterator = type => {
|
||||
let n = type._start
|
||||
/**
|
||||
* @type {Array<any>|null}
|
||||
*/
|
||||
let currentContent = null
|
||||
let currentContentIndex = 0
|
||||
return {
|
||||
next: () => {
|
||||
// find some content
|
||||
if (currentContent === null) {
|
||||
while (n !== null && n.deleted) {
|
||||
n = n.right
|
||||
}
|
||||
}
|
||||
// check if we reached the end, no need to check currentContent, because it does not exist
|
||||
if (n === null) {
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
// currentContent could exist from the last iteration
|
||||
if (currentContent === null) {
|
||||
// we found n, so we can set currentContent
|
||||
currentContent = n.getContent()
|
||||
currentContentIndex = 0
|
||||
}
|
||||
const value = currentContent[currentContentIndex++]
|
||||
// check if we need to empty currentContent
|
||||
if (currentContent.length <= currentContentIndex) {
|
||||
currentContent = null
|
||||
}
|
||||
return {
|
||||
done: false,
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a provided function on once on overy 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 {Snapshot} snapshot
|
||||
*/
|
||||
export const typeArrayForEachSnapshot = (type, f, snapshot) => {
|
||||
let index = 0
|
||||
let n = type._start
|
||||
while (n !== null) {
|
||||
if (n.countable && isVisible(n, snapshot)) {
|
||||
const c = n.getContent()
|
||||
for (let i = 0; i < c.length; i++) {
|
||||
f(c[i], index++, type)
|
||||
}
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType} type
|
||||
* @param {number} index
|
||||
* @return {any}
|
||||
*/
|
||||
export const typeArrayGet = (type, index) => {
|
||||
for (let n = type._start; n !== null; n = n.right) {
|
||||
if (!n.deleted && n.countable) {
|
||||
if (index < n.length) {
|
||||
return n.getContent()[index]
|
||||
}
|
||||
index -= n.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType} parent
|
||||
* @param {AbstractItem?} referenceItem
|
||||
* @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content
|
||||
*/
|
||||
export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
|
||||
let left = referenceItem
|
||||
const right = referenceItem === null ? parent._start : referenceItem.right
|
||||
/**
|
||||
* @type {Array<Object|Array|number>}
|
||||
*/
|
||||
let jsonContent = []
|
||||
content.forEach(c => {
|
||||
switch (c.constructor) {
|
||||
case Object:
|
||||
case Array:
|
||||
case String:
|
||||
jsonContent.push(c)
|
||||
break
|
||||
default:
|
||||
if (jsonContent.length > 0) {
|
||||
const item = new ItemJSON(nextID(transaction), left, right, parent, null, jsonContent)
|
||||
item.integrate(transaction)
|
||||
jsonContent = []
|
||||
}
|
||||
switch (c.constructor) {
|
||||
case ArrayBuffer:
|
||||
// @ts-ignore c is definitely an ArrayBuffer
|
||||
new ItemBinary(nextID(transaction), left, right, parent, null, c).integrate(transaction)
|
||||
break
|
||||
default:
|
||||
if (c instanceof AbstractType) {
|
||||
new ItemType(nextID(transaction), left, right, parent, null, c).integrate(transaction)
|
||||
} else {
|
||||
throw new Error('Unexpected content type in insert operation')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType} parent
|
||||
* @param {number} index
|
||||
* @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content
|
||||
*/
|
||||
export const typeArrayInsertGenerics = (transaction, parent, index, content) => {
|
||||
if (index === 0) {
|
||||
typeArrayInsertGenericsAfter(transaction, parent, null, content)
|
||||
}
|
||||
for (let n = parent._start; n !== null; n = n.right) {
|
||||
if (!n.deleted && n.countable) {
|
||||
if (index <= n.length) {
|
||||
if (index < n.length) {
|
||||
getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index))
|
||||
}
|
||||
return typeArrayInsertGenericsAfter(transaction, parent, n, content)
|
||||
}
|
||||
index -= n.length
|
||||
}
|
||||
}
|
||||
throw new Error('Index exceeds array range')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType} parent
|
||||
* @param {string} key
|
||||
*/
|
||||
export const typeMapDelete = (transaction, parent, key) => {
|
||||
const c = parent._map.get(key)
|
||||
if (c !== undefined) {
|
||||
c.delete(transaction)
|
||||
}
|
||||
}
|
||||
|
@ -2,27 +2,27 @@
|
||||
* @module types
|
||||
*/
|
||||
|
||||
import { AbstractType } from './AbstractType.js'
|
||||
import { ItemJSON } from '../structs/ItemJSON.js'
|
||||
import { ItemString } from '../structs/ItemString.js'
|
||||
import { ItemBinary } from '../structs/ItemBinary.js'
|
||||
import { stringifyItemID, logItemHelper } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { AbstractType, typeArrayGet, typeArrayToArray, typeArrayForEach, typeArrayCreateIterator, typeArrayInsertGenerics } from './AbstractType.js'
|
||||
import { YEvent } from '../utils/YEvent.js'
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import { isVisible, HistorySnapshot } from '../utils/snapshot.js' // eslint-disable-line
|
||||
import { getItemCleanStart, getItemCleanEnd } from '../utils/StructStore.js'
|
||||
import { createID } from '../utils/ID.js'
|
||||
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YArray
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
export class YArrayEvent extends YEvent {
|
||||
/**
|
||||
* @param {YArray} yarray The changed type
|
||||
* @param {Boolean} remote Whether the changed was caused by a remote peer
|
||||
* @param {YArray<T>} yarray The changed type
|
||||
* @param {Transaction} transaction The transaction object
|
||||
*/
|
||||
constructor (yarray, remote, transaction) {
|
||||
constructor (yarray, transaction) {
|
||||
super(yarray)
|
||||
this.remote = remote
|
||||
this._transaction = transaction
|
||||
this._addedElements = null
|
||||
this._removedElements = null
|
||||
@ -31,7 +31,7 @@ export class YArrayEvent extends YEvent {
|
||||
/**
|
||||
* Child elements that were added in this transaction.
|
||||
*
|
||||
* @return {Set}
|
||||
* @return {Set<AbstractItem>}
|
||||
*/
|
||||
get addedElements () {
|
||||
if (this._addedElements === null) {
|
||||
@ -39,7 +39,7 @@ export class YArrayEvent extends YEvent {
|
||||
const transaction = this._transaction
|
||||
const addedElements = new Set()
|
||||
transaction.added.forEach(type => {
|
||||
if (type._parent === target && !transaction.deleted.has(type)) {
|
||||
if (type.parent === target && !transaction.deleted.has(type)) {
|
||||
addedElements.add(type)
|
||||
}
|
||||
})
|
||||
@ -51,7 +51,7 @@ export class YArrayEvent extends YEvent {
|
||||
/**
|
||||
* Child elements that were removed in this transaction.
|
||||
*
|
||||
* @return {Set}
|
||||
* @return {Set<AbstractItem>}
|
||||
*/
|
||||
get removedElements () {
|
||||
if (this._removedElements === null) {
|
||||
@ -59,7 +59,7 @@ export class YArrayEvent extends YEvent {
|
||||
const transaction = this._transaction
|
||||
const removedElements = new Set()
|
||||
transaction.deleted.forEach(struct => {
|
||||
if (struct._parent === target && !transaction.added.has(struct)) {
|
||||
if (struct.parent === target && !transaction.added.has(struct)) {
|
||||
removedElements.add(struct)
|
||||
}
|
||||
})
|
||||
@ -71,144 +71,106 @@ export class YArrayEvent extends YEvent {
|
||||
|
||||
/**
|
||||
* A shared Array implementation.
|
||||
* @template T
|
||||
*/
|
||||
export class YArray extends AbstractType {
|
||||
constructor () {
|
||||
super()
|
||||
this.length = 0
|
||||
/**
|
||||
* @type {Array<any>?}
|
||||
*/
|
||||
this._prelimContent = []
|
||||
}
|
||||
/**
|
||||
* Creates YArray Event and calls observers.
|
||||
* 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 {Transaction} transaction The Yjs instance
|
||||
* @param {ItemType} item
|
||||
* @private
|
||||
*/
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
this._callEventHandler(transaction, new YArrayEvent(this, remote, transaction))
|
||||
_integrate (transaction, item) {
|
||||
super._integrate(transaction, item)
|
||||
// @ts-ignore
|
||||
this.insert(0, this._prelimContent)
|
||||
this._prelimContent = null
|
||||
}
|
||||
get length () {
|
||||
return this._prelimContent === null ? this._length : this._prelimContent.length
|
||||
}
|
||||
/**
|
||||
* Creates YArrayEvent and calls observers.
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
this._callEventHandler(transaction, new YArrayEvent(this, transaction))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the i-th element from a YArray.
|
||||
*
|
||||
* @param {number} index The index of the element to return from the YArray
|
||||
* @return {any}
|
||||
* @return {T}
|
||||
*/
|
||||
get (index) {
|
||||
let n = this._start
|
||||
while (n !== null) {
|
||||
if (!n._deleted && n._countable) {
|
||||
if (index < n._length) {
|
||||
switch (n.constructor) {
|
||||
case ItemJSON:
|
||||
case ItemString:
|
||||
return n._content[index]
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
index -= n._length
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
return typeArrayGet(this, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this YArray to a JavaScript Array.
|
||||
*
|
||||
* @param {Object} [snapshot]
|
||||
* @return {Array}
|
||||
* @return {Array<T>}
|
||||
*/
|
||||
toArray (snapshot) {
|
||||
return this.map(c => c, snapshot)
|
||||
toArray () {
|
||||
return typeArrayToArray(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this Shared Type to a JSON object.
|
||||
*
|
||||
* @return {Array}
|
||||
* @return {Array<any>}
|
||||
*/
|
||||
toJSON () {
|
||||
return this.map(c => {
|
||||
if (c instanceof AbstractType) {
|
||||
return c.toJSON()
|
||||
}
|
||||
return c
|
||||
})
|
||||
return this.map(c => c instanceof AbstractType ? c.toJSON() : c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Array with the result of calling a provided function on every
|
||||
* element of this YArray.
|
||||
*
|
||||
* @param {Function} f Function that produces an element of the new Array
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
* @return {Array} A new array with each element being the result of the
|
||||
* @template M
|
||||
* @param {function(T,number,YArray<T>):M} f Function that produces an element of the new Array
|
||||
* @return {Array<M>} A new array with each element being the result of the
|
||||
* callback function
|
||||
*/
|
||||
map (f, snapshot) {
|
||||
const res = []
|
||||
map (f) {
|
||||
/**
|
||||
* @type {Array<M>}
|
||||
*/
|
||||
const result = []
|
||||
this.forEach((c, i) => {
|
||||
res.push(f(c, i, this))
|
||||
}, snapshot)
|
||||
return res
|
||||
result.push(f(c, i, this))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a provided function on once on overy element of this YArray.
|
||||
*
|
||||
* @param {Function} f A function to execute on every element of this YArray.
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
* @param {function(T,number):void} f A function to execute on every element of this YArray.
|
||||
*/
|
||||
forEach (f, snapshot) {
|
||||
let index = 0
|
||||
let n = this._start
|
||||
while (n !== null) {
|
||||
if (isVisible(n, snapshot) && n._countable) {
|
||||
if (n instanceof Type) {
|
||||
f(n, index++, this)
|
||||
} else if (n.constructor === ItemBinary) {
|
||||
f(n._content, index++, this)
|
||||
} else {
|
||||
const content = n._content
|
||||
const contentLen = content.length
|
||||
for (let i = 0; i < contentLen; i++) {
|
||||
index++
|
||||
f(content[i], index, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
forEach (f) {
|
||||
typeArrayForEach(this, f)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
return {
|
||||
next: function () {
|
||||
while (this._item !== null && (this._item._deleted || this._item._length <= this._itemElement)) {
|
||||
// item is deleted or itemElement does not exist (is deleted)
|
||||
this._item = this._item._right
|
||||
this._itemElement = 0
|
||||
}
|
||||
if (this._item === null) {
|
||||
return {
|
||||
done: true
|
||||
}
|
||||
}
|
||||
let content
|
||||
if (this._item instanceof Type) {
|
||||
content = this._item
|
||||
this._item = this._item._right
|
||||
} else {
|
||||
content = this._item._content[this._itemElement++]
|
||||
}
|
||||
return {
|
||||
value: content,
|
||||
done: false
|
||||
}
|
||||
},
|
||||
_item: this._start,
|
||||
_itemElement: 0,
|
||||
_count: 0
|
||||
}
|
||||
return typeArrayCreateIterator(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,121 +180,37 @@ export class YArray extends AbstractType {
|
||||
* @param {number} length The number of elements to remove. Defaults to 1.
|
||||
*/
|
||||
delete (index, length = 1) {
|
||||
this._y.transact(() => {
|
||||
let item = this._start
|
||||
let count = 0
|
||||
while (item !== null && length > 0) {
|
||||
if (!item._deleted && item._countable) {
|
||||
if (count <= index && index < count + item._length) {
|
||||
const diffDel = index - count
|
||||
item = item._splitAt(this._y, diffDel)
|
||||
item._splitAt(this._y, length)
|
||||
length -= item._length
|
||||
item._delete(this._y)
|
||||
count += diffDel
|
||||
} else {
|
||||
count += item._length
|
||||
if (this._y !== null) {
|
||||
this._y.transact(transaction => {
|
||||
const store = transaction.y.store
|
||||
let item = this._start
|
||||
let count = 0
|
||||
while (item !== null && length > 0) {
|
||||
if (!item.deleted && item.countable) {
|
||||
if (count <= index && index < count + item.length) {
|
||||
const diffDel = index - count
|
||||
if (diffDel > 0) {
|
||||
item = getItemCleanStart(store, transaction, createID(item.id.client, item.id.clock + diffDel))
|
||||
}
|
||||
if (length < item.length) {
|
||||
getItemCleanEnd(store, transaction, createID(item.id.client, item.id.clock + length))
|
||||
}
|
||||
length -= item.length
|
||||
item.delete(transaction)
|
||||
count += diffDel
|
||||
} else {
|
||||
count += item.length
|
||||
}
|
||||
}
|
||||
item = item.right
|
||||
}
|
||||
item = item._right
|
||||
}
|
||||
})
|
||||
if (length > 0) {
|
||||
throw new Error('Delete exceeds the range of the YArray')
|
||||
})
|
||||
} else {
|
||||
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
||||
this._prelimContent.splice(index, length)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts content after an element container.
|
||||
*
|
||||
* @private
|
||||
* @param {Item} left The element container to use as a reference.
|
||||
* @param {Array<number|string|Object|ArrayBuffer>} content The Array of content to insert (see {@see insert})
|
||||
*/
|
||||
insertAfter (left, content) {
|
||||
this._transact(y => {
|
||||
let right
|
||||
if (left === null) {
|
||||
right = this._start
|
||||
} else {
|
||||
right = left._right
|
||||
}
|
||||
let prevJsonIns = null
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let c = content[i]
|
||||
if (typeof c === 'function') {
|
||||
c = new c() // eslint-disable-line new-cap
|
||||
}
|
||||
if (c instanceof Type) {
|
||||
if (prevJsonIns !== null) {
|
||||
if (y !== null) {
|
||||
prevJsonIns._integrate(y)
|
||||
}
|
||||
left = prevJsonIns
|
||||
prevJsonIns = null
|
||||
}
|
||||
c._origin = left
|
||||
c._left = left
|
||||
c._right = right
|
||||
c._right_origin = right
|
||||
c._parent = this
|
||||
if (y !== null) {
|
||||
c._integrate(y)
|
||||
} else if (left === null) {
|
||||
this._start = c
|
||||
} else {
|
||||
left._right = c
|
||||
}
|
||||
left = c
|
||||
} else if (c.constructor === ArrayBuffer) {
|
||||
if (prevJsonIns !== null) {
|
||||
if (y !== null) {
|
||||
prevJsonIns._integrate(y)
|
||||
}
|
||||
left = prevJsonIns
|
||||
prevJsonIns = null
|
||||
}
|
||||
const itemBinary = new ItemBinary()
|
||||
itemBinary._origin = left
|
||||
itemBinary._left = left
|
||||
itemBinary._right = right
|
||||
itemBinary._right_origin = right
|
||||
itemBinary._parent = this
|
||||
itemBinary._content = c
|
||||
if (y !== null) {
|
||||
itemBinary._integrate(y)
|
||||
} else if (left === null) {
|
||||
this._start = itemBinary
|
||||
} else {
|
||||
left._right = itemBinary
|
||||
}
|
||||
left = itemBinary
|
||||
} else {
|
||||
if (prevJsonIns === null) {
|
||||
prevJsonIns = new ItemJSON()
|
||||
prevJsonIns._origin = left
|
||||
prevJsonIns._left = left
|
||||
prevJsonIns._right = right
|
||||
prevJsonIns._right_origin = right
|
||||
prevJsonIns._parent = this
|
||||
prevJsonIns._content = []
|
||||
}
|
||||
prevJsonIns._content.push(c)
|
||||
}
|
||||
}
|
||||
if (prevJsonIns !== null) {
|
||||
if (y !== null) {
|
||||
prevJsonIns._integrate(y)
|
||||
} else if (prevJsonIns._left === null) {
|
||||
this._start = prevJsonIns
|
||||
} else {
|
||||
left._right = prevJsonIns
|
||||
}
|
||||
}
|
||||
})
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new content at an index.
|
||||
*
|
||||
@ -347,62 +225,30 @@ export class YArray extends AbstractType {
|
||||
* yarray.insert(2, [1, 2])
|
||||
*
|
||||
* @param {number} index The index to insert content at.
|
||||
* @param {Array<number|string|ArrayBuffer|Type>} content The array of content
|
||||
* @param {Array<number|string|ArrayBuffer|AbstractType>} content The array of content
|
||||
*/
|
||||
insert (index, content) {
|
||||
this._transact(() => {
|
||||
let left = null
|
||||
let right = this._start
|
||||
let count = 0
|
||||
const y = this._y
|
||||
while (right !== null) {
|
||||
const rightLen = right._deleted ? 0 : (right._length - 1)
|
||||
if (count <= index && index <= count + rightLen) {
|
||||
const splitDiff = index - count
|
||||
right = right._splitAt(y, splitDiff)
|
||||
left = right._left
|
||||
count += splitDiff
|
||||
break
|
||||
}
|
||||
if (!right._deleted) {
|
||||
count += right._length
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
if (index > count) {
|
||||
throw new Error('Index exceeds array range!')
|
||||
}
|
||||
this.insertAfter(left, content)
|
||||
})
|
||||
if (this._y !== null) {
|
||||
this._y.transact(transaction => {
|
||||
typeArrayInsertGenerics(transaction, this, index, content)
|
||||
})
|
||||
} else {
|
||||
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
||||
this._prelimContent.splice(index, 0, ...content)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content to this YArray.
|
||||
*
|
||||
* @param {Array<number|string|ArrayBuffer|Type>} content Array of content to append.
|
||||
* @param {Array<number|string|ArrayBuffer|AbstractType>} content Array of content to append.
|
||||
*/
|
||||
push (content) {
|
||||
let n = this._start
|
||||
let lastUndeleted = null
|
||||
while (n !== null) {
|
||||
if (!n._deleted) {
|
||||
lastUndeleted = n
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
this.insertAfter(lastUndeleted, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform this YXml Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_logString () {
|
||||
return logItemHelper('YArray', this, `start:${stringifyItemID(this._start)}"`)
|
||||
this.insert(this.length, content)
|
||||
}
|
||||
}
|
||||
|
||||
export const readYArray = decoder => new YArray()
|
||||
/**
|
||||
* @param {decoding.Decoder} decoder
|
||||
*/
|
||||
export const readYArray = decoder => new YArray()
|
||||
|
@ -2,11 +2,37 @@
|
||||
* @module types
|
||||
*/
|
||||
|
||||
import { AbstractType } from './AbstractType.js'
|
||||
import { AbstractType, typeMapDelete } from './AbstractType.js'
|
||||
import { ItemJSON } from '../structs/ItemJSON.js'
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { YEvent } from '../utils/YEvent.js'
|
||||
import { ItemBinary } from '../structs/ItemBinary.js'
|
||||
import { HistorySnapshot, isVisible } from '../utils/snapshot.js' // eslint-disable-line
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
|
||||
class YMapIterator {
|
||||
/**
|
||||
* @param {Array<any>} vals
|
||||
*/
|
||||
constructor (vals) {
|
||||
this.vals = vals
|
||||
this.i = 0
|
||||
}
|
||||
[Symbol.iterator] () {
|
||||
return this
|
||||
}
|
||||
next () {
|
||||
let value
|
||||
let done = true
|
||||
if (this.i < this.vals.length) {
|
||||
value = this.vals[this.i]
|
||||
done = false
|
||||
}
|
||||
return {
|
||||
value,
|
||||
done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YMap.
|
||||
@ -15,12 +41,10 @@ export class YMapEvent extends YEvent {
|
||||
/**
|
||||
* @param {YMap} ymap The YArray that changed.
|
||||
* @param {Set<any>} subs The keys that changed.
|
||||
* @param {boolean} remote Whether the change was created by a remote peer.
|
||||
*/
|
||||
constructor (ymap, subs, remote) {
|
||||
constructor (ymap, subs) {
|
||||
super(ymap)
|
||||
this.keysChanged = subs
|
||||
this.remote = remote
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,37 +52,56 @@ export class YMapEvent extends YEvent {
|
||||
* A shared Map implementation.
|
||||
*/
|
||||
export class YMap extends AbstractType {
|
||||
constructor () {
|
||||
super()
|
||||
/**
|
||||
* @type {Map<string,any>?}
|
||||
*/
|
||||
this._prelimContent = new Map()
|
||||
}
|
||||
/**
|
||||
* Creates YMap Event and calls observers.
|
||||
* 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 {Transaction} transaction The Yjs instance
|
||||
* @param {ItemType} item
|
||||
* @private
|
||||
*/
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
this._callEventHandler(transaction, new YMapEvent(this, parentSubs, remote))
|
||||
_integrate (transaction, item) {
|
||||
super._integrate(transaction, item)
|
||||
// @ts-ignore
|
||||
for (let [key, value] of this._prelimContent) {
|
||||
this.set(key, value)
|
||||
}
|
||||
this._prelimContent = null
|
||||
}
|
||||
/**
|
||||
* Creates YMapEvent and calls observers.
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
this._callEventHandler(transaction, new YMapEvent(this, parentSubs))
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this Shared Type to a JSON object.
|
||||
*
|
||||
* @return {Object}
|
||||
* @return {Object<string,number|string|Object|Array|ArrayBuffer>}
|
||||
*/
|
||||
toJSON () {
|
||||
/**
|
||||
* @type {Object<string,number|string|Object|Array|ArrayBuffer>}
|
||||
*/
|
||||
const map = {}
|
||||
for (let [key, item] of this._map) {
|
||||
if (!item._deleted) {
|
||||
let res
|
||||
if (item instanceof Type) {
|
||||
if (item.toJSON !== undefined) {
|
||||
res = item.toJSON()
|
||||
} else {
|
||||
res = item.toString()
|
||||
}
|
||||
} else if (item.constructor === ItemBinary) {
|
||||
res = item._content
|
||||
} else {
|
||||
res = item._content[0]
|
||||
}
|
||||
map[key] = res
|
||||
if (!item.deleted) {
|
||||
map[key] = item.getContent()[0]
|
||||
}
|
||||
}
|
||||
return map
|
||||
@ -67,26 +110,30 @@ export class YMap extends AbstractType {
|
||||
/**
|
||||
* Returns the keys for each element in the YMap Type.
|
||||
*
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
* @return {Array}
|
||||
* @return {YMapIterator}
|
||||
*/
|
||||
keys (snapshot) {
|
||||
// TODO: Should return either Iterator or Set!
|
||||
let keys = []
|
||||
if (snapshot === undefined) {
|
||||
for (let [key, value] of this._map) {
|
||||
if (value._deleted) {
|
||||
keys.push(key)
|
||||
}
|
||||
keys () {
|
||||
const keys = []
|
||||
for (let [key, value] of this._map) {
|
||||
if (value.deleted) {
|
||||
keys.push(key)
|
||||
}
|
||||
} else {
|
||||
this._map.forEach((_, key) => {
|
||||
if (YMap.prototype.has.call(this, key, snapshot)) {
|
||||
keys.push(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
return keys
|
||||
return new YMapIterator(keys)
|
||||
}
|
||||
|
||||
entries () {
|
||||
const entries = []
|
||||
for (let [key, value] of this._map) {
|
||||
if (value.deleted) {
|
||||
entries.push([key, value.getContent()[0]])
|
||||
}
|
||||
}
|
||||
return new YMapIterator(entries)
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
return this.entries()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,19 +142,21 @@ export class YMap extends AbstractType {
|
||||
* @param {string} key The key of the element to remove.
|
||||
*/
|
||||
delete (key) {
|
||||
this._transact((y) => {
|
||||
let c = this._map.get(key)
|
||||
if (y !== null && c !== undefined) {
|
||||
c._delete(y)
|
||||
}
|
||||
})
|
||||
if (this._y !== null) {
|
||||
this._y.transact(transaction => {
|
||||
typeMapDelete(transaction, this, key)
|
||||
})
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this._prelimContent.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates an element with a specified key and value.
|
||||
*
|
||||
* @param {string} key The key of the element to add to this YMap
|
||||
* @param {Object | string | number | Type | ArrayBuffer } value The value of the element to add
|
||||
* @param {Object | string | number | AbstractType | ArrayBuffer } value The value of the element to add
|
||||
*/
|
||||
set (key, value) {
|
||||
this._transact(y => {
|
||||
@ -198,16 +247,6 @@ export class YMap extends AbstractType {
|
||||
}
|
||||
return isVisible(v, snapshot)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform this YXml Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_logString () {
|
||||
return logItemHelper('YMap', this, `mapSize:${this._map.size}`)
|
||||
}
|
||||
}
|
||||
|
||||
export const readYMap = decoder => new YMap()
|
@ -7,25 +7,7 @@ import { ItemEmbed } from '../structs/ItemEmbed.js'
|
||||
import { ItemString } from '../structs/ItemString.js'
|
||||
import { ItemFormat } from '../structs/ItemFormat.js'
|
||||
import { YArrayEvent, YArray } from './YArray.js'
|
||||
import { isVisible } from '../utils/snapshot.js'
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
const integrateItem = (item, parent, y, left, right) => {
|
||||
item._origin = left
|
||||
item._left = left
|
||||
item._right = right
|
||||
item._right_origin = right
|
||||
item._parent = parent
|
||||
if (y !== null) {
|
||||
item._integrate(y)
|
||||
} else if (left === null) {
|
||||
parent._start = item
|
||||
} else {
|
||||
left._right = item
|
||||
}
|
||||
}
|
||||
import { isVisible } from '../utils/Snapshot.js'
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -478,12 +460,14 @@ export class YText extends YArray {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates YMap Event and calls observers.
|
||||
*
|
||||
* Creates YTextEvent and calls observers.
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
this._callEventHandler(transaction, new YTextEvent(this, remote, transaction))
|
||||
_callObserver (transaction, parentSubs) {
|
||||
this._callEventHandler(transaction, new YTextEvent(this, transaction))
|
||||
}
|
||||
|
||||
toDom () {
|
||||
|
@ -2,7 +2,8 @@
|
||||
* @module types
|
||||
*/
|
||||
|
||||
import { logItemHelper } from '../structs/AbstractItem.js/index.js'
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import { logItemHelper } from '../structs/AbstractItem.js'
|
||||
import { YMap } from './YMap.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
@ -22,6 +23,15 @@ import { YXmlEvent } from './YXmlEvent.js'
|
||||
* @typedef {string} CSS_Selector
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dom filter function.
|
||||
*
|
||||
* @callback domFilter
|
||||
* @param {string} nodeName The nodeName of the element
|
||||
* @param {Map} attributes The map of attributes.
|
||||
* @return {boolean} Whether to include the Dom node in the YXmlElement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
|
||||
* position within them.
|
||||
@ -31,9 +41,16 @@ import { YXmlEvent } from './YXmlEvent.js'
|
||||
* @public
|
||||
*/
|
||||
export class YXmlTreeWalker {
|
||||
/**
|
||||
* @param {YXmlFragment | YXmlElement} root
|
||||
* @param {function} f
|
||||
*/
|
||||
constructor (root, f) {
|
||||
this._filter = f || (() => true)
|
||||
this._root = root
|
||||
/**
|
||||
* @type {YXmlFragment | YXmlElement}
|
||||
*/
|
||||
this._currentNode = root
|
||||
this._firstCall = true
|
||||
}
|
||||
@ -85,29 +102,6 @@ export class YXmlTreeWalker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dom filter function.
|
||||
*
|
||||
* @callback domFilter
|
||||
* @param {string} nodeName The nodeName of the element
|
||||
* @param {Map} attributes The map of attributes.
|
||||
* @return {boolean} Whether to include the Dom node in the YXmlElement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define the elements to which a set of CSS queries apply.
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
|
||||
*
|
||||
* @example
|
||||
* query = '.classSelector'
|
||||
* query = 'nodeSelector'
|
||||
* query = '#idSelector'
|
||||
*
|
||||
* @typedef {string} CSS_Selector
|
||||
*//**
|
||||
* @module types
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
|
||||
* A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
|
||||
@ -181,12 +175,14 @@ export class YXmlFragment extends YArray {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates YArray Event and calls observers.
|
||||
*
|
||||
* Creates YXmlEvent and calls observers.
|
||||
* @private
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote, transaction))
|
||||
_callObserver (transaction, parentSubs) {
|
||||
this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, transaction))
|
||||
}
|
||||
|
||||
toString () {
|
||||
@ -302,13 +298,13 @@ export class YXmlElement extends YXmlFragment {
|
||||
* * Sets domFilter
|
||||
*
|
||||
* @private
|
||||
* @param {Y} y The Yjs instance
|
||||
* @param {Transaction} transaction The Yjs instance
|
||||
*/
|
||||
_integrate (y) {
|
||||
_integrate (transaction) {
|
||||
if (this.nodeName === null) {
|
||||
throw new Error('nodeName must be defined!')
|
||||
}
|
||||
super._integrate(y)
|
||||
super._integrate(transaction)
|
||||
}
|
||||
|
||||
toString () {
|
||||
|
@ -15,7 +15,7 @@ import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
export class YXmlEvent extends YEvent {
|
||||
/**
|
||||
* @param {Type} target The target on which the event is created.
|
||||
* @param {Set} subs The set of changed attributes. `null` is included if the
|
||||
* @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
|
||||
* child list changed.
|
||||
* @param {Boolean} remote Whether this change was created by a remote peer.
|
||||
* @param {Transaction} transaction The transaction instance with wich the
|
||||
@ -35,7 +35,7 @@ export class YXmlEvent extends YEvent {
|
||||
this.childListChanged = false
|
||||
/**
|
||||
* Set of all changed attributes.
|
||||
* @type {Set}
|
||||
* @type {Set<string|null>}
|
||||
*/
|
||||
this.attributesChanged = new Set()
|
||||
/**
|
||||
|
@ -5,7 +5,6 @@
|
||||
import { YMap } from './YMap.js'
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* You can manage binding to a custom type with YXmlHook.
|
||||
@ -14,14 +13,11 @@ import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
*/
|
||||
export class YXmlHook extends YMap {
|
||||
/**
|
||||
* @param {String} [hookName] nodeName of the Dom Node.
|
||||
* @param {string} hookName nodeName of the Dom Node.
|
||||
*/
|
||||
constructor (hookName) {
|
||||
super()
|
||||
this.hookName = null
|
||||
if (hookName !== undefined) {
|
||||
this.hookName = hookName
|
||||
}
|
||||
this.hookName = hookName
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,9 +26,7 @@ export class YXmlHook extends YMap {
|
||||
* @private
|
||||
*/
|
||||
_copy () {
|
||||
const struct = super._copy()
|
||||
struct.hookName = this.hookName
|
||||
return struct
|
||||
return new YXmlHook(this.hookName)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +37,7 @@ export class YXmlHook extends YMap {
|
||||
* nodejs)
|
||||
* @param {Object.<string, any>} [hooks] Optional property to customize how hooks
|
||||
* are presented in the DOM
|
||||
* @param {DomBinding} [binding] You should not set this property. This is
|
||||
* @param {any} [binding] You should not set this property. This is
|
||||
* used if DomBinding wants to create a
|
||||
* association to the created DOM type
|
||||
* @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
@ -65,22 +59,6 @@ export class YXmlHook extends YMap {
|
||||
return dom
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next Item in a Decoder and fill this Item with the read data.
|
||||
*
|
||||
* This is called when data is received from a remote peer.
|
||||
*
|
||||
* @param {Y} y The Yjs instance that this Item belongs to.
|
||||
* @param {decoding.Decoder} decoder The decoder object to read data from.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_fromBinary (y, decoder) {
|
||||
const missing = super._fromBinary(y, decoder)
|
||||
this.hookName = decoding.readVarString(decoder)
|
||||
return missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the properties of this type to binary and write it to an
|
||||
* BinaryEncoder.
|
||||
@ -91,28 +69,15 @@ export class YXmlHook extends YMap {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
_write (encoder) {
|
||||
super._write(encoder)
|
||||
encoding.writeVarString(encoder, this.hookName)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Y} y The Yjs instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_integrate (y) {
|
||||
if (this.hookName === null) {
|
||||
throw new Error('hookName must be defined!')
|
||||
}
|
||||
super._integrate(y)
|
||||
}
|
||||
}
|
||||
|
||||
export const readYXmlHook = decoder => new YXmlHook()
|
||||
/**
|
||||
* @param {decoding.Decoder} decoder
|
||||
* @return {YXmlHook}
|
||||
*/
|
||||
export const readYXmlHook = decoder =>
|
||||
new YXmlHook(decoding.readVarString(decoder))
|
||||
|
@ -3,6 +3,8 @@ import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { StructStore, getItemRange } from './StructStore.js' // eslint-disable-line
|
||||
import { Transaction } from './Transaction.js' // eslint-disable-line
|
||||
import * as error from 'lib0/error.js'
|
||||
import { ID } from './ID.js'
|
||||
|
||||
class DeleteItem {
|
||||
/**
|
||||
@ -38,6 +40,15 @@ export class DeleteSet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DeleteSet} ds
|
||||
* @param {ID} id
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isDeleted = (ds, id) => {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DeleteSet} ds
|
||||
*/
|
||||
@ -70,7 +81,7 @@ export const sortAndMergeDeleteSet = ds => {
|
||||
export const createDeleteSetFromTransaction = transaction => {
|
||||
const ds = new DeleteSet()
|
||||
transaction.deleted.forEach(item => {
|
||||
map.setTfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
|
||||
map.setIfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
|
||||
})
|
||||
sortAndMergeDeleteSet(ds)
|
||||
return ds
|
||||
|
23
src/utils/Snapshot.js
Normal file
23
src/utils/Snapshot.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { DeleteSet, isDeleted } from './DeleteSet'
|
||||
import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
|
||||
export class Snapshot {
|
||||
/**
|
||||
* @param {DeleteSet} ds delete store
|
||||
* @param {Map<number,number>} sm state map
|
||||
* @param {Map<number,string>} userMap
|
||||
*/
|
||||
constructor (ds, sm, userMap) {
|
||||
this.ds = new DeleteSet()
|
||||
this.sm = sm
|
||||
this.userMap = userMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractItem} item
|
||||
* @param {Snapshot} [snapshot]
|
||||
*/
|
||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
|
||||
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item._id)
|
||||
)
|
@ -31,6 +31,19 @@ export const getStates = store =>
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
* @param {number} client
|
||||
*/
|
||||
export const getState = (store, client) => {
|
||||
const structs = store.clients.get(client)
|
||||
if (structs === undefined) {
|
||||
return 0
|
||||
}
|
||||
const lastStruct = structs[structs.length - 1]
|
||||
return lastStruct.id.clock + lastStruct.length
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
*/
|
||||
@ -51,7 +64,7 @@ export const integretyCheck = store => {
|
||||
* @param {AbstractStruct} struct
|
||||
*/
|
||||
export const addStruct = (store, struct) => {
|
||||
map.setTfUndefined(store.clients, struct.id.client, () => []).push(struct)
|
||||
map.setIfUndefined(store.clients, struct.id.client, () => []).push(struct)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,21 +120,6 @@ const find = (store, id) => {
|
||||
// @ts-ignore
|
||||
export const getItemType = (store, id) => find(store, id)
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractItem} struct
|
||||
* @param {number} diff
|
||||
*/
|
||||
const splitStruct = (transaction, struct, diff) => {
|
||||
const right = struct.splitAt(diff)
|
||||
if (transaction.added.has(struct)) {
|
||||
transaction.added.add(right)
|
||||
} else if (transaction.deleted.has(struct)) {
|
||||
transaction.deleted.add(right)
|
||||
}
|
||||
return right
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
||||
* @param {StructStore} store
|
||||
@ -139,10 +137,11 @@ export const getItemCleanStart = (store, transaction, id) => {
|
||||
const structs = store.clients.get(id.client)
|
||||
const index = findIndex(structs, id.clock)
|
||||
/**
|
||||
* @type {any}
|
||||
* @type {AbstractItem}
|
||||
*/
|
||||
let struct = structs[index]
|
||||
if (struct.id.clock < id.clock) {
|
||||
struct.splitAt()
|
||||
struct = splitStruct(transaction, struct, id.clock - struct.id.clock)
|
||||
structs.splice(index, 0, struct)
|
||||
}
|
||||
@ -213,11 +212,11 @@ export const getItemRange = (store, transaction, client, clock, len) => {
|
||||
* @param {AbstractStruct} struct
|
||||
* @param {AbstractStruct} newStruct
|
||||
*/
|
||||
export const replace = (store, struct, newStruct) => {
|
||||
export const replaceStruct = (store, struct, newStruct) => {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(struct.id.client)
|
||||
structs[findIndex(structs, struct.id)] = newStruct
|
||||
structs[findIndex(structs, struct.id.clock)] = newStruct
|
||||
}
|
||||
|
@ -8,8 +8,10 @@ import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { Y } from './Y.js' // eslint-disable-line
|
||||
import { YEvent } from './YEvent.js' // eslint-disable-line
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { getState } from './StructStore.js'
|
||||
import { writeStructsFromTransaction } from './structEncoding.js'
|
||||
import { createID } from './ID.js' // eslint-disable-line
|
||||
import { createDeleteSetFromTransaction, writeDeleteSet } from './DeleteSet.js'
|
||||
import { getState } from './StructStore.js'
|
||||
|
||||
/**
|
||||
* A transaction is created for every change on the Yjs model. It is possible
|
||||
@ -54,26 +56,41 @@ export class Transaction {
|
||||
*/
|
||||
this.deleted = new Set()
|
||||
/**
|
||||
* Saves the old state set of the Yjs instance. If a state was modified,
|
||||
* the original value is saved here.
|
||||
* If a state was modified, the original value is saved here.
|
||||
* Use `stateUpdates` to compute the original state before the transaction,
|
||||
* or to compute the set of inserted operations.
|
||||
* @type {Map<Number,Number>}
|
||||
*/
|
||||
this.beforeState = new Map()
|
||||
this.stateUpdates = new Map()
|
||||
/**
|
||||
* All types that were directly modified (property added or child
|
||||
* inserted/deleted). New types are not included in this Set.
|
||||
* Maps from type to parentSubs (`item._parentSub = null` for YArray)
|
||||
* @type {Map<ItemType,Set<String|null>>}
|
||||
* @type {Map<AbstractType,Set<String|null>>}
|
||||
*/
|
||||
this.changed = new Map()
|
||||
/**
|
||||
* Stores the events for the types that observe also child elements.
|
||||
* It is mainly used by `observeDeep`.
|
||||
* @type {Map<ItemType,Array<YEvent>>}
|
||||
* @type {Map<AbstractType,Array<YEvent>>}
|
||||
*/
|
||||
this.changedParentTypes = new Map()
|
||||
this.encodedStructsLen = 0
|
||||
this.encodedStructs = encoding.createEncoder()
|
||||
/**
|
||||
* @type {encoding.Encoder|null}
|
||||
*/
|
||||
this._updateMessage = null
|
||||
}
|
||||
/**
|
||||
* @type {encoding.Encoder}
|
||||
*/
|
||||
get updateMessage () {
|
||||
if (this._updateMessage === null) {
|
||||
const encoder = encoding.createEncoder()
|
||||
writeStructsFromTransaction(encoder, this)
|
||||
writeDeleteSet(encoder, createDeleteSetFromTransaction(this))
|
||||
this._updateMessage = encoder
|
||||
}
|
||||
return this._updateMessage
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { DeleteStore } from './DeleteSet.js/index.js' // TODO: remove
|
||||
import { OperationStore } from './OperationStore.js'
|
||||
import { StateStore } from './StateStore.js'
|
||||
import { StructStore } from './StructStore.js'
|
||||
import * as random from 'lib0/random.js'
|
||||
import * as map from 'lib0/map.js'
|
||||
@ -8,22 +5,10 @@ import { Observable } from 'lib0/observable.js'
|
||||
import { Transaction } from './Transaction.js'
|
||||
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js' // eslint-disable-line
|
||||
import { AbstractType } from '../types/AbstractType.js'
|
||||
import { YArray } from '../types/YArray.js'
|
||||
|
||||
/**
|
||||
* Anything that can be encoded with `JSON.stringify` and can be decoded with
|
||||
* `JSON.parse`.
|
||||
*
|
||||
* The following property should hold:
|
||||
* `JSON.parse(JSON.stringify(key))===key`
|
||||
*
|
||||
* At the moment the only safe values are number and string.
|
||||
*
|
||||
* @typedef {(number|string|Object)} encodable
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Yjs instance handles the state of shared data.
|
||||
* @extends Observable<string>
|
||||
*/
|
||||
export class Y extends Observable {
|
||||
/**
|
||||
@ -33,6 +18,9 @@ export class Y extends Observable {
|
||||
super()
|
||||
this.gcEnabled = conf.gc || false
|
||||
this.clientID = random.uint32()
|
||||
/**
|
||||
* @type {Map<string, AbstractType>}
|
||||
*/
|
||||
this.share = new Map()
|
||||
this.store = new StructStore()
|
||||
/**
|
||||
@ -65,7 +53,7 @@ export class Y extends Observable {
|
||||
* that happened inside of the transaction are sent as one message to the
|
||||
* other peers.
|
||||
*
|
||||
* @param {Function} f The function that should be executed as a transaction
|
||||
* @param {function(Transaction):void} f The function that should be executed as a transaction
|
||||
* @param {?Boolean} remote Optional. Whether this transaction is initiated by
|
||||
* a remote peer. This should not be set manually!
|
||||
* Defaults to false.
|
||||
@ -78,7 +66,7 @@ export class Y extends Observable {
|
||||
this.emit('beforeTransaction', [this, this._transaction, remote])
|
||||
}
|
||||
try {
|
||||
f(this)
|
||||
f(this._transaction)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@ -88,7 +76,7 @@ export class Y extends Observable {
|
||||
this._transaction = null
|
||||
// emit change events on changed types
|
||||
transaction.changed.forEach((subs, itemtype) => {
|
||||
if (!itemtype._deleted) {
|
||||
if (!itemtype._item.deleted) {
|
||||
itemtype.type._callObserver(transaction, subs, remote)
|
||||
}
|
||||
})
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { DeleteStore } from './DeleteSet'
|
||||
|
||||
export class HistorySnapshot {
|
||||
/**
|
||||
* @param {DeleteStore} ds delete store
|
||||
* @param {Map<number,number>} sm state map
|
||||
* @param {Map<number,string>} userMap
|
||||
*/
|
||||
constructor (ds, sm, userMap) {
|
||||
this.ds = new DeleteStore()
|
||||
this.sm = sm
|
||||
this.userMap = userMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
*/
|
||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
|
||||
snapshot.sm.has(item._id.user) && (snapshot.sm.get(item._id.user) || 0) > item._id.clock && !snapshot.ds.isDeleted(item._id)
|
||||
)
|
@ -1,13 +1,10 @@
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { getStructReference } from './structReferences.js'
|
||||
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js'
|
||||
import { ID, createID, writeID, writeNullID } from './ID.js'
|
||||
import * as binary from 'lib0/binary.js'
|
||||
|
||||
export const writeStructToTransaction = (transaction, struct) => {
|
||||
transaction.encodedStructsLen++
|
||||
struct._toBinary(transaction.encodedStructs)
|
||||
}
|
||||
import { Transaction } from './Transaction.js'
|
||||
import { findIndex } from './StructStore.js'
|
||||
|
||||
const structRefs = [
|
||||
ItemBinaryRef
|
||||
@ -18,7 +15,6 @@ const structRefs = [
|
||||
*
|
||||
* This is called when data is received from a remote peer.
|
||||
*
|
||||
* @param {Y} y The Yjs instance that this Item belongs to.
|
||||
* @param {decoding.Decoder} decoder The decoder object to read data from.
|
||||
* @return {AbstractRef}
|
||||
*
|
||||
@ -28,3 +24,23 @@ export const read = decoder => {
|
||||
const info = decoding.readUint8(decoder)
|
||||
return new structRefs[binary.BITS5 & info](decoder, info)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
export const writeStructsFromTransaction = (encoder, transaction) => {
|
||||
const stateUpdates = transaction.stateUpdates
|
||||
const y = transaction.y
|
||||
encoding.writeVarUint(encoder, stateUpdates.size)
|
||||
stateUpdates.forEach((clock, client) => {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = y.store.clients.get(client)
|
||||
for (let i = findIndex(structs, clock); i < structs.length; i++) {
|
||||
structs[i].write(encoder, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user