Y.Text snapshot support (toDelta)
This commit is contained in:
parent
e78d84ee59
commit
8bcff6138c
@ -37,6 +37,8 @@ export {
|
|||||||
compareIDs,
|
compareIDs,
|
||||||
getState,
|
getState,
|
||||||
Snapshot,
|
Snapshot,
|
||||||
|
createSnapshot,
|
||||||
|
createSnapshotFromDoc,
|
||||||
findRootTypeKey,
|
findRootTypeKey,
|
||||||
typeListToArraySnapshot,
|
typeListToArraySnapshot,
|
||||||
typeMapGetSnapshot,
|
typeMapGetSnapshot,
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
ContentEmbed,
|
ContentEmbed,
|
||||||
ContentFormat,
|
ContentFormat,
|
||||||
ContentString,
|
ContentString,
|
||||||
|
splitSnapshotAffectedStructs,
|
||||||
Doc, Item, Snapshot, StructStore, Transaction // eslint-disable-line
|
Doc, Item, Snapshot, StructStore, Transaction // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -723,6 +724,7 @@ export class YText extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
const ops = []
|
const ops = []
|
||||||
const currentAttributes = new Map()
|
const currentAttributes = new Map()
|
||||||
|
const doc = /** @type {Doc} */ (this.doc)
|
||||||
let str = ''
|
let str = ''
|
||||||
let n = this._start
|
let n = this._start
|
||||||
function packStr () {
|
function packStr () {
|
||||||
@ -748,42 +750,54 @@ export class YText extends AbstractType {
|
|||||||
str = ''
|
str = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (n !== null) {
|
// snapshots are merged again after the transaction, so we need to keep the
|
||||||
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
|
// transalive until we are done
|
||||||
switch (n.content.constructor) {
|
transact(doc, transaction => {
|
||||||
case ContentString:
|
if (snapshot) {
|
||||||
const cur = currentAttributes.get('ychange')
|
splitSnapshotAffectedStructs(transaction, snapshot)
|
||||||
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
|
||||||
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') {
|
|
||||||
packStr()
|
|
||||||
currentAttributes.set('ychange', { user: n.id.client, state: 'removed' })
|
|
||||||
}
|
|
||||||
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
|
||||||
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'added') {
|
|
||||||
packStr()
|
|
||||||
currentAttributes.set('ychange', { user: n.id.client, state: 'added' })
|
|
||||||
}
|
|
||||||
} else if (cur !== undefined) {
|
|
||||||
packStr()
|
|
||||||
currentAttributes.delete('ychange')
|
|
||||||
}
|
|
||||||
str += /** @type {ContentString} */ (n.content).str
|
|
||||||
break
|
|
||||||
case ContentEmbed:
|
|
||||||
packStr()
|
|
||||||
ops.push({
|
|
||||||
insert: /** @type {ContentEmbed} */ (n.content).embed
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case ContentFormat:
|
|
||||||
packStr()
|
|
||||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
n = n.right
|
if (prevSnapshot) {
|
||||||
}
|
splitSnapshotAffectedStructs(transaction, prevSnapshot)
|
||||||
packStr()
|
}
|
||||||
|
while (n !== null) {
|
||||||
|
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
|
||||||
|
switch (n.content.constructor) {
|
||||||
|
case ContentString:
|
||||||
|
const cur = currentAttributes.get('ychange')
|
||||||
|
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
||||||
|
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') {
|
||||||
|
packStr()
|
||||||
|
currentAttributes.set('ychange', { user: n.id.client, state: 'removed' })
|
||||||
|
}
|
||||||
|
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
||||||
|
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'added') {
|
||||||
|
packStr()
|
||||||
|
currentAttributes.set('ychange', { user: n.id.client, state: 'added' })
|
||||||
|
}
|
||||||
|
} else if (cur !== undefined) {
|
||||||
|
packStr()
|
||||||
|
currentAttributes.delete('ychange')
|
||||||
|
}
|
||||||
|
str += /** @type {ContentString} */ (n.content).str
|
||||||
|
break
|
||||||
|
case ContentEmbed:
|
||||||
|
packStr()
|
||||||
|
ops.push({
|
||||||
|
insert: /** @type {ContentEmbed} */ (n.content).embed
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case ContentFormat:
|
||||||
|
if (isVisible(n, snapshot)) {
|
||||||
|
packStr()
|
||||||
|
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = n.right
|
||||||
|
}
|
||||||
|
packStr()
|
||||||
|
}, splitSnapshotAffectedStructs)
|
||||||
return ops
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
isDeleted,
|
isDeleted,
|
||||||
DeleteSet, Item // eslint-disable-line
|
createDeleteSetFromStructStore,
|
||||||
|
getStateVector,
|
||||||
|
getItemCleanStart,
|
||||||
|
createID,
|
||||||
|
iterateDeletedStructs,
|
||||||
|
Transaction, Doc, DeleteSet, Item // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
|
import * as map from 'lib0/map.js'
|
||||||
|
import * as set from 'lib0/set.js'
|
||||||
|
|
||||||
export class Snapshot {
|
export class Snapshot {
|
||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} ds
|
||||||
@ -27,9 +35,16 @@ export class Snapshot {
|
|||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} ds
|
||||||
* @param {Map<number,number>} sm
|
* @param {Map<number,number>} sm
|
||||||
|
* @return {Snapshot}
|
||||||
*/
|
*/
|
||||||
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Doc} doc
|
||||||
|
* @return {Snapshot}
|
||||||
|
*/
|
||||||
|
export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
* @param {Snapshot|undefined} snapshot
|
* @param {Snapshot|undefined} snapshot
|
||||||
@ -40,3 +55,20 @@ export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
|
|||||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
|
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
|
||||||
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @param {Snapshot} snapshot
|
||||||
|
*/
|
||||||
|
export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
|
||||||
|
const meta = map.setIfUndefined(transaction.meta, splitSnapshotAffectedStructs, set.create)
|
||||||
|
const store = transaction.doc.store
|
||||||
|
// check if we already split for this snapshot
|
||||||
|
if (!meta.has(snapshot)) {
|
||||||
|
snapshot.sm.forEach((clock, client) => {
|
||||||
|
getItemCleanStart(transaction, store, createID(client, clock))
|
||||||
|
})
|
||||||
|
iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
|
||||||
|
meta.add(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -90,6 +90,11 @@ export class Transaction {
|
|||||||
* @type {any}
|
* @type {any}
|
||||||
*/
|
*/
|
||||||
this.origin = origin
|
this.origin = origin
|
||||||
|
/**
|
||||||
|
* Stores meta information on the transaction
|
||||||
|
* @type {Map<any,any>}
|
||||||
|
*/
|
||||||
|
this.meta = new Map()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { init, compare } from './testHelper.js'
|
import * as Y from './testHelper.js'
|
||||||
|
|
||||||
import * as t from 'lib0/testing.js'
|
import * as t from 'lib0/testing.js'
|
||||||
|
const { init, compare } = Y
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
@ -87,3 +87,65 @@ export const testGetDeltaWithEmbeds = tc => {
|
|||||||
insert: {linebreak: 's'}
|
insert: {linebreak: 's'}
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testSnapshot = tc => {
|
||||||
|
const { text0 } = init(tc, { users: 1 })
|
||||||
|
const doc0 = /** @type {Y.Doc} */ (text0.doc)
|
||||||
|
doc0.gc = false
|
||||||
|
text0.applyDelta([{
|
||||||
|
insert: 'abcd'
|
||||||
|
}])
|
||||||
|
const snapshot1 = Y.createSnapshotFromDoc(doc0)
|
||||||
|
text0.applyDelta([{
|
||||||
|
retain: 1
|
||||||
|
}, {
|
||||||
|
insert: 'x'
|
||||||
|
}, {
|
||||||
|
delete: 1
|
||||||
|
}])
|
||||||
|
const snapshot2 = Y.createSnapshotFromDoc(doc0)
|
||||||
|
text0.applyDelta([{
|
||||||
|
retain: 2
|
||||||
|
}, {
|
||||||
|
delete: 3
|
||||||
|
}, {
|
||||||
|
insert: 'x'
|
||||||
|
}, {
|
||||||
|
delete: 1
|
||||||
|
}])
|
||||||
|
const state1 = text0.toDelta(snapshot1)
|
||||||
|
t.compare(state1, [{ insert: 'abcd' }])
|
||||||
|
const state2 = text0.toDelta(snapshot2)
|
||||||
|
t.compare(state2, [{ insert: 'axcd' }])
|
||||||
|
const state2Diff = text0.toDelta(snapshot2, snapshot1)
|
||||||
|
// @ts-ignore Remove userid info
|
||||||
|
state2Diff.forEach(v => {
|
||||||
|
if (v.attributes && v.attributes.ychange) {
|
||||||
|
delete v.attributes.ychange.user
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.compare(state2Diff, [{insert: 'a'}, {insert: 'x', attributes: {ychange: { state: 'added' }}}, {insert: 'b', attributes: {ychange: { state: 'removed' }}}, { insert: 'cd' }])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testSnapshotDeleteAfter = tc => {
|
||||||
|
const { text0 } = init(tc, { users: 1 })
|
||||||
|
const doc0 = /** @type {Y.Doc} */ (text0.doc)
|
||||||
|
doc0.gc = false
|
||||||
|
text0.applyDelta([{
|
||||||
|
insert: 'abcd'
|
||||||
|
}])
|
||||||
|
const snapshot1 = Y.createSnapshotFromDoc(doc0)
|
||||||
|
text0.applyDelta([{
|
||||||
|
retain: 4
|
||||||
|
}, {
|
||||||
|
insert: 'e'
|
||||||
|
}])
|
||||||
|
const state1 = text0.toDelta(snapshot1)
|
||||||
|
t.compare(state1, [{ insert: 'abcd' }])
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user