basics of range based quotations

This commit is contained in:
Bartosz Sypytkowski 2023-07-07 10:12:30 +02:00
parent 337cc1e202
commit 02367b612b
4 changed files with 130 additions and 56 deletions

View File

@ -113,13 +113,15 @@ export class ContentType {
// when removing weak links, remove references to them
// from type they're pointing to
const type = /** @type {WeakLink<any>} */ (this.type);
if (type._linkedItem !== null && !type._linkedItem.deleted) {
const item = /** @type {Item} */ (type._linkedItem)
for (let item = type._firstItem; item !== null; item = item.right) {
if (item.linked) {
unlinkFrom(transaction, item, type)
}
type._linkedItem = null
if (item === type._lastItem) {
break;
}
}
type._firstItem = type._lastItem = null
}
let item = this.type._start

View File

@ -396,9 +396,15 @@ export class Item extends AbstractStruct {
}
if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
// make sure that linked content is integrated first
const linkSource = /** @type {any} */ (this.content).type._id
if (linkSource.clock >= getState(store, linkSource.client)) {
return linkSource.client
const content = /** @type {ContentType} */ (this.content)
const link = /** @type {YWeakLink<any>} */ (content.type)
const start = link._quoteStart.item
if (start !== null && start.clock >= getState(store, start.client)) {
return start.client
}
const end = link._quoteEnd.item
if (end !== null && end.clock >= getState(store, end.client)) {
return end.client
}
}

View File

@ -215,7 +215,7 @@ export class YArray extends AbstractType {
return arrayWeakLink(transaction, this, index)
})
} else {
throw new Error('todo')
throw new Error('cannot create a link to an YArray that has not been integrated into YDoc')
}
}

View File

@ -10,7 +10,8 @@ import {
callTypeObservers,
YWeakLinkRefID,
writeID,
readID
readID,
RelativePosition
} from "../internals.js"
/**
@ -36,13 +37,26 @@ export class YWeakLinkEvent extends YEvent {
*/
export class YWeakLink extends AbstractType {
/**
* @param {ID} id
* @param {Item|GC|null} item
* @param {RelativePosition} start
* @param {RelativePosition} end
* @param {Item|null} firstItem
* @param {Item|null} lastItem
*/
constructor(id, item) {
constructor(start, end, firstItem, lastItem) {
super()
this._id = id
this._linkedItem = item
this._quoteStart = start
this._quoteEnd = end
this._firstItem = firstItem
this._lastItem = lastItem
}
/**
* Check if current link contains only a single element.
*
* @returns {boolean}
*/
isSingle() {
return this._quoteStart.item === this._quoteEnd.item
}
/**
@ -51,21 +65,40 @@ export class YWeakLink extends AbstractType {
* @return {T|undefined}
*/
deref() {
if (this._linkedItem !== null && this._linkedItem.constructor === Item) {
let item = this._linkedItem
if (this._firstItem !== null) {
let item = this._firstItem
if (item.parentSub !== null) {
// for map types go to the most recent one
while (item.right !== null) {
item = item.right
}
this._linkedItem = item
// we don't support quotations over maps
this._firstItem = item
}
if (!item.deleted) {
return item.content.getContent()[0]
if (!this._firstItem.deleted) {
return this._firstItem.content.getContent()[0]
}
}
return undefined;
}
/**
* Returns an array of references to all elements quoted by current weak link.
*
* @return {Array<any>}
*/
unqote() {
let result = /** @type {Array<any>} */ ([])
let item = this._firstItem
//TODO: moved elements
while (item !== null && item !== this._lastItem) {
if (!item.deleted) {
result = result.concat(item.content.getContent())
}
item = item.right
}
return result
}
/**
* Integrate this type into the Yjs instance.
@ -84,19 +117,25 @@ export class YWeakLink extends AbstractType {
// link may refer to a single element in multi-element block
// in such case we need to cut of the linked element into a
// separate block
let sourceItem = this._linkedItem !== null ? this._linkedItem : getItemCleanStart(transaction, this._id)
if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
let firstItem = this._firstItem !== null ? this._firstItem : getItemCleanStart(transaction, /** @type {ID} */ (this._quoteStart.item))
let lastItem = this._lastItem !== null ? this._lastItem : getItemCleanEnd(transaction, y.store, /** @type {ID} */(this._quoteEnd.item))
if (firstItem.parentSub !== null) {
// for maps, advance to most recent item
while (sourceItem.right !== null) {
sourceItem = sourceItem.right
while (firstItem.right !== null) {
firstItem = firstItem.right
}
lastItem = firstItem
}
if (!sourceItem.deleted && sourceItem.length > 1) {
sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
}
this._linkedItem = sourceItem
if (!sourceItem.deleted) {
createLink(transaction, /** @type {Item} */ (sourceItem), this)
this._firstItem = firstItem
this._lastItem = lastItem
/** @type {Item|null} */
let item = firstItem
for (; item !== null; item = item.right) {
createLink(transaction, item, this)
if (item === lastItem) {
break;
}
}
})
}
@ -106,14 +145,14 @@ export class YWeakLink extends AbstractType {
* @return {YWeakLink<T>}
*/
_copy () {
return new YWeakLink(this._id, this._linkedItem)
return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem, this._lastItem)
}
/**
* @return {YWeakLink<T>}
*/
clone () {
return new YWeakLink(this._id, this._linkedItem)
return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem, this._lastItem)
}
/**
@ -132,9 +171,13 @@ export class YWeakLink extends AbstractType {
*/
_write (encoder) {
encoder.writeTypeRef(YWeakLinkRefID)
const flags = 0 // flags that could be used in the future
encoding.writeUint8(encoder.restEncoder, flags)
writeID(encoder.restEncoder, this._id)
const isSingle = this.isSingle()
const info = (isSingle ? 0 : 1) | (this._quoteStart.assoc >= 0 ? 2 : 0) | (this._quoteEnd.assoc >= 0 ? 4 :0)
encoding.writeUint8(encoder.restEncoder, info)
writeID(encoder.restEncoder, /** @type {ID} */ (this._quoteStart.item))
if (!isSingle) {
writeID(encoder.restEncoder, /** @type {ID} */ (this._quoteEnd.item))
}
}
}
@ -144,9 +187,14 @@ export class YWeakLink extends AbstractType {
* @return {YWeakLink<any>}
*/
export const readYWeakLink = decoder => {
const flags = decoding.readUint8(decoder.restDecoder)
const id = readID(decoder.restDecoder)
return new YWeakLink(id, null)
const info = decoding.readUint8(decoder.restDecoder)
const isSingle = (info & 1) !== 1
const startAssoc = (info & 2) === 2 ? 0 : -1
const endAssoc = (info & 4) === 4 ? 0 : -1
const startID = readID(decoder.restDecoder)
const start = new RelativePosition(null, null, startID, startAssoc)
const end = new RelativePosition(null, null, isSingle ? startID : readID(decoder.restDecoder), endAssoc)
return new YWeakLink(start, end, null, null)
}
const lengthExceeded = error.create('Length exceeded!')
@ -159,27 +207,43 @@ const lengthExceeded = error.create('Length exceeded!')
* @param {number} index
* @return {YWeakLink<any>}
*/
export const arrayWeakLink = (transaction, parent, index) => {
let item = parent._start
for (; item !== null; item = item.right) {
if (!item.deleted && item.countable) {
if (index < item.length) {
export const arrayWeakLink = (transaction, parent, index, length = 1) => {
let startItem = parent._start
for (;startItem !== null; startItem = startItem.right) {
if (!startItem.deleted && startItem.countable) {
if (index < startItem.length) {
if (index > 0) {
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index))
startItem = getItemCleanStart(transaction, createID(startItem.id.client, startItem.id.clock + index))
}
if (item.length > 1) {
item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock))
}
const link = new YWeakLink(item.id, item)
if (parent.doc !== null) {
const source = /** @type {Item} */ (item)
transact(parent.doc, (transaction) => {
createLink(transaction, source, link)
})
}
return link
break;
}
index -= item.length
index -= startItem.length
}
}
if (startItem !== null) {
let endItem = startItem
let remaining = length
for (;endItem !== null && endItem.right !== null && endItem.length > remaining; endItem = endItem.right) {
// iterate over the items to reach the last block in the quoted range
remaining -= endItem.length
}
if (endItem.length >= remaining) {
endItem = getItemCleanEnd(transaction, transaction.doc.store, createID(startItem.id.client, startItem.id.clock + remaining - 1))
const start = new RelativePosition(null, null, startItem.id, 0)
const end = new RelativePosition(null, null, endItem.lastId, -1)
const link = new YWeakLink(start, end, startItem, endItem)
if (parent.doc !== null) {
transact(parent.doc, (transaction) => {
for (let item = link._firstItem; item !== null; item = item = item.right) {
createLink(transaction, item, link)
if (item === link._lastItem) {
break;
}
}
})
}
return link
}
}
@ -196,7 +260,9 @@ export const arrayWeakLink = (transaction, parent, index) => {
export const mapWeakLink = (parent, key) => {
const item = parent._map.get(key)
if (item !== undefined) {
const link = new YWeakLink(item.id, item)
const start = new RelativePosition(null, null, item.id, 0)
const end = new RelativePosition(null, null, item.id, -1)
const link = new YWeakLink(start, end, item, item)
if (parent.doc !== null) {
transact(parent.doc, (transaction) => {
createLink(transaction, item, link)