From a54d826d6d03df9c76bc1eadd636adf71736b383 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 27 Apr 2018 18:33:28 +0200 Subject: [PATCH] bugfixes --- examples/html-editor-drawing-hook/index.js | 6 ++-- examples/xml/index.js | 2 +- package.json | 3 +- src/Bindings/DomBinding/DomBinding.js | 4 +-- src/Bindings/DomBinding/domToType.js | 37 +++++++++----------- src/Bindings/DomBinding/filter.js | 2 +- src/Bindings/DomBinding/util.js | 37 +++++++++++++++++--- src/MessageHandler/integrateRemoteStructs.js | 2 +- src/Struct/GC.js | 2 +- src/Struct/Item.js | 2 +- src/Struct/Type.js | 2 ++ src/Types/YArray/YArray.js | 2 +- src/Types/YXml/YXmlFragment.js | 2 ++ src/Types/YXml/YXmlHook.js | 2 +- src/Types/YXml/YXmlText.js | 2 ++ src/Util/relativePosition.js | 6 +++- src/Util/structReferences.js | 2 +- src/Y.dist.js | 6 ++++ test/DeleteStore.tests.js | 1 - tests-lib/helper.js | 37 +------------------- 20 files changed, 82 insertions(+), 77 deletions(-) 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