basics of range based quotations
This commit is contained in:
parent
337cc1e202
commit
02367b612b
@ -113,13 +113,15 @@ export class ContentType {
|
|||||||
// when removing weak links, remove references to them
|
// when removing weak links, remove references to them
|
||||||
// from type they're pointing to
|
// from type they're pointing to
|
||||||
const type = /** @type {WeakLink<any>} */ (this.type);
|
const type = /** @type {WeakLink<any>} */ (this.type);
|
||||||
if (type._linkedItem !== null && !type._linkedItem.deleted) {
|
for (let item = type._firstItem; item !== null; item = item.right) {
|
||||||
const item = /** @type {Item} */ (type._linkedItem)
|
|
||||||
if (item.linked) {
|
if (item.linked) {
|
||||||
unlinkFrom(transaction, item, type)
|
unlinkFrom(transaction, item, type)
|
||||||
}
|
}
|
||||||
type._linkedItem = null
|
if (item === type._lastItem) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
type._firstItem = type._lastItem = null
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = this.type._start
|
let item = this.type._start
|
||||||
|
@ -396,9 +396,15 @@ export class Item extends AbstractStruct {
|
|||||||
}
|
}
|
||||||
if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
|
if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
|
||||||
// make sure that linked content is integrated first
|
// make sure that linked content is integrated first
|
||||||
const linkSource = /** @type {any} */ (this.content).type._id
|
const content = /** @type {ContentType} */ (this.content)
|
||||||
if (linkSource.clock >= getState(store, linkSource.client)) {
|
const link = /** @type {YWeakLink<any>} */ (content.type)
|
||||||
return linkSource.client
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ export class YArray extends AbstractType {
|
|||||||
return arrayWeakLink(transaction, this, index)
|
return arrayWeakLink(transaction, this, index)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('todo')
|
throw new Error('cannot create a link to an YArray that has not been integrated into YDoc')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ import {
|
|||||||
callTypeObservers,
|
callTypeObservers,
|
||||||
YWeakLinkRefID,
|
YWeakLinkRefID,
|
||||||
writeID,
|
writeID,
|
||||||
readID
|
readID,
|
||||||
|
RelativePosition
|
||||||
} from "../internals.js"
|
} from "../internals.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,13 +37,26 @@ export class YWeakLinkEvent extends YEvent {
|
|||||||
*/
|
*/
|
||||||
export class YWeakLink extends AbstractType {
|
export class YWeakLink extends AbstractType {
|
||||||
/**
|
/**
|
||||||
* @param {ID} id
|
* @param {RelativePosition} start
|
||||||
* @param {Item|GC|null} item
|
* @param {RelativePosition} end
|
||||||
|
* @param {Item|null} firstItem
|
||||||
|
* @param {Item|null} lastItem
|
||||||
*/
|
*/
|
||||||
constructor(id, item) {
|
constructor(start, end, firstItem, lastItem) {
|
||||||
super()
|
super()
|
||||||
this._id = id
|
this._quoteStart = start
|
||||||
this._linkedItem = item
|
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}
|
* @return {T|undefined}
|
||||||
*/
|
*/
|
||||||
deref() {
|
deref() {
|
||||||
if (this._linkedItem !== null && this._linkedItem.constructor === Item) {
|
if (this._firstItem !== null) {
|
||||||
let item = this._linkedItem
|
let item = this._firstItem
|
||||||
if (item.parentSub !== null) {
|
if (item.parentSub !== null) {
|
||||||
// for map types go to the most recent one
|
|
||||||
while (item.right !== null) {
|
while (item.right !== null) {
|
||||||
item = item.right
|
item = item.right
|
||||||
}
|
}
|
||||||
this._linkedItem = item
|
// we don't support quotations over maps
|
||||||
|
this._firstItem = item
|
||||||
}
|
}
|
||||||
if (!item.deleted) {
|
if (!this._firstItem.deleted) {
|
||||||
return item.content.getContent()[0]
|
return this._firstItem.content.getContent()[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
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.
|
* 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
|
// link may refer to a single element in multi-element block
|
||||||
// in such case we need to cut of the linked element into a
|
// in such case we need to cut of the linked element into a
|
||||||
// separate block
|
// separate block
|
||||||
let sourceItem = this._linkedItem !== null ? this._linkedItem : getItemCleanStart(transaction, this._id)
|
let firstItem = this._firstItem !== null ? this._firstItem : getItemCleanStart(transaction, /** @type {ID} */ (this._quoteStart.item))
|
||||||
if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
|
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
|
// for maps, advance to most recent item
|
||||||
while (sourceItem.right !== null) {
|
while (firstItem.right !== null) {
|
||||||
sourceItem = sourceItem.right
|
firstItem = firstItem.right
|
||||||
}
|
}
|
||||||
|
lastItem = firstItem
|
||||||
}
|
}
|
||||||
if (!sourceItem.deleted && sourceItem.length > 1) {
|
this._firstItem = firstItem
|
||||||
sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
|
this._lastItem = lastItem
|
||||||
}
|
|
||||||
this._linkedItem = sourceItem
|
/** @type {Item|null} */
|
||||||
if (!sourceItem.deleted) {
|
let item = firstItem
|
||||||
createLink(transaction, /** @type {Item} */ (sourceItem), this)
|
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>}
|
* @return {YWeakLink<T>}
|
||||||
*/
|
*/
|
||||||
_copy () {
|
_copy () {
|
||||||
return new YWeakLink(this._id, this._linkedItem)
|
return new YWeakLink(this._quoteStart, this._quoteEnd, this._firstItem, this._lastItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {YWeakLink<T>}
|
* @return {YWeakLink<T>}
|
||||||
*/
|
*/
|
||||||
clone () {
|
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) {
|
_write (encoder) {
|
||||||
encoder.writeTypeRef(YWeakLinkRefID)
|
encoder.writeTypeRef(YWeakLinkRefID)
|
||||||
const flags = 0 // flags that could be used in the future
|
const isSingle = this.isSingle()
|
||||||
encoding.writeUint8(encoder.restEncoder, flags)
|
const info = (isSingle ? 0 : 1) | (this._quoteStart.assoc >= 0 ? 2 : 0) | (this._quoteEnd.assoc >= 0 ? 4 :0)
|
||||||
writeID(encoder.restEncoder, this._id)
|
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>}
|
* @return {YWeakLink<any>}
|
||||||
*/
|
*/
|
||||||
export const readYWeakLink = decoder => {
|
export const readYWeakLink = decoder => {
|
||||||
const flags = decoding.readUint8(decoder.restDecoder)
|
const info = decoding.readUint8(decoder.restDecoder)
|
||||||
const id = readID(decoder.restDecoder)
|
const isSingle = (info & 1) !== 1
|
||||||
return new YWeakLink(id, null)
|
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!')
|
const lengthExceeded = error.create('Length exceeded!')
|
||||||
@ -159,27 +207,43 @@ const lengthExceeded = error.create('Length exceeded!')
|
|||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @return {YWeakLink<any>}
|
* @return {YWeakLink<any>}
|
||||||
*/
|
*/
|
||||||
export const arrayWeakLink = (transaction, parent, index) => {
|
export const arrayWeakLink = (transaction, parent, index, length = 1) => {
|
||||||
let item = parent._start
|
let startItem = parent._start
|
||||||
for (; item !== null; item = item.right) {
|
for (;startItem !== null; startItem = startItem.right) {
|
||||||
if (!item.deleted && item.countable) {
|
if (!startItem.deleted && startItem.countable) {
|
||||||
if (index < item.length) {
|
if (index < startItem.length) {
|
||||||
if (index > 0) {
|
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) {
|
break;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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) => {
|
export const mapWeakLink = (parent, key) => {
|
||||||
const item = parent._map.get(key)
|
const item = parent._map.get(key)
|
||||||
if (item !== undefined) {
|
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) {
|
if (parent.doc !== null) {
|
||||||
transact(parent.doc, (transaction) => {
|
transact(parent.doc, (transaction) => {
|
||||||
createLink(transaction, item, link)
|
createLink(transaction, item, link)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user