204 lines
5.6 KiB
JavaScript
204 lines
5.6 KiB
JavaScript
|
|
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<string, any>|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<string, any>} */ (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()}</${nodeName}>`
|
|
}
|
|
|
|
/**
|
|
* 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<string,any>} */ (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<string, any>} */ (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<string, any>} [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))
|