more work
This commit is contained in:
parent
c7539b028a
commit
d062cc5420
@ -1,30 +1,33 @@
|
|||||||
|
import { decoding, encoding, error } from 'lib0'
|
||||||
import {
|
import {
|
||||||
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
|
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore, // eslint-disable-line
|
||||||
|
WeakLink,
|
||||||
|
findRootTypeKey,
|
||||||
|
ID,
|
||||||
|
find,
|
||||||
|
ContentType
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
export class ContentLink {
|
export class ContentLink {
|
||||||
/**
|
/**
|
||||||
* @param {Item} item
|
* @param {WeakLink<any>|{parent:string|ID,item:string|ID}} link
|
||||||
*/
|
*/
|
||||||
constructor (item) {
|
constructor (link) {
|
||||||
/**
|
this.link = link
|
||||||
* @type {Item}
|
|
||||||
*/
|
|
||||||
this.item = item
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
getLength () {
|
getLength () {
|
||||||
return this.item.length
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array<any>}
|
* @return {Array<any>}
|
||||||
*/
|
*/
|
||||||
getContent () {
|
getContent () {
|
||||||
throw new Error('not implemented')
|
return [this.link]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +41,7 @@ import {
|
|||||||
* @return {ContentLink}
|
* @return {ContentLink}
|
||||||
*/
|
*/
|
||||||
copy () {
|
copy () {
|
||||||
return new ContentLink(this.item)
|
return new ContentLink(this.link)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +49,7 @@ import {
|
|||||||
* @return {ContentLink}
|
* @return {ContentLink}
|
||||||
*/
|
*/
|
||||||
splice (offset) {
|
splice (offset) {
|
||||||
throw new Error('not implemented')
|
throw error.methodUnimplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,19 +57,57 @@ import {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
mergeWith (right) {
|
mergeWith (right) {
|
||||||
throw new Error('not implemented')
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
*/
|
*/
|
||||||
integrate (transaction, item) {}
|
integrate (transaction, item) {
|
||||||
|
if (this.link.constructor !== WeakLink) {
|
||||||
|
let { parent, item } = /** @type {any} */ (this.link)
|
||||||
|
let key = null
|
||||||
|
if (parent.constructor === ID) {
|
||||||
|
const parentItem = find(transaction.doc.store, parent)
|
||||||
|
if (parentItem.constructor === Item) {
|
||||||
|
parent = /** @type {ContentType} */ (parentItem.content).type
|
||||||
|
} else {
|
||||||
|
parent = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent = transaction.doc.share.get(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.constructor === ID) {
|
||||||
|
item = find(transaction.doc.store, item)
|
||||||
|
} else {
|
||||||
|
key = item
|
||||||
|
item = parent._map.get(key)
|
||||||
|
}
|
||||||
|
this.link = new WeakLink(parent, item, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = /** @type {WeakLink<any>} */ (this.link)
|
||||||
|
if (link.item.constructor === Item) {
|
||||||
|
if (link.item.linkedBy === null) {
|
||||||
|
link.item.linkedBy = new Set()
|
||||||
|
}
|
||||||
|
link.item.linkedBy.add(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
delete (transaction) {}
|
delete (transaction) {
|
||||||
|
const link = /** @type {WeakLink<any>} */ (this.link)
|
||||||
|
if (link.item.constructor === Item) {
|
||||||
|
if (link.item.linkedBy !== null) {
|
||||||
|
link.item.linkedBy.delete(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StructStore} store
|
* @param {StructStore} store
|
||||||
@ -78,14 +119,34 @@ import {
|
|||||||
* @param {number} offset
|
* @param {number} offset
|
||||||
*/
|
*/
|
||||||
write (encoder, offset) {
|
write (encoder, offset) {
|
||||||
throw new Error('not implemented')
|
const link = /** @type {WeakLink<any>} */ (this.link)
|
||||||
|
let flags = 0
|
||||||
|
const parentItem = link.source._item
|
||||||
|
if (parentItem) {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
if (link.key) {
|
||||||
|
flags |= 2
|
||||||
|
}
|
||||||
|
encoding.writeVarUint(encoder.restEncoder, flags)
|
||||||
|
if (parentItem) {
|
||||||
|
encoder.writeLeftID(parentItem.id)
|
||||||
|
} else {
|
||||||
|
const ykey = findRootTypeKey(link.source)
|
||||||
|
encoder.writeString(ykey)
|
||||||
|
}
|
||||||
|
if (link.key !== null) {
|
||||||
|
encoder.writeString(link.key)
|
||||||
|
} else {
|
||||||
|
encoder.writeLeftID(link.item.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
getRef () {
|
getRef () {
|
||||||
return 10
|
return 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +155,19 @@ import {
|
|||||||
* @return {ContentLink}
|
* @return {ContentLink}
|
||||||
*/
|
*/
|
||||||
export const readContentWeakLink = decoder => {
|
export const readContentWeakLink = decoder => {
|
||||||
throw new Error('not implemented')
|
const flags = decoding.readVarUint(decoder.restDecoder)
|
||||||
|
let parent
|
||||||
|
let item
|
||||||
|
if ((flags & 1) !== 0) {
|
||||||
|
parent = decoder.readLeftID()
|
||||||
|
} else {
|
||||||
|
parent = decoder.readString()
|
||||||
|
}
|
||||||
|
if ((flags & 2) !== 0) {
|
||||||
|
item = decoder.readString()
|
||||||
|
} else {
|
||||||
|
item = decoder.readLeftID()
|
||||||
|
}
|
||||||
|
return new ContentLink({parent, item})
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +24,9 @@ import {
|
|||||||
readContentType,
|
readContentType,
|
||||||
addChangedTypeToTransaction,
|
addChangedTypeToTransaction,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
|
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
|
||||||
|
WeakLink,
|
||||||
|
ContentLink
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as error from 'lib0/error'
|
import * as error from 'lib0/error'
|
||||||
@ -296,6 +298,13 @@ export class Item extends AbstractStruct {
|
|||||||
* @type {ID | null}
|
* @type {ID | null}
|
||||||
*/
|
*/
|
||||||
this.redone = null
|
this.redone = null
|
||||||
|
/**
|
||||||
|
* If this item was referenced by other weak links, here we keep the references
|
||||||
|
* to these weak refs.
|
||||||
|
*
|
||||||
|
* @type {Set<WeakLink<any>> | null}
|
||||||
|
*/
|
||||||
|
this.linkedBy = null
|
||||||
/**
|
/**
|
||||||
* @type {AbstractContent}
|
* @type {AbstractContent}
|
||||||
*/
|
*/
|
||||||
@ -511,6 +520,7 @@ export class Item extends AbstractStruct {
|
|||||||
/** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
|
/** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
|
||||||
if (this.left !== null) {
|
if (this.left !== null) {
|
||||||
// this is the current attribute value of parent. delete right
|
// this is the current attribute value of parent. delete right
|
||||||
|
this.linkedBy = this.left.linkedBy
|
||||||
this.left.delete(transaction)
|
this.left.delete(transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,6 +589,8 @@ export class Item extends AbstractStruct {
|
|||||||
this.deleted === right.deleted &&
|
this.deleted === right.deleted &&
|
||||||
this.redone === null &&
|
this.redone === null &&
|
||||||
right.redone === null &&
|
right.redone === null &&
|
||||||
|
this.linkedBy === null &&
|
||||||
|
right.linkedBy === null &&
|
||||||
this.content.constructor === right.content.constructor &&
|
this.content.constructor === right.content.constructor &&
|
||||||
this.content.mergeWith(right.content)
|
this.content.mergeWith(right.content)
|
||||||
) {
|
) {
|
||||||
@ -624,6 +636,7 @@ export class Item extends AbstractStruct {
|
|||||||
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
|
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
|
||||||
addChangedTypeToTransaction(transaction, parent, this.parentSub)
|
addChangedTypeToTransaction(transaction, parent, this.parentSub)
|
||||||
this.content.delete(transaction)
|
this.content.delete(transaction)
|
||||||
|
this.linkedBy = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,8 +733,8 @@ export const contentRefs = [
|
|||||||
readContentType, // 7
|
readContentType, // 7
|
||||||
readContentAny, // 8
|
readContentAny, // 8
|
||||||
readContentDoc, // 9
|
readContentDoc, // 9
|
||||||
readContentWeakLink, // 10
|
() => { error.unexpectedCase() }, // 10 - Skip is not ItemContent
|
||||||
() => { error.unexpectedCase() } // 10 - Skip is not ItemContent
|
readContentWeakLink // 11
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
ContentAny,
|
ContentAny,
|
||||||
ContentBinary,
|
ContentBinary,
|
||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line
|
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, WeakLink, ContentLink, // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as map from 'lib0/map'
|
import * as map from 'lib0/map'
|
||||||
@ -669,6 +669,10 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|||||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
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(transaction, 0)
|
left.integrate(transaction, 0)
|
||||||
break
|
break
|
||||||
|
case WeakLink:
|
||||||
|
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentLink(/** @type {WeakLink<any>} */ (c)))
|
||||||
|
left.integrate(transaction, 0)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
if (c instanceof AbstractType) {
|
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 = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
|
||||||
@ -851,6 +855,9 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
|||||||
case Doc:
|
case Doc:
|
||||||
content = new ContentDoc(/** @type {Doc} */ (value))
|
content = new ContentDoc(/** @type {Doc} */ (value))
|
||||||
break
|
break
|
||||||
|
case WeakLink:
|
||||||
|
content = new ContentLink(/** @type {WeakLink<any>} */ (value))
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (value instanceof AbstractType) {
|
if (value instanceof AbstractType) {
|
||||||
content = new ContentType(value)
|
content = new ContentType(value)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { AbstractType, GC, Item, createID } from "yjs"
|
||||||
|
import { findMarker, typeMapGet } from "./AbstractType.js"
|
||||||
|
import { error } from "lib0"
|
||||||
|
import { Transaction, getItemCleanEnd, getItemCleanStart } from "src/internals.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
@ -5,13 +9,82 @@
|
|||||||
* Weak link to another value stored somewhere in the document.
|
* Weak link to another value stored somewhere in the document.
|
||||||
*/
|
*/
|
||||||
export class WeakLink {
|
export class WeakLink {
|
||||||
|
/**
|
||||||
|
* @param {AbstractType<any>} source
|
||||||
|
* @param {Item|GC} item
|
||||||
|
* @param {string|null} key
|
||||||
|
*/
|
||||||
|
constructor(source, item, key) {
|
||||||
|
this.source = source
|
||||||
|
this.item = item
|
||||||
|
this.key = key
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to an underlying value existing somewhere on in the document.
|
* Returns a reference to an underlying value existing somewhere on in the document.
|
||||||
*
|
*
|
||||||
* @return {T|undefined}
|
* @return {T|undefined}
|
||||||
*/
|
*/
|
||||||
deref() {
|
deref() {
|
||||||
throw new Error('not implemented')
|
if (this.key) {
|
||||||
|
return /** @type {T|undefined} */ (typeMapGet(this.source, this.key))
|
||||||
|
} else {
|
||||||
|
if (this.item.constructor === Item) {
|
||||||
|
return this.item.content.getContent()[0]
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lengthExceeded = error.create('Length exceeded!')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {WeakLink} to an YArray element at given index.
|
||||||
|
*
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @param {AbstractType<any>} parent
|
||||||
|
* @param {number} index
|
||||||
|
* @return {WeakLink<any>}
|
||||||
|
*/
|
||||||
|
export const arrayWeakLink = (transaction, parent, index) => {
|
||||||
|
const marker = findMarker(parent, index)
|
||||||
|
let n = parent._start
|
||||||
|
if (marker !== null) {
|
||||||
|
n = marker.p
|
||||||
|
index -= marker.index
|
||||||
|
}
|
||||||
|
for (; n !== null; n = n.right) {
|
||||||
|
if (!n.deleted && n.countable) {
|
||||||
|
if (index < n.length) {
|
||||||
|
if (index > 0) {
|
||||||
|
n = getItemCleanStart(transaction, createID(n.id.clock, n.id.clock + index))
|
||||||
|
}
|
||||||
|
if (n.length > 1) {
|
||||||
|
n = getItemCleanEnd(transaction, transaction.doc.store, createID(n.id.clock, n.id.clock + 1))
|
||||||
|
}
|
||||||
|
return new WeakLink(parent, n, null)
|
||||||
|
}
|
||||||
|
index -= n.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lengthExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {WeakLink} to an YMap element at given key.
|
||||||
|
*
|
||||||
|
* @param {AbstractType<any>} parent
|
||||||
|
* @param {string} key
|
||||||
|
* @return {WeakLink<any>|undefined}
|
||||||
|
*/
|
||||||
|
export const mapWeakLink = (parent, key) => {
|
||||||
|
const item = parent._map.get(key)
|
||||||
|
if (item !== undefined) {
|
||||||
|
return new WeakLink(parent, item, key)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,7 +17,8 @@ import {
|
|||||||
callTypeObservers,
|
callTypeObservers,
|
||||||
transact,
|
transact,
|
||||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||||
WeakLink
|
WeakLink,
|
||||||
|
arrayWeakLink
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
import { typeListSlice } from './AbstractType.js'
|
import { typeListSlice } from './AbstractType.js'
|
||||||
|
|
||||||
@ -209,7 +210,13 @@ export class YArray extends AbstractType {
|
|||||||
* @return {WeakLink<T>}
|
* @return {WeakLink<T>}
|
||||||
*/
|
*/
|
||||||
link(index) {
|
link(index) {
|
||||||
throw new Error('Method not implemented.')
|
if (this.doc !== null) {
|
||||||
|
return transact(this.doc, transaction => {
|
||||||
|
return arrayWeakLink(transaction, this, index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('todo')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
callTypeObservers,
|
callTypeObservers,
|
||||||
transact,
|
transact,
|
||||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||||
WeakLink
|
WeakLink,
|
||||||
|
mapWeakLink
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as iterator from 'lib0/iterator'
|
import * as iterator from 'lib0/iterator'
|
||||||
@ -241,7 +242,7 @@ export class YMap extends AbstractType {
|
|||||||
* @return {WeakLink<MapType>|undefined}
|
* @return {WeakLink<MapType>|undefined}
|
||||||
*/
|
*/
|
||||||
link(key) {
|
link(key) {
|
||||||
throw new Error('Method not implemented.')
|
return mapWeakLink(this, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +21,8 @@ if (isBrowser) {
|
|||||||
log.createVConsole(document.body)
|
log.createVConsole(document.body)
|
||||||
}
|
}
|
||||||
runTests({
|
runTests({
|
||||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, weakLinks
|
//doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions,
|
||||||
|
weakLinks
|
||||||
}).then(success => {
|
}).then(success => {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
|
@ -5,18 +5,35 @@ import { init, compare } from './testHelper.js'
|
|||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testBasic = tc => {
|
export const testBasicMap = tc => {
|
||||||
const doc = new Y.Doc()
|
const doc = new Y.Doc()
|
||||||
const map = doc.getMap('map')
|
const map = doc.getMap('map')
|
||||||
|
|
||||||
const nested = new Y.Map()
|
const nested = new Y.Map()
|
||||||
nested.set('a1', 'hello')
|
nested.set('a1', 'hello')
|
||||||
map.set('a', nested)
|
map.set('a', nested)
|
||||||
const link = nested.link('a')
|
const link = map.link('a')
|
||||||
map.set('b', link)
|
map.set('b', link)
|
||||||
|
|
||||||
const nested2 = map.get('b')
|
const link2 = /** @type {Y.WeakLink<any>} */ (map.get('b'))
|
||||||
t.compare(nested2.toJSON(), nested.toJSON())
|
const expected = nested.toJSON()
|
||||||
|
const actual = link2.deref().toJSON()
|
||||||
|
t.compare(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testBasicArray = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const array = doc.getArray('array')
|
||||||
|
array.insert(0, [1,2,3])
|
||||||
|
array.insert(3, [array.link(1)])
|
||||||
|
|
||||||
|
t.compare(array.get(0), 1)
|
||||||
|
t.compare(array.get(1), 2)
|
||||||
|
t.compare(array.get(2), 3)
|
||||||
|
t.compare(array.get(3).deref(), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user