import { YXmlFragment, transact, typeMapDelete, typeMapSet, typeMapGet, typeMapGetAll, typeListForEach, YXmlElementRefID, Snapshot, Doc, Item // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' import * as decoding from 'lib0/decoding.js' /** * An YXmlElement imitates the behavior of a * {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}. * * * An YXmlElement has attributes (key value pairs) * * An YXmlElement has childElements that must inherit from YXmlElement */ export class YXmlElement extends YXmlFragment { constructor (nodeName = 'UNDEFINED') { super() this.nodeName = nodeName /** * @type {Map|null} * @private */ this._prelimAttrs = new Map() } /** * 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 {Doc} y The Yjs instance * @param {Item} item * @private */ _integrate (y, item) { super._integrate(y, item) ;(/** @type {Map} */ (this._prelimAttrs)).forEach((value, key) => { this.setAttribute(key, value) }) this._prelimAttrs = null } /** * Creates an Item with the same effect as this Item (without position effect) * * @return {YXmlElement} * @private */ _copy () { return new YXmlElement(this.nodeName) } /** * Returns the XML serialization 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() const stringBuilder = [] const keys = [] for (let key in attrs) { keys.push(key) } keys.sort() const keysLen = keys.length for (let i = 0; i < keysLen; i++) { const key = keys[i] stringBuilder.push(key + '="' + attrs[key] + '"') } const nodeName = this.nodeName.toLocaleLowerCase() const attrsString = stringBuilder.length > 0 ? ' ' + stringBuilder.join(' ') : '' return `<${nodeName}${attrsString}>${super.toString()}` } /** * Removes an attribute from this YXmlElement. * * @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 {Map} */ (this._prelimAttrs).delete(attributeName) } } /** * Sets or updates an attribute. * * @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) { if (this.doc !== null) { transact(this.doc, transaction => { typeMapSet(transaction, this, attributeName, attributeValue) }) } else { /** @type {Map} */ (this._prelimAttrs).set(attributeName, attributeValue) } } /** * Returns an attribute value that belongs to the attribute name. * * @param {String} attributeName The attribute name that identifies the * queried value. * @return {String} The queried attribute value. * * @public */ getAttribute (attributeName) { return /** @type {any} */ (typeMapGet(this, attributeName)) } /** * Returns all attribute name/value pairs in a JSON Object. * * @param {Snapshot} [snapshot] * @return {Object} A JSON Object that describes the attributes. * * @public */ getAttributes (snapshot) { return typeMapGetAll(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 {Object} [hooks={}] Optional property to customize how hooks * are presented in the DOM * @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 {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} * * @public */ toDOM (_document = document, hooks = {}, binding) { const dom = _document.createElement(this.nodeName) let attrs = this.getAttributes() for (let key in attrs) { dom.setAttribute(key, attrs[key]) } typeListForEach(this, yxml => { dom.appendChild(yxml.toDOM(_document, hooks, binding)) }) if (binding !== undefined) { binding._createAssociation(dom, this) } return dom } /** * 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. * * @private * @param {encoding.Encoder} encoder The encoder to write data to. */ _write (encoder) { encoding.writeVarUint(encoder, YXmlElementRefID) encoding.writeVarString(encoder, this.nodeName) } } /** * @param {decoding.Decoder} decoder * @return {YXmlElement} * * @private * @function */ export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))