From 0aca7bbefaa6bc7e9d48a492c3d48acfa9873e10 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 13 Nov 2020 12:40:53 +0100 Subject: [PATCH] implement attributes on Y.Text --- src/types/YText.js | 95 +++++++++++++++++++++++++++++++++++++++++- src/types/YXmlEvent.js | 6 +-- tests/y-xml.tests.js | 14 +++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/types/YText.js b/src/types/YText.js index 2c129385..a12d0747 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -21,6 +21,10 @@ import { iterateDeletedStructs, iterateStructs, findMarker, + typeMapDelete, + typeMapSet, + typeMapGet, + typeMapGetAll, updateMarkerChanges, ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line } from '../internals.js' @@ -512,13 +516,32 @@ export class YTextEvent extends YEvent { /** * @param {YText} ytext * @param {Transaction} transaction + * @param {Set} subs The keys that changed */ - constructor (ytext, transaction) { + constructor (ytext, transaction, subs) { super(ytext, transaction) /** * @type {Array|null} */ this._delta = null + /** + * Whether the children changed. + * @type {Boolean} + * @private + */ + this.childListChanged = false + /** + * Set of all changed attributes. + * @type {Set} + */ + this.keysChanged = new Set() + subs.forEach((sub) => { + if (sub === null) { + this.childListChanged = true + } else { + this.keysChanged.add(sub) + } + }) } /** @@ -779,7 +802,7 @@ export class YText extends AbstractType { */ _callObserver (transaction, parentSubs) { super._callObserver(transaction, parentSubs) - const event = new YTextEvent(this, transaction) + const event = new YTextEvent(this, transaction, parentSubs) const doc = transaction.doc // If a remote change happened, we try to cleanup potential formatting duplicates. if (!transaction.local) { @@ -1111,6 +1134,74 @@ export class YText extends AbstractType { } } + /** + * Removes an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be removed. + * + * @public + */ + removeAttribute (attributeName) { + if (this.doc !== null) { + transact(this.doc, transaction => { + typeMapDelete(transaction, this, attributeName) + }) + } else { + /** @type {Array} */ (this._pending).push(() => this.removeAttribute(attributeName)) + } + } + + /** + * Sets or updates an attribute. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that is to be set. + * @param {any} attributeValue The attribute value that is to be set. + * + * @public + */ + setAttribute (attributeName, attributeValue) { + if (this.doc !== null) { + transact(this.doc, transaction => { + typeMapSet(transaction, this, attributeName, attributeValue) + }) + } else { + /** @type {Array} */ (this._pending).push(() => this.setAttribute(attributeName, attributeValue)) + } + } + + /** + * Returns an attribute value that belongs to the attribute name. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {String} attributeName The attribute name that identifies the + * queried value. + * @return {any} The queried attribute value. + * + * @public + */ + getAttribute (attributeName) { + return /** @type {any} */ (typeMapGet(this, attributeName)) + } + + /** + * Returns all attribute name/value pairs in a JSON Object. + * + * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks. + * + * @param {Snapshot} [snapshot] + * @return {Object} A JSON Object that describes the attributes. + * + * @public + */ + getAttributes (snapshot) { + return typeMapGetAll(this) + } + /** * @param {AbstractUpdateEncoder} encoder */ diff --git a/src/types/YXmlEvent.js b/src/types/YXmlEvent.js index c41ba3d4..535e55d6 100644 --- a/src/types/YXmlEvent.js +++ b/src/types/YXmlEvent.js @@ -1,7 +1,7 @@ import { YEvent, - YXmlElement, YXmlFragment, Transaction // eslint-disable-line + YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line } from '../internals.js' /** @@ -9,7 +9,7 @@ import { */ export class YXmlEvent extends YEvent { /** - * @param {YXmlElement|YXmlFragment} target The target on which the event is created. + * @param {YXmlElement|YXmlText|YXmlFragment} 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 {Transaction} transaction The transaction instance with wich the @@ -25,7 +25,7 @@ export class YXmlEvent extends YEvent { this.childListChanged = false /** * Set of all changed attributes. - * @type {Set} + * @type {Set} */ this.attributesChanged = new Set() subs.forEach((sub) => { diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index 8c2023fc..d59ee6c8 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -73,3 +73,17 @@ export const testTreewalker = tc => { t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1') compare(users) } + +/** + * @param {t.TestCase} tc + */ +export const testYtextAttributes = tc => { + const ydoc = new Y.Doc() + const ytext = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText)) + ytext.observe(event => { + t.compare(event.changes.keys.get('test'), { action: 'add', oldValue: undefined }) + }) + ytext.setAttribute('test', 42) + t.compare(ytext.getAttribute('test'), 42) + t.compare(ytext.getAttributes(), { test: 42 }) +}