implement some of the commented todos
This commit is contained in:
parent
1b06f59d1c
commit
52ec698635
@ -9,7 +9,7 @@ import 'codemirror/mode/javascript/javascript.js'
|
|||||||
|
|
||||||
const provider = new WebsocketProvider(conf.serverAddress)
|
const provider = new WebsocketProvider(conf.serverAddress)
|
||||||
const ydocument = provider.get('codemirror')
|
const ydocument = provider.get('codemirror')
|
||||||
const ytext = ydocument.define('codemirror', Y.Text)
|
const ytext = ydocument.getText('codemirror')
|
||||||
|
|
||||||
const editor = new CodeMirror(document.querySelector('#container'), {
|
const editor = new CodeMirror(document.querySelector('#container'), {
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import * as Y from 'yjs'
|
|
||||||
import { WebsocketProvider } from 'y-websocket'
|
import { WebsocketProvider } from 'y-websocket'
|
||||||
import { TextareaBinding } from 'y-textarea'
|
import { TextareaBinding } from 'y-textarea'
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ import * as conf from './exampleConfig.js'
|
|||||||
|
|
||||||
const provider = new WebsocketProvider(conf.serverAddress)
|
const provider = new WebsocketProvider(conf.serverAddress)
|
||||||
const ydocument = provider.get('textarea')
|
const ydocument = provider.get('textarea')
|
||||||
const type = ydocument.define('textarea', Y.Text)
|
const type = ydocument.getText('textarea')
|
||||||
const textarea = document.querySelector('textarea')
|
const textarea = document.querySelector('textarea')
|
||||||
const binding = new TextareaBinding(type, textarea)
|
const binding = new TextareaBinding(type, textarea)
|
||||||
|
|
||||||
|
@ -56,8 +56,8 @@ export default [{
|
|||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
paths: path => {
|
paths: path => {
|
||||||
if (/^funlib\//.test(path)) {
|
if (/^lib0\//.test(path)) {
|
||||||
return `lib0/dist${path.slice(6)}`
|
return `lib0/dist/${path.slice(5)}`
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -85,9 +85,8 @@ export default [{
|
|||||||
}),
|
}),
|
||||||
commonjs()
|
commonjs()
|
||||||
]
|
]
|
||||||
}
|
}, {
|
||||||
/* {
|
input: ['./examples/codemirror.js', './examples/textarea.js'/*, './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'*/], // fs.readdirSync('./examples').filter(file => /(?<!\.(test|config))\.js$/.test(file)).map(file => './examples/' + file),
|
||||||
input: ['./examples/codemirror.js', './examples/textarea.js', './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'], // fs.readdirSync('./examples').filter(file => /(?<!\.(test|config))\.js$/.test(file)).map(file => './examples/' + file),
|
|
||||||
output: {
|
output: {
|
||||||
dir: 'examples/build',
|
dir: 'examples/build',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
@ -103,4 +102,4 @@ export default [{
|
|||||||
commonjs(),
|
commonjs(),
|
||||||
...minificationPlugins
|
...minificationPlugins
|
||||||
]
|
]
|
||||||
} */]
|
}]
|
||||||
|
@ -15,6 +15,8 @@ export {
|
|||||||
compareRelativePositions,
|
compareRelativePositions,
|
||||||
writeRelativePosition,
|
writeRelativePosition,
|
||||||
readRelativePosition,
|
readRelativePosition,
|
||||||
|
createRelativePositionFromJSON,
|
||||||
|
toAbsolutePosition,
|
||||||
AbsolutePosition,
|
AbsolutePosition,
|
||||||
RelativePosition,
|
RelativePosition,
|
||||||
ID,
|
ID,
|
||||||
|
@ -29,14 +29,11 @@ import * as binary from 'lib0/binary.js'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Split leftItem into two items
|
* Split leftItem into two items
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {AbstractItem} leftItem
|
* @param {AbstractItem} leftItem
|
||||||
* @param {number} diff
|
* @param {number} diff
|
||||||
* @return {AbstractItem}
|
* @return {AbstractItem}
|
||||||
*
|
|
||||||
* @todo remove store param0
|
|
||||||
*/
|
*/
|
||||||
export const splitItem = (store, leftItem, diff) => {
|
export const splitItem = (leftItem, diff) => {
|
||||||
const id = leftItem.id
|
const id = leftItem.id
|
||||||
// create rightItem
|
// create rightItem
|
||||||
const rightItem = leftItem.copy(
|
const rightItem = leftItem.copy(
|
||||||
@ -182,7 +179,7 @@ export class AbstractItem extends AbstractStruct {
|
|||||||
if (o.id.client < id.client) {
|
if (o.id.client < id.client) {
|
||||||
this.left = o
|
this.left = o
|
||||||
conflictingItems.clear()
|
conflictingItems.clear()
|
||||||
} // TODO: verify break else?
|
}
|
||||||
} else if (o.origin !== null && itemsBeforeOrigin.has(getItem(store, o.origin))) {
|
} else if (o.origin !== null && itemsBeforeOrigin.has(getItem(store, o.origin))) {
|
||||||
// case 2
|
// case 2
|
||||||
if (o.origin === null || !conflictingItems.has(getItem(store, o.origin))) {
|
if (o.origin === null || !conflictingItems.has(getItem(store, o.origin))) {
|
||||||
@ -192,9 +189,6 @@ export class AbstractItem extends AbstractStruct {
|
|||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// TODO: experiment with rightOrigin.
|
|
||||||
// Then you could basically omit conflictingItems!
|
|
||||||
// Note: you probably can't use right_origin in every case.. only when setting _left
|
|
||||||
o = o.right
|
o = o.right
|
||||||
}
|
}
|
||||||
// reconnect left/right + update parent map/start if necessary
|
// reconnect left/right + update parent map/start if necessary
|
||||||
@ -340,8 +334,6 @@ export class AbstractItem extends AbstractStruct {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the last content address of this Item.
|
* Computes the last content address of this Item.
|
||||||
* TODO: do still need this?
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
get lastId () {
|
get lastId () {
|
||||||
return createID(this.id.client, this.id.clock + this.length - 1)
|
return createID(this.id.client, this.id.clock + this.length - 1)
|
||||||
@ -378,11 +370,10 @@ export class AbstractItem extends AbstractStruct {
|
|||||||
*
|
*
|
||||||
* This method should only be cally by StructStore.
|
* This method should only be cally by StructStore.
|
||||||
*
|
*
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {number} diff
|
* @param {number} diff
|
||||||
* @return {AbstractItem}
|
* @return {AbstractItem}
|
||||||
*/
|
*/
|
||||||
splitAt (store, diff) {
|
splitAt (diff) {
|
||||||
throw new Error('unimplemented')
|
throw new Error('unimplemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ import * as encoding from 'lib0/encoding.js'
|
|||||||
|
|
||||||
export const structGCRefNumber = 0
|
export const structGCRefNumber = 0
|
||||||
|
|
||||||
// TODO should have the same base class as Item
|
|
||||||
export class GC extends AbstractStruct {
|
export class GC extends AbstractStruct {
|
||||||
/**
|
/**
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
* @module structs
|
* @module structs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: ItemBinary should be able to merge with right (similar to other items). Or the other items (ItemJSON) should not be able to merge - extra byte + consistency
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractItem,
|
AbstractItem,
|
||||||
AbstractItemRef,
|
AbstractItemRef,
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
* @module structs
|
* @module structs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: ItemBinary should be able to merge with right (similar to other items). Or the other items (ItemJSON) should not be able to merge - extra byte + consistency
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractItem,
|
AbstractItem,
|
||||||
AbstractItemRef,
|
AbstractItemRef,
|
||||||
@ -59,15 +57,14 @@ export class ItemDeleted extends AbstractItem {
|
|||||||
addToDeleteSet(transaction.deleteSet, this.id, this.length)
|
addToDeleteSet(transaction.deleteSet, this.id, this.length)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {number} diff
|
* @param {number} diff
|
||||||
*/
|
*/
|
||||||
splitAt (store, diff) {
|
splitAt (diff) {
|
||||||
/**
|
/**
|
||||||
* @type {ItemDeleted}
|
* @type {ItemDeleted}
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const right = splitItem(store, this, diff)
|
const right = splitItem(this, diff)
|
||||||
right._len -= diff
|
right._len -= diff
|
||||||
this._len = diff
|
this._len = diff
|
||||||
return right
|
return right
|
||||||
|
@ -54,15 +54,14 @@ export class ItemJSON extends AbstractItem {
|
|||||||
return this.content
|
return this.content
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {number} diff
|
* @param {number} diff
|
||||||
*/
|
*/
|
||||||
splitAt (store, diff) {
|
splitAt (diff) {
|
||||||
/**
|
/**
|
||||||
* @type {ItemJSON}
|
* @type {ItemJSON}
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const right = splitItem(store, this, diff)
|
const right = splitItem(this, diff)
|
||||||
right.content = this.content.splice(diff)
|
right.content = this.content.splice(diff)
|
||||||
return right
|
return right
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import * as encoding from 'lib0/encoding.js'
|
|||||||
import * as decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
export const structStringRefNumber = 6
|
export const structStringRefNumber = 6
|
||||||
// TODO: we can probably try to omit rightOrigin. We can just use .right
|
|
||||||
export class ItemString extends AbstractItem {
|
export class ItemString extends AbstractItem {
|
||||||
/**
|
/**
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
@ -53,16 +53,15 @@ export class ItemString extends AbstractItem {
|
|||||||
return this.string.length
|
return this.string.length
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {number} diff
|
* @param {number} diff
|
||||||
* @return {ItemString}
|
* @return {ItemString}
|
||||||
*/
|
*/
|
||||||
splitAt (store, diff) {
|
splitAt (diff) {
|
||||||
/**
|
/**
|
||||||
* @type {ItemString}
|
* @type {ItemString}
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const right = splitItem(store, this, diff)
|
const right = splitItem(this, diff)
|
||||||
right.string = this.string.slice(diff)
|
right.string = this.string.slice(diff)
|
||||||
this.string = this.string.slice(0, diff)
|
this.string = this.string.slice(0, diff)
|
||||||
return right
|
return right
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
readYXmlFragment,
|
readYXmlFragment,
|
||||||
readYXmlHook,
|
readYXmlHook,
|
||||||
readYXmlText,
|
readYXmlText,
|
||||||
StructStore, Y, GC, ItemDeleted, Transaction, ID, AbstractType // eslint-disable-line
|
StructStore, Y, GC, Transaction, ID, AbstractType // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
||||||
@ -83,7 +83,7 @@ export class ItemType extends AbstractItem {
|
|||||||
* @param {ID | null} rightOrigin
|
* @param {ID | null} rightOrigin
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {string | null} parentSub
|
* @param {string | null} parentSub
|
||||||
* @return {AbstractItem} TODO, returns itemtype
|
* @return {ItemType}
|
||||||
*/
|
*/
|
||||||
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
|
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
|
||||||
return new ItemType(id, left, origin, right, rightOrigin, parent, parentSub, this.type._copy())
|
return new ItemType(id, left, origin, right, rightOrigin, parent, parentSub, this.type._copy())
|
||||||
|
@ -22,6 +22,29 @@ import * as iterator from 'lib0/iterator.js'
|
|||||||
import * as error from 'lib0/error.js'
|
import * as error from 'lib0/error.js'
|
||||||
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call event listeners with an event. This will also add an event to all
|
||||||
|
* parents (for `.observeDeep` handlers).
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @template EventType
|
||||||
|
* @param {AbstractType<EventType>} type
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @param {EventType} event
|
||||||
|
*/
|
||||||
|
export const callTypeObservers = (type, transaction, event) => {
|
||||||
|
callEventHandlerListeners(type._eH, [event, transaction])
|
||||||
|
const changedParentTypes = transaction.changedParentTypes
|
||||||
|
while (true) {
|
||||||
|
// @ts-ignore
|
||||||
|
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
||||||
|
if (type._item === null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
type = type._item.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template EventType
|
* @template EventType
|
||||||
* Abstract Yjs Type class
|
* Abstract Yjs Type class
|
||||||
@ -100,42 +123,14 @@ export class AbstractType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates YEvent and calls _callEventHandler.
|
* Creates YEvent and calls all type observers.
|
||||||
* Must be implemented by each type.
|
* Must be implemented by each type.
|
||||||
* @todo Rename to _createEvent
|
|
||||||
* @private
|
* @private
|
||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) { /* skip if no type is specified */ }
|
||||||
throw error.methodUnimplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call event listeners with an event. This will also add an event to all
|
|
||||||
* parents (for `.observeDeep` handlers).
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @param {any} event
|
|
||||||
*/
|
|
||||||
_callEventHandler (transaction, event) {
|
|
||||||
callEventHandlerListeners(this._eH, [event, transaction])
|
|
||||||
const changedParentTypes = transaction.changedParentTypes
|
|
||||||
/**
|
|
||||||
* @type {AbstractType<EventType>}
|
|
||||||
*/
|
|
||||||
let type = this
|
|
||||||
while (true) {
|
|
||||||
// @ts-ignore
|
|
||||||
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
|
||||||
if (type._item === null) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
type = type._item.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe all events that are created on this type.
|
* Observe all events that are created on this type.
|
||||||
|
@ -13,6 +13,8 @@ import {
|
|||||||
typeArrayDelete,
|
typeArrayDelete,
|
||||||
typeArrayMap,
|
typeArrayMap,
|
||||||
YArrayRefID,
|
YArrayRefID,
|
||||||
|
callTypeObservers,
|
||||||
|
transact,
|
||||||
Y, Transaction, ItemType, // eslint-disable-line
|
Y, Transaction, ItemType, // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ export class YArray extends AbstractType {
|
|||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
this._callEventHandler(transaction, new YArrayEvent(this, transaction))
|
callTypeObservers(this, transaction, new YArrayEvent(this, transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +143,7 @@ export class YArray extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
delete (index, length = 1) {
|
delete (index, length = 1) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeArrayDelete(transaction, this, index, length)
|
typeArrayDelete(transaction, this, index, length)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -168,7 +170,7 @@ export class YArray extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
insert (index, content) {
|
insert (index, content) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeArrayInsertGenerics(transaction, this, index, content)
|
typeArrayInsertGenerics(transaction, this, index, content)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
typeMapHas,
|
typeMapHas,
|
||||||
createMapIterator,
|
createMapIterator,
|
||||||
YMapRefID,
|
YMapRefID,
|
||||||
|
callTypeObservers,
|
||||||
|
transact,
|
||||||
Y, Transaction, ItemType, // eslint-disable-line
|
Y, Transaction, ItemType, // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ export class YMap extends AbstractType {
|
|||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
this._callEventHandler(transaction, new YMapEvent(this, transaction, parentSubs))
|
callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +127,7 @@ export class YMap extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
delete (key) {
|
delete (key) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeMapDelete(transaction, this, key)
|
typeMapDelete(transaction, this, key)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -142,7 +144,7 @@ export class YMap extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
set (key, value) {
|
set (key, value) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeMapSet(transaction, this, key, value)
|
typeMapSet(transaction, this, key, value)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,8 @@ import {
|
|||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
isVisible,
|
isVisible,
|
||||||
YTextRefID,
|
YTextRefID,
|
||||||
|
callTypeObservers,
|
||||||
|
transact,
|
||||||
Y, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line
|
Y, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -295,7 +297,6 @@ const deleteText = (transaction, parent, left, right, currentAttributes, length)
|
|||||||
return { left, right }
|
return { left, right }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: In the quill delta representation we should also use the format {ops:[..]}
|
|
||||||
/**
|
/**
|
||||||
* The Quill Delta format represents changes on a text document with
|
* The Quill Delta format represents changes on a text document with
|
||||||
* formatting information. For mor information visit {@link https://quilljs.com/docs/delta/|Quill Delta}
|
* formatting information. For mor information visit {@link https://quilljs.com/docs/delta/|Quill Delta}
|
||||||
@ -354,7 +355,7 @@ class YTextEvent extends YEvent {
|
|||||||
if (this._delta === null) {
|
if (this._delta === null) {
|
||||||
const y = this.target._y
|
const y = this.target._y
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
y.transact(transaction => {
|
transact(y, transaction => {
|
||||||
/**
|
/**
|
||||||
* @type {Array<{delete:number|undefined,retain:number|undefined,insert:string|undefined,attributes:Object<string,any>}>}
|
* @type {Array<{delete:number|undefined,retain:number|undefined,insert:string|undefined,attributes:Object<string,any>}>}
|
||||||
*/
|
*/
|
||||||
@ -586,7 +587,7 @@ export class YText extends AbstractType {
|
|||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
this._callEventHandler(transaction, new YTextEvent(this, transaction))
|
callTypeObservers(this, transaction, new YTextEvent(this, transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
toDom () {
|
toDom () {
|
||||||
@ -657,7 +658,7 @@ export class YText extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
applyDelta (delta) {
|
applyDelta (delta) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
/**
|
/**
|
||||||
* @type {{left:AbstractItem|null,right:AbstractItem|null}}
|
* @type {{left:AbstractItem|null,right:AbstractItem|null}}
|
||||||
*/
|
*/
|
||||||
@ -772,7 +773,7 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
const y = this._y
|
const y = this._y
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
y.transact(transaction => {
|
transact(y, transaction => {
|
||||||
const {left, right, currentAttributes} = findPosition(transaction, y.store, this, index)
|
const {left, right, currentAttributes} = findPosition(transaction, y.store, this, index)
|
||||||
insertText(transaction, this, left, right, currentAttributes, text, attributes)
|
insertText(transaction, this, left, right, currentAttributes, text, attributes)
|
||||||
})
|
})
|
||||||
@ -795,7 +796,7 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
const y = this._y
|
const y = this._y
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
y.transact(transaction => {
|
transact(y, transaction => {
|
||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
||||||
})
|
})
|
||||||
@ -816,7 +817,7 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
const y = this._y
|
const y = this._y
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
y.transact(transaction => {
|
transact(y, transaction => {
|
||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
deleteText(transaction, this, left, right, currentAttributes, length)
|
deleteText(transaction, this, left, right, currentAttributes, length)
|
||||||
})
|
})
|
||||||
@ -836,7 +837,7 @@ export class YText extends AbstractType {
|
|||||||
format (index, length, attributes) {
|
format (index, length, attributes) {
|
||||||
const y = this._y
|
const y = this._y
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
y.transact(transaction => {
|
transact(y, transaction => {
|
||||||
let { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
let { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
if (right === null) {
|
if (right === null) {
|
||||||
return
|
return
|
||||||
|
@ -14,6 +14,8 @@ import {
|
|||||||
typeMapSet,
|
typeMapSet,
|
||||||
typeMapDelete,
|
typeMapDelete,
|
||||||
YXmlElementRefID,
|
YXmlElementRefID,
|
||||||
|
callTypeObservers,
|
||||||
|
transact,
|
||||||
Y, Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line
|
Y, Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -192,7 +194,7 @@ export class YXmlFragment extends AbstractType {
|
|||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, transaction))
|
callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
toString () {
|
toString () {
|
||||||
@ -329,7 +331,7 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*/
|
*/
|
||||||
removeAttribute (attributeName) {
|
removeAttribute (attributeName) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeMapDelete(transaction, this, attributeName)
|
typeMapDelete(transaction, this, attributeName)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -348,7 +350,7 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*/
|
*/
|
||||||
setAttribute (attributeName, attributeValue) {
|
setAttribute (attributeName, attributeValue) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeMapSet(transaction, this, attributeName, attributeValue)
|
typeMapSet(transaction, this, attributeName, attributeValue)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -395,7 +397,7 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*/
|
*/
|
||||||
insert (index, content) {
|
insert (index, content) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeArrayInsertGenerics(transaction, this, index, content)
|
typeArrayInsertGenerics(transaction, this, index, content)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -412,7 +414,7 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*/
|
*/
|
||||||
delete (index, length = 1) {
|
delete (index, length = 1) {
|
||||||
if (this._y !== null) {
|
if (this._y !== null) {
|
||||||
this._y.transact(transaction => {
|
transact(this._y, transaction => {
|
||||||
typeArrayDelete(transaction, this, index, length)
|
typeArrayDelete(transaction, this, index, length)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -193,7 +193,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
|
|||||||
let struct = structs[index]
|
let struct = structs[index]
|
||||||
// split the first item if necessary
|
// split the first item if necessary
|
||||||
if (!struct.deleted && struct.id.clock < clock) {
|
if (!struct.deleted && struct.id.clock < clock) {
|
||||||
structs.splice(index + 1, 0, struct.splitAt(store, clock - struct.id.clock))
|
structs.splice(index + 1, 0, struct.splitAt(clock - struct.id.clock))
|
||||||
index++ // increase we now want to use the next struct
|
index++ // increase we now want to use the next struct
|
||||||
}
|
}
|
||||||
while (index < structs.length) {
|
while (index < structs.length) {
|
||||||
@ -202,7 +202,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
|
|||||||
if (struct.id.clock < clock + len) {
|
if (struct.id.clock < clock + len) {
|
||||||
if (!struct.deleted) {
|
if (!struct.deleted) {
|
||||||
if (clock + len < struct.id.clock + struct.length) {
|
if (clock + len < struct.id.clock + struct.length) {
|
||||||
structs.splice(index, 0, struct.splitAt(store, clock + len - struct.id.clock))
|
structs.splice(index, 0, struct.splitAt(clock + len - struct.id.clock))
|
||||||
}
|
}
|
||||||
struct.delete(transaction)
|
struct.delete(transaction)
|
||||||
}
|
}
|
||||||
|
@ -24,27 +24,13 @@ export class ID {
|
|||||||
this.clock = clock
|
this.clock = clock
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @return {ID}
|
* @deprecated
|
||||||
|
* @todo remove and adapt relative position implementation
|
||||||
*/
|
*/
|
||||||
clone () {
|
toJSON () {
|
||||||
return new ID(this.client, this.clock)
|
return {
|
||||||
}
|
client: this.client,
|
||||||
/**
|
clock: this.clock
|
||||||
* @param {ID} id
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
equals (id) {
|
|
||||||
return id !== null && id.client === this.client && id.clock === this.clock
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {ID} id
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
lessThan (id) {
|
|
||||||
if (id.constructor === ID) {
|
|
||||||
return this.client < id.client || (this.client === id.client && this.clock < id.clock)
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ export const getItemCleanStart = (store, id) => {
|
|||||||
*/
|
*/
|
||||||
let struct = structs[index]
|
let struct = structs[index]
|
||||||
if (struct.id.clock < id.clock && struct.constructor !== GC) {
|
if (struct.id.clock < id.clock && struct.constructor !== GC) {
|
||||||
struct = struct.splitAt(store, id.clock - struct.id.clock)
|
struct = struct.splitAt(id.clock - struct.id.clock)
|
||||||
structs.splice(index + 1, 0, struct)
|
structs.splice(index + 1, 0, struct)
|
||||||
}
|
}
|
||||||
return struct
|
return struct
|
||||||
@ -198,7 +198,7 @@ export const getItemCleanEnd = (store, id) => {
|
|||||||
const index = findIndexSS(structs, id.clock)
|
const index = findIndexSS(structs, id.clock)
|
||||||
const struct = structs[index]
|
const struct = structs[index]
|
||||||
if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) {
|
if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) {
|
||||||
structs.splice(index + 1, 0, struct.splitAt(store, id.clock - struct.id.clock + 1))
|
structs.splice(index + 1, 0, struct.splitAt(id.clock - struct.id.clock + 1))
|
||||||
}
|
}
|
||||||
return struct
|
return struct
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,16 @@ import {
|
|||||||
DeleteSet,
|
DeleteSet,
|
||||||
sortAndMergeDeleteSet,
|
sortAndMergeDeleteSet,
|
||||||
getStates,
|
getStates,
|
||||||
|
findIndexSS,
|
||||||
|
callEventHandlerListeners,
|
||||||
|
AbstractItem,
|
||||||
|
ItemDeleted,
|
||||||
AbstractType, AbstractStruct, YEvent, Y // eslint-disable-line
|
AbstractType, AbstractStruct, YEvent, Y // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import * as map from 'lib0/map.js'
|
import * as map from 'lib0/map.js'
|
||||||
|
import * as math from 'lib0/math.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transaction is created for every change on the Yjs model. It is possible
|
* A transaction is created for every change on the Yjs model. It is possible
|
||||||
@ -108,3 +113,119 @@ export const nextID = transaction => {
|
|||||||
const y = transaction.y
|
const y = transaction.y
|
||||||
return createID(y.clientID, getState(y.store, y.clientID))
|
return createID(y.clientID, getState(y.store, y.clientID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Y} y
|
||||||
|
* @param {function(Transaction):void} f
|
||||||
|
*/
|
||||||
|
export const transact = (y, f) => {
|
||||||
|
let initialCall = false
|
||||||
|
if (y._transaction === null) {
|
||||||
|
initialCall = true
|
||||||
|
y._transaction = new Transaction(y)
|
||||||
|
y.emit('beforeTransaction', [y, y._transaction])
|
||||||
|
}
|
||||||
|
const transaction = y._transaction
|
||||||
|
try {
|
||||||
|
f(transaction)
|
||||||
|
} finally {
|
||||||
|
if (initialCall) {
|
||||||
|
y._transaction = null
|
||||||
|
y.emit('beforeObserverCalls', [y, y._transaction])
|
||||||
|
// emit change events on changed types
|
||||||
|
transaction.changed.forEach((subs, itemtype) => {
|
||||||
|
itemtype._callObserver(transaction, subs)
|
||||||
|
})
|
||||||
|
transaction.changedParentTypes.forEach((events, type) => {
|
||||||
|
events = events
|
||||||
|
.filter(event =>
|
||||||
|
event.target._item === null || !event.target._item.deleted
|
||||||
|
)
|
||||||
|
events
|
||||||
|
.forEach(event => {
|
||||||
|
event.currentTarget = type
|
||||||
|
})
|
||||||
|
// we don't need to check for events.length
|
||||||
|
// because we know it has at least one element
|
||||||
|
callEventHandlerListeners(type._dEH, [events, transaction])
|
||||||
|
})
|
||||||
|
// only call afterTransaction listeners if anything changed
|
||||||
|
transaction.afterState = getStates(transaction.y.store)
|
||||||
|
// when all changes & events are processed, emit afterTransaction event
|
||||||
|
// transaction cleanup
|
||||||
|
const store = transaction.y.store
|
||||||
|
const ds = transaction.deleteSet
|
||||||
|
// replace deleted items with ItemDeleted / GC
|
||||||
|
sortAndMergeDeleteSet(ds)
|
||||||
|
y.emit('afterTransaction', [y, transaction])
|
||||||
|
for (const [client, deleteItems] of ds.clients) {
|
||||||
|
/**
|
||||||
|
* @type {Array<AbstractStruct>}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
const structs = store.clients.get(client)
|
||||||
|
for (let di = 0; di < deleteItems.length; di++) {
|
||||||
|
const deleteItem = deleteItems[di]
|
||||||
|
for (let si = findIndexSS(structs, deleteItem.clock); si < structs.length; si++) {
|
||||||
|
const struct = structs[si]
|
||||||
|
if (deleteItem.clock + deleteItem.len < struct.id.clock) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (struct.deleted && struct instanceof AbstractItem && (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted))) {
|
||||||
|
// check if we can GC
|
||||||
|
struct.gc(transaction, store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Array<AbstractStruct>} structs
|
||||||
|
* @param {number} pos
|
||||||
|
*/
|
||||||
|
const tryToMergeWithLeft = (structs, pos) => {
|
||||||
|
const left = structs[pos - 1]
|
||||||
|
const right = structs[pos]
|
||||||
|
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
||||||
|
if (left.mergeWith(right)) {
|
||||||
|
structs.splice(pos, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// on all affected store.clients props, try to merge
|
||||||
|
for (const [client, clock] of transaction.afterState) {
|
||||||
|
const beforeClock = transaction.beforeState.get(client) || 0
|
||||||
|
if (beforeClock !== clock) {
|
||||||
|
/**
|
||||||
|
* @type {Array<AbstractStruct>}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
const structs = store.clients.get(client)
|
||||||
|
// we iterate from right to left so we can safely remove entries
|
||||||
|
const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1)
|
||||||
|
for (let i = structs.length - 1; i >= firstChangePos; i--) {
|
||||||
|
tryToMergeWithLeft(structs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try to merge replacedItems
|
||||||
|
for (const replacedItem of transaction._replacedItems) {
|
||||||
|
const id = replacedItem.id
|
||||||
|
const client = id.client
|
||||||
|
const clock = id.clock
|
||||||
|
/**
|
||||||
|
* @type {Array<AbstractStruct>}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
const structs = store.clients.get(client)
|
||||||
|
const replacedStructPos = findIndexSS(structs, clock)
|
||||||
|
if (replacedStructPos + 1 < structs.length) {
|
||||||
|
tryToMergeWithLeft(structs, replacedStructPos + 1)
|
||||||
|
}
|
||||||
|
if (replacedStructPos > 0) {
|
||||||
|
tryToMergeWithLeft(structs, replacedStructPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y.emit('afterTransactionCleanup', [y, transaction])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
isParentOf,
|
isParentOf,
|
||||||
createID
|
createID,
|
||||||
|
transact
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
class ReverseOperation {
|
class ReverseOperation {
|
||||||
@ -33,7 +34,7 @@ class ReverseOperation {
|
|||||||
function applyReverseOperation (y, scope, reverseBuffer) {
|
function applyReverseOperation (y, scope, reverseBuffer) {
|
||||||
let performedUndo = false
|
let performedUndo = false
|
||||||
let undoOp = null
|
let undoOp = null
|
||||||
y.transact(() => {
|
transact(y, () => {
|
||||||
while (!performedUndo && reverseBuffer.length > 0) {
|
while (!performedUndo && reverseBuffer.length > 0) {
|
||||||
undoOp = reverseBuffer.pop()
|
undoOp = reverseBuffer.pop()
|
||||||
// make sure that it is possible to iterate {from}-{to}
|
// make sure that it is possible to iterate {from}-{to}
|
||||||
@ -107,7 +108,6 @@ export class UndoManager {
|
|||||||
this._lastTransactionWasUndo = false
|
this._lastTransactionWasUndo = false
|
||||||
const y = scope._y
|
const y = scope._y
|
||||||
this.y = y
|
this.y = y
|
||||||
y._hasUndoManager = true
|
|
||||||
let bindingInfos
|
let bindingInfos
|
||||||
y.on('beforeTransaction', (y, transaction, remote) => {
|
y.on('beforeTransaction', (y, transaction, remote) => {
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
|
123
src/utils/Y.js
123
src/utils/Y.js
@ -1,26 +1,18 @@
|
|||||||
import { getStates } from './StructStore.js'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
callEventHandlerListeners,
|
|
||||||
sortAndMergeDeleteSet,
|
|
||||||
StructStore,
|
StructStore,
|
||||||
findIndexSS,
|
|
||||||
Transaction,
|
|
||||||
AbstractType,
|
AbstractType,
|
||||||
AbstractItem,
|
|
||||||
YArray,
|
YArray,
|
||||||
YText,
|
YText,
|
||||||
YMap,
|
YMap,
|
||||||
YXmlFragment,
|
YXmlFragment,
|
||||||
ItemDeleted,
|
transact,
|
||||||
YEvent, GC, AbstractStruct // eslint-disable-line
|
Transaction, YEvent // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import { Observable } from 'lib0/observable.js'
|
import { Observable } from 'lib0/observable.js'
|
||||||
import * as error from 'lib0/error.js'
|
import * as error from 'lib0/error.js'
|
||||||
import * as random from 'lib0/random.js'
|
import * as random from 'lib0/random.js'
|
||||||
import * as map from 'lib0/map.js'
|
import * as map from 'lib0/map.js'
|
||||||
import * as math from 'lib0/math.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Yjs instance handles the state of shared data.
|
* A Yjs instance handles the state of shared data.
|
||||||
@ -43,7 +35,6 @@ export class Y extends Observable {
|
|||||||
* @type {Transaction | null}
|
* @type {Transaction | null}
|
||||||
*/
|
*/
|
||||||
this._transaction = null
|
this._transaction = null
|
||||||
this._hasUndoManager = false
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @type {Transaction}
|
* @type {Transaction}
|
||||||
@ -66,115 +57,7 @@ export class Y extends Observable {
|
|||||||
* @todo separate this into a separate function
|
* @todo separate this into a separate function
|
||||||
*/
|
*/
|
||||||
transact (f) {
|
transact (f) {
|
||||||
let initialCall = false
|
transact(this, f)
|
||||||
if (this._transaction === null) {
|
|
||||||
initialCall = true
|
|
||||||
this._transaction = new Transaction(this)
|
|
||||||
this.emit('beforeTransaction', [this, this._transaction])
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
f(this._transaction)
|
|
||||||
} finally {
|
|
||||||
if (initialCall) {
|
|
||||||
const transaction = this._transaction
|
|
||||||
this._transaction = null
|
|
||||||
this.emit('beforeObserverCalls', [this, this._transaction])
|
|
||||||
// emit change events on changed types
|
|
||||||
transaction.changed.forEach((subs, itemtype) => {
|
|
||||||
itemtype._callObserver(transaction, subs)
|
|
||||||
})
|
|
||||||
transaction.changedParentTypes.forEach((events, type) => {
|
|
||||||
events = events
|
|
||||||
.filter(event =>
|
|
||||||
event.target._item === null || !event.target._item.deleted
|
|
||||||
)
|
|
||||||
events
|
|
||||||
.forEach(event => {
|
|
||||||
event.currentTarget = type
|
|
||||||
})
|
|
||||||
// we don't need to check for events.length
|
|
||||||
// because we know it has at least one element
|
|
||||||
callEventHandlerListeners(type._dEH, [events, transaction])
|
|
||||||
})
|
|
||||||
// only call afterTransaction listeners if anything changed
|
|
||||||
transaction.afterState = getStates(transaction.y.store)
|
|
||||||
// when all changes & events are processed, emit afterTransaction event
|
|
||||||
// transaction cleanup
|
|
||||||
const store = transaction.y.store
|
|
||||||
const ds = transaction.deleteSet
|
|
||||||
// replace deleted items with ItemDeleted / GC
|
|
||||||
sortAndMergeDeleteSet(ds)
|
|
||||||
this.emit('afterTransaction', [this, transaction])
|
|
||||||
for (const [client, deleteItems] of ds.clients) {
|
|
||||||
/**
|
|
||||||
* @type {Array<AbstractStruct>}
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
const structs = store.clients.get(client)
|
|
||||||
for (let di = 0; di < deleteItems.length; di++) {
|
|
||||||
const deleteItem = deleteItems[di]
|
|
||||||
for (let si = findIndexSS(structs, deleteItem.clock); si < structs.length; si++) {
|
|
||||||
const struct = structs[si]
|
|
||||||
if (deleteItem.clock + deleteItem.len < struct.id.clock) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (struct.deleted && struct instanceof AbstractItem && (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted))) {
|
|
||||||
// check if we can GC
|
|
||||||
struct.gc(transaction, store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {Array<AbstractStruct>} structs
|
|
||||||
* @param {number} pos
|
|
||||||
*/
|
|
||||||
const tryToMergeWithLeft = (structs, pos) => {
|
|
||||||
const left = structs[pos - 1]
|
|
||||||
const right = structs[pos]
|
|
||||||
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
|
||||||
if (left.mergeWith(right)) {
|
|
||||||
structs.splice(pos, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// on all affected store.clients props, try to merge
|
|
||||||
for (const [client, clock] of transaction.afterState) {
|
|
||||||
const beforeClock = transaction.beforeState.get(client) || 0
|
|
||||||
if (beforeClock !== clock) {
|
|
||||||
/**
|
|
||||||
* @type {Array<AbstractStruct>}
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
const structs = store.clients.get(client)
|
|
||||||
// we iterate from right to left so we can safely remove entries
|
|
||||||
const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1)
|
|
||||||
for (let i = structs.length - 1; i >= firstChangePos; i--) {
|
|
||||||
tryToMergeWithLeft(structs, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try to merge replacedItems
|
|
||||||
for (const replacedItem of transaction._replacedItems) {
|
|
||||||
const id = replacedItem.id
|
|
||||||
const client = id.client
|
|
||||||
const clock = id.clock
|
|
||||||
/**
|
|
||||||
* @type {Array<AbstractStruct>}
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
const structs = store.clients.get(client)
|
|
||||||
const replacedStructPos = findIndexSS(structs, clock)
|
|
||||||
if (replacedStructPos + 1 < structs.length) {
|
|
||||||
tryToMergeWithLeft(structs, replacedStructPos + 1)
|
|
||||||
}
|
|
||||||
if (replacedStructPos > 0) {
|
|
||||||
tryToMergeWithLeft(structs, replacedStructPos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.emit('afterTransactionCleanup', [this, transaction])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Define a shared data type.
|
* Define a shared data type.
|
||||||
|
@ -61,8 +61,27 @@ export class RelativePosition {
|
|||||||
*/
|
*/
|
||||||
this.item = item
|
this.item = item
|
||||||
}
|
}
|
||||||
|
toJSON () {
|
||||||
|
const json = {}
|
||||||
|
if (this.type !== null) {
|
||||||
|
json.type = this.type.toJSON()
|
||||||
|
}
|
||||||
|
if (this.tname !== null) {
|
||||||
|
json.tname = this.tname
|
||||||
|
}
|
||||||
|
if (this.item !== null) {
|
||||||
|
json.item = this.item.toJSON()
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} json
|
||||||
|
* @return {RelativePosition}
|
||||||
|
*/
|
||||||
|
export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock))
|
||||||
|
|
||||||
export class AbsolutePosition {
|
export class AbsolutePosition {
|
||||||
/**
|
/**
|
||||||
* @param {AbstractType<any>} type
|
* @param {AbstractType<any>} type
|
||||||
@ -175,11 +194,11 @@ export const readRelativePosition = (decoder, y, store) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {RelativePosition} rpos
|
* @param {RelativePosition} rpos
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {Y} y
|
* @param {Y} y
|
||||||
* @return {AbsolutePosition|null}
|
* @return {AbsolutePosition|null}
|
||||||
*/
|
*/
|
||||||
export const toAbsolutePosition = (rpos, store, y) => {
|
export const toAbsolutePosition = (rpos, y) => {
|
||||||
|
const store = y.store
|
||||||
const rightID = rpos.item
|
const rightID = rpos.item
|
||||||
const typeID = rpos.type
|
const typeID = rpos.type
|
||||||
const tname = rpos.tname
|
const tname = rpos.tname
|
||||||
@ -193,7 +212,7 @@ export const toAbsolutePosition = (rpos, store, y) => {
|
|||||||
if (!(right instanceof AbstractItem)) {
|
if (!(right instanceof AbstractItem)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
offset = right.deleted ? 0 : rightID.clock - right.id.clock
|
offset = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock
|
||||||
let n = right.left
|
let n = right.left
|
||||||
while (n !== null) {
|
while (n !== null) {
|
||||||
if (!n.deleted && n.countable) {
|
if (!n.deleted && n.countable) {
|
||||||
@ -251,9 +270,8 @@ export const toRelativePosition = (apos, y) => {
|
|||||||
* @param {RelativePosition|null} b
|
* @param {RelativePosition|null} b
|
||||||
*/
|
*/
|
||||||
export const compareRelativePositions = (a, b) => a === b || (
|
export const compareRelativePositions = (a, b) => a === b || (
|
||||||
a !== null && b !== null && (
|
a !== null && b !== null && a.tname === b.tname && (
|
||||||
(a.item !== null && b.item !== null && compareIDs(a.item, b.item)) ||
|
(a.item !== null && b.item !== null && compareIDs(a.item, b.item)) ||
|
||||||
(a.tname !== null && a.tname === b.tname) ||
|
|
||||||
(a.type !== null && b.type !== null && compareIDs(a.type, b.type))
|
(a.type !== null && b.type !== null && compareIDs(a.type, b.type))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -381,11 +381,8 @@ export const applyRandomTests = (tc, mods, iterations) => {
|
|||||||
testConnector.reconnectRandom()
|
testConnector.reconnectRandom()
|
||||||
}
|
}
|
||||||
} else if (prng.int31(gen, 0, 100) <= 1) {
|
} else if (prng.int31(gen, 0, 100) <= 1) {
|
||||||
// 1% chance to flush all & garbagecollect
|
// 1% chance to flush all
|
||||||
// TODO: We do not gc all users as this does not work yet
|
|
||||||
// await garbageCollectUsers(t, users)
|
|
||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
// await users[0].db.emptyGarbageCollector() // TODO: reintroduce GC tests!
|
|
||||||
} else if (prng.int31(gen, 0, 100) <= 50) {
|
} else if (prng.int31(gen, 0, 100) <= 50) {
|
||||||
// 50% chance to flush a random message
|
// 50% chance to flush a random message
|
||||||
testConnector.flushRandomMessage()
|
testConnector.flushRandomMessage()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user