more type fixes and rethinking writeStructs
This commit is contained in:
@@ -14,7 +14,8 @@ import { isVisible, Snapshot } from '../utils/Snapshot.js' // eslint-disable-lin
|
||||
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'
|
||||
import { getItemCleanStart, getItemCleanEnd } from '../utils/StructStore.js'
|
||||
import * as iterator from 'lib0/iterator.js'
|
||||
|
||||
/**
|
||||
* Abstract Yjs Type class
|
||||
@@ -203,6 +204,23 @@ export const typeArrayForEach = (type, f) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template C,R
|
||||
* @param {AbstractType} type
|
||||
* @param {function(C,number,AbstractType):R} f
|
||||
* @return {Array<R>}
|
||||
*/
|
||||
export const typeArrayMap = (type, f) => {
|
||||
/**
|
||||
* @type {Array<any>}
|
||||
*/
|
||||
const result = []
|
||||
typeArrayForEach(type, (c, i) => {
|
||||
result.push(f(c, i, type))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType} type
|
||||
*/
|
||||
@@ -351,6 +369,37 @@ export const typeArrayInsertGenerics = (transaction, parent, index, content) =>
|
||||
throw new Error('Index exceeds array range')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType} parent
|
||||
* @param {number} index
|
||||
* @param {number} length
|
||||
*/
|
||||
export const typeArrayDelete = (transaction, parent, index, length) => {
|
||||
let n = parent._start
|
||||
for (; n !== null; n = n.right) {
|
||||
if (!n.deleted && n.countable) {
|
||||
if (index <= n.length) {
|
||||
if (index < n.length) {
|
||||
n = getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index))
|
||||
}
|
||||
break
|
||||
}
|
||||
index -= n.length
|
||||
}
|
||||
}
|
||||
while (length > 0 && n !== null) {
|
||||
if (!n.deleted) {
|
||||
if (length < n.length) {
|
||||
getItemCleanEnd(transaction.y.store, transaction, createID(n.id.client, n.id.clock + length))
|
||||
}
|
||||
n.delete(transaction)
|
||||
length -= n.length
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType} parent
|
||||
@@ -400,6 +449,23 @@ export const typeMapGet = (parent, key) => {
|
||||
return val !== undefined && !val.deleted ? val.getContent()[0] : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType} parent
|
||||
* @return {Object<string,Object<string,any>|number|Array<any>|string|ArrayBuffer|AbstractType|undefined>}
|
||||
*/
|
||||
export const typeMapGetAll = (parent) => {
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
let res = {}
|
||||
for (const [key, value] of parent._map) {
|
||||
if (!value.deleted) {
|
||||
res[key] = value.getContent()[0]
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType} parent
|
||||
* @param {string} key
|
||||
@@ -423,3 +489,9 @@ export const typeMapGetSnapshot = (parent, key, snapshot) => {
|
||||
}
|
||||
return v !== null && isVisible(v, snapshot) ? v.getContent()[0] : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map<string,AbstractItem>} map
|
||||
* @return {Iterator<[string,AbstractItem]>}
|
||||
*/
|
||||
export const createMapIterator = map => iterator.iteratorFilter(map.entries(), entry => !entry[1].deleted)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
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, typeArrayDelete } from './AbstractType.js'
|
||||
import { AbstractType, typeArrayGet, typeArrayToArray, typeArrayForEach, typeArrayCreateIterator, typeArrayInsertGenerics, typeArrayDelete, typeArrayMap } from './AbstractType.js'
|
||||
import { YEvent } from '../utils/YEvent.js'
|
||||
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
|
||||
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
|
||||
@@ -140,20 +140,14 @@ export class YArray extends AbstractType {
|
||||
* Returns an Array with the result of calling a provided function on every
|
||||
* element of this YArray.
|
||||
*
|
||||
* @template M
|
||||
* @template T,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) {
|
||||
/**
|
||||
* @type {Array<M>}
|
||||
*/
|
||||
const result = []
|
||||
this.forEach((c, i) => {
|
||||
result.push(f(c, i, this))
|
||||
})
|
||||
return result
|
||||
// @ts-ignore
|
||||
return typeArrayMap(this, f)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,36 +2,13 @@
|
||||
* @module types
|
||||
*/
|
||||
|
||||
import { AbstractType, typeMapDelete, typeMapSet, typeMapGet, typeMapHas } from './AbstractType.js'
|
||||
import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { AbstractType, typeMapDelete, typeMapSet, typeMapGet, typeMapHas, createMapIterator } from './AbstractType.js'
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { YEvent } from '../utils/YEvent.js'
|
||||
import * as decoding from 'lib0/decoding.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
|
||||
}
|
||||
}
|
||||
}
|
||||
import * as iterator from 'lib0/iterator.js'
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YMap.
|
||||
@@ -109,26 +86,18 @@ export class YMap extends AbstractType {
|
||||
/**
|
||||
* Returns the keys for each element in the YMap Type.
|
||||
*
|
||||
* @return {YMapIterator}
|
||||
* @return {Iterator<string>}
|
||||
*/
|
||||
keys () {
|
||||
const keys = []
|
||||
for (let [key, value] of this._map) {
|
||||
if (value.deleted) {
|
||||
keys.push(key)
|
||||
}
|
||||
}
|
||||
return new YMapIterator(keys)
|
||||
return iterator.iteratorMap(createMapIterator(this._map), v => v[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for each element in the YMap Type.
|
||||
*
|
||||
* @return {Iterator<string|number|ArrayBuffer|Object<string,any>|Array<any>>}
|
||||
*/
|
||||
entries () {
|
||||
const entries = []
|
||||
for (let [key, value] of this._map) {
|
||||
if (value.deleted) {
|
||||
entries.push([key, value.getContent()[0]])
|
||||
}
|
||||
}
|
||||
return new YMapIterator(entries)
|
||||
return iterator.iteratorMap(createMapIterator(this._map), v => v[1].getContent()[0])
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
|
||||
@@ -7,8 +7,12 @@ 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
|
||||
import { YArray } from './YArray.js'
|
||||
import { YXmlEvent } from './YXmlEvent.js'
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { YXmlText } from './YXmlText.js' // eslint-disable-line
|
||||
import { YXmlHook } from './YXmlHook.js' // eslint-disable-line
|
||||
import { AbstractType, typeArrayMap, typeArrayForEach, typeMapGet, typeMapGetAll } from './AbstractType.js'
|
||||
import { Snapshot } from '../utils/Snapshot.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Define the elements to which a set of CSS queries apply.
|
||||
@@ -42,16 +46,16 @@ import { YXmlEvent } from './YXmlEvent.js'
|
||||
export class YXmlTreeWalker {
|
||||
/**
|
||||
* @param {YXmlFragment | YXmlElement} root
|
||||
* @param {function} f
|
||||
* @param {function(AbstractType):boolean} f
|
||||
*/
|
||||
constructor (root, f) {
|
||||
this._filter = f || (() => true)
|
||||
this._root = root
|
||||
/**
|
||||
* @type {YXmlFragment | YXmlElement}
|
||||
* @type {ItemType | null}
|
||||
*/
|
||||
this._currentNode = root
|
||||
this._firstCall = true
|
||||
// @ts-ignore
|
||||
this._currentNode = root._start
|
||||
}
|
||||
[Symbol.iterator] () {
|
||||
return this
|
||||
@@ -59,45 +63,40 @@ export class YXmlTreeWalker {
|
||||
/**
|
||||
* Get the next node.
|
||||
*
|
||||
* @return {YXmlElement} The next node.
|
||||
* @return {IteratorResult<YXmlElement|YXmlText|YXmlHook>} The next node.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
next () {
|
||||
let n = this._currentNode
|
||||
if (this._firstCall) {
|
||||
this._firstCall = false
|
||||
if (!n._deleted && this._filter(n)) {
|
||||
return { value: n, done: false }
|
||||
}
|
||||
if (n === null) {
|
||||
// @ts-ignore return undefined if done=true (the expected result)
|
||||
return { value: undefined, done: true }
|
||||
}
|
||||
const nextValue = n
|
||||
do {
|
||||
if (!n._deleted && (n.constructor === YXmlElement || n.constructor === YXmlFragment) && n._start !== null) {
|
||||
if (!n.deleted && (n.constructor === YXmlElement || n.constructor === YXmlFragment) && n.type._start !== null) {
|
||||
// walk down in the tree
|
||||
n = n._start
|
||||
// @ts-ignore
|
||||
n = n.type._start
|
||||
} else {
|
||||
// walk right or up in the tree
|
||||
while (n !== this._root) {
|
||||
if (n._right !== null) {
|
||||
n = n._right
|
||||
while (n !== null) {
|
||||
if (n.right !== null) {
|
||||
// @ts-ignore
|
||||
n = n.right
|
||||
break
|
||||
} else if (n.parent === this._root) {
|
||||
n = null
|
||||
} else {
|
||||
n = n.parent._item
|
||||
}
|
||||
n = n._parent
|
||||
}
|
||||
if (n === this._root) {
|
||||
n = null
|
||||
}
|
||||
}
|
||||
if (n === this._root) {
|
||||
break
|
||||
}
|
||||
} while (n !== null && (n._deleted || !this._filter(n)))
|
||||
} while (n !== null && (n.deleted || !this._filter(n.type)))
|
||||
this._currentNode = n
|
||||
if (n === null) {
|
||||
return { done: true }
|
||||
} else {
|
||||
return { value: n, done: false }
|
||||
}
|
||||
// @ts-ignore
|
||||
return { value: nextValue.type, done: false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +108,7 @@ export class YXmlTreeWalker {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class YXmlFragment extends YArray {
|
||||
export class YXmlFragment extends AbstractType {
|
||||
/**
|
||||
* Create a subtree of childNodes.
|
||||
*
|
||||
@@ -120,7 +119,7 @@ export class YXmlFragment extends YArray {
|
||||
* nop(node)
|
||||
* }
|
||||
*
|
||||
* @param {Function} filter Function that is called on each child element and
|
||||
* @param {function(AbstractType):boolean} filter Function that is called on each child element and
|
||||
* returns a Boolean indicating whether the child
|
||||
* is to be included in the subtree.
|
||||
* @return {YXmlTreeWalker} A subtree and a position within it.
|
||||
@@ -142,12 +141,13 @@ export class YXmlFragment extends YArray {
|
||||
* - attribute
|
||||
*
|
||||
* @param {CSS_Selector} query The query on the children.
|
||||
* @return {YXmlElement} The first element that matches the query or null.
|
||||
* @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
querySelector (query) {
|
||||
query = query.toUpperCase()
|
||||
// @ts-ignore
|
||||
const iterator = new YXmlTreeWalker(this, element => element.nodeName === query)
|
||||
const next = iterator.next()
|
||||
if (next.done) {
|
||||
@@ -164,12 +164,13 @@ export class YXmlFragment extends YArray {
|
||||
* TODO: Does not yet support all queries. Currently only query by tagName.
|
||||
*
|
||||
* @param {CSS_Selector} query The query on the children
|
||||
* @return {Array<YXmlElement>} The elements that match this query.
|
||||
* @return {Array<YXmlElement|YXmlText|YXmlHook|null>} The elements that match this query.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
querySelectorAll (query) {
|
||||
query = query.toUpperCase()
|
||||
// @ts-ignore
|
||||
return Array.from(new YXmlTreeWalker(this, element => element.nodeName === query))
|
||||
}
|
||||
|
||||
@@ -194,7 +195,7 @@ export class YXmlFragment extends YArray {
|
||||
* @return {string} The string representation of all children.
|
||||
*/
|
||||
toDomString () {
|
||||
return this.map(xml => xml.toDomString()).join('')
|
||||
return typeArrayMap(this, xml => xml.toDomString()).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,10 +206,10 @@ export class YXmlFragment extends YArray {
|
||||
* 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 {DocumentFragment} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -217,20 +218,11 @@ export class YXmlFragment extends YArray {
|
||||
if (binding !== undefined) {
|
||||
binding._createAssociation(fragment, this)
|
||||
}
|
||||
this.forEach(xmlType => {
|
||||
typeArrayForEach(this, xmlType => {
|
||||
fragment.insertBefore(xmlType.toDom(_document, hooks, binding), null)
|
||||
})
|
||||
return fragment
|
||||
}
|
||||
/**
|
||||
* Transform this YXml Type to a readable format.
|
||||
* Useful for logging as all Items and Delete implement this method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_logString () {
|
||||
return logItemHelper('YXml', this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,27 +241,11 @@ export class YXmlElement extends YXmlFragment {
|
||||
/**
|
||||
* Creates an Item with the same effect as this Item (without position effect)
|
||||
*
|
||||
* @return {YXmlElement}
|
||||
* @private
|
||||
*/
|
||||
_copy () {
|
||||
let struct = super._copy()
|
||||
struct.nodeName = this.nodeName
|
||||
return struct
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @private
|
||||
* @param {Y} y The Yjs instance that this Item belongs to.
|
||||
* @param {decoding.Decoder} decoder The decoder object to read data from.
|
||||
*/
|
||||
_fromBinary (y, decoder) {
|
||||
const missing = super._fromBinary(y, decoder)
|
||||
this.nodeName = decoding.readVarString(decoder)
|
||||
return missing
|
||||
return new YXmlElement(this.nodeName)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,31 +257,10 @@ export class YXmlElement extends YXmlFragment {
|
||||
* @private
|
||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||
*/
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
_write (encoder) {
|
||||
encoding.writeVarString(encoder, this.nodeName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrates this Item into the shared structure.
|
||||
*
|
||||
* This method actually applies the change to the Yjs instance. In case of
|
||||
* Item it connects _left and _right to this Item and calls the
|
||||
* {@link Item#beforeChange} method.
|
||||
*
|
||||
* * Checks for nodeName
|
||||
* * Sets domFilter
|
||||
*
|
||||
* @private
|
||||
* @param {Transaction} transaction The Yjs instance
|
||||
*/
|
||||
_integrate (transaction) {
|
||||
if (this.nodeName === null) {
|
||||
throw new Error('nodeName must be defined!')
|
||||
}
|
||||
super._integrate(transaction)
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.toDomString()
|
||||
}
|
||||
@@ -365,37 +320,25 @@ export class YXmlElement extends YXmlFragment {
|
||||
*
|
||||
* @param {String} attributeName The attribute name that identifies the
|
||||
* queried value.
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
* @return {String} The queried attribute value.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getAttribute (attributeName, snapshot) {
|
||||
return YMap.prototype.get.call(this, attributeName, snapshot)
|
||||
getAttribute (attributeName) {
|
||||
// @ts-ignore
|
||||
return typeMapGet(this, attributeName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all attribute name/value pairs in a JSON Object.
|
||||
*
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
* @param {Snapshot} [snapshot]
|
||||
* @return {Object} A JSON Object that describes the attributes.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getAttributes (snapshot) {
|
||||
const obj = {}
|
||||
if (snapshot === undefined) {
|
||||
for (let [key, value] of this._map) {
|
||||
if (!value._deleted) {
|
||||
obj[key] = value._content[0]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
YMap.prototype.keys.call(this, snapshot).forEach(key => {
|
||||
obj[key] = YMap.prototype.get.call(this, key, snapshot)
|
||||
})
|
||||
}
|
||||
return obj
|
||||
return typeMapGetAll(this)
|
||||
}
|
||||
// TODO: outsource the binding property.
|
||||
/**
|
||||
@@ -406,10 +349,10 @@ export class YXmlElement extends YXmlFragment {
|
||||
* 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}
|
||||
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -419,7 +362,7 @@ export class YXmlElement extends YXmlFragment {
|
||||
for (let key in attrs) {
|
||||
dom.setAttribute(key, attrs[key])
|
||||
}
|
||||
this.forEach(yxml => {
|
||||
typeArrayForEach(this, yxml => {
|
||||
dom.appendChild(yxml.toDom(_document, hooks, binding))
|
||||
})
|
||||
if (binding !== undefined) {
|
||||
@@ -429,5 +372,13 @@ export class YXmlElement extends YXmlFragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {decoding.Decoder} decoder
|
||||
* @return {YXmlElement}
|
||||
*/
|
||||
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))
|
||||
export const readYXmlFragment = decoder => new YXmlFragment()
|
||||
/**
|
||||
* @param {decoding.Decoder} decoder
|
||||
* @return {YXmlFragment}
|
||||
*/
|
||||
export const readYXmlFragment = decoder => new YXmlFragment()
|
||||
|
||||
@@ -17,11 +17,10 @@ export class YXmlEvent extends YEvent {
|
||||
* @param {AbstractType} target The target on which the event is created.
|
||||
* @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
|
||||
* change was created.
|
||||
*/
|
||||
constructor (target, subs, remote, transaction) {
|
||||
constructor (target, subs, transaction) {
|
||||
super(target)
|
||||
/**
|
||||
* The transaction instance for the computed change.
|
||||
@@ -38,11 +37,6 @@ export class YXmlEvent extends YEvent {
|
||||
* @type {Set<string|null>}
|
||||
*/
|
||||
this.attributesChanged = new Set()
|
||||
/**
|
||||
* Whether this change was created by a remote peer.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.remote = remote
|
||||
subs.forEach((sub) => {
|
||||
if (sub === null) {
|
||||
this.childListChanged = true
|
||||
|
||||
Reference in New Issue
Block a user