made simple one-time move work
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
ContentType,
|
||||
ContentDoc,
|
||||
Doc,
|
||||
ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
|
||||
RelativePosition, ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
const lengthExceeded = error.create('Length exceeded!')
|
||||
@@ -122,8 +122,8 @@ export class ListIterator {
|
||||
len += this.rel
|
||||
this.rel = 0
|
||||
}
|
||||
while (item && !this.reachedEnd && (len > 0 || (len === 0 && (!item.countable || item.deleted)))) {
|
||||
if (item.countable && !item.deleted && item.moved === this.currMove) {
|
||||
while (item && !this.reachedEnd && (len > 0 || (len === 0 && (!item.countable || item.deleted || item === this.currMoveEnd)))) {
|
||||
if (item.countable && !item.deleted && item.moved === this.currMove && len > 0) {
|
||||
len -= item.length
|
||||
if (len < 0) {
|
||||
this.rel = item.length + len
|
||||
@@ -228,10 +228,10 @@ export class ListIterator {
|
||||
_slice (tr, len, value, slice, concat) {
|
||||
this.index += len
|
||||
while (len > 0 && !this.reachedEnd) {
|
||||
while (this.nextItem && this.nextItem.countable && !this.reachedEnd && len > 0) {
|
||||
if (!this.nextItem.deleted) {
|
||||
while (this.nextItem && this.nextItem.countable && !this.reachedEnd && len > 0 && this.nextItem !== this.currMoveEnd) {
|
||||
if (!this.nextItem.deleted && this.nextItem.moved === this.currMove) {
|
||||
const item = this.nextItem
|
||||
const slicedContent = slice(this.nextItem.content, this.rel, len)
|
||||
const slicedContent = slice(item.content, this.rel, len)
|
||||
len -= slicedContent.length
|
||||
value = concat(value, slicedContent)
|
||||
if (item.length !== slicedContent.length) {
|
||||
@@ -268,18 +268,16 @@ export class ListIterator {
|
||||
const sm = this.type._searchMarker
|
||||
let item = this.nextItem
|
||||
while (len > 0 && !this.reachedEnd) {
|
||||
while (item && item.countable && !this.reachedEnd && len > 0) {
|
||||
if (!item.deleted) {
|
||||
if (this.rel > 0) {
|
||||
item = getItemCleanStart(tr, createID(item.id.client, item.id.clock + this.rel))
|
||||
this.rel = 0
|
||||
}
|
||||
if (len < item.length) {
|
||||
getItemCleanStart(tr, createID(item.id.client, item.id.clock + len))
|
||||
}
|
||||
len -= item.length
|
||||
item.delete(tr)
|
||||
while (item && !item.deleted && item.countable && !this.reachedEnd && len > 0) {
|
||||
if (this.rel > 0) {
|
||||
item = getItemCleanStart(tr, createID(item.id.client, item.id.clock + this.rel))
|
||||
this.rel = 0
|
||||
}
|
||||
if (len < item.length) {
|
||||
getItemCleanStart(tr, createID(item.id.client, item.id.clock + len))
|
||||
}
|
||||
len -= item.length
|
||||
item.delete(tr)
|
||||
if (item.right) {
|
||||
item = item.right
|
||||
} else {
|
||||
@@ -300,12 +298,8 @@ export class ListIterator {
|
||||
|
||||
/**
|
||||
* @param {Transaction} tr
|
||||
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
|
||||
*/
|
||||
insertArrayValue (tr, content) {
|
||||
/**
|
||||
* @type {Item | null}
|
||||
*/
|
||||
_splitRel (tr) {
|
||||
if (this.rel > 0) {
|
||||
/**
|
||||
* @type {ID}
|
||||
@@ -314,6 +308,14 @@ export class ListIterator {
|
||||
this.nextItem = getItemCleanStart(tr, createID(itemid.client, itemid.clock + this.rel))
|
||||
this.rel = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} tr
|
||||
* @param {Array<AbstractContent>} content
|
||||
*/
|
||||
insertContents (tr, content) {
|
||||
this._splitRel(tr)
|
||||
const sm = this.type._searchMarker
|
||||
const parent = this.type
|
||||
const store = tr.doc.store
|
||||
@@ -327,18 +329,55 @@ export class ListIterator {
|
||||
* @type {Item | null}
|
||||
*/
|
||||
let left = this.left
|
||||
content.forEach(c => {
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, c)
|
||||
left.integrate(tr, 0)
|
||||
})
|
||||
if (right === null && left !== null) {
|
||||
this.nextItem = left
|
||||
this.reachedEnd = true
|
||||
} else {
|
||||
this.nextItem = right
|
||||
}
|
||||
if (sm) {
|
||||
updateMarkerChanges(sm, this.index, content.length, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} tr
|
||||
* @param {RelativePosition} start
|
||||
* @param {RelativePosition} end
|
||||
*/
|
||||
insertMove (tr, start, end) {
|
||||
this.insertContents(tr, [new ContentMove(start, end, 1)]) // @todo adjust priority
|
||||
// @todo is there a better alrogirthm to update searchmarkers? We could simply remove the markers that are in the updated range.
|
||||
// Also note that searchmarkers are updated in insertContents as well.
|
||||
const sm = this.type._searchMarker
|
||||
if (sm) sm.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} tr
|
||||
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} values
|
||||
*/
|
||||
insertArrayValue (tr, values) {
|
||||
this._splitRel(tr)
|
||||
/**
|
||||
* @type {Array<AbstractContent>}
|
||||
*/
|
||||
const contents = []
|
||||
/**
|
||||
* @type {Array<Object|Array<any>|number|null>}
|
||||
*/
|
||||
let jsonContent = []
|
||||
const packJsonContent = () => {
|
||||
if (jsonContent.length > 0) {
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent))
|
||||
left.integrate(tr, 0)
|
||||
contents.push(new ContentAny(jsonContent))
|
||||
jsonContent = []
|
||||
}
|
||||
}
|
||||
content.forEach(c => {
|
||||
values.forEach(c => {
|
||||
if (c === null) {
|
||||
jsonContent.push(c)
|
||||
} else {
|
||||
@@ -355,17 +394,14 @@ export class ListIterator {
|
||||
switch (c.constructor) {
|
||||
case Uint8Array:
|
||||
case ArrayBuffer:
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
|
||||
left.integrate(tr, 0)
|
||||
contents.push(new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
|
||||
break
|
||||
case Doc:
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
||||
left.integrate(tr, 0)
|
||||
contents.push(new ContentDoc(/** @type {Doc} */ (c)))
|
||||
break
|
||||
default:
|
||||
if (c instanceof AbstractType) {
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
|
||||
left.integrate(tr, 0)
|
||||
contents.push(new ContentType(c))
|
||||
} else {
|
||||
throw new Error('Unexpected content type in insert operation')
|
||||
}
|
||||
@@ -374,16 +410,8 @@ export class ListIterator {
|
||||
}
|
||||
})
|
||||
packJsonContent()
|
||||
if (right === null && left !== null) {
|
||||
this.nextItem = left
|
||||
this.reachedEnd = true
|
||||
} else {
|
||||
this.nextItem = right
|
||||
}
|
||||
if (sm) {
|
||||
updateMarkerChanges(sm, this.index, content.length, this)
|
||||
}
|
||||
this.index += content.length
|
||||
this.insertContents(tr, contents)
|
||||
this.index += values.length
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
import {
|
||||
isDeleted,
|
||||
Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||
getMovedCoords,
|
||||
ContentMove, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as set from 'lib0/set'
|
||||
@@ -153,62 +154,105 @@ export class YEvent {
|
||||
get changes () {
|
||||
let changes = this._changes
|
||||
if (changes === null) {
|
||||
const target = this.target
|
||||
const added = set.create()
|
||||
const deleted = set.create()
|
||||
/**
|
||||
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
||||
*/
|
||||
const delta = []
|
||||
changes = {
|
||||
added,
|
||||
deleted,
|
||||
delta,
|
||||
keys: this.keys
|
||||
}
|
||||
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||
if (changed.has(null)) {
|
||||
this.transaction.doc.transact(tr => {
|
||||
const target = this.target
|
||||
const added = set.create()
|
||||
const deleted = set.create()
|
||||
/**
|
||||
* @type {any}
|
||||
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
||||
*/
|
||||
let lastOp = null
|
||||
const packOp = () => {
|
||||
if (lastOp) {
|
||||
delta.push(lastOp)
|
||||
}
|
||||
const delta = []
|
||||
changes = {
|
||||
added,
|
||||
deleted,
|
||||
delta,
|
||||
keys: this.keys
|
||||
}
|
||||
for (let item = target._start; item !== null; item = item.right) {
|
||||
if (item.deleted) {
|
||||
if (this.deletes(item) && !this.adds(item)) {
|
||||
if (lastOp === null || lastOp.delete === undefined) {
|
||||
packOp()
|
||||
lastOp = { delete: 0 }
|
||||
}
|
||||
lastOp.delete += item.length
|
||||
deleted.add(item)
|
||||
} // else nop
|
||||
} else {
|
||||
if (this.adds(item)) {
|
||||
if (lastOp === null || lastOp.insert === undefined) {
|
||||
packOp()
|
||||
lastOp = { insert: [] }
|
||||
}
|
||||
lastOp.insert = lastOp.insert.concat(item.content.getContent())
|
||||
added.add(item)
|
||||
} else {
|
||||
if (lastOp === null || lastOp.retain === undefined) {
|
||||
packOp()
|
||||
lastOp = { retain: 0 }
|
||||
}
|
||||
lastOp.retain += item.length
|
||||
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||
if (changed.has(null)) {
|
||||
/**
|
||||
* @type {Array<{ end: Item | null, move: Item | null, isNew : boolean }>}
|
||||
*/
|
||||
const movedStack = []
|
||||
/**
|
||||
* @type {Item | null}
|
||||
*/
|
||||
let currMove = null
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
let currMoveIsNew = false
|
||||
/**
|
||||
* @type {Item | null}
|
||||
*/
|
||||
let currMoveEnd = null
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
let lastOp = null
|
||||
const packOp = () => {
|
||||
if (lastOp) {
|
||||
delta.push(lastOp)
|
||||
}
|
||||
}
|
||||
for (let item = target._start; item !== null;) {
|
||||
if (item === currMoveEnd) {
|
||||
item = currMove
|
||||
const { end, move, isNew } = movedStack.pop() || { end: null, move: null, isNew: false }
|
||||
currMoveIsNew = isNew
|
||||
currMoveEnd = end
|
||||
currMove = move
|
||||
} else if (item.content.constructor === ContentMove) {
|
||||
if (item.moved === currMove) {
|
||||
movedStack.push({ end: currMoveEnd, move: currMove, isNew: currMoveIsNew })
|
||||
const { start, end } = getMovedCoords(item.content, tr)
|
||||
currMove = item
|
||||
currMoveEnd = end
|
||||
currMoveIsNew = this.adds(item)
|
||||
item = start
|
||||
continue // do not move to item.right
|
||||
}
|
||||
} else if (item.moved !== currMove) {
|
||||
if (!currMoveIsNew && item.countable && !this.adds(item)) {
|
||||
if (lastOp === null || lastOp.delete === undefined) {
|
||||
packOp()
|
||||
lastOp = { delete: 0 }
|
||||
}
|
||||
lastOp.delete += item.length
|
||||
}
|
||||
} else if (item.deleted) {
|
||||
if (!currMoveIsNew && this.deletes(item) && !this.adds(item)) {
|
||||
if (lastOp === null || lastOp.delete === undefined) {
|
||||
packOp()
|
||||
lastOp = { delete: 0 }
|
||||
}
|
||||
lastOp.delete += item.length
|
||||
deleted.add(item)
|
||||
}
|
||||
} else {
|
||||
if (currMoveIsNew || this.adds(item)) {
|
||||
if (lastOp === null || lastOp.insert === undefined) {
|
||||
packOp()
|
||||
lastOp = { insert: [] }
|
||||
}
|
||||
lastOp.insert = lastOp.insert.concat(item.content.getContent())
|
||||
added.add(item)
|
||||
} else {
|
||||
if (lastOp === null || lastOp.retain === undefined) {
|
||||
packOp()
|
||||
lastOp = { retain: 0 }
|
||||
}
|
||||
lastOp.retain += item.length
|
||||
}
|
||||
}
|
||||
item = /** @type {Item} */ (item).right
|
||||
}
|
||||
if (lastOp !== null && lastOp.retain === undefined) {
|
||||
packOp()
|
||||
}
|
||||
}
|
||||
if (lastOp !== null && lastOp.retain === undefined) {
|
||||
packOp()
|
||||
}
|
||||
}
|
||||
this._changes = changes
|
||||
this._changes = changes
|
||||
})
|
||||
}
|
||||
return /** @type {any} */ (changes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user