diff --git a/examples/html-editor/index.js b/examples/html-editor/index.js index 80623031..a13010a4 100644 --- a/examples/html-editor/index.js +++ b/examples/html-editor/index.js @@ -1,7 +1,7 @@ /* global Y */ window.onload = function () { - window.domBinding = new Y.DomBinding(window.yXmlType, document.body) + window.domBinding = new Y.DomBinding(window.yXmlType, document.body, { scrollingElement: document.scrollingElement }) } let y = new Y('htmleditor', { diff --git a/package-lock.json b/package-lock.json index ac06c647..286c3036 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-56", + "version": "13.0.0-60", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 266af208..fd7cd781 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-56", + "version": "13.0.0-60", "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js", @@ -71,8 +71,5 @@ "rollup-watch": "^3.2.2", "standard": "^10.0.2", "tag-dist-files": "^0.1.6" - }, - "dependencies": { - "debug": "^2.6.8" } } diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js index 1568f08d..b3ecb625 100644 --- a/src/Bindings/DomBinding/DomBinding.js +++ b/src/Bindings/DomBinding/DomBinding.js @@ -33,6 +33,7 @@ export default class DomBinding extends Binding { this.opts = opts opts.document = opts.document || document opts.hooks = opts.hooks || {} + this.scrollingElement = opts.scrollingElement || null /** * Maps each DOM element to the type that it is associated with. * @type {Map} @@ -105,17 +106,6 @@ export default class DomBinding extends Binding { createAssociation(this, target, type) } - /** - * Enables the smart scrolling functionality for a Dom Binding. - * This is useful when YXml is bound to a shared editor. When activated, - * the viewport will be changed to accommodate remote changes. - * - * @param {Element} scrollElement The node that is - */ - enableSmartScrolling (scrollElement) { - // @TODO: implement smart scrolling - } - /** * NOTE: currently does not apply filter to existing elements! * @param {FilterFunction} filter The filter function to use from now on. diff --git a/src/Bindings/DomBinding/domObserver.js b/src/Bindings/DomBinding/domObserver.js index 8304c079..0dfb176e 100644 --- a/src/Bindings/DomBinding/domObserver.js +++ b/src/Bindings/DomBinding/domObserver.js @@ -60,8 +60,8 @@ function applyChangesFromDom (binding, dom, yxml, _document) { removeAssociation(binding, childNode, childType) } else { // child was moved to a different position. - childType._delete(y) removeAssociation(binding, childNode, childType) + childType._delete(y) } prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) } else { @@ -90,8 +90,19 @@ export default function domObserver (mutations, _document) { mutations.forEach(mutation => { const dom = mutation.target const yxml = this.domToType.get(dom) - if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) { - // dom element is filtered + if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom + let parent = dom + let yParent + do { + parent = parent.parentElement + yParent = this.domToType.get(parent) + } while (yParent === undefined && parent !== null) + if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) { + diffChildren.add(parent) + } + return + } else if (yxml === false || yxml.constructor === YXmlHook) { + // dom element is filtered / a dom hook return } switch (mutation.type) { @@ -125,9 +136,6 @@ export default function domObserver (mutations, _document) { } }) for (let dom of diffChildren) { - if (dom.yOnChildrenChanged !== undefined) { - dom.yOnChildrenChanged() - } const yxml = this.domToType.get(dom) applyChangesFromDom(this, dom, yxml, _document) } diff --git a/src/Bindings/DomBinding/typeObserver.js b/src/Bindings/DomBinding/typeObserver.js index 26c168b5..762af70d 100644 --- a/src/Bindings/DomBinding/typeObserver.js +++ b/src/Bindings/DomBinding/typeObserver.js @@ -1,13 +1,49 @@ +/* global getSelection */ import YXmlText from '../../Types/YXml/YXmlText.js' import YXmlHook from '../../Types/YXml/YXmlHook.js' import { removeDomChildrenUntilElementFound } from './util.js' +function findScrollReference (scrollingElement) { + if (scrollingElement !== null) { + let anchor = getSelection().anchorNode + if (anchor == null) { + let children = scrollingElement.children // only iterate through non-text nodes + for (let i = 0; i < children.length; i++) { + const elem = children[i] + const rect = elem.getBoundingClientRect() + if (rect.top >= 0) { + return { elem, top: rect.top } + } + } + } else { + if (anchor.nodeType === document.TEXT_NODE) { + anchor = anchor.parentElement + } + const top = anchor.getBoundingClientRect().top + return { elem: anchor, top: top } + } + } + return null +} + +function fixScroll (scrollingElement, ref) { + if (ref !== null) { + const { elem, top } = ref + const currentTop = elem.getBoundingClientRect().top + const newScroll = scrollingElement.scrollTop + currentTop - top + if (newScroll >= 0) { + scrollingElement.scrollTop = newScroll + } + } +} + /** * @private */ export default function typeObserver (events) { this._mutualExclude(() => { + const scrollRef = findScrollReference(this.scrollingElement) events.forEach(event => { const yxml = event.target const dom = this.typeToDom.get(yxml) @@ -58,5 +94,6 @@ export default function typeObserver (events) { } } }) + fixScroll(this.scrollingElement, scrollRef) }) } diff --git a/src/MessageHandler/integrateRemoteStructs.js b/src/MessageHandler/integrateRemoteStructs.js index 390f0924..802c3af6 100644 --- a/src/MessageHandler/integrateRemoteStructs.js +++ b/src/MessageHandler/integrateRemoteStructs.js @@ -25,7 +25,7 @@ function _integrateRemoteStructHelper (y, struct) { if (y.ss.getState(id.user) > id.clock) { return } - if (struct.constructor === GC || (struct._parent.constructor !== GC && struct._parent._deleted === false)) { + if (!y.gcEnabled || struct.constructor === GC || (struct._parent.constructor !== GC && struct._parent._deleted === false)) { // Is either a GC or Item with an undeleted parent // save to integrate struct._integrate(y) diff --git a/src/Struct/Type.js b/src/Struct/Type.js index 6202454b..c8b63395 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -217,8 +217,8 @@ export default class Type extends Item { * collect the children of this type. */ _delete (y, createDelete, gcChildren) { - if (gcChildren === undefined) { - gcChildren = y._hasUndoManager === false + if (gcChildren === undefined || !y.gcEnabled) { + gcChildren = y._hasUndoManager === false && y.gcEnabled } super._delete(y, createDelete, gcChildren) y._transaction.changedTypes.delete(this) diff --git a/src/Y.js b/src/Y.js index 7a1ab318..0279cf17 100644 --- a/src/Y.js +++ b/src/Y.js @@ -28,8 +28,9 @@ export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js' * @param {AbstractPersistence} persistence Persistence adapter instance */ export default class Y extends NamedEventHandler { - constructor (room, opts, persistence) { + constructor (room, opts, persistence, conf = {}) { super() + this.gcEnabled = conf.gc || false /** * The room name that this Yjs instance connects to. * @type {String}