implemented undo 🙌
This commit is contained in:
parent
c545118637
commit
0208d83f91
@ -10,8 +10,23 @@ let y = new Y({
|
||||
}
|
||||
})
|
||||
window.yXml = y
|
||||
window.yXmlType = y.get('xml', Y.XmlFragment)
|
||||
window.onload = function () {
|
||||
console.log('start!')
|
||||
// Bind children of XmlFragment to the document.body
|
||||
y.get('xml', Y.XmlFragment).bindToDom(document.body)
|
||||
window.yXmlType.bindToDom(document.body)
|
||||
}
|
||||
window.undoManager = new Y.utils.UndoManager(window.yXmlType)
|
||||
|
||||
document.onkeydown = function interceptUndoRedo (e) {
|
||||
if (e.keyCode === 90 && e.ctrlKey) {
|
||||
if (!e.shiftKey) {
|
||||
console.info('Undo!')
|
||||
window.undoManager.undo()
|
||||
} else {
|
||||
console.info('Redo!')
|
||||
window.undoManager.redo()
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,10 @@ export default class StateStore {
|
||||
}
|
||||
setState (user, state) {
|
||||
// TODO: modify missingi structs here
|
||||
const beforeState = this.y._transaction.beforeState
|
||||
if (!beforeState.has(user)) {
|
||||
beforeState.set(user, this.getState(user))
|
||||
}
|
||||
this.state.set(user, state)
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,19 @@ export default class Item {
|
||||
this._parentSub = null
|
||||
this._deleted = false
|
||||
}
|
||||
/**
|
||||
* Copy the effect of struct
|
||||
*/
|
||||
_copy () {
|
||||
let struct = new this.constructor()
|
||||
struct._origin = this._left
|
||||
struct._left = this._left
|
||||
struct._right = this
|
||||
struct._right_origin = this
|
||||
struct._parent = this._parent
|
||||
struct._parentSub = this._parentSub
|
||||
return struct
|
||||
}
|
||||
get _lastId () {
|
||||
return new ID(this._id.user, this._id.clock + this._length - 1)
|
||||
}
|
||||
@ -83,6 +96,7 @@ export default class Item {
|
||||
del._integrate(y, true)
|
||||
}
|
||||
transactionTypeChanged(y, this._parent, this._parentSub)
|
||||
y._transaction.deletedStructs.add(this)
|
||||
}
|
||||
/**
|
||||
* This is called right before this struct receives any children.
|
||||
|
@ -6,6 +6,11 @@ export default class ItemJSON extends Item {
|
||||
super()
|
||||
this._content = null
|
||||
}
|
||||
_copy () {
|
||||
let struct = super._copy()
|
||||
struct._content = this._content
|
||||
return struct
|
||||
}
|
||||
get _length () {
|
||||
return this._content.length
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ export default class ItemString extends Item {
|
||||
super()
|
||||
this._content = null
|
||||
}
|
||||
_copy () {
|
||||
let struct = super._copy()
|
||||
struct._content = this._content
|
||||
return struct
|
||||
}
|
||||
get _length () {
|
||||
return this._content.length
|
||||
}
|
||||
|
@ -37,6 +37,48 @@ export default class Type extends Item {
|
||||
this._start = null
|
||||
this._y = null
|
||||
this._eventHandler = new EventHandler()
|
||||
this._deepEventHandler = new EventHandler()
|
||||
}
|
||||
_callEventHandler (event) {
|
||||
this._eventHandler.callEventListeners(event)
|
||||
let type = this
|
||||
while (type !== this._y) {
|
||||
type._deepEventHandler.callEventListeners(event)
|
||||
type = type._parent
|
||||
}
|
||||
}
|
||||
_copy (undeleteChildren) {
|
||||
let copy = super._copy()
|
||||
let map = new Map()
|
||||
copy._map = map
|
||||
for (let [key, value] of this._map) {
|
||||
if (undeleteChildren.has(value) || !value.deleted) {
|
||||
let _item = value._copy(undeleteChildren)
|
||||
_item._parent = copy
|
||||
map.set(key, value._copy(undeleteChildren))
|
||||
}
|
||||
}
|
||||
let prevUndeleted = null
|
||||
copy._start = null
|
||||
let item = this._start
|
||||
while (item !== null) {
|
||||
if (undeleteChildren.has(item) || !item.deleted) {
|
||||
let _item = item._copy(undeleteChildren)
|
||||
_item._left = prevUndeleted
|
||||
_item._origin = prevUndeleted
|
||||
_item._right = null
|
||||
_item._right_origin = null
|
||||
_item._parent = copy
|
||||
if (prevUndeleted === null) {
|
||||
copy._start = _item
|
||||
} else {
|
||||
prevUndeleted._right = _item
|
||||
}
|
||||
prevUndeleted = _item
|
||||
}
|
||||
item = item._right
|
||||
}
|
||||
return copy
|
||||
}
|
||||
_transact (f) {
|
||||
const y = this._y
|
||||
@ -49,9 +91,15 @@ export default class Type extends Item {
|
||||
observe (f) {
|
||||
this._eventHandler.addEventListener(f)
|
||||
}
|
||||
observeDeep (f) {
|
||||
this._deepEventHandler.addEventListener(f)
|
||||
}
|
||||
unobserve (f) {
|
||||
this._eventHandler.removeEventListener(f)
|
||||
}
|
||||
unobserveDeep (f) {
|
||||
this._deepEventHandler.removeEventListener(f)
|
||||
}
|
||||
_integrate (y) {
|
||||
y._transaction.newTypes.add(this)
|
||||
super._integrate(y)
|
||||
|
@ -5,8 +5,10 @@ export default class Transaction {
|
||||
// types added during transaction
|
||||
this.newTypes = new Set()
|
||||
// changed types (does not include new types)
|
||||
// maps from type to parentSubs (item.parentSub = null for array elements)
|
||||
// maps from type to parentSubs (item._parentSub = null for array elements)
|
||||
this.changedTypes = new Map()
|
||||
this.deletedStructs = new Set()
|
||||
this.beforeState = new Map()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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') {
|
||||
|
@ -20,7 +20,8 @@ export default class EventHandler {
|
||||
callEventListeners (event) {
|
||||
for (var i = 0; i < this.eventListeners.length; i++) {
|
||||
try {
|
||||
this.eventListeners[i](event)
|
||||
const f = this.eventListeners[i]
|
||||
f(event)
|
||||
} catch (e) {
|
||||
/*
|
||||
Your observer threw an error. This error was caught so that Yjs
|
||||
|
92
src/Util/UndoManager.js
Normal file
92
src/Util/UndoManager.js
Normal file
@ -0,0 +1,92 @@
|
||||
import ID from './ID.js'
|
||||
|
||||
class ReverseOperation {
|
||||
constructor (y) {
|
||||
const beforeState = y._transaction.beforeState
|
||||
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
|
||||
if (beforeState.has(y.userID)) {
|
||||
this.fromState = new ID(y.userID, beforeState.get(y.userID))
|
||||
} else {
|
||||
this.fromState = this.toState
|
||||
}
|
||||
this.deletedStructs = y._transaction.deletedStructs
|
||||
}
|
||||
}
|
||||
|
||||
function isStructInScope (y, struct, scope) {
|
||||
while (struct !== y) {
|
||||
if (struct === scope) {
|
||||
return true
|
||||
}
|
||||
struct = struct._parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export default class UndoManager {
|
||||
constructor (scope) {
|
||||
this._undoBuffer = []
|
||||
this._redoBuffer = []
|
||||
this._scope = scope
|
||||
this._undoing = false
|
||||
this._redoing = false
|
||||
const y = scope._y
|
||||
this.y = y
|
||||
y.on('afterTransaction', (y, remote) => {
|
||||
if (!remote && (y._transaction.beforeState.has(y.userID) || y._transaction.deletedStructs.size > 0)) {
|
||||
let reverseOperation = new ReverseOperation(y)
|
||||
if (!this._undoing) {
|
||||
this._undoBuffer.push(reverseOperation)
|
||||
if (!this._redoing) {
|
||||
this._redoBuffer = []
|
||||
}
|
||||
} else {
|
||||
this._redoBuffer.push(reverseOperation)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
undo () {
|
||||
this._undoing = true
|
||||
this._applyReverseOperation(this._undoBuffer)
|
||||
this._undoing = false
|
||||
}
|
||||
redo () {
|
||||
this._redoing = true
|
||||
this._applyReverseOperation(this._redoBuffer)
|
||||
this._redoing = false
|
||||
}
|
||||
_applyReverseOperation (reverseBuffer) {
|
||||
this.y.transact(() => {
|
||||
let performedUndo = false
|
||||
while (!performedUndo && reverseBuffer.length > 0) {
|
||||
let undoOp = reverseBuffer.pop()
|
||||
// make sure that it is possible to iterate {from}-{to}
|
||||
this.y.os.getItemCleanStart(undoOp.fromState)
|
||||
this.y.os.getItemCleanEnd(undoOp.toState)
|
||||
this.y.os.iterate(undoOp.fromState, undoOp.toState, op => {
|
||||
if (!op._deleted && isStructInScope(this.y, op, this._scope)) {
|
||||
performedUndo = true
|
||||
op._delete(this.y)
|
||||
}
|
||||
})
|
||||
for (let op of undoOp.deletedStructs) {
|
||||
if (
|
||||
isStructInScope(this.y, op, this._scope) &&
|
||||
op._parent !== this.y &&
|
||||
!op._parent._deleted &&
|
||||
(
|
||||
op._parent._id.user !== this.y.userID ||
|
||||
op._parent._id.clock < undoOp.fromState.clock ||
|
||||
op._parent._id.clock > undoOp.fromState.clock
|
||||
)
|
||||
) {
|
||||
performedUndo = true
|
||||
op = op._copy(undoOp.deletedStructs)
|
||||
op._integrate(this.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
4
src/Y.js
4
src/Y.js
@ -4,6 +4,7 @@ import StateStore from './Store/StateStore.js'
|
||||
import { generateUserID } from './Util/generateUserID.js'
|
||||
import RootID from './Util/RootID.js'
|
||||
import NamedEventHandler from './Util/NamedEventHandler.js'
|
||||
import UndoManager from './Util/UndoManager.js'
|
||||
|
||||
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js'
|
||||
|
||||
@ -132,7 +133,8 @@ Y.XmlFragment = YXmlFragment
|
||||
Y.XmlText = YXmlText
|
||||
|
||||
Y.utils = {
|
||||
BinaryDecoder
|
||||
BinaryDecoder,
|
||||
UndoManager
|
||||
}
|
||||
|
||||
Y.debug = debug
|
||||
|
Loading…
x
Reference in New Issue
Block a user