implemented undo 🙌
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user