mirror of
https://github.com/yjs/yjs.git
synced 2026-02-24 04:01:14 +01:00
Implement & test getContentDeep for all types. Improve ability to compare things using lib0/traits.
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "13.6.24",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.104",
|
||||
"lib0": "^0.2.105",
|
||||
"y-protocols": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2774,9 +2774,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.104",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.104.tgz",
|
||||
"integrity": "sha512-1tqKRANSPTcjs/yjPoKh52oRM2u5AYdd8jie8sDiN8/5kpWWiQSHUGgtB4VEXLw1chVL3QPSPp8q9RWqzSn2FA==",
|
||||
"version": "0.2.105",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.105.tgz",
|
||||
"integrity": "sha512-5vtbuBi2P43ZYOfVMV+TZYkWEa0J9kijXirzEgrPA+nJDQCtMx805/rqW4G1nXbM9IRIhwW+OyNNgcQdbhKfSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
},
|
||||
"homepage": "https://docs.yjs.dev",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.104",
|
||||
"lib0": "^0.2.105",
|
||||
"y-protocols": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
ContentAny,
|
||||
ContentBinary,
|
||||
getItemCleanStart,
|
||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item,
|
||||
createAttributionFromAttrs, // eslint-disable-line
|
||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttrs, AbstractAttributionManager, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
@@ -22,6 +21,13 @@ import * as error from 'lib0/error'
|
||||
import * as math from 'lib0/math'
|
||||
import * as log from 'lib0/logging'
|
||||
|
||||
/**
|
||||
* @typedef {delta.ArrayDelta|delta.TextDelta|{ children: delta.ArrayDelta<Array<YXmlDeepContent>> }|{ children: delta.ArrayDelta, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution } } }} YXmlDeepContent
|
||||
*/
|
||||
/**
|
||||
* @typedef {delta.ArrayDelta|delta.TextDelta|{ children: delta.ArrayDelta<Array<DeepContent>> }|{ children: delta.ArrayDelta, attributes: {[key:string]:{ value: any, prevValue: any, attribution: import('../utils/AttributionManager.js').Attribution} } }} DeepContent
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
|
||||
*/
|
||||
@@ -406,6 +412,22 @@ export class AbstractType {
|
||||
* @return {any}
|
||||
*/
|
||||
toJSON () {}
|
||||
|
||||
/**
|
||||
* @param {AbstractAttributionManager} _am
|
||||
* @return {any}
|
||||
*/
|
||||
getContent (_am) {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractAttributionManager} _am
|
||||
* @return {DeepContent}
|
||||
*/
|
||||
getContentDeep (_am) {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,17 +498,15 @@ export const typeListToArray = type => {
|
||||
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @template MapType
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {delta.Delta<Array<MapType>>} The Delta representation of this type.
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const typeListGetContent = (type, am) => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
const d = /** @type {delta.DeltaBuilder<Array<MapType>>} */ (delta.create())
|
||||
const d = delta.createArrayDelta()
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
@@ -502,7 +522,7 @@ export const typeListGetContent = (type, am) => {
|
||||
d.insert(content.getContent(), null, attribution)
|
||||
}
|
||||
}
|
||||
return d.done()
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1014,7 +1034,6 @@ export const typeMapGetContent = (parent, am) => {
|
||||
return mapcontent
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {string} key
|
||||
|
||||
@@ -17,10 +17,13 @@ import {
|
||||
callTypeObservers,
|
||||
transact,
|
||||
warnPrematureAccess,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||
AbstractAttributionManager
|
||||
typeListGetContent,
|
||||
typeListSlice,
|
||||
noAttributionsManager,
|
||||
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
import { typeListGetContent, typeListSlice } from './AbstractType.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YArray
|
||||
@@ -216,11 +219,31 @@ export class YArray extends AbstractType {
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {import('../utils/Delta.js').Delta<Array<T>>} The Delta representation of this type.
|
||||
* @return {import('../utils/Delta.js').ArrayDelta<Array<import('../types/AbstractType.js').DeepContent>>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am) {
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
return this.getContent(am).map(d => /** @type {any} */ (
|
||||
d instanceof delta.InsertOp && d.insert instanceof Array
|
||||
? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
*
|
||||
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {import('../utils/Delta.js').ArrayDelta<Array<T>>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am = noAttributionsManager) {
|
||||
return typeListGetContent(this, am)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,11 @@ import {
|
||||
YMapRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
typeMapGetContent,
|
||||
warnPrematureAccess,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||
createAttributionFromAttrs,
|
||||
typeMapGetContent
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as array from 'lib0/array'
|
||||
import * as iterator from 'lib0/iterator'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1000,7 +1000,27 @@ export class YText extends AbstractType {
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {import('../utils/Delta.js').Delta<import('../utils/Delta.js').TextDeltaContent>} The Delta representation of this type.
|
||||
* @return {import('../utils/Delta.js').TextDelta<string | import('./AbstractType.js').DeepContent >} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
return this.getContent(am).map(d =>
|
||||
d instanceof delta.InsertOp && d.insert instanceof AbstractType
|
||||
? new delta.InsertOp(d.insert.getContent(am), d.attributes, d.attribution)
|
||||
: d
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
*
|
||||
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {import('../utils/Delta.js').TextDelta} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -1068,7 +1088,7 @@ export class YText extends AbstractType {
|
||||
}
|
||||
}
|
||||
}
|
||||
return d.done()
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
YXmlElementRefID,
|
||||
typeMapGetContent,
|
||||
noAttributionsManager,
|
||||
Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line
|
||||
AbstractAttributionManager, Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
/**
|
||||
* @typedef {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} ValueTypes
|
||||
*/
|
||||
@@ -208,6 +210,36 @@ export class YXmlElement extends YXmlFragment {
|
||||
return /** @type {any} */ (snapshot ? typeMapGetAllSnapshot(this, snapshot) : typeMapGetAll(this))
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
*
|
||||
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {{ nodeName: string, children: delta.ArrayDelta<Array<import('./AbstractType.js').DeepContent>>, attributes: import('./AbstractType.js').MapAttributedContent<any> }}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
const { children: origChildren, attributes: origAttributes } = this.getContent(am)
|
||||
const children = origChildren.map(d => /** @type {any} */ (
|
||||
(d instanceof delta.InsertOp && d.insert instanceof Array)
|
||||
? new delta.InsertOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
/**
|
||||
* @todo there is a Attributes type and a DeepAttributes type.
|
||||
* @type {import('./AbstractType.js').MapAttributedContent<any>}
|
||||
*/
|
||||
const attributes = {}
|
||||
object.forEach(origAttributes, (v, key) => {
|
||||
attributes[key] = Object.assign({}, v, { value: v.value instanceof AbstractType ? v.value.getContentDeep(am) : v.value })
|
||||
})
|
||||
return { nodeName: this.nodeName, children, attributes }
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
|
||||
@@ -383,13 +383,28 @@ export class YXmlFragment extends AbstractType {
|
||||
* Calculate the attributed content using the attribution manager.
|
||||
*
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {{ children: import('../utils/Delta.js').Delta<Array<YXmlElement|YXmlText|YXmlHook>> }}
|
||||
* @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<YXmlElement|YXmlText|YXmlHook>> }}
|
||||
*/
|
||||
getContent (am = noAttributionsManager) {
|
||||
const children = typeListGetContent(this, am)
|
||||
return { children }
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the attributed content using the attribution manager.
|
||||
*
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {{ children: import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>> }}
|
||||
*/
|
||||
getContentDeep (am) {
|
||||
const { children: origChildren } = this.getContent()
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>>}
|
||||
*/
|
||||
const children = origChildren.map(d => /** @type {any} */ (d instanceof AbstractType ? d.getContentDeep(am) : d))
|
||||
return { children }
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content to this YArray.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as object from 'lib0/object'
|
||||
import * as fun from 'lib0/function'
|
||||
import * as traits from 'lib0/traits'
|
||||
|
||||
/**
|
||||
* @template {string|Array<any>|{[key: string]: any}} Content
|
||||
@@ -29,9 +30,20 @@ export class InsertOp {
|
||||
this.attribution = attribution
|
||||
}
|
||||
|
||||
get length () {
|
||||
return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InsertOp<Content>} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteOp {
|
||||
@@ -42,9 +54,20 @@ export class DeleteOp {
|
||||
this.delete = len
|
||||
}
|
||||
|
||||
get length () {
|
||||
return 0
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return { delete: this.delete }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DeleteOp} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return this.delete === other.delete
|
||||
}
|
||||
}
|
||||
|
||||
export class RetainOp {
|
||||
@@ -59,16 +82,48 @@ export class RetainOp {
|
||||
this.attribution = attribution
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this.retain
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RetainOp} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return this.retain === other.retain && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {string|Array<any>|{[key: string]: any}} Content
|
||||
* @typedef {Array<any>} ArrayDeltaContent
|
||||
*/
|
||||
export class Delta {
|
||||
constructor () {
|
||||
|
||||
/**
|
||||
* @typedef {string | { [key: string]: any }} TextDeltaContent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ array: ArrayDeltaContent, text: TextDeltaContent, custom: string|Array<any>|{[key:string]:any}}} DeltaTypeMapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(TextDelta | ArrayDelta)} Delta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {'array' | 'text' | 'custom'} Type
|
||||
* @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
|
||||
*/
|
||||
export class AbstractDelta {
|
||||
/**
|
||||
* @param {Type} type
|
||||
*/
|
||||
constructor (type) {
|
||||
this.type = type
|
||||
/**
|
||||
* @type {Array<DeltaOp<Content>>}
|
||||
*/
|
||||
@@ -76,48 +131,49 @@ export class Delta {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Delta<Content>} d
|
||||
* @template {DeltaTypeMapper[Type]} MContent
|
||||
* @param {(d:DeltaOp<Content>)=>DeltaOp<MContent>} f
|
||||
* @return {DeltaBuilder<Type, MContent>}
|
||||
*/
|
||||
map (f) {
|
||||
const d = /** @type {DeltaBuilder<Type,any>} */ (new /** @type {any} */ (this.constructor)(this.type))
|
||||
d.ops = this.ops.map(f)
|
||||
// @ts-ignore
|
||||
d._lastOp = d.ops[d.ops.length - 1] ?? null
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(d:DeltaOp<Content>,index:number)=>void} f
|
||||
*/
|
||||
forEach (f) {
|
||||
for (
|
||||
let i = 0, index = 0, op = this.ops[i];
|
||||
i < this.ops.length;
|
||||
i++, index += op.length, op = this.ops[i]
|
||||
) {
|
||||
f(op, index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractDelta<Type, Content>} other
|
||||
* @return {boolean}
|
||||
*/
|
||||
equals (d) {
|
||||
return this.ops.length === d.ops.length && this.ops.every((op, i) => {
|
||||
const dop = d.ops[i]
|
||||
if (op.constructor !== dop.constructor) return false
|
||||
switch (op.constructor) {
|
||||
case DeleteOp: {
|
||||
if (/** @type {DeleteOp} */ (op).delete !== /** @type {DeleteOp} */ (dop).delete) {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
case InsertOp: {
|
||||
if (
|
||||
!fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).insert, /** @type {InsertOp<Content>} */ (dop).insert)
|
||||
|| !fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).attributes, /** @type {InsertOp<Content>} */ (dop).attributes)
|
||||
|| !fun.equalityDeep(/** @type {InsertOp<Content>} */ (op).attribution, /** @type {InsertOp<Content>} */ (dop).attribution)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
case RetainOp: {
|
||||
if (
|
||||
/** @type {RetainOp} */ (op).retain !== /** @type {RetainOp} */ (dop).retain
|
||||
|| !fun.equalityDeep(/** @type {RetainOp} */ (op).attributes, /** @type {RetainOp} */ (dop).attributes)
|
||||
|| !fun.equalityDeep(/** @type {RetainOp} */ (op).attribution, /** @type {RetainOp} */ (dop).attribution)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
equals (other) {
|
||||
return this[traits.EqualityTraitSymbol](other)
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return { ops: this.ops.map(o => o.toJSON()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractDelta<Type,Content>} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return this.type === other.type && fun.equalityDeep(this.ops, other.ops)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,12 +190,16 @@ const mergeAttrs = (a, b) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {string|Array<any>|{[key: string]: any}} Content
|
||||
* @extends Delta<Content>
|
||||
* @template {'array' | 'text' | 'custom'} [Type='custom']
|
||||
* @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
|
||||
* @extends AbstractDelta<Type,Content>
|
||||
*/
|
||||
export class DeltaBuilder extends Delta {
|
||||
constructor () {
|
||||
super()
|
||||
export class DeltaBuilder extends AbstractDelta {
|
||||
/**
|
||||
* @param {Type} type
|
||||
*/
|
||||
constructor (type) {
|
||||
super(type)
|
||||
/**
|
||||
* @type {FormattingAttributes?}
|
||||
*/
|
||||
@@ -185,10 +245,10 @@ export class DeltaBuilder extends Delta {
|
||||
const mergedAttribution = attribution == null ? this.usedAttribution : mergeAttrs(this.usedAttribution, attribution)
|
||||
if (this._lastOp instanceof InsertOp && (mergedAttributes === this._lastOp.attributes || fun.equalityDeep(mergedAttributes, this._lastOp.attributes)) && (mergedAttribution === this._lastOp.attribution || fun.equalityDeep(mergedAttribution, this._lastOp.attribution))) {
|
||||
if (insert.constructor === String) {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
this._lastOp.insert += insert
|
||||
} else if (insert.constructor === Array && this._lastOp.insert.constructor === Array) {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
this._lastOp.insert.push(...insert)
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution))
|
||||
@@ -230,31 +290,39 @@ export class DeltaBuilder extends Delta {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Delta<Content>}
|
||||
* @return {AbstractDelta<Type,Content>}
|
||||
*/
|
||||
done () {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export const create = () => new DeltaBuilder()
|
||||
/**
|
||||
* @template {ArrayDeltaContent} [Content=ArrayDeltaContent]
|
||||
* @extends DeltaBuilder<'array',Content>
|
||||
*/
|
||||
export class ArrayDelta extends DeltaBuilder {
|
||||
constructor () {
|
||||
super('array')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {string | { [key: string]: any }} TextDeltaContent
|
||||
* @template {TextDeltaContent} [Content=TextDeltaContent]
|
||||
* @extends DeltaBuilder<'text',Content>
|
||||
*/
|
||||
export class TextDelta extends DeltaBuilder {
|
||||
constructor () {
|
||||
super('text')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {TextDeltaContent} Content
|
||||
* @return {DeltaBuilder<Content>}
|
||||
* @return {TextDelta<TextDeltaContent>}
|
||||
*/
|
||||
export const createTextDelta = () => new DeltaBuilder()
|
||||
export const createTextDelta = () => new TextDelta()
|
||||
|
||||
/**
|
||||
* @typedef {Array<any>} ArrayDeltaContent
|
||||
* @return {ArrayDelta<ArrayDeltaContent>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ArrayDeltaContent} Content
|
||||
* @return {DeltaBuilder<Content>}
|
||||
*/
|
||||
export const createArrayDelta = () => new DeltaBuilder()
|
||||
export const createArrayDelta = () => new ArrayDelta()
|
||||
|
||||
@@ -414,7 +414,6 @@ export const createInsertionSetFromStructStore = ss => {
|
||||
return idset
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
||||
* @param {IdSet} idSet
|
||||
|
||||
@@ -135,6 +135,9 @@
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/traits.js": "./node_modules/lib0/traits.js",
|
||||
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
|
||||
"lib0/traits": "./node_modules/lib0/traits.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
@@ -296,6 +299,9 @@
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/traits.js": "./node_modules/lib0/traits.js",
|
||||
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
|
||||
"lib0/traits": "./node_modules/lib0/traits.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import * as idmap from '../src/utils/IdMap.js'
|
||||
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution, validateIdMap } from './testHelper.js'
|
||||
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution } from './testHelper.js'
|
||||
import * as YY from '../src/internals.js'
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as delta from '../src/utils/Delta.js'
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testDelta = _tc => {
|
||||
const d = delta.create().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done()
|
||||
const d = delta.createTextDelta().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!').done()
|
||||
t.compare(d.toJSON().ops, [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }])
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export const testDelta = _tc => {
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testDeltaMerging = _tc => {
|
||||
const d = delta.create()
|
||||
const d = delta.createTextDelta()
|
||||
.insert('hello')
|
||||
.insert('world')
|
||||
.insert(' ', { italic: true })
|
||||
|
||||
@@ -19,7 +19,6 @@ export const testAfterTransactionRecursion = _tc => {
|
||||
}, 'test')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
@@ -39,7 +38,7 @@ export const testFindTypeInOtherDoc = _tc => {
|
||||
const ydoc = /** @type {Y.Doc} */ (ytype.doc)
|
||||
if (ytype._item === null) {
|
||||
/**
|
||||
* If is a root type, we need to find the root key in the original ydoc
|
||||
* If is a root type, we need to find the root key in the original ydoc
|
||||
* and use it to get the type in the other ydoc.
|
||||
*/
|
||||
const rootKey = Array.from(ydoc.share.keys()).find(
|
||||
@@ -68,7 +67,6 @@ export const testFindTypeInOtherDoc = _tc => {
|
||||
t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
|
||||
@@ -528,8 +528,8 @@ export const testAttributedContent = _tc => {
|
||||
yarray.delete(0, 1)
|
||||
yarray.insert(1, [42])
|
||||
})
|
||||
let expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
|
||||
let attributedContent = yarray.getContent(attributionManager)
|
||||
const expectedContent = delta.createArrayDelta().insert([1], null, { delete: [] }).insert([2]).insert([42], null, { insert: [] })
|
||||
const attributedContent = yarray.getContent(attributionManager)
|
||||
console.log(attributedContent.toJSON().ops)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as Y from '../src/index.js'
|
||||
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
import {
|
||||
compareIDs,
|
||||
noAttributionsManager,
|
||||
@@ -631,22 +630,22 @@ export const testAttributedContent = _tc => {
|
||||
})
|
||||
t.group('initial value', () => {
|
||||
ymap.set('test', 42)
|
||||
let expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } }
|
||||
let attributedContent = ymap.getContent(attributionManager)
|
||||
const expectedContent = { test: { prevValue: undefined, value: 42, attribution: { insert: [] } } }
|
||||
const attributedContent = ymap.getContent(attributionManager)
|
||||
console.log(attributedContent)
|
||||
t.compare(expectedContent, attributedContent)
|
||||
})
|
||||
t.group('overwrite value', () => {
|
||||
ymap.set('test', 'fourtytwo')
|
||||
let expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } }
|
||||
let attributedContent = ymap.getContent(attributionManager)
|
||||
const expectedContent = { test: { prevValue: 42, value: 'fourtytwo', attribution: { insert: [] } } }
|
||||
const attributedContent = ymap.getContent(attributionManager)
|
||||
console.log(attributedContent)
|
||||
t.compare(expectedContent, attributedContent)
|
||||
})
|
||||
t.group('delete value', () => {
|
||||
ymap.delete('test')
|
||||
let expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } }
|
||||
let attributedContent = ymap.getContent(attributionManager)
|
||||
const expectedContent = { test: { prevValue: 'fourtytwo', value: undefined, attribution: { delete: [] } } }
|
||||
const attributedContent = ymap.getContent(attributionManager)
|
||||
console.log(attributedContent)
|
||||
t.compare(expectedContent, attributedContent)
|
||||
})
|
||||
|
||||
@@ -2316,15 +2316,15 @@ export const testAttributedContent = _tc => {
|
||||
})
|
||||
t.group('insert / delete / format', () => {
|
||||
ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }])
|
||||
let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!')
|
||||
let attributedContent = ytext.getContent(attributionManager)
|
||||
const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!')
|
||||
const attributedContent = ytext.getContent(attributionManager)
|
||||
console.log(attributedContent.toJSON().ops)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
})
|
||||
t.group('unformat', () => {
|
||||
ytext.applyDelta([{retain: 5, attributes: { italic: null }}])
|
||||
let expectedContent = delta.create().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
|
||||
let attributedContent = ytext.getContent(attributionManager)
|
||||
ytext.applyDelta([{ retain: 5, attributes: { italic: null } }])
|
||||
const expectedContent = delta.createTextDelta().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
|
||||
const attributedContent = ytext.getContent(attributionManager)
|
||||
console.log(attributedContent.toJSON().ops)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
})
|
||||
@@ -2355,9 +2355,9 @@ export const testAttributedDiffing = _tc => {
|
||||
// implementations is the TwosetAttributionManager
|
||||
const attributionManager = new TwosetAttributionManager(attributedInsertions, attributedDeletions)
|
||||
// we render the attributed content with the attributionManager
|
||||
let attributedContent = ytext.getContent(attributionManager)
|
||||
const attributedContent = ytext.getContent(attributionManager)
|
||||
console.log(JSON.stringify(attributedContent.toJSON().ops, null, 2))
|
||||
let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!')
|
||||
const expectedContent = delta.createTextDelta().insert('Hell', { italic: true }, { attributes: { italic: ['Bob'] } }).insert('o ').insert('World', {}, { delete: ['Bob'] }).insert('attributions', {}, { insert: ['Bob'] }).insert('!')
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
console.log(Y.encodeIdMap(attributedInsertions).length)
|
||||
}
|
||||
@@ -2588,14 +2588,14 @@ const checkResult = result => {
|
||||
*/
|
||||
const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d
|
||||
|
||||
t.info('length of text = ' + result.users[i-1].getText('text').length)
|
||||
t.info('length of text = ' + result.users[i - 1].getText('text').length)
|
||||
t.measureTime('original toDelta perf', () => {
|
||||
result.users[i-1].getText('text').toDelta().map(typeToObject)
|
||||
result.users[i - 1].getText('text').toDelta().map(typeToObject)
|
||||
})
|
||||
t.measureTime('getContent(attributionManager) performance)', () => {
|
||||
result.users[i-1].getText('text').getContent()
|
||||
result.users[i - 1].getText('text').getContent()
|
||||
})
|
||||
const p1 = result.users[i-1].getText('text').toDelta().map(typeToObject)
|
||||
const p1 = result.users[i - 1].getText('text').toDelta().map(typeToObject)
|
||||
const p2 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||
t.compare(p1, p2)
|
||||
}
|
||||
@@ -2629,7 +2629,7 @@ export const testAttributionManagerDefaultPerformance = tc => {
|
||||
ytext.insert(index, content)
|
||||
}
|
||||
}
|
||||
t.info(`number of changes: ${N/1000}k`)
|
||||
t.info(`number of changes: ${N / 1000}k`)
|
||||
t.info(`length of text: ${ytext.length}`)
|
||||
const M = 100
|
||||
t.measureTime(`original toString perf <executed ${M} times>`, () => {
|
||||
|
||||
@@ -243,8 +243,8 @@ export const testFragmentAttributedContent = _tc => {
|
||||
yfragment.delete(0, 1)
|
||||
yfragment.insert(1, [elem3])
|
||||
})
|
||||
let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
let attributedContent = yfragment.getContent(attributionManager)
|
||||
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const attributedContent = yfragment.getContent(attributionManager)
|
||||
console.log(attributedContent.children.toJSON().ops)
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON())
|
||||
@@ -257,8 +257,8 @@ export const testFragmentAttributedContent = _tc => {
|
||||
export const testElementAttributedContent = _tc => {
|
||||
const ydoc = new Y.Doc({ gc: false })
|
||||
const yelement = ydoc.getXmlElement('p')
|
||||
const elem1 = new Y.XmlElement('span')
|
||||
const elem2 = new Y.XmlText('hello')
|
||||
const elem1 = new Y.XmlText('hello')
|
||||
const elem2 = new Y.XmlElement('span')
|
||||
const elem3 = new Y.XmlText('world')
|
||||
yelement.insert(0, [elem1, elem2])
|
||||
let attributionManager = Y.noAttributionsManager
|
||||
@@ -272,12 +272,28 @@ export const testElementAttributedContent = _tc => {
|
||||
yelement.insert(1, [elem3])
|
||||
yelement.setAttribute('key', '42')
|
||||
})
|
||||
let expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
let attributedContent = yelement.getContent(attributionManager)
|
||||
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const attributedContent = yelement.getContent(attributionManager)
|
||||
console.log('children', attributedContent.children.toJSON().ops)
|
||||
console.log('attributes', attributedContent.attributes)
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.group('test getContentDeep', () => {
|
||||
const expectedContent = delta.createArrayDelta().insert(
|
||||
[delta.createTextDelta().insert('hello', null, { delete: [] })],
|
||||
null,
|
||||
{ delete: [] }
|
||||
).insert([{ nodeName: 'span', children: delta.createArrayDelta(), attributes: {} }])
|
||||
.insert([
|
||||
delta.createTextDelta().insert('world', null, { insert: [] })
|
||||
], null, { insert: [] })
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', JSON.stringify(attributedContent.children.toJSON().ops, null, 2))
|
||||
console.log('cs expec', JSON.stringify(expectedContent.toJSON().ops, null, 2))
|
||||
console.log('attributes', attributedContent.attributes)
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.nodeName === 'UNDEFINED')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user