implemented undo 🙌

This commit is contained in:
Kevin Jahns
2017-10-30 11:33:00 +01:00
parent c545118637
commit 0208d83f91
16 changed files with 302 additions and 109 deletions

View File

@@ -11,7 +11,7 @@ class YArrayEvent {
export default class YArray extends Type {
_callObserver (parentSubs, remote) {
this._eventHandler.callEventListeners(new YArrayEvent(this, remote))
this._callEventHandler(new YArrayEvent(this, remote))
}
get (pos) {
let n = this._start

View File

@@ -13,7 +13,7 @@ class YMapEvent {
export default class YMap extends Type {
_callObserver (parentSubs, remote) {
this._eventHandler.callEventListeners(new YMapEvent(this, parentSubs, remote))
this._callEventHandler(new YMapEvent(this, parentSubs, remote))
}
toJSON () {
const map = {}

View File

@@ -1,5 +1,3 @@
/* global MutationObserver */
// import diff from 'fast-diff'
import { defaultDomFilter } from './utils.js'
@@ -23,12 +21,18 @@ export default class YXmlElement extends YXmlFragment {
this._domFilter = arg2
}
}
_copy (undeleteChildren) {
let struct = super._copy(undeleteChildren)
struct.nodeName = this.nodeName
return struct
}
_setDom (dom) {
if (this._dom != null) {
throw new Error('Only call this method if you know what you are doing ;)')
} else if (dom._yxml != null) { // TODO do i need to check this? - no.. but for dev purps..
throw new Error('Already bound to an YXml type')
} else {
this._dom = dom
dom._yxml = this
// tag is already set in constructor
// set attributes
@@ -43,9 +47,7 @@ export default class YXmlElement extends YXmlFragment {
this.setAttribute(attrName, attrValue)
}
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes))
if (MutationObserver != null) {
this._dom = this._bindToDom(dom)
}
this._bindToDom(dom)
return dom
}
}
@@ -112,6 +114,7 @@ export default class YXmlElement extends YXmlFragment {
let dom = this._dom
if (dom == null) {
dom = document.createElement(this.nodeName)
this._dom = dom
dom._yxml = this
let attrs = this.getAttributes()
for (let key in attrs) {
@@ -120,9 +123,7 @@ export default class YXmlElement extends YXmlFragment {
this.forEach(yxml => {
dom.appendChild(yxml.getDom())
})
if (MutationObserver !== null) {
this._dom = this._bindToDom(dom)
}
this._bindToDom(dom)
}
return dom
}

View File

@@ -1,11 +1,13 @@
/* global MutationObserver */
import { defaultDomFilter, applyChangesFromDom, reflectChangesOnDom } from './utils.js'
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js'
import YArray from '../YArray.js'
import YXmlText from './YXmlText.js'
import YXmlEvent from './YXmlEvent.js'
import { logID } from '../../MessageHandler/messageToString.js'
import diff from 'fast-diff'
function domToYXml (parent, doms) {
const types = []
@@ -52,8 +54,6 @@ export default class YXmlFragment extends YArray {
token = true
}
}
// Apply Y.Xml events to dom
this.observe(reflectChangesOnDom)
}
enableSmartScrolling (scrollElement) {
this._scrollElement = scrollElement
@@ -68,7 +68,7 @@ export default class YXmlFragment extends YArray {
})
}
_callObserver (parentSubs, remote) {
this._eventHandler.callEventListeners(new YXmlEvent(this, parentSubs, remote))
this._callEventHandler(new YXmlEvent(this, parentSubs, remote))
}
toString () {
return this.map(xml => xml.toString()).join('')
@@ -111,57 +111,91 @@ export default class YXmlFragment extends YArray {
throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!')
}
dom.innerHTML = ''
this._dom = dom
dom._yxml = this
this.forEach(t => {
dom.insertBefore(t.getDom(), null)
})
this._dom = dom
dom._yxml = this
this._bindToDom(dom)
}
// binds to a dom element
// Only call if dom and YXml are isomorph
_bindToDom (dom) {
if (this._parent === null || this._parent._dom != null || typeof MutationObserver === 'undefined') {
// only bind if parent did not already bind
return
}
this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords())
})
this._y.on('beforeTransaction', beforeTransactionSelectionFixer)
this._y.on('afterTransaction', afterTransactionSelectionFixer)
// Apply Y.Xml events to dom
this.observeDeep(reflectChangesOnDom.bind(this))
// Apply Dom changes on Y.Xml
this._domObserverListener = mutations => {
this._mutualExclude(() => {
this._y.transact(() => {
let diffChildren = false
let diffChildren = new Set()
mutations.forEach(mutation => {
if (mutation.type === 'attributes') {
let name = mutation.attributeName
// check if filter accepts attribute
if (this._domFilter(this._dom, [name]).length > 0) {
var val = mutation.target.getAttribute(name)
if (this.getAttribute(name) !== val) {
if (val == null) {
this.removeAttribute(name)
} else {
this.setAttribute(name, val)
const dom = mutation.target
const yxml = dom._yxml
if (yxml == null) {
// dom element is filtered
return
}
switch (mutation.type) {
case 'characterData':
var diffs = diff(yxml.toString(), dom.nodeValue)
var pos = 0
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i]
if (d[0] === 0) { // EQUAL
pos += d[1].length
} else if (d[0] === -1) { // DELETE
yxml.delete(pos, d[1].length)
} else { // INSERT
yxml.insert(pos, d[1])
pos += d[1].length
}
}
}
} else if (mutation.type === 'childList') {
diffChildren = true
break
case 'attributes':
let name = mutation.attributeName
// check if filter accepts attribute
if (this._domFilter(dom, [name]).length > 0) {
var val = dom.getAttribute(name)
if (yxml.getAttribute(name) !== val) {
if (val == null) {
yxml.removeAttribute(name)
} else {
yxml.setAttribute(name, val)
}
}
}
break
case 'childList':
diffChildren.add(mutation.target)
break
}
})
if (diffChildren) {
applyChangesFromDom(this)
for (let dom of diffChildren) {
if (dom._yxml != null) {
applyChangesFromDom(dom)
}
}
})
})
}
this._domObserver = new MutationObserver(this._domObserverListener)
const observeOptions = { childList: true }
if (this instanceof YXmlFragment._YXmlElement) {
observeOptions.attributes = true
}
this._domObserver.observe(dom, observeOptions)
this._domObserver.observe(dom, {
childList: true,
attributes: true,
characterData: true,
subtree: true
})
return dom
}
_beforeChange () {
if (this._domObserver != null) {
this._domObserverListener(this._domObserver.takeRecords())
}
}
_logString () {
const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null

View File

@@ -1,9 +1,4 @@
/* global MutationObserver */
import diff from 'fast-diff'
import YText from '../YText.js'
import { getAnchorViewPosition, fixScrollPosition, getBoundingClientRect } from './utils.js'
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js'
export default class YXmlText extends YText {
constructor (arg1) {
@@ -25,6 +20,7 @@ export default class YXmlText extends YText {
if (dom !== null) {
this._setDom(arg1)
}
/*
var token = true
this._mutualExclude = f => {
if (token) {
@@ -54,11 +50,7 @@ export default class YXmlText extends YText {
})
}
})
}
_integrate (y) {
super._integrate(y)
y.on('beforeTransaction', beforeTransactionSelectionFixer)
y.on('afterTransaction', afterTransactionSelectionFixer)
*/
}
setDomFilter () {}
enableSmartScrolling (scrollElement) {
@@ -74,42 +66,15 @@ export default class YXmlText extends YText {
// set marker
this._dom = dom
dom._yxml = this
if (typeof MutationObserver === 'undefined') {
return
}
this._domObserverListener = () => {
this._mutualExclude(() => {
var diffs = diff(this.toString(), dom.nodeValue)
var pos = 0
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i]
if (d[0] === 0) { // EQUAL
pos += d[1].length
} else if (d[0] === -1) { // DELETE
this.delete(pos, d[1].length)
} else { // INSERT
this.insert(pos, d[1])
pos += d[1].length
}
}
})
}
this._domObserver = new MutationObserver(this._domObserverListener)
this._domObserver.observe(this._dom, { characterData: true })
}
getDom () {
if (this._dom == null) {
if (this._dom === null) {
const dom = document.createTextNode(this.toString())
this._setDom(dom)
return dom
}
return this._dom
}
_beforeChange () {
if (this._domObserver != null && this._y !== null) { // TODO: do I need th y condition
this._domObserverListener(this._domObserver.takeRecords())
}
}
_delete (y, createDelete) {
this._unbindFromDom()
super._delete(y, createDelete)

View File

@@ -1,3 +1,4 @@
import YXmlText from './YXmlText.js'
export function defaultDomFilter (node, attributes) {
return attributes
@@ -81,11 +82,12 @@ function _insertNodeHelper (yxml, prevExpectedNode, child) {
* You can detect that a node was moved because expectedId
* !== actualId in the list
*/
export function applyChangesFromDom (yxml) {
export function applyChangesFromDom (dom) {
const yxml = dom._yxml
const y = yxml._y
let knownChildren =
new Set(
Array.prototype.map.call(yxml._dom.childNodes, child => child._yxml)
Array.prototype.map.call(dom.childNodes, child => child._yxml)
.filter(id => id !== undefined)
)
// 1. Check if any of the nodes was deleted
@@ -95,7 +97,7 @@ export function applyChangesFromDom (yxml) {
}
})
// 2. iterate
let childNodes = yxml._dom.childNodes
let childNodes = dom.childNodes
let len = childNodes.length
let prevExpectedNode = null
let expectedNode = iterateUntilUndeleted(yxml._start)
@@ -137,33 +139,36 @@ export function reflectChangesOnDom (event) {
const yxml = event.target
const dom = yxml._dom
if (dom != null) {
yxml._mutualExclude(() => {
this._mutualExclude(() => {
// TODO: do this once before applying stuff
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
// update attributes
event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName)
if (value === undefined) {
dom.removeAttribute(attributeName)
} else {
dom.setAttribute(attributeName, value)
}
})
if (event.childListChanged) {
// create fragment of undeleted nodes
const fragment = document.createDocumentFragment()
yxml.forEach(function (t) {
fragment.append(t.getDom())
if (yxml.constructor === YXmlText) {
yxml._dom.nodeValue = yxml.toString()
} else {
// update attributes
event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName)
if (value === undefined) {
dom.removeAttribute(attributeName)
} else {
dom.setAttribute(attributeName, value)
}
})
// remove remainding nodes
let lastChild = dom.lastChild
while (lastChild !== null) {
dom.removeChild(lastChild)
lastChild = dom.lastChild
if (event.childListChanged) {
// create fragment of undeleted nodes
const fragment = document.createDocumentFragment()
yxml.forEach(function (t) {
fragment.append(t.getDom())
})
// remove remainding nodes
let lastChild = dom.lastChild
while (lastChild !== null) {
dom.removeChild(lastChild)
lastChild = dom.lastChild
}
// insert fragment of undeleted nodes
dom.append(fragment)
}
// insert fragment of undeleted nodes
dom.append(fragment)
}
/* TODO: smartscrolling
.. else if (event.type === 'childInserted' || event.type === 'insert') {