fixed YArray

This commit is contained in:
Kevin Jahns
2019-03-29 01:02:44 +01:00
parent d9ab593b07
commit ff981a8697
25 changed files with 761 additions and 780 deletions

View File

@@ -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)
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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 () {

View File

@@ -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 () {

View File

@@ -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()
/**

View File

@@ -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))