From 53f2344017d168bfeb5763f1a2c0300f98a04e96 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 8 Nov 2020 01:51:39 +0100 Subject: [PATCH] implement .clone, .slice, and yxml.get --- README.md | 14 +++++++++++ src/types/AbstractType.js | 44 +++++++++++++++++++++++++++++++++ src/types/YArray.js | 23 ++++++++++++++++++ src/types/YMap.js | 11 +++++++++ src/types/YText.js | 9 +++++++ src/types/YXmlElement.js | 16 +++++++++++- src/types/YXmlFragment.js | 51 +++++++++++++++++++++++++++++++++++++++ src/types/YXmlHook.js | 11 +++++++++ src/types/YXmlText.js | 9 +++++++ tests/y-array.tests.js | 15 ++++++++++++ 10 files changed, 202 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2dc629a..f4f8c5db 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,8 @@ position 0.
get(index:number)
+ slice(start:number, end:number):Array<Object|boolean|Array|string|number|Uint8Array|Y.Type> +
Retrieve a range of content
length:number
@@ -320,6 +322,8 @@ or any of its children.
get(index:number)
+ clone():Y.Map +
Clone this type into a fresh Yjs type.
toJSON():Object<string, Object|boolean|Array|string|number|Uint8Array>
Copies the [key,value] pairs of this YMap to a new Object.It @@ -451,8 +455,12 @@ or any of its children.
get(index:number)
+ slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText> +
Retrieve a range of content
length:number
+ clone():Y.XmlFragment +
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
toDOM():DocumentFragment @@ -512,6 +520,12 @@ content and be actually XML compliant.
getAttributes(attributeName:string):Object<string,string>
+ get(i:number):Y.XmlElement|Y.XmlText +
Retrieve the i-th element.
+ slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText> +
Retrieve a range of content
+ clone():Y.XmlElement +
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
toDOM():Element diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index b628b902..c3513d68 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -309,6 +309,13 @@ export class AbstractType { throw error.methodUnimplemented() } + /** + * @return {AbstractType} + */ + clone () { + throw error.methodUnimplemented() + } + /** * @param {AbstractUpdateEncoder} encoder */ @@ -381,6 +388,43 @@ export class AbstractType { toJSON () {} } +/** + * @param {AbstractType} type + * @param {number} start + * @param {number} end + * @return {Array} + * + * @private + * @function + */ +export const typeListSlice = (type, start, end) => { + if (start < 0) { + start = type._length + start + } + if (end < 0) { + end = type._length + end + } + let len = end - start + const cs = [] + let n = type._start + while (n !== null && len > 0) { + if (n.countable && !n.deleted) { + const c = n.content.getContent() + if (c.length <= start) { + start -= c.length + } else { + for (let i = start; i < c.length && len > 0; i++) { + cs.push(c[i]) + len-- + } + start = 0 + } + } + n = n.right + } + return cs +} + /** * @param {AbstractType} type * @return {Array} diff --git a/src/types/YArray.js b/src/types/YArray.js index b0f78fdb..e5991750 100644 --- a/src/types/YArray.js +++ b/src/types/YArray.js @@ -17,6 +17,7 @@ import { transact, ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' +import { typeListSlice } from './AbstractType.js' /** * Event that describes the changes on a YArray @@ -73,6 +74,17 @@ export class YArray extends AbstractType { return new YArray() } + /** + * @return {YArray} + */ + clone () { + const arr = new YArray() + arr.insert(0, this.toArray().map(el => + el instanceof AbstractType ? el.clone() : el + )) + return arr + } + get length () { return this._prelimContent === null ? this._length : this._prelimContent.length } @@ -167,6 +179,17 @@ export class YArray extends AbstractType { return typeListToArray(this) } + /** + * Transforms this YArray to a JavaScript Array. + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice (start = 0, end = this.length) { + return typeListSlice(this, start, end) + } + /** * Transforms this Shared Type to a JSON object. * diff --git a/src/types/YMap.js b/src/types/YMap.js index a0cb9733..efaa16f5 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -84,6 +84,17 @@ export class YMap extends AbstractType { return new YMap() } + /** + * @return {YMap} + */ + clone () { + const map = new YMap() + this.forEach((value, key) => { + map.set(key, value instanceof AbstractType ? value.clone() : value) + }) + return map + } + /** * Creates YMapEvent and calls observers. * diff --git a/src/types/YText.js b/src/types/YText.js index 6430a1f4..2c129385 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -762,6 +762,15 @@ export class YText extends AbstractType { return new YText() } + /** + * @return {YText} + */ + clone () { + const text = new YText() + text.applyDelta(this.toDelta()) + return text + } + /** * Creates YTextEvent and calls observers. * diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index 6d91da15..0757e1b2 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -8,7 +8,7 @@ import { typeMapGetAll, typeListForEach, YXmlElementRefID, - AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line + AbstractType, AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line } from '../internals.js' /** @@ -55,6 +55,20 @@ export class YXmlElement extends YXmlFragment { return new YXmlElement(this.nodeName) } + /** + * @return {YXmlElement} + */ + clone () { + const el = new YXmlElement(this.nodeName) + const attrs = this.getAttributes() + for (const key in attrs) { + el.setAttribute(key, attrs[key]) + } + // @ts-ignore + el.insert(0, el.toArray().map(item => item instanceof AbstractType ? item.clone() : item)) + return el + } + /** * Returns the XML serialization of this YXmlElement. * The attributes are ordered by attribute-name, so you can easily use this diff --git a/src/types/YXmlFragment.js b/src/types/YXmlFragment.js index 4fcf09e7..711c9687 100644 --- a/src/types/YXmlFragment.js +++ b/src/types/YXmlFragment.js @@ -14,6 +14,8 @@ import { YXmlFragmentRefID, callTypeObservers, transact, + typeListGet, + typeListSlice, AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line } from '../internals.js' @@ -148,6 +150,16 @@ export class YXmlFragment extends AbstractType { return new YXmlFragment() } + /** + * @return {YXmlFragment} + */ + clone () { + const el = new YXmlFragment() + // @ts-ignore + el.insert(0, el.toArray().map(item => item instanceof AbstractType ? item.clone() : item)) + return el + } + get length () { return this._prelimContent === null ? this._length : this._prelimContent.length } @@ -316,6 +328,45 @@ export class YXmlFragment extends AbstractType { return typeListToArray(this) } + /** + * Appends content to this YArray. + * + * @param {Array} content Array of content to append. + */ + push (content) { + this.insert(this.length, content) + } + + /** + * Preppends content to this YArray. + * + * @param {Array} content Array of content to preppend. + */ + unshift (content) { + this.insert(0, content) + } + + /** + * Returns the i-th element from a YArray. + * + * @param {number} index The index of the element to return from the YArray + * @return {YXmlElement|YXmlText} + */ + get (index) { + return typeListGet(this, index) + } + + /** + * Transforms this YArray to a JavaScript Array. + * + * @param {number} [start] + * @param {number} [end] + * @return {Array} + */ + slice (start = 0, end = this.length) { + return typeListSlice(this, start, end) + } + /** * Transform the properties of this type to binary and write it to an * BinaryEncoder. diff --git a/src/types/YXmlHook.js b/src/types/YXmlHook.js index 51a98366..e28f70a3 100644 --- a/src/types/YXmlHook.js +++ b/src/types/YXmlHook.js @@ -29,6 +29,17 @@ export class YXmlHook extends YMap { return new YXmlHook(this.hookName) } + /** + * @return {YXmlHook} + */ + clone () { + const el = new YXmlHook(this.hookName) + this.forEach((value, key) => { + el.set(key, value) + }) + return el + } + /** * Creates a Dom Element that mirrors this YXmlElement. * diff --git a/src/types/YXmlText.js b/src/types/YXmlText.js index cada1585..697e31c9 100644 --- a/src/types/YXmlText.js +++ b/src/types/YXmlText.js @@ -14,6 +14,15 @@ export class YXmlText extends YText { return new YXmlText() } + /** + * @return {YXmlText} + */ + clone () { + const text = new YXmlText() + text.applyDelta(this.toDelta()) + return text + } + /** * Creates a Dom Element that mirrors this YXmlText. * diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 5b0827c2..45022ed6 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -17,6 +17,21 @@ export const testBasicUpdate = tc => { t.compare(doc2.getArray('array').toArray(), ['hi']) } +/** + * @param {t.TestCase} tc + */ +export const testSlice = tc => { + const doc1 = new Y.Doc() + const arr = doc1.getArray('array') + arr.insert(0, [1, 2, 3]) + t.compareArrays(arr.slice(0), [1, 2, 3]) + t.compareArrays(arr.slice(1), [2, 3]) + t.compareArrays(arr.slice(0, -1), [1, 2]) + arr.insert(0, [0]) + t.compareArrays(arr.slice(0), [0, 1, 2, 3]) + t.compareArrays(arr.slice(0, 2), [0, 1]) +} + /** * @param {t.TestCase} tc */