diff --git a/examples/html-editor-drawing-hook/index.js b/examples/html-editor-drawing-hook/index.js
index dca6a6d3..7da324bc 100644
--- a/examples/html-editor-drawing-hook/index.js
+++ b/examples/html-editor-drawing-hook/index.js
@@ -1,7 +1,7 @@
/* global Y, d3 */
const hooks = {
- "magic-drawing": {
+ 'magic-drawing': {
fillType: function (dom, type) {
initDrawingBindings(type, dom)
},
@@ -19,7 +19,7 @@ window.onload = function () {
window.addMagicDrawing = function addMagicDrawing () {
let mt = document.createElement('magic-drawing')
- mt.dataset.yjsHook = 'magic-drawing'
+ mt.setAttribute('data-yjs-hook', 'magic-drawing')
document.body.append(mt)
}
@@ -30,7 +30,7 @@ var renderPath = d3.svg.line()
function initDrawingBindings (type, dom) {
dom.contentEditable = 'false'
- dom.dataset.yjsHook = 'magic-drawing'
+ dom.setAttribute('data-yjs-hook', 'magic-drawing')
var drawing = type.get('drawing')
if (drawing === undefined) {
drawing = type.set('drawing', new Y.Array())
diff --git a/examples/xml/index.js b/examples/xml/index.js
index b6c7596a..02b33561 100644
--- a/examples/xml/index.js
+++ b/examples/xml/index.js
@@ -9,5 +9,5 @@ let y = new Y('xml-example', {
window.yXml = y
// bind xml type to a dom, and put it in body
-window.sharedDom = y.define('xml', Y.XmlElement).getDom()
+window.sharedDom = y.define('xml', Y.XmlElement).toDom()
document.body.appendChild(window.sharedDom)
diff --git a/package.json b/package.json
index 9de600a2..309b5929 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,8 @@
"files": [
"y.*",
"src/*",
- ".esdoc.json"
+ ".esdoc.json",
+ "docs/*"
],
"standard": {
"ignore": [
diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js
index ae5a2894..1568f08d 100644
--- a/src/Bindings/DomBinding/DomBinding.js
+++ b/src/Bindings/DomBinding/DomBinding.js
@@ -51,9 +51,9 @@ export default class DomBinding extends Binding {
this.filter = opts.filter || defaultFilter
// set initial value
target.innerHTML = ''
- for (let child of type) {
+ type.forEach(child => {
target.insertBefore(child.toDom(opts.document, opts.hooks, this), null)
- }
+ })
this._typeObserver = typeObserver.bind(this)
this._domObserver = (mutations) => {
domObserver.call(this, mutations, opts.document)
diff --git a/src/Bindings/DomBinding/domToType.js b/src/Bindings/DomBinding/domToType.js
index b906b53b..a81583f7 100644
--- a/src/Bindings/DomBinding/domToType.js
+++ b/src/Bindings/DomBinding/domToType.js
@@ -1,35 +1,37 @@
import { YXmlText, YXmlElement, YXmlHook } from '../../Types/YXml/YXml.js'
-import { createAssociation } from './util.js'
-import { filterDomAttributes } from './filter.js'
+import { createAssociation, domsToTypes } from './util.js'
+import { filterDomAttributes, defaultFilter } from './filter.js'
/**
* Creates a Yjs type (YXml) based on the contents of a DOM Element.
*
* @param {Element|TextNode} element The DOM Element
- * @param {?Document} _document Optional. Provide the global document object.
- * @param {?DomBinding} binding This property should only be set if the type
- * is going to be bound with the dom-binding.
+ * @param {?Document} _document Optional. Provide the global document object
+ * @param {Hooks} [hooks = {}] Optional. Set of Yjs Hooks
+ * @param {Filter} [filter=defaultFilter] Optional. Dom element filter
+ * @param {?DomBinding} binding Warning: This property is for internal use only!
* @return {YXmlElement | YXmlText}
*/
-export default function domToType (element, _document = document, binding) {
+export default function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) {
let type
switch (element.nodeType) {
case _document.ELEMENT_NODE:
- let hookName = element.dataset.yjsHook
+ let hookName = null
let hook
// configure `hookName !== undefined` if element is a hook.
- if (hookName !== undefined) {
- hook = binding.opts.hooks[hookName]
+ if (element.hasAttribute('data-yjs-hook')) {
+ hookName = element.getAttribute('data-yjs-hook')
+ hook = hooks[hookName]
if (hook === undefined) {
console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`)
- delete element.dataset.yjsHook
- hookName = undefined
+ delete element.removeAttribute('data-yjs-hook')
+ hookName = null
}
}
- if (hookName === undefined) {
+ if (hookName === null) {
// Not a hook
- const attrs = filterDomAttributes(element, binding.filter)
+ const attrs = filterDomAttributes(element, filter)
if (attrs === null) {
type = false
} else {
@@ -37,14 +39,7 @@ export default function domToType (element, _document = document, binding) {
attrs.forEach((val, key) => {
type.setAttribute(key, val)
})
- const children = []
- for (let elem of element.childNodes) {
- const type = domToType(elem, _document, binding)
- if (type !== false) {
- children.push(type)
- }
- }
- type.insert(0, children)
+ type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding))
}
} else {
// Is a hook
diff --git a/src/Bindings/DomBinding/filter.js b/src/Bindings/DomBinding/filter.js
index 4fea3f84..08c208e7 100644
--- a/src/Bindings/DomBinding/filter.js
+++ b/src/Bindings/DomBinding/filter.js
@@ -14,7 +14,7 @@ export function defaultFilter (nodeName, attrs) {
}
/**
- *
+ *
*/
export function filterDomAttributes (dom, filter) {
const attrs = new Map()
diff --git a/src/Bindings/DomBinding/util.js b/src/Bindings/DomBinding/util.js
index e3f0ed0e..b4f6148d 100644
--- a/src/Bindings/DomBinding/util.js
+++ b/src/Bindings/DomBinding/util.js
@@ -17,7 +17,10 @@ export function iterateUntilUndeleted (item) {
* Removes an association (the information that a DOM element belongs to a
* type).
*
- * @private
+ * @param {DomBinding} domBinding The binding object
+ * @param {Element} dom The dom that is to be associated with type
+ * @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
+ *
*/
export function removeAssociation (domBinding, dom, type) {
domBinding.domToType.delete(dom)
@@ -28,7 +31,10 @@ export function removeAssociation (domBinding, dom, type) {
* Creates an association (the information that a DOM element belongs to a
* type).
*
- * @private
+ * @param {DomBinding} domBinding The binding object
+ * @param {Element} dom The dom that is to be associated with type
+ * @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
+ *
*/
export function createAssociation (domBinding, dom, type) {
if (domBinding !== undefined) {
@@ -37,6 +43,24 @@ export function createAssociation (domBinding, dom, type) {
}
}
+/**
+ * If oldDom is associated with a type, associate newDom with the type and
+ * forget about oldDom. If oldDom is not associated with any type, nothing happens.
+ *
+ * @param {DomBinding} domBinding The binding object
+ * @param {Element} oldDom The existing dom
+ * @param {Element} newDom The new dom object
+ */
+export function switchAssociation (domBinding, oldDom, newDom) {
+ if (domBinding !== undefined) {
+ const type = domBinding.domToType.get(oldDom)
+ if (type !== undefined) {
+ removeAssociation(domBinding, oldDom, type)
+ createAssociation(domBinding, newDom, type)
+ }
+ }
+}
+
/**
* Insert Dom Elements after one of the children of this YXmlFragment.
* The Dom elements will be bound to a new YXmlElement and inserted at the
@@ -54,14 +78,19 @@ export function createAssociation (domBinding, dom, type) {
* @private
*/
export function insertDomElementsAfter (type, prev, doms, _document, binding) {
+ const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding)
+ return type.insertAfter(prev, types)
+}
+
+export function domsToTypes (doms, _document, hooks, filter, binding) {
const types = []
for (let dom of doms) {
- const t = domToType(dom, _document, binding)
+ const t = domToType(dom, _document, hooks, filter, binding)
if (t !== false) {
types.push(t)
}
}
- return type.insertAfter(prev, types)
+ return types
}
/**
diff --git a/src/MessageHandler/integrateRemoteStructs.js b/src/MessageHandler/integrateRemoteStructs.js
index d2067f3c..390f0924 100644
--- a/src/MessageHandler/integrateRemoteStructs.js
+++ b/src/MessageHandler/integrateRemoteStructs.js
@@ -1,7 +1,7 @@
import { getStruct } from '../Util/structReferences.js'
import BinaryDecoder from '../Util/Binary/Decoder.js'
import { logID } from './messageToString.js'
-import GC from '../Struct/GC.js';
+import GC from '../Struct/GC.js'
class MissingEntry {
constructor (decoder, missing, struct) {
diff --git a/src/Struct/GC.js b/src/Struct/GC.js
index a23d55b6..6db86017 100644
--- a/src/Struct/GC.js
+++ b/src/Struct/GC.js
@@ -91,4 +91,4 @@ export default class GC {
gc._length = this._length - diff
return gc
}
-}
\ No newline at end of file
+}
diff --git a/src/Struct/Item.js b/src/Struct/Item.js
index 1ff928f2..9f17d1e7 100644
--- a/src/Struct/Item.js
+++ b/src/Struct/Item.js
@@ -238,7 +238,7 @@ export default class Item {
}
_gcChildren (y) {}
-
+
_gc (y) {
const gc = new GC()
gc._id = this._id
diff --git a/src/Struct/Type.js b/src/Struct/Type.js
index 44f075ad..6202454b 100644
--- a/src/Struct/Type.js
+++ b/src/Struct/Type.js
@@ -213,6 +213,8 @@ export default class Type extends Item {
* @param {Y} y The Yjs instance
* @param {boolean} createDelete Whether to propagate a message that this
* Type was deleted.
+ * @param {boolean} [gcChildren=y._hasUndoManager===false] Whether to garbage
+ * collect the children of this type.
*/
_delete (y, createDelete, gcChildren) {
if (gcChildren === undefined) {
diff --git a/src/Types/YArray/YArray.js b/src/Types/YArray/YArray.js
index 27af984c..5914dc7b 100644
--- a/src/Types/YArray/YArray.js
+++ b/src/Types/YArray/YArray.js
@@ -198,7 +198,7 @@ export default class YArray extends Type {
content = this._item._content[this._itemElement++]
}
return {
- value: [this._count, content],
+ value: content,
done: false
}
},
diff --git a/src/Types/YXml/YXmlFragment.js b/src/Types/YXml/YXmlFragment.js
index 827356a0..3c450756 100644
--- a/src/Types/YXml/YXmlFragment.js
+++ b/src/Types/YXml/YXmlFragment.js
@@ -123,6 +123,8 @@ 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.
+ * @param {boolean} [gcChildren=y._hasUndoManager===false] Whether to garbage
+ * collect the children of this type.
*
* @private
*/
diff --git a/src/Types/YXml/YXmlHook.js b/src/Types/YXml/YXmlHook.js
index 445f107b..08ad5935 100644
--- a/src/Types/YXml/YXmlHook.js
+++ b/src/Types/YXml/YXmlHook.js
@@ -52,7 +52,7 @@ export default class YXmlHook extends YMap {
} else {
dom = document.createElement(this.hookName)
}
- dom.dataset.yjsHook = this.hookName
+ dom.setAttribute('data-yjs-hook', this.hookName)
createAssociation(binding, dom, this)
return dom
}
diff --git a/src/Types/YXml/YXmlText.js b/src/Types/YXml/YXmlText.js
index c0339806..4c34c982 100644
--- a/src/Types/YXml/YXmlText.js
+++ b/src/Types/YXml/YXmlText.js
@@ -35,6 +35,8 @@ export default class YXmlText extends YText {
* @param {Y} y The Yjs instance
* @param {boolean} createDelete Whether to propagate a message that this
* Type was deleted.
+ * @param {boolean} [gcChildren=y._hasUndoManager===false] Whether to garbage
+ * collect the children of this type.
*
* @private
*/
diff --git a/src/Util/relativePosition.js b/src/Util/relativePosition.js
index 05b6b5d9..df85b00c 100644
--- a/src/Util/relativePosition.js
+++ b/src/Util/relativePosition.js
@@ -1,5 +1,6 @@
import ID from './ID/ID.js'
import RootID from './ID/RootID.js'
+import GC from '../Struct/GC.js'
// TODO: Implement function to describe ranges
@@ -76,6 +77,9 @@ export function fromRelativePosition (y, rpos) {
id = new RootID(rpos[3], rpos[4])
}
const type = y.os.get(id)
+ if (type === null || type.constructor === GC) {
+ return null
+ }
return {
type,
offset: type.length
@@ -84,7 +88,7 @@ export function fromRelativePosition (y, rpos) {
let offset = 0
let struct = y.os.findNodeWithUpperBound(new ID(rpos[0], rpos[1])).val
const parent = struct._parent
- if (parent._deleted) {
+ if (struct.constructor === GC || parent._deleted) {
return null
}
if (!struct._deleted) {
diff --git a/src/Util/structReferences.js b/src/Util/structReferences.js
index a79d8c71..0c4f7182 100644
--- a/src/Util/structReferences.js
+++ b/src/Util/structReferences.js
@@ -56,4 +56,4 @@ registerStruct(7, YXmlElement)
registerStruct(8, YXmlText)
registerStruct(9, YXmlHook)
-registerStruct(12, GC)
\ No newline at end of file
+registerStruct(12, GC)
diff --git a/src/Y.dist.js b/src/Y.dist.js
index 92df64c7..6074cf0e 100644
--- a/src/Y.dist.js
+++ b/src/Y.dist.js
@@ -20,6 +20,8 @@ import DomBinding from './Bindings/DomBinding/DomBinding.js'
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
import debug from 'debug'
+import domToType from './Bindings/DomBinding/domToType.js'
+import { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js'
// TODO: The following assignments should be moved to yjs-dist
Y.AbstractConnector = Connector
@@ -36,6 +38,10 @@ Y.TextareaBinding = TextareaBinding
Y.QuillBinding = QuillBinding
Y.DomBinding = DomBinding
+DomBinding.domToType = domToType
+DomBinding.domsToTypes = domsToTypes
+DomBinding.switchAssociation = switchAssociation
+
Y.utils = {
BinaryDecoder,
UndoManager,
diff --git a/test/DeleteStore.tests.js b/test/DeleteStore.tests.js
index 52e54382..5f5c3a7a 100644
--- a/test/DeleteStore.tests.js
+++ b/test/DeleteStore.tests.js
@@ -1,5 +1,4 @@
import { test } from '../node_modules/cutest/cutest.mjs'
-import simpleDiff from '../src/Util/simpleDiff.js'
import Chance from 'chance'
import DeleteStore from '../src/Store/DeleteStore.js'
import ID from '../src/Util/ID/ID.js'
diff --git a/tests-lib/helper.js b/tests-lib/helper.js
index a8425a9d..fe59c55f 100644
--- a/tests-lib/helper.js
+++ b/tests-lib/helper.js
@@ -8,7 +8,7 @@ import ItemJSON from '../src/Struct/ItemJSON.js'
import ItemString from '../src/Struct/ItemString.js'
import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
import Quill from 'quill'
-import GC from '../src/Struct/GC.js';
+import GC from '../src/Struct/GC.js'
export const Y = _Y
@@ -42,41 +42,6 @@ function getDeleteSet (y) {
return ds
}
-// TODO: remove?
-export function attrsObject (dom) {
- let keys = []
- let yxml = dom._yxml
- for (let i = 0; i < dom.attributes.length; i++) {
- keys.push(dom.attributes[i].name)
- }
- keys = yxml._domFilter(dom, keys)
- let obj = {}
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i]
- obj[key] = dom.getAttribute(key)
- }
- return obj
-}
-
-// TODO: remove?
-export function domToJson (dom) {
- if (dom.nodeType === document.TEXT_NODE) {
- return dom.textContent
- } else if (dom.nodeType === document.ELEMENT_NODE) {
- let attributes = attrsObject(dom)
- let children = Array.from(dom.childNodes.values())
- .filter(d => d._yxml !== false)
- .map(domToJson)
- return {
- name: dom.nodeName,
- children: children,
- attributes: attributes
- }
- } else {
- throw new Error('Unsupported node type')
- }
-}
-
/*
* 1. reconnect and flush all
* 2. user 0 gc