From 135c6d31bea790e7f8e7066779113fdb604cf935 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 29 Mar 2018 11:58:02 +0200 Subject: [PATCH] documentation and fix tests --- src/Bindings/DomBinding/DomBinding.js | 6 +-- src/Bindings/DomBinding/util.js | 1 - src/MessageHandler/messageToString.js | 17 ++++++ src/Struct/Delete.js | 5 +- src/Struct/Item.js | 76 ++++++++++++++++++++++----- src/Struct/ItemEmbed.js | 12 +++-- src/Struct/ItemFormat.js | 12 +++-- src/Struct/ItemJSON.js | 12 +++-- src/Struct/ItemString.js | 12 +++-- src/Struct/Type.js | 8 +-- src/Types/YArray/YArray.js | 11 ++-- src/Types/YMap/YMap.js | 11 ++-- src/Types/YText/YText.js | 72 +++++++++++++++++++++---- src/Types/YXml/YXmlElement.js | 32 +++++++++-- src/Types/YXml/YXmlEvent.js | 26 +++++++++ src/Types/YXml/YXmlFragment.js | 73 ++++++++++++++++--------- src/Types/YXml/YXmlHook.js | 30 ++++++++--- src/Types/YXml/YXmlText.js | 16 ++++-- src/Types/YXml/YXmlTreeWalker.js | 4 ++ test/index.html | 2 +- test/y-xml.tests.js | 17 +++--- tests-lib/test-connector.js | 1 - 22 files changed, 341 insertions(+), 115 deletions(-) diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js index 0694b66a..a2d10bf1 100644 --- a/src/Bindings/DomBinding/DomBinding.js +++ b/src/Bindings/DomBinding/DomBinding.js @@ -1,15 +1,11 @@ /* global MutationObserver */ import Binding from '../Binding.js' -import diff from '../../Util/simpleDiff.js' -import YXmlFragment from '../../Types/YXml/YXmlFragment.js' -import YXmlHook from '../../Types/YXml/YXmlHook.js' -import { removeDomChildrenUntilElementFound, createAssociation } from './util.js' +import { createAssociation, removeAssociation } from './util.js' import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js' import { defaultFilter, applyFilterOnType } from './filter.js' import typeObserver from './typeObserver.js' import domObserver from './domObserver.js' -import { removeAssociation } from './util.js' /** * A binding that binds the children of a YXmlFragment to a DOM element. diff --git a/src/Bindings/DomBinding/util.js b/src/Bindings/DomBinding/util.js index 5396766b..259a4f31 100644 --- a/src/Bindings/DomBinding/util.js +++ b/src/Bindings/DomBinding/util.js @@ -69,7 +69,6 @@ export function insertNodeHelper (yxml, prevExpectedNode, child, _document, bind } } - /** * Remove children until `elem` is found. * diff --git a/src/MessageHandler/messageToString.js b/src/MessageHandler/messageToString.js index 4c6e3b72..8b4c507f 100644 --- a/src/MessageHandler/messageToString.js +++ b/src/MessageHandler/messageToString.js @@ -46,3 +46,20 @@ export function logID (id) { throw new Error('This is not a valid ID!') } } + +/** + * Helper utility to convert an item to a readable format. + * + * @param {String} name The name of the item class (YText, ItemString, ..). + * @param {Item} 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. + * + * @private + */ +export function logItemHelper (name, item, append) { + const left = item._left !== null ? item._left._lastId : null + const origin = item._origin !== null ? item._origin._lastId : null + return `${name}(id:${logID(item._id)},start:${logID(item._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})` +} diff --git a/src/Struct/Delete.js b/src/Struct/Delete.js index 2747bbfd..16b0bd0d 100644 --- a/src/Struct/Delete.js +++ b/src/Struct/Delete.js @@ -111,9 +111,10 @@ export default class Delete { } /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * * @private - * Transform this Delete to a readable format. - * Useful for logging as all Items implement this method. */ _logString () { return `Delete - target: ${logID(this._targetID)}, len: ${this._length}` diff --git a/src/Struct/Item.js b/src/Struct/Item.js index f7487f70..79f97f3c 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -48,33 +48,76 @@ export function splitHelper (y, a, b, diff) { } /** - * @private * Abstract class that represents any content. */ export default class Item { constructor () { + /** + * The uniqe identifier of this type. + * @type {ID} + */ this._id = null + /** + * The item that was originally to the left of this item. + * @type {Item} + */ this._origin = null + /** + * The item that is currently to the left of this item. + * @type {Item} + */ this._left = null + /** + * The item that is currently to the right of this item. + * @type {Item} + */ this._right = null + /** + * The item that was originally to the right of this item. + * @type {Item} + */ this._right_origin = null + /** + * The parent type. + * @type {Y|YType} + */ this._parent = 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 + * to insert this item. If `parentSub = null` type._start is the list in + * which to insert to. Otherwise it is `parent._start`. + * @type {String} + */ this._parentSub = null + /** + * Whether this item was deleted or not. + * @type {Boolean} + */ this._deleted = false + /** + * If this type's effect is reundone this type refers to the type that undid + * this operation. + * @type {Item} + */ this._redone = null } /** - * @private * Creates an Item with the same effect as this Item (without position effect) + * + * @private */ _copy () { return new this.constructor() } /** - * @private * Redoes the effect of this operation. + * + * @param {Y} y The Yjs instance. + * + * @private */ _redo (y) { if (this._redone !== null) { @@ -117,35 +160,37 @@ export default class Item { } /** - * @private * Computes the last content address of this Item. + * + * @private */ get _lastId () { return new ID(this._id.user, this._id.clock + this._length - 1) } /** - * @private * Computes the length of this Item. + * + * @private */ get _length () { return 1 } /** - * @private * Should return false if this Item is some kind of meta information * (e.g. format information). * * * Whether this Item should be addressable via `yarray.get(i)` * * Whether this Item should be counted when computing yarray.length + * + * @private */ get _countable () { return true } /** - * @private * Splits this Item so that another Items can be inserted in-between. * This must be overwritten if _length > 1 * Returns right part after split @@ -153,6 +198,8 @@ export default class Item { * * diff === length => this._right * * otherwise => split _content and return right part of split * (see {@link ItemJSON}/{@link ItemString} for implementation) + * + * @private */ _splitAt (y, diff) { if (diff === 0) { @@ -162,12 +209,13 @@ export default class Item { } /** - * @private * Mark this Item as deleted. * * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. + * + * @private */ _delete (y, createDelete = true) { if (!this._deleted) { @@ -189,16 +237,16 @@ export default class Item { } /** - * @private * This is called right before this Item receives any children. * It can be overwritten to apply pending changes before applying remote changes + * + * @private */ _beforeChange () { // nop } /** - * @private * Integrates this Item into the shared structure. * * This method actually applies the change to the Yjs instance. In case of @@ -208,6 +256,8 @@ export default class Item { * * Integrate the struct so that other types/structs can see it * * Add this struct to y.os * * Check if this is struct deleted + * + * @private */ _integrate (y) { y._transaction.newTypes.add(this) @@ -328,13 +378,14 @@ export default class Item { } /** - * @private * Transform the properties of this type to binary and write it to an * BinaryEncoder. * * This is called when this Item is sent to a remote peer. * * @param {BinaryEncoder} encoder The encoder to write data to. + * + * @private */ _toBinary (encoder) { encoder.writeUint8(getStructReference(this.constructor)) @@ -378,13 +429,14 @@ export default class Item { } /** - * @private * 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 {BinaryDecoder} decoder The decoder object to read data from. + * + * @private */ _fromBinary (y, decoder) { let missing = [] diff --git a/src/Struct/ItemEmbed.js b/src/Struct/ItemEmbed.js index 22c9fc2f..e4972e56 100644 --- a/src/Struct/ItemEmbed.js +++ b/src/Struct/ItemEmbed.js @@ -1,5 +1,5 @@ import { default as Item } from './Item.js' -import { logID } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../MessageHandler/messageToString.js' export default class ItemEmbed extends Item { constructor () { @@ -23,9 +23,13 @@ export default class ItemEmbed extends Item { super._toBinary(encoder) encoder.writeVarString(JSON.stringify(this.embed)) } + /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * + * @private + */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `ItemEmbed(id:${logID(this._id)},embed:${JSON.stringify(this.embed)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('ItemEmbed', this, `embed:${JSON.stringify(this.embed)}`) } } diff --git a/src/Struct/ItemFormat.js b/src/Struct/ItemFormat.js index b384406b..88094cfb 100644 --- a/src/Struct/ItemFormat.js +++ b/src/Struct/ItemFormat.js @@ -1,5 +1,5 @@ import { default as Item } from './Item.js' -import { logID } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../MessageHandler/messageToString.js' export default class ItemFormat extends Item { constructor () { @@ -30,9 +30,13 @@ export default class ItemFormat extends Item { encoder.writeVarString(this.key) encoder.writeVarString(JSON.stringify(this.value)) } + /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * + * @private + */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `ItemFormat(id:${logID(this._id)},key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('ItemFormat', this, `key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)}`) } } diff --git a/src/Struct/ItemJSON.js b/src/Struct/ItemJSON.js index f8d22dd4..177bdb2e 100644 --- a/src/Struct/ItemJSON.js +++ b/src/Struct/ItemJSON.js @@ -1,5 +1,5 @@ import { splitHelper, default as Item } from './Item.js' -import { logID } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../MessageHandler/messageToString.js' export default class ItemJSON extends Item { constructor () { @@ -45,10 +45,14 @@ export default class ItemJSON extends Item { encoder.writeVarString(encoded) } } + /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * + * @private + */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('ItemJSON', this, `content:${JSON.stringify(this._content)}`) } _splitAt (y, diff) { if (diff === 0) { diff --git a/src/Struct/ItemString.js b/src/Struct/ItemString.js index a5a0b90c..889b4f8e 100644 --- a/src/Struct/ItemString.js +++ b/src/Struct/ItemString.js @@ -1,5 +1,5 @@ import { splitHelper, default as Item } from './Item.js' -import { logID } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../MessageHandler/messageToString.js' export default class ItemString extends Item { constructor () { @@ -23,10 +23,14 @@ export default class ItemString extends Item { super._toBinary(encoder) encoder.writeVarString(this._content) } + /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * + * @private + */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('ItemString', this, `content:"${this._content}"`) } _splitAt (y, diff) { if (diff === 0) { diff --git a/src/Struct/Type.js b/src/Struct/Type.js index 692a6e28..cb98f374 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -62,22 +62,22 @@ export default class Type extends Item { } const path = [] const y = this._y - while (type._parent !== this && this._parent !== y) { + while (type !== this && type !== y) { let parent = type._parent if (type._parentSub !== null) { - path.push(type._parentSub) + path.unshift(type._parentSub) } else { // parent is array-ish for (let [i, child] of parent) { if (child === type) { - path.push(i) + path.unshift(i) break } } } type = parent } - if (this._parent !== this) { + if (type !== this) { throw new Error('The type is not a child of this node') } return path diff --git a/src/Types/YArray/YArray.js b/src/Types/YArray/YArray.js index a901a89b..27af984c 100644 --- a/src/Types/YArray/YArray.js +++ b/src/Types/YArray/YArray.js @@ -1,7 +1,7 @@ import Type from '../../Struct/Type.js' import ItemJSON from '../../Struct/ItemJSON.js' import ItemString from '../../Struct/ItemString.js' -import { logID } from '../../MessageHandler/messageToString.js' +import { logID, logItemHelper } from '../../MessageHandler/messageToString.js' import YEvent from '../../Util/YEvent.js' /** @@ -367,13 +367,12 @@ export default class YArray extends Type { } /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * * @private - * Transform this YArray to a readable format. - * Useful for logging as all Items implement this method. */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `YArray(id:${logID(this._id)},start:${logID(this._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('YArray', this, `start:${logID(this._start)}"`) } } diff --git a/src/Types/YMap/YMap.js b/src/Types/YMap/YMap.js index 4ce81e6c..b204d52f 100644 --- a/src/Types/YMap/YMap.js +++ b/src/Types/YMap/YMap.js @@ -1,7 +1,7 @@ import Type from '../../Struct/Type.js' import Item from '../../Struct/Item.js' import ItemJSON from '../../Struct/ItemJSON.js' -import { logID } from '../../MessageHandler/messageToString.js' +import { logItemHelper } from '../../MessageHandler/messageToString.js' import YEvent from '../../Util/YEvent.js' /** @@ -164,13 +164,12 @@ export default class YMap extends Type { } /** + * Transform this YXml Type to a readable format. + * Useful for logging as all Items and Delete implement this method. + * * @private - * Transform this YMap to a readable format. - * Useful for logging as all Items implement this method. */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `YMap(id:${logID(this._id)},mapSize:${this._map.size},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('YMap', this, `mapSize:${this._map.size}`) } } diff --git a/src/Types/YText/YText.js b/src/Types/YText/YText.js index b75931fb..8e1e9406 100644 --- a/src/Types/YText/YText.js +++ b/src/Types/YText/YText.js @@ -1,9 +1,12 @@ import ItemString from '../../Struct/ItemString.js' import ItemEmbed from '../../Struct/ItemEmbed.js' import ItemFormat from '../../Struct/ItemFormat.js' -import { logID } from '../../MessageHandler/messageToString.js' +import { logItemHelper } from '../../MessageHandler/messageToString.js' import { YArrayEvent, default as YArray } from '../YArray/YArray.js' +/** + * @private + */ function integrateItem (item, parent, y, left, right) { item._origin = left item._left = left @@ -19,6 +22,9 @@ function integrateItem (item, parent, y, left, right) { } } +/** + * @private + */ function findNextPosition (currentAttributes, parent, left, right, count) { while (right !== null && count > 0) { switch (right.constructor) { @@ -46,6 +52,9 @@ function findNextPosition (currentAttributes, parent, left, right, count) { return [left, right, currentAttributes] } +/** + * @private + */ function findPosition (parent, index) { let currentAttributes = new Map() let left = null @@ -53,7 +62,11 @@ function findPosition (parent, index) { return findNextPosition(currentAttributes, parent, left, right, index) } -// negate applied formats +/** + * Negate applied formats + * + * @private + */ function insertNegatedAttributes (y, parent, left, right, negatedAttributes) { // check if we really need to remove attributes while ( @@ -80,6 +93,9 @@ function insertNegatedAttributes (y, parent, left, right, negatedAttributes) { return [left, right] } +/** + * @private + */ function updateCurrentAttributes (currentAttributes, item) { const value = item.value const key = item.key @@ -90,6 +106,9 @@ function updateCurrentAttributes (currentAttributes, item) { } } +/** + * @private + */ function minimizeAttributeChanges (left, right, currentAttributes, attributes) { // go right while attributes[right.key] === right.value (or right is deleted) while (true) { @@ -109,6 +128,9 @@ function minimizeAttributeChanges (left, right, currentAttributes, attributes) { return [left, right] } +/** + * @private + */ function insertAttributes (y, parent, left, right, attributes, currentAttributes) { const negatedAttributes = new Map() // insert format-start items @@ -125,9 +147,12 @@ function insertAttributes (y, parent, left, right, attributes, currentAttributes left = format } } - return negatedAttributes + return [left, right, negatedAttributes] } +/** + * @private + */ function insertText (y, text, parent, left, right, currentAttributes, attributes) { for (let [key] of currentAttributes) { if (attributes.hasOwnProperty(key) === false) { @@ -135,7 +160,8 @@ function insertText (y, text, parent, left, right, currentAttributes, attributes } } [left, right] = minimizeAttributeChanges(left, right, currentAttributes, attributes) - const negatedAttributes = insertAttributes(y, parent, left, right, attributes, currentAttributes) + let negatedAttributes + [left, right, negatedAttributes] = insertAttributes(y, parent, left, right, attributes, currentAttributes) // insert content let item if (text.constructor === String) { @@ -150,9 +176,13 @@ function insertText (y, text, parent, left, right, currentAttributes, attributes return insertNegatedAttributes(y, parent, left, right, negatedAttributes) } +/** + * @private + */ function formatText (y, length, parent, left, right, currentAttributes, attributes) { [left, right] = minimizeAttributeChanges(left, right, currentAttributes, attributes) - const negatedAttributes = insertAttributes(y, parent, left, right, attributes, currentAttributes) + let negatedAttributes + [left, right, negatedAttributes] = insertAttributes(y, parent, left, right, attributes, currentAttributes) // iterate until first non-format or null is found // delete all formats with attributes[format.key] != null while (length > 0 && right !== null) { @@ -182,6 +212,9 @@ function formatText (y, length, parent, left, right, currentAttributes, attribut return insertNegatedAttributes(y, parent, left, right, negatedAttributes) } +/** + * @private + */ function deleteText (y, length, parent, left, right, currentAttributes) { while (length > 0 && right !== null) { if (right._deleted === false) { @@ -234,18 +267,23 @@ function deleteText (y, length, parent, left, right, currentAttributes) { /** * Event that describes the changes on a YText type. + * + * @private */ class YTextEvent extends YArrayEvent { constructor (ytext, remote, transaction) { super(ytext, remote, transaction) this._delta = null } - + // TODO: Should put this in a separate function. toDelta shouldn't be included + // in every Yjs distribution /** * Compute the changes in the delta format. * * @return {Delta} A {@link https://quilljs.com/docs/delta/|Quill Delta}) that * represents the changes on the document. + * + * @public */ get delta () { if (this._delta === null) { @@ -438,6 +476,8 @@ export default class YText extends YArray { /** * Returns the unformatted string representation of this YText type. + * + * @public */ toString () { let str = '' @@ -455,6 +495,8 @@ export default class YText extends YArray { * Apply a {@link Delta} on this shared YText type. * * @param {Delta} delta The changes to apply on this element. + * + * @public */ applyDelta (delta) { this._transact(y => { @@ -478,6 +520,8 @@ export default class YText extends YArray { * Returns the Delta representation of this YText type. * * @return {Delta} The Delta representation of this type. + * + * @public */ toDelta () { let ops = [] @@ -527,6 +571,8 @@ export default class YText extends YArray { * @param {TextAttributes} attributes Optionally define some formatting * information to apply on the inserted * Text. + * + * @public */ insert (index, text, attributes = {}) { if (text.length <= 0) { @@ -546,6 +592,7 @@ export default class YText extends YArray { * @param {TextAttributes} attributes Attribute information to apply on the * embed * + * @public */ insertEmbed (index, embed, attributes = {}) { if (embed.constructor !== Object) { @@ -562,6 +609,8 @@ export default class YText extends YArray { * * @param {Integer} index Index at which to start deleting. * @param {Integer} length The number of characters to remove. Defaults to 1. + * + * @public */ delete (index, length) { if (length === 0) { @@ -580,6 +629,8 @@ export default class YText extends YArray { * @param {Integer} length The amount of characters to assign properties to. * @param {TextAttributes} attributes Attribute information to apply on the * text. + * + * @public */ format (index, length, attributes) { this._transact(y => { @@ -590,15 +641,14 @@ export default class YText extends YArray { formatText(y, length, this, left, right, currentAttributes, attributes) }) } - + // TODO: De-duplicate code. The following code is in every type. /** - * @private * Transform this YText to a readable format. * Useful for logging as all Items implement this method. + * + * @private */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `YText(id:${logID(this._id)},start:${logID(this._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('YText', this) } } diff --git a/src/Types/YXml/YXmlElement.js b/src/Types/YXml/YXmlElement.js index 382396aa..0e5463e5 100644 --- a/src/Types/YXml/YXmlElement.js +++ b/src/Types/YXml/YXmlElement.js @@ -43,13 +43,14 @@ export default class YXmlElement extends YXmlFragment { } /** - * @private * Transform the properties of this type to binary and write it to an * BinaryEncoder. * * This is called when this Item is sent to a remote peer. * * @param {BinaryEncoder} encoder The encoder to write data to. + * + * @private */ _toBinary (encoder) { super._toBinary(encoder) @@ -57,7 +58,6 @@ export default class YXmlElement extends YXmlFragment { } /** - * @private * Integrates this Item into the shared structure. * * This method actually applies the change to the Yjs instance. In case of @@ -66,6 +66,10 @@ export default class YXmlElement extends YXmlFragment { * * * Checks for nodeName * * Sets domFilter + * + * @param {Y} y The Yjs instance + * + * @private */ _integrate (y) { if (this.nodeName === null) { @@ -78,6 +82,10 @@ export default class YXmlElement extends YXmlFragment { * Returns the string representation of this YXmlElement. * The attributes are ordered by attribute-name, so you can easily use this * method to compare YXmlElements + * + * @return {String} The string representation of this type. + * + * @public */ toString () { const attrs = this.getAttributes() @@ -101,6 +109,8 @@ export default class YXmlElement extends YXmlFragment { * Removes an attribute from this YXmlElement. * * @param {String} attributeName The attribute name that is to be removed. + * + * @public */ removeAttribute (attributeName) { return YMap.prototype.delete.call(this, attributeName) @@ -111,6 +121,8 @@ export default class YXmlElement extends YXmlFragment { * * @param {String} attributeName The attribute name that is to be set. * @param {String} attributeValue The attribute value that is to be set. + * + * @public */ setAttribute (attributeName, attributeValue) { return YMap.prototype.set.call(this, attributeName, attributeValue) @@ -121,7 +133,9 @@ export default class YXmlElement extends YXmlFragment { * * @param {String} attributeName The attribute name that identifies the * queried value. - * @return {String} The queried attribute value + * @return {String} The queried attribute value. + * + * @public */ getAttribute (attributeName) { return YMap.prototype.get.call(this, attributeName) @@ -131,6 +145,8 @@ export default class YXmlElement extends YXmlFragment { * Returns all attribute name/value pairs in a JSON Object. * * @return {Object} A JSON Object that describes the attributes. + * + * @public */ getAttributes () { const obj = {} @@ -141,11 +157,19 @@ export default class YXmlElement extends YXmlFragment { } return obj } - + // TODO: outsource the binding property. /** * Creates a Dom Element that mirrors this YXmlElement. * + * @param {Document} [_document=document] The document object (you must define + * this when calling this method in + * nodejs) + * @param {DomBinding} [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} + * + * @public */ toDom (_document = document, binding) { const dom = _document.createElement(this.nodeName) diff --git a/src/Types/YXml/YXmlEvent.js b/src/Types/YXml/YXmlEvent.js index 1fd884f4..49d8f680 100644 --- a/src/Types/YXml/YXmlEvent.js +++ b/src/Types/YXml/YXmlEvent.js @@ -2,13 +2,39 @@ import YEvent from '../../Util/YEvent.js' /** * An Event that describes changes on a YXml Element or Yxml Fragment + * + * @protected */ export default class YXmlEvent extends YEvent { + /** + * @param {YType} target The target on which the event is created. + * @param {Set} 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) { super(target) + /** + * The transaction instance for the computed change. + * @type {Transaction} + */ this._transaction = transaction + /** + * Whether the children changed. + * @type {Boolean} + */ this.childListChanged = false + /** + * Set of all changed attributes. + * @type {Set} + */ this.attributesChanged = new Set() + /** + * Whether this change was created by a remote peer. + * @type {Boolean} + */ this.remote = remote subs.forEach((sub) => { if (sub === null) { diff --git a/src/Types/YXml/YXmlFragment.js b/src/Types/YXml/YXmlFragment.js index 6661fdd7..babb1638 100644 --- a/src/Types/YXml/YXmlFragment.js +++ b/src/Types/YXml/YXmlFragment.js @@ -1,13 +1,18 @@ -/* global MutationObserver */ - import { createAssociation } from '../../Bindings/DomBinding/util.js' import YXmlTreeWalker from './YXmlTreeWalker.js' import YArray from '../YArray/YArray.js' import YXmlEvent from './YXmlEvent.js' -import { YXmlText, YXmlHook } from './YXml.js' -import { logID } from '../../MessageHandler/messageToString.js' -import diff from '../../Util/simpleDiff.js' +import { logItemHelper } from '../../MessageHandler/messageToString.js' + +/** + * 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. @@ -21,20 +26,31 @@ import diff from '../../Util/simpleDiff.js' * @typedef {string} CSS_Selector */ - /** - * Represents a list of {@link YXmlElement}. - * A YxmlFragment does not have a nodeName and it does not have attributes. - * Therefore it also must not be added as a childElement. + * Represents a list of {@link YXmlElement}.and {@link YXmlText} types. + * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a + * nodeName and it does not have attributes. Though it can be bound to a DOM + * element - in this case the attributes and the nodeName are not shared. + * + * @public */ export default class YXmlFragment extends YArray { /** * Create a subtree of childNodes. * + * @example + * const walker = elem.createTreeWalker(dom => dom.nodeName === 'div') + * for (let node in walker) { + * // `node` is a div node + * nop(node) + * } + * * @param {Function} 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 {TreeWalker} A subtree and a position within it. + * + * @public */ createTreeWalker (filter) { return new YXmlTreeWalker(this, filter) @@ -52,6 +68,8 @@ export default class YXmlFragment extends YArray { * * @param {CSS_Selector} query The query on the children. * @return {?YXmlElement} The first element that matches the query or null. + * + * @public */ querySelector (query) { query = query.toUpperCase() @@ -72,6 +90,8 @@ export default class YXmlFragment extends YArray { * * @param {CSS_Selector} query The query on the children * @return {Array} The elements that match this query. + * + * @public */ querySelectorAll (query) { query = query.toUpperCase() @@ -79,17 +99,9 @@ export default class YXmlFragment extends YArray { } /** - * 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. - */ - - /** - * @private * Creates YArray Event and calls observers. + * + * @private */ _callObserver (transaction, parentSubs, remote) { this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote, transaction)) @@ -111,13 +123,25 @@ export default class YXmlFragment extends YArray { * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. + * + * @private */ _delete (y, createDelete) { super._delete(y, createDelete) } /** - * @return {DocumentFragment} The dom representation of this + * Creates a Dom Element that mirrors this YXmlElement. + * + * @param {Document} [_document=document] The document object (you must define + * this when calling this method in + * nodejs) + * @param {DomBinding} [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} + * + * @public */ toDom (_document = document, binding) { const fragment = _document.createDocumentFragment() @@ -128,13 +152,12 @@ export default class YXmlFragment extends YArray { return fragment } /** - * @private * Transform this YXml Type to a readable format. - * Useful for logging as all Items implement this method. + * Useful for logging as all Items and Delete implement this method. + * + * @private */ _logString () { - const left = this._left !== null ? this._left._lastId : null - const origin = this._origin !== null ? this._origin._lastId : null - return `YXml(id:${logID(this._id)},left:${logID(left)},origin:${logID(origin)},right:${this._right},parent:${logID(this._parent)},parentSub:${this._parentSub})` + return logItemHelper('YXml', this) } } diff --git a/src/Types/YXml/YXmlHook.js b/src/Types/YXml/YXmlHook.js index f0fb0f52..d840ab11 100644 --- a/src/Types/YXml/YXmlHook.js +++ b/src/Types/YXml/YXmlHook.js @@ -4,21 +4,24 @@ import { getHook, addHook } from './hooks.js' /** * You can manage binding to a custom type with YXmlHook. * - * @param {String} hookName nodeName of the Dom Node. + * @public */ export default class YXmlHook extends YMap { + /** + * @param {String} hookName nodeName of the Dom Node. + */ constructor (hookName) { super() this.hookName = null if (hookName !== undefined) { this.hookName = hookName - dom._yjsHook = hookName } } /** - * @private * Creates an Item with the same effect as this Item (without position effect) + * + * @private */ _copy () { const struct = super._copy() @@ -27,9 +30,17 @@ export default class YXmlHook extends YMap { } /** - * Creates a DOM element that represents this YXmlHook. + * Creates a Dom Element that mirrors this YXmlElement. * - * @return Element The DOM representation of this Type. + * @param {Document} [_document=document] The document object (you must define + * this when calling this method in + * nodejs) + * @param {DomBinding} [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} + * + * @public */ toDom (_document = document) { const dom = getHook(this.hookName).createDom(this) @@ -38,13 +49,14 @@ export default class YXmlHook extends YMap { } /** - * @private * 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 {BinaryDecoder} decoder The decoder object to read data from. + * + * @private */ _fromBinary (y, decoder) { const missing = super._fromBinary(y, decoder) @@ -53,13 +65,14 @@ export default class YXmlHook extends YMap { } /** - * @private * Transform the properties of this type to binary and write it to an * BinaryEncoder. * * This is called when this Item is sent to a remote peer. * * @param {BinaryEncoder} encoder The encoder to write data to. + * + * @private */ _toBinary (encoder) { super._toBinary(encoder) @@ -67,7 +80,6 @@ export default class YXmlHook extends YMap { } /** - * @private * Integrate this type into the Yjs instance. * * * Save this struct in the os @@ -75,6 +87,8 @@ export default class YXmlHook extends YMap { * * Observer functions are fired * * @param {Y} y The Yjs instance + * + * @private */ _integrate (y) { if (this.hookName === null) { diff --git a/src/Types/YXml/YXmlText.js b/src/Types/YXml/YXmlText.js index 19013356..848fe985 100644 --- a/src/Types/YXml/YXmlText.js +++ b/src/Types/YXml/YXmlText.js @@ -8,11 +8,18 @@ import { createAssociation } from '../../Bindings/DomBinding/util.js' * @param {String} arg1 Initial value. */ export default class YXmlText extends YText { - /** - * Creates a TextNode with the same textual content. + * Creates a Dom Element that mirrors this YXmlText. * - * @return TextNode + * @param {Document} [_document=document] The document object (you must define + * this when calling this method in + * nodejs) + * @param {DomBinding} [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} + * + * @public */ toDom (_document = document, binding) { const dom = _document.createTextNode(this.toString()) @@ -21,12 +28,13 @@ export default class YXmlText extends YText { } /** - * @private * Mark this Item as deleted. * * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. + * + * @private */ _delete (y, createDelete) { super._delete(y, createDelete) diff --git a/src/Types/YXml/YXmlTreeWalker.js b/src/Types/YXml/YXmlTreeWalker.js index 84d43f02..1ee724e6 100644 --- a/src/Types/YXml/YXmlTreeWalker.js +++ b/src/Types/YXml/YXmlTreeWalker.js @@ -17,6 +17,8 @@ import YXmlFragment from './YXmlFragment.js' * position within them. * * Can be created with {@link YXmlFragment#createTreeWalker} + * + * @public */ export default class YXmlTreeWalker { constructor (root, f) { @@ -32,6 +34,8 @@ export default class YXmlTreeWalker { * Get the next node. * * @return {YXmlElement} The next node. + * + * @public */ next () { let n = this._currentNode diff --git a/test/index.html b/test/index.html index 3a0725fe..5f423d4b 100644 --- a/test/index.html +++ b/test/index.html @@ -3,6 +3,6 @@ - + diff --git a/test/y-xml.tests.js b/test/y-xml.tests.js index d168691c..6d77515c 100644 --- a/test/y-xml.tests.js +++ b/test/y-xml.tests.js @@ -14,7 +14,6 @@ test('events', async function xml1 (t) { var { users, xml0, xml1 } = await initArrays(t, { users: 2 }) var event var remoteEvent - let expectedEvent xml0.observe(function (e) { delete e._content delete e.nodes @@ -30,21 +29,21 @@ test('events', async function xml1 (t) { xml0.setAttribute('key', 'value') t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key') await flushAll(t, users) - t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key (remote)') + t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key (remote)') // check attributeRemoved xml0.removeAttribute('key') t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute') await flushAll(t, users) - t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute (remote)') + t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute (remote)') xml0.insert(0, [new Y.XmlText('some text')]) t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element') await flushAll(t, users) - t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element (remote)') + t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on inserted element (remote)') // test childRemoved xml0.delete(0) t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element') await flushAll(t, users) - t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)') + t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)') await compareUsers(t, users) }) @@ -180,7 +179,7 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { }) test('move element to a different position', async function xml13 (t) { - var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) + var { users, dom0, dom1 } = await initArrays(t, { users: 3 }) dom0.append(document.createElement('div')) dom0.append(document.createElement('h1')) await flushAll(t, users) @@ -193,7 +192,7 @@ test('move element to a different position', async function xml13 (t) { }) test('filter node', async function xml14 (t) { - var { users, xml0, xml1, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) + var { users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { if (nodeName === 'H1') { return null @@ -212,7 +211,7 @@ test('filter node', async function xml14 (t) { }) test('filter attribute', async function xml15 (t) { - var { users, xml0, xml1, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) + var { users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { attrs.delete('hidden') return attrs @@ -231,7 +230,7 @@ test('filter attribute', async function xml15 (t) { }) test('deep element insert', async function xml16 (t) { - var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) + var { users, dom0, dom1 } = await initArrays(t, { users: 3 }) let deepElement = document.createElement('p') let boldElement = document.createElement('b') let attrElement = document.createElement('img') diff --git a/tests-lib/test-connector.js b/tests-lib/test-connector.js index 8cb0fdb6..b83f2777 100644 --- a/tests-lib/test-connector.js +++ b/tests-lib/test-connector.js @@ -1,4 +1,3 @@ -/* global Y */ import { wait } from './helper' import { messageToString } from '../src/MessageHandler/messageToString' import AbstractConnector from '../src/Connector.js'