fixed selection handler befor/after transactions
This commit is contained in:
parent
c619aa33d9
commit
c545118637
@ -1,6 +1,5 @@
|
||||
import { getStruct } from '../Util/structReferences.js'
|
||||
import BinaryDecoder from '../Binary/Decoder.js'
|
||||
import Delete from '../Struct/Delete.js'
|
||||
import { logID } from './messageToString.js'
|
||||
|
||||
class MissingEntry {
|
||||
@ -17,9 +16,14 @@ class MissingEntry {
|
||||
* integrate.
|
||||
*/
|
||||
function _integrateRemoteStructHelper (y, struct) {
|
||||
struct._integrate(y)
|
||||
if (struct.constructor !== Delete) {
|
||||
const id = struct._id
|
||||
const id = struct._id
|
||||
if (id === undefined) {
|
||||
struct._integrate(y)
|
||||
} else {
|
||||
if (y.ss.getState(id.user) > id.clock) {
|
||||
return
|
||||
}
|
||||
struct._integrate(y)
|
||||
let msu = y._missingStructs.get(id.user)
|
||||
if (msu != null) {
|
||||
let clock = id.clock
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Item from './Item.js'
|
||||
import EventHandler from '../Util/EventHandler.js'
|
||||
import ID from '../Util/ID.js'
|
||||
|
||||
// restructure children as if they were inserted one after another
|
||||
function integrateChildren (y, start) {
|
||||
@ -14,6 +15,21 @@ function integrateChildren (y, start) {
|
||||
} while (right !== null)
|
||||
}
|
||||
|
||||
export function getListItemIDByPosition (type, i) {
|
||||
let pos = 0
|
||||
let n = type._start
|
||||
while (n !== null) {
|
||||
if (!n._deleted) {
|
||||
if (pos <= i && i < pos + n._length) {
|
||||
const id = n._id
|
||||
return new ID(id.user, id.clock + i - pos)
|
||||
}
|
||||
pos++
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
}
|
||||
|
||||
export default class Type extends Item {
|
||||
constructor () {
|
||||
super()
|
||||
|
@ -11,7 +11,7 @@ export default class Transaction {
|
||||
}
|
||||
|
||||
export function transactionTypeChanged (y, type, sub) {
|
||||
if (type !== y && !type._deleted) {
|
||||
if (type !== y && !type._deleted && !y._transaction.newTypes.has(type)) {
|
||||
const changedTypes = y._transaction.changedTypes
|
||||
let subs = changedTypes.get(type)
|
||||
if (subs === undefined) {
|
||||
|
@ -13,9 +13,17 @@ export default class YArray extends Type {
|
||||
_callObserver (parentSubs, remote) {
|
||||
this._eventHandler.callEventListeners(new YArrayEvent(this, remote))
|
||||
}
|
||||
get (i) {
|
||||
// TODO: This can be improved!
|
||||
return this.toArray()[i]
|
||||
get (pos) {
|
||||
let n = this._start
|
||||
while (n !== null) {
|
||||
if (!n._deleted) {
|
||||
if (pos < n._length) {
|
||||
return n._content[n._length - pos]
|
||||
}
|
||||
pos -= n._length
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
}
|
||||
toArray () {
|
||||
return this.map(c => c)
|
||||
|
@ -26,10 +26,10 @@ export default class YXmlElement extends YXmlFragment {
|
||||
_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..
|
||||
} 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 {
|
||||
dom.__yxml = this
|
||||
dom._yxml = this
|
||||
// tag is already set in constructor
|
||||
// set attributes
|
||||
let attrNames = []
|
||||
@ -112,7 +112,7 @@ export default class YXmlElement extends YXmlFragment {
|
||||
let dom = this._dom
|
||||
if (dom == null) {
|
||||
dom = document.createElement(this.nodeName)
|
||||
dom.__yxml = this
|
||||
dom._yxml = this
|
||||
let attrs = this.getAttributes()
|
||||
for (let key in attrs) {
|
||||
dom.setAttribute(key, attrs[key])
|
||||
|
@ -10,8 +10,8 @@ import { logID } from '../../MessageHandler/messageToString.js'
|
||||
function domToYXml (parent, doms) {
|
||||
const types = []
|
||||
doms.forEach(d => {
|
||||
if (d.__yxml != null && d.__yxml !== false) {
|
||||
d.__yxml._unbindFromDom()
|
||||
if (d._yxml != null && d._yxml !== false) {
|
||||
d._yxml._unbindFromDom()
|
||||
}
|
||||
if (parent._domFilter(d, []) !== null) {
|
||||
let type
|
||||
@ -25,7 +25,7 @@ function domToYXml (parent, doms) {
|
||||
type.enableSmartScrolling(parent._scrollElement)
|
||||
types.push(type)
|
||||
} else {
|
||||
d.__yxml = false
|
||||
d._yxml = false
|
||||
}
|
||||
})
|
||||
return types
|
||||
@ -83,7 +83,7 @@ export default class YXmlFragment extends YArray {
|
||||
this._domObserver = null
|
||||
}
|
||||
if (this._dom != null) {
|
||||
this._dom.__yxml = null
|
||||
this._dom._yxml = null
|
||||
this._dom = null
|
||||
}
|
||||
}
|
||||
@ -97,12 +97,15 @@ export default class YXmlFragment extends YArray {
|
||||
this.insert(pos, types)
|
||||
return types
|
||||
}
|
||||
getDom () {
|
||||
return this._dom
|
||||
}
|
||||
bindToDom (dom) {
|
||||
if (this._dom != null) {
|
||||
this._unbindFromDom()
|
||||
}
|
||||
if (dom.__yxml != null) {
|
||||
dom.__yxml._unbindFromDom()
|
||||
if (dom._yxml != null) {
|
||||
dom._yxml._unbindFromDom()
|
||||
}
|
||||
if (MutationObserver == null) {
|
||||
throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!')
|
||||
@ -112,7 +115,7 @@ export default class YXmlFragment extends YArray {
|
||||
dom.insertBefore(t.getDom(), null)
|
||||
})
|
||||
this._dom = dom
|
||||
dom.__yxml = this
|
||||
dom._yxml = this
|
||||
this._bindToDom(dom)
|
||||
}
|
||||
// binds to a dom element
|
||||
|
@ -1,20 +1,9 @@
|
||||
/* global getSelection, MutationObserver */
|
||||
/* global MutationObserver */
|
||||
|
||||
import diff from 'fast-diff'
|
||||
import YText from '../YText.js'
|
||||
import { getAnchorViewPosition, fixScrollPosition, getBoundingClientRect } from './utils.js'
|
||||
|
||||
function fixPosition (event, pos) {
|
||||
if (event.index <= pos) {
|
||||
if (event.type === 'delete') {
|
||||
return pos - Math.min(pos - event.index, event.length)
|
||||
} else {
|
||||
return pos + 1
|
||||
}
|
||||
} else {
|
||||
return pos
|
||||
}
|
||||
}
|
||||
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js'
|
||||
|
||||
export default class YXmlText extends YText {
|
||||
constructor (arg1) {
|
||||
@ -53,25 +42,6 @@ export default class YXmlText extends YText {
|
||||
if (this._dom != null) {
|
||||
const dom = this._dom
|
||||
this._mutualExclude(() => {
|
||||
let selection = null
|
||||
let shouldUpdateSelection = false
|
||||
let anchorNode = null
|
||||
let anchorOffset = null
|
||||
let focusNode = null
|
||||
let focusOffset = null
|
||||
if (typeof getSelection !== 'undefined') {
|
||||
selection = getSelection()
|
||||
if (selection.anchorNode === dom) {
|
||||
anchorNode = selection.anchorNode
|
||||
anchorOffset = fixPosition(event, selection.anchorOffset)
|
||||
shouldUpdateSelection = true
|
||||
}
|
||||
if (selection.focusNode === dom) {
|
||||
focusNode = selection.focusNode
|
||||
focusOffset = fixPosition(event, selection.focusOffset)
|
||||
shouldUpdateSelection = true
|
||||
}
|
||||
}
|
||||
let anchorViewPosition = getAnchorViewPosition(this._scrollElement)
|
||||
let anchorViewFix
|
||||
if (anchorViewPosition !== null && (anchorViewPosition.anchor !== null || getBoundingClientRect(this._dom).top <= 0)) {
|
||||
@ -81,19 +51,15 @@ export default class YXmlText extends YText {
|
||||
}
|
||||
dom.nodeValue = this.toString()
|
||||
fixScrollPosition(this._scrollElement, anchorViewFix)
|
||||
|
||||
if (shouldUpdateSelection) {
|
||||
selection.setBaseAndExtent(
|
||||
anchorNode || selection.anchorNode,
|
||||
anchorOffset || selection.anchorOffset,
|
||||
focusNode || selection.focusNode,
|
||||
focusOffset || selection.focusOffset
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
_integrate (y) {
|
||||
super._integrate(y)
|
||||
y.on('beforeTransaction', beforeTransactionSelectionFixer)
|
||||
y.on('afterTransaction', afterTransactionSelectionFixer)
|
||||
}
|
||||
setDomFilter () {}
|
||||
enableSmartScrolling (scrollElement) {
|
||||
this._scrollElement = scrollElement
|
||||
@ -102,12 +68,12 @@ export default class YXmlText extends YText {
|
||||
if (this._dom != null) {
|
||||
this._unbindFromDom()
|
||||
}
|
||||
if (dom.__yxml != null) {
|
||||
dom.__yxml._unbindFromDom()
|
||||
if (dom._yxml != null) {
|
||||
dom._yxml._unbindFromDom()
|
||||
}
|
||||
// set marker
|
||||
this._dom = dom
|
||||
dom.__yxml = this
|
||||
dom._yxml = this
|
||||
if (typeof MutationObserver === 'undefined') {
|
||||
return
|
||||
}
|
||||
@ -154,7 +120,7 @@ export default class YXmlText extends YText {
|
||||
this._domObserver = null
|
||||
}
|
||||
if (this._dom != null) {
|
||||
this._dom.__yxml = null
|
||||
this._dom._yxml = null
|
||||
this._dom = null
|
||||
}
|
||||
}
|
||||
|
73
src/Type/y-xml/selection.js
Normal file
73
src/Type/y-xml/selection.js
Normal file
@ -0,0 +1,73 @@
|
||||
/* globals getSelection */
|
||||
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.js'
|
||||
|
||||
let browserSelection = null
|
||||
let relativeSelection = null
|
||||
|
||||
export let beforeTransactionSelectionFixer
|
||||
if (typeof getSelection !== 'undefined') {
|
||||
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, remote) {
|
||||
if (!remote) {
|
||||
return
|
||||
}
|
||||
relativeSelection = { from: null, to: null, fromY: null, toY: null }
|
||||
browserSelection = getSelection()
|
||||
const anchorNode = browserSelection.anchorNode
|
||||
if (anchorNode !== null && anchorNode._yxml != null) {
|
||||
const yxml = anchorNode._yxml
|
||||
relativeSelection.from = getRelativePosition(yxml, browserSelection.anchorOffset)
|
||||
relativeSelection.fromY = yxml._y
|
||||
}
|
||||
const focusNode = browserSelection.focusNode
|
||||
if (focusNode !== null && focusNode._yxml != null) {
|
||||
const yxml = anchorNode._yxml
|
||||
relativeSelection.to = getRelativePosition(yxml, browserSelection.focusOffset)
|
||||
relativeSelection.toY = yxml._y
|
||||
}
|
||||
}
|
||||
} else {
|
||||
beforeTransactionSelectionFixer = function _fakeBeforeTransactionSelectionFixer () {}
|
||||
}
|
||||
|
||||
export function afterTransactionSelectionFixer (y, remote) {
|
||||
if (relativeSelection === null || !remote) {
|
||||
return
|
||||
}
|
||||
const to = relativeSelection.to
|
||||
const from = relativeSelection.from
|
||||
const fromY = relativeSelection.fromY
|
||||
const toY = relativeSelection.toY
|
||||
let shouldUpdate = false
|
||||
let anchorNode = browserSelection.anchorNode
|
||||
let anchorOffset = browserSelection.anchorOffset
|
||||
let focusNode = browserSelection.focusNode
|
||||
let focusOffset = browserSelection.focusOffset
|
||||
if (from !== null) {
|
||||
let sel = fromRelativePosition(fromY, from)
|
||||
if (sel !== null) {
|
||||
shouldUpdate = true
|
||||
anchorNode = sel.type.getDom()
|
||||
anchorOffset = sel.offset
|
||||
}
|
||||
}
|
||||
if (to !== null) {
|
||||
let sel = fromRelativePosition(toY, to)
|
||||
if (sel !== null) {
|
||||
focusNode = sel.type.getDom()
|
||||
focusOffset = sel.offset
|
||||
shouldUpdate = true
|
||||
}
|
||||
}
|
||||
if (shouldUpdate) {
|
||||
browserSelection.setBaseAndExtent(
|
||||
anchorNode,
|
||||
anchorOffset,
|
||||
focusNode,
|
||||
focusOffset
|
||||
)
|
||||
}
|
||||
// delete, so the objects can be gc'd
|
||||
relativeSelection = null
|
||||
browserSelection = null
|
||||
}
|
@ -73,7 +73,7 @@ function _insertNodeHelper (yxml, prevExpectedNode, child) {
|
||||
/*
|
||||
* 1. Check if any of the nodes was deleted
|
||||
* 2. Iterate over the children.
|
||||
* 2.1 If a node exists without __yxml property, insert a new node
|
||||
* 2.1 If a node exists without _yxml property, insert a new node
|
||||
* 2.2 If _contents.length < dom.childNodes.length, fill the
|
||||
* rest of _content with childNodes
|
||||
* 2.3 If a node was moved, delete it and
|
||||
@ -85,7 +85,7 @@ export function applyChangesFromDom (yxml) {
|
||||
const y = yxml._y
|
||||
let knownChildren =
|
||||
new Set(
|
||||
Array.prototype.map.call(yxml._dom.childNodes, child => child.__yxml)
|
||||
Array.prototype.map.call(yxml._dom.childNodes, child => child._yxml)
|
||||
.filter(id => id !== undefined)
|
||||
)
|
||||
// 1. Check if any of the nodes was deleted
|
||||
@ -101,7 +101,7 @@ export function applyChangesFromDom (yxml) {
|
||||
let expectedNode = iterateUntilUndeleted(yxml._start)
|
||||
for (let domCnt = 0; domCnt < len; domCnt++) {
|
||||
const child = childNodes[domCnt]
|
||||
const childYXml = child.__yxml
|
||||
const childYXml = child._yxml
|
||||
if (childYXml != null) {
|
||||
if (childYXml === false) {
|
||||
// should be ignored or is going to be deleted
|
||||
@ -112,7 +112,7 @@ export function applyChangesFromDom (yxml) {
|
||||
// 2.3 Not expected node
|
||||
if (childYXml._parent !== this) {
|
||||
// element is going to be deleted by its previous parent
|
||||
child.__yxml = null
|
||||
child._yxml = null
|
||||
} else {
|
||||
childYXml._delete(y)
|
||||
}
|
||||
|
@ -1,26 +1,31 @@
|
||||
export default class NamedEventHandler {
|
||||
constructor () {
|
||||
this._eventListener = {}
|
||||
this._eventListener = new Map()
|
||||
}
|
||||
on (name, f) {
|
||||
if (this._eventListener[name] == null) {
|
||||
this._eventListener[name] = []
|
||||
let fSet = this._eventListener.get(name)
|
||||
if (fSet === undefined) {
|
||||
fSet = new Set()
|
||||
this._eventListener.set(name, fSet)
|
||||
}
|
||||
this._eventListener[name].push(f)
|
||||
fSet.add(f)
|
||||
}
|
||||
off (name, f) {
|
||||
if (name == null || f == null) {
|
||||
throw new Error('You must specify event name and function!')
|
||||
}
|
||||
let listener = this._eventListener[name] || []
|
||||
this._eventListener[name] = listener.filter(e => e !== f)
|
||||
}
|
||||
emit (name, value) {
|
||||
let listener = this._eventListener[name] || []
|
||||
if (name === 'error' && listener.length === 0) {
|
||||
console.error(value)
|
||||
const listener = this._eventListener.get(name)
|
||||
if (listener !== undefined) {
|
||||
listener.remove(f)
|
||||
}
|
||||
}
|
||||
emit (name, ...args) {
|
||||
const listener = this._eventListener.get(name)
|
||||
if (listener !== undefined) {
|
||||
listener.forEach(f => f.apply(null, args))
|
||||
} else if (name === 'error') {
|
||||
console.error(args[0])
|
||||
}
|
||||
listener.forEach(l => l(value))
|
||||
}
|
||||
destroy () {
|
||||
this._eventListener = null
|
||||
|
@ -4,14 +4,20 @@ export function getRelativePosition (type, offset) {
|
||||
if (offset === 0) {
|
||||
return ['startof', type._id.user, type._id.clock]
|
||||
} else {
|
||||
let t = type.start
|
||||
while (t !== null && t.length < offset) {
|
||||
let t = type._start
|
||||
while (t !== null) {
|
||||
if (t._length >= offset) {
|
||||
return [t._id.user, t._id.clock + offset - 1]
|
||||
}
|
||||
if (t._right === null) {
|
||||
return [t._id.user, t._id.clock + t._length - 1]
|
||||
}
|
||||
if (!t._deleted) {
|
||||
offset -= t.length
|
||||
offset -= t._length
|
||||
}
|
||||
t = t._right
|
||||
}
|
||||
return [t._id.user, t._id.clock + offset - 1]
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,19 +29,20 @@ export function fromRelativePosition (y, rpos) {
|
||||
}
|
||||
} else {
|
||||
let offset = 0
|
||||
let struct = y.os.findNodeWithUpperBound(new ID(rpos[0], rpos[1]))
|
||||
let parent = struct._parent
|
||||
let struct = y.os.findNodeWithUpperBound(new ID(rpos[0], rpos[1])).val
|
||||
const parent = struct._parent
|
||||
if (parent._deleted) {
|
||||
return null
|
||||
}
|
||||
if (!struct.deleted) {
|
||||
offset = rpos[1] - struct._id.clock
|
||||
if (!struct._deleted) {
|
||||
offset = rpos[1] - struct._id.clock + 1
|
||||
}
|
||||
while (struct.left !== null) {
|
||||
struct = struct.left
|
||||
if (!struct.deleted) {
|
||||
struct = struct._left
|
||||
while (struct !== null) {
|
||||
if (!struct._deleted) {
|
||||
offset += struct._length
|
||||
}
|
||||
struct = struct._left
|
||||
}
|
||||
return {
|
||||
type: parent,
|
||||
|
6
src/Y.js
6
src/Y.js
@ -36,13 +36,13 @@ export default class Y extends NamedEventHandler {
|
||||
this.connected = true
|
||||
this._missingStructs = new Map()
|
||||
this._readyToIntegrate = []
|
||||
this._transactionsInProgress = 0
|
||||
this._transaction = null
|
||||
}
|
||||
_beforeChange () {}
|
||||
transact (f, remote = false) {
|
||||
let initialCall = this._transaction === null
|
||||
if (initialCall) {
|
||||
this.emit('beforeTransaction', this, remote)
|
||||
this._transaction = new Transaction(this)
|
||||
}
|
||||
try {
|
||||
@ -55,9 +55,9 @@ export default class Y extends NamedEventHandler {
|
||||
this._transaction.changedTypes.forEach(function (subs, type) {
|
||||
type._callObserver(subs, remote)
|
||||
})
|
||||
this._transaction = null
|
||||
// when all changes & events are processed, emit afterTransaction event
|
||||
this.emit('afterTransaction', this)
|
||||
this.emit('afterTransaction', this, remote)
|
||||
this._transaction = null
|
||||
}
|
||||
}
|
||||
// fake _start for root properties (y.set('name', type))
|
||||
|
@ -41,7 +41,7 @@ function getDeleteSet (y) {
|
||||
|
||||
export function attrsObject (dom) {
|
||||
let keys = []
|
||||
let yxml = dom.__yxml
|
||||
let yxml = dom._yxml
|
||||
for (let i = 0; i < dom.attributes.length; i++) {
|
||||
keys.push(dom.attributes[i].name)
|
||||
}
|
||||
@ -60,7 +60,7 @@ export function domToJson (dom) {
|
||||
} else if (dom.nodeType === document.ELEMENT_NODE) {
|
||||
let attributes = attrsObject(dom)
|
||||
let children = Array.from(dom.childNodes.values())
|
||||
.filter(d => d.__yxml !== false)
|
||||
.filter(d => d._yxml !== false)
|
||||
.map(domToJson)
|
||||
return {
|
||||
name: dom.nodeName,
|
||||
|
Loading…
x
Reference in New Issue
Block a user