mirror of
https://github.com/yjs/yjs.git
synced 2025-12-16 11:47:46 +01:00
[diffing] event returns delta class object, migrate away from legacy deltas, work on snapshots using attribution manager. WIP
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "14.0.0-5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.105"
|
||||
"lib0": "^0.2.107"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
@@ -2796,9 +2796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.105",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.105.tgz",
|
||||
"integrity": "sha512-5vtbuBi2P43ZYOfVMV+TZYkWEa0J9kijXirzEgrPA+nJDQCtMx805/rqW4G1nXbM9IRIhwW+OyNNgcQdbhKfSw==",
|
||||
"version": "0.2.107",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.107.tgz",
|
||||
"integrity": "sha512-2xih/AugT0dJSgeSfsW/bqIPILlsqzEtmw8hXzWEnMLrOz12DTK5z9rjNgUT21/HkBjHSznOQBr67bcZdc8Ltg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"debug": "npm run gentesthtml && 0serve -o test.html",
|
||||
"trace-deopt": "clear && node --trace-deopt ./tests/index.js",
|
||||
"trace-opt": "clear && node --trace-opt ./tests/index.js",
|
||||
"gentesthtml": "0gentesthtml --script ./tests/index.js > test.html"
|
||||
"gentesthtml": "0gentesthtml --script ./tests/index.js --include-dependencies @y/protocols > test.html"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -85,7 +85,7 @@
|
||||
},
|
||||
"homepage": "https://docs.yjs.dev",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.105"
|
||||
"lib0": "^0.2.107"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@y/protocols": "^1.0.6-1",
|
||||
|
||||
@@ -728,6 +728,8 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
||||
case Boolean:
|
||||
case Array:
|
||||
case String:
|
||||
case BigInt:
|
||||
case Date:
|
||||
jsonContent.push(c)
|
||||
break
|
||||
default:
|
||||
@@ -916,6 +918,8 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
||||
case Boolean:
|
||||
case Array:
|
||||
case String:
|
||||
case Date:
|
||||
case BigInt:
|
||||
content = new ContentAny([value])
|
||||
break
|
||||
case Uint8Array:
|
||||
|
||||
@@ -225,8 +225,8 @@ export class YArray extends AbstractType {
|
||||
*/
|
||||
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 instanceof delta.InsertArrayOp && d.insert instanceof Array
|
||||
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
AbstractType,
|
||||
getItemCleanStart,
|
||||
getState,
|
||||
isVisible,
|
||||
createID,
|
||||
YTextRefID,
|
||||
callTypeObservers,
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
GC,
|
||||
ContentFormat,
|
||||
ContentString,
|
||||
splitSnapshotAffectedStructs,
|
||||
iterateStructsByIdSet,
|
||||
findMarker,
|
||||
typeMapDelete,
|
||||
@@ -26,7 +24,7 @@ import {
|
||||
updateMarkerChanges,
|
||||
ContentType,
|
||||
warnPrematureAccess,
|
||||
noAttributionsManager, AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction, // eslint-disable-line
|
||||
noAttributionsManager, AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, Transaction, // eslint-disable-line
|
||||
createAttributionFromAttributionItems
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -622,12 +620,12 @@ export class YTextEvent extends YEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta}}
|
||||
*/
|
||||
get changes () {
|
||||
if (this._changes === null) {
|
||||
/**
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string|AbstractType<any>|object, delete?:number, retain?:number}>}}
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta}}
|
||||
*/
|
||||
const changes = {
|
||||
keys: this.keys,
|
||||
@@ -644,192 +642,106 @@ export class YTextEvent extends YEvent {
|
||||
* Compute the changes in the delta format.
|
||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
||||
*
|
||||
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
* @type {delta.TextDelta}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
get delta () {
|
||||
if (this._delta === null) {
|
||||
const y = /** @type {Doc} */ (this.target.doc)
|
||||
/**
|
||||
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
*/
|
||||
const delta = []
|
||||
transact(y, transaction => {
|
||||
const currentAttributes = new Map() // saves all current attributes for insert
|
||||
const oldAttributes = new Map()
|
||||
const ydoc = /** @type {Doc} */ (this.target.doc)
|
||||
const d = this._delta = delta.createTextDelta()
|
||||
transact(ydoc, transaction => {
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let currentAttributes = {} // saves all current attributes for insert
|
||||
let usingCurrentAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
let changedAttributes = {} // saves changed attributes for retain
|
||||
let usingChangedAttributes = false
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
const previousAttributes = {} // The value before changes
|
||||
const tr = this.transaction
|
||||
let item = this.target._start
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
let action = null
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const attributes = {} // counts added or removed new attributes for retain
|
||||
/**
|
||||
* @type {string|object}
|
||||
*/
|
||||
let insert = ''
|
||||
let retain = 0
|
||||
let deleteLen = 0
|
||||
const addOp = () => {
|
||||
if (action !== null) {
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
let op = null
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
if (deleteLen > 0) {
|
||||
op = { delete: deleteLen }
|
||||
}
|
||||
deleteLen = 0
|
||||
break
|
||||
case 'insert':
|
||||
if (typeof insert === 'object' || insert.length > 0) {
|
||||
op = { insert }
|
||||
if (currentAttributes.size > 0) {
|
||||
op.attributes = {}
|
||||
currentAttributes.forEach((value, key) => {
|
||||
if (value !== null) {
|
||||
op.attributes[key] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
insert = ''
|
||||
break
|
||||
case 'retain':
|
||||
if (retain > 0) {
|
||||
op = { retain }
|
||||
if (!object.isEmpty(attributes)) {
|
||||
op.attributes = object.assign({}, attributes)
|
||||
}
|
||||
}
|
||||
retain = 0
|
||||
break
|
||||
}
|
||||
if (op) delta.push(op)
|
||||
action = null
|
||||
}
|
||||
}
|
||||
while (item !== null) {
|
||||
const freshDelete = item.deleted && tr.deleteSet.hasId(item.id) && !tr.insertSet.hasId(item.id)
|
||||
const freshInsert = !item.deleted && tr.insertSet.hasId(item.id)
|
||||
switch (item.content.constructor) {
|
||||
case ContentType:
|
||||
case ContentEmbed:
|
||||
if (this.adds(item)) {
|
||||
if (!this.deletes(item)) {
|
||||
addOp()
|
||||
action = 'insert'
|
||||
insert = item.content.getContent()[0]
|
||||
addOp()
|
||||
}
|
||||
} else if (this.deletes(item)) {
|
||||
if (action !== 'delete') {
|
||||
addOp()
|
||||
action = 'delete'
|
||||
}
|
||||
deleteLen += 1
|
||||
if (freshInsert) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
d.insert(item.content.getContent()[0])
|
||||
} else if (freshDelete) {
|
||||
d.delete(1)
|
||||
} else if (!item.deleted) {
|
||||
if (action !== 'retain') {
|
||||
addOp()
|
||||
action = 'retain'
|
||||
}
|
||||
retain += 1
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(1)
|
||||
}
|
||||
break
|
||||
case ContentString:
|
||||
if (this.adds(item)) {
|
||||
if (!this.deletes(item)) {
|
||||
if (action !== 'insert') {
|
||||
addOp()
|
||||
action = 'insert'
|
||||
}
|
||||
insert += /** @type {ContentString} */ (item.content).str
|
||||
}
|
||||
} else if (this.deletes(item)) {
|
||||
if (action !== 'delete') {
|
||||
addOp()
|
||||
action = 'delete'
|
||||
}
|
||||
deleteLen += item.length
|
||||
if (freshInsert) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
d.insert(/** @type {ContentString} */ (item.content).str)
|
||||
} else if (freshDelete) {
|
||||
d.delete(item.length)
|
||||
} else if (!item.deleted) {
|
||||
if (action !== 'retain') {
|
||||
addOp()
|
||||
action = 'retain'
|
||||
}
|
||||
retain += item.length
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(item.length)
|
||||
}
|
||||
break
|
||||
case ContentFormat: {
|
||||
const { key, value } = /** @type {ContentFormat} */ (item.content)
|
||||
if (this.adds(item)) {
|
||||
if (!this.deletes(item)) {
|
||||
const curVal = currentAttributes.get(key) ?? null
|
||||
if (!equalAttrs(curVal, value)) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
if (equalAttrs(value, (oldAttributes.get(key) ?? null))) {
|
||||
delete attributes[key]
|
||||
} else {
|
||||
attributes[key] = value
|
||||
}
|
||||
} else if (value !== null) {
|
||||
item.delete(transaction)
|
||||
}
|
||||
const currAttrVal = currentAttributes[key] ?? null
|
||||
if (freshDelete || freshInsert) {
|
||||
// create fresh references
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
} else if (this.deletes(item)) {
|
||||
oldAttributes.set(key, value)
|
||||
const curVal = currentAttributes.get(key) ?? null
|
||||
if (!equalAttrs(curVal, value)) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
attributes[key] = curVal
|
||||
}
|
||||
} else if (!item.deleted) {
|
||||
oldAttributes.set(key, value)
|
||||
const attr = attributes[key]
|
||||
if (attr !== undefined) {
|
||||
if (!equalAttrs(attr, value)) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
if (value === null) {
|
||||
delete attributes[key]
|
||||
} else {
|
||||
attributes[key] = value
|
||||
}
|
||||
} else if (attr !== null) { // this will be cleaned up automatically by the contextless cleanup function
|
||||
item.delete(transaction)
|
||||
}
|
||||
if (usingChangedAttributes) {
|
||||
usingChangedAttributes = false
|
||||
changedAttributes = object.assign({}, changedAttributes)
|
||||
}
|
||||
}
|
||||
if (!item.deleted) {
|
||||
if (action === 'insert') {
|
||||
addOp()
|
||||
if (freshInsert) {
|
||||
if (equalAttrs(value, currAttrVal)) {
|
||||
item.delete(transaction)
|
||||
} else if (equalAttrs(value, previousAttributes[key] ?? null)) {
|
||||
delete currentAttributes[key]
|
||||
delete changedAttributes[key]
|
||||
} else {
|
||||
currentAttributes[key] = value
|
||||
changedAttributes[key] = value
|
||||
}
|
||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (item.content))
|
||||
} else if (freshDelete) {
|
||||
changedAttributes[key] = currAttrVal
|
||||
currentAttributes[key] = currAttrVal
|
||||
previousAttributes[key] = value
|
||||
} else if (!item.deleted) {
|
||||
// fresh reference to currentAttributes only
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
currentAttributes[key] = value
|
||||
previousAttributes[key] = value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
item = item.right
|
||||
}
|
||||
addOp()
|
||||
while (delta.length > 0) {
|
||||
const lastOp = delta[delta.length - 1]
|
||||
if (lastOp.retain !== undefined && lastOp.attributes === undefined) {
|
||||
// retain delta's if they don't assign attributes
|
||||
delta.pop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
this._delta = delta
|
||||
d.done()
|
||||
}
|
||||
return /** @type {any} */ (this._delta)
|
||||
}
|
||||
@@ -903,7 +815,7 @@ export class YText extends AbstractType {
|
||||
*/
|
||||
clone () {
|
||||
const text = new YText()
|
||||
text.applyDelta(this.toDelta())
|
||||
text.applyDelta(this.getContent())
|
||||
return text
|
||||
}
|
||||
|
||||
@@ -957,7 +869,7 @@ export class YText extends AbstractType {
|
||||
/**
|
||||
* Apply a {@link Delta} on this shared YText type.
|
||||
*
|
||||
* @param {Array<any>} delta The changes to apply on this element.
|
||||
* @param {Array<any> | delta.Delta} delta The changes to apply on this element.
|
||||
* @param {object} opts
|
||||
* @param {boolean} [opts.sanitize] Sanitize input delta. Removes ending newlines if set to true.
|
||||
*
|
||||
@@ -967,16 +879,20 @@ export class YText extends AbstractType {
|
||||
applyDelta (delta, { sanitize = true } = {}) {
|
||||
if (this.doc !== null) {
|
||||
transact(this.doc, transaction => {
|
||||
/**
|
||||
* @type {Array<any>}
|
||||
*/
|
||||
const deltaOps = /** @type {Array<any>} */ (/** @type {delta.Delta} */ (delta).ops instanceof Array ? /** @type {delta.Delta} */ (delta).ops : delta)
|
||||
const currPos = new ItemTextListPosition(null, this._start, 0, new Map())
|
||||
for (let i = 0; i < delta.length; i++) {
|
||||
const op = delta[i]
|
||||
for (let i = 0; i < deltaOps.length; i++) {
|
||||
const op = deltaOps[i]
|
||||
if (op.insert !== undefined) {
|
||||
// Quill assumes that the content starts with an empty paragraph.
|
||||
// Yjs/Y.Text assumes that it starts empty. We always hide that
|
||||
// there is a newline at the end of the content.
|
||||
// If we omit this step, clients will see a different number of
|
||||
// paragraphs, but nothing bad will happen.
|
||||
const ins = (!sanitize && typeof op.insert === 'string' && i === delta.length - 1 && currPos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert
|
||||
const ins = (!sanitize && typeof op.insert === 'string' && i === deltaOps.length - 1 && currPos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert
|
||||
if (typeof ins !== 'string' || ins.length > 0) {
|
||||
insertText(transaction, this, currPos, ins, op.attributes || {})
|
||||
}
|
||||
@@ -1006,8 +922,8 @@ export class YText extends AbstractType {
|
||||
*/
|
||||
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 instanceof delta.InsertStringOp && d.insert instanceof AbstractType
|
||||
? new delta.InsertStringOp(d.insert.getContent(am), d.attributes, d.attribution)
|
||||
: d
|
||||
)
|
||||
}
|
||||
@@ -1091,121 +1007,6 @@ export class YText extends AbstractType {
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Delta representation of this YText type.
|
||||
*
|
||||
* @param {Snapshot} [snapshot]
|
||||
* @param {Snapshot} [prevSnapshot]
|
||||
* @param {function('removed' | 'added', ID):any} [computeYChange]
|
||||
* @return {any} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toDelta (snapshot, prevSnapshot, computeYChange) {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
/**
|
||||
* @type{Array<any>}
|
||||
*/
|
||||
const ops = []
|
||||
const currentAttributes = new Map()
|
||||
const doc = /** @type {Doc} */ (this.doc)
|
||||
let str = ''
|
||||
let n = this._start
|
||||
function packStr () {
|
||||
if (str.length > 0) {
|
||||
// pack str with attributes to ops
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const attributes = {}
|
||||
let addAttributes = false
|
||||
currentAttributes.forEach((value, key) => {
|
||||
addAttributes = true
|
||||
attributes[key] = value
|
||||
})
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = { insert: str }
|
||||
if (addAttributes) {
|
||||
op.attributes = attributes
|
||||
}
|
||||
ops.push(op)
|
||||
str = ''
|
||||
}
|
||||
}
|
||||
const computeDelta = () => {
|
||||
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.type !== 'removed') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' })
|
||||
}
|
||||
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
||||
if (cur === undefined || cur.user !== n.id.client || cur.type !== 'added') {
|
||||
packStr()
|
||||
currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' })
|
||||
}
|
||||
} else if (cur !== undefined) {
|
||||
packStr()
|
||||
currentAttributes.delete('ychange')
|
||||
}
|
||||
str += /** @type {ContentString} */ (n.content).str
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
packStr()
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = {
|
||||
insert: n.content.getContent()[0]
|
||||
}
|
||||
if (currentAttributes.size > 0) {
|
||||
const attrs = /** @type {Object<string,any>} */ ({})
|
||||
op.attributes = attrs
|
||||
currentAttributes.forEach((value, key) => {
|
||||
attrs[key] = value
|
||||
})
|
||||
}
|
||||
ops.push(op)
|
||||
break
|
||||
}
|
||||
case ContentFormat:
|
||||
if (isVisible(n, snapshot)) {
|
||||
packStr()
|
||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
n = n.right
|
||||
}
|
||||
packStr()
|
||||
}
|
||||
if (snapshot || prevSnapshot) {
|
||||
// snapshots are merged again after the transaction, so we need to keep the
|
||||
// transaction alive until we are done
|
||||
transact(doc, transaction => {
|
||||
if (snapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, snapshot)
|
||||
}
|
||||
if (prevSnapshot) {
|
||||
splitSnapshotAffectedStructs(transaction, prevSnapshot)
|
||||
}
|
||||
computeDelta()
|
||||
}, 'cleanup')
|
||||
} else {
|
||||
computeDelta()
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text at a given index.
|
||||
*
|
||||
|
||||
@@ -225,8 +225,8 @@ export class YXmlElement extends YXmlFragment {
|
||||
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 instanceof delta.InsertArrayOp && d.insert instanceof Array)
|
||||
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDelta<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
/**
|
||||
|
||||
@@ -403,8 +403,8 @@ export class YXmlFragment extends AbstractType {
|
||||
* @type {import('../utils/Delta.js').ArrayDelta<Array<import('./AbstractType.js').YXmlDeepContent>>}
|
||||
*/
|
||||
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 ? e.getContentDeep(am) : e), d.attributes, d.attribution)
|
||||
d instanceof delta.InsertArrayOp && d.insert instanceof Array
|
||||
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? e.getContentDeep(am) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
return { children }
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
ContentType, YXmlElement, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
/**
|
||||
* Represents text in a Dom Element. In the future this type will also handle
|
||||
* simple formatting information like bold and italic.
|
||||
@@ -38,7 +40,7 @@ export class YXmlText extends YText {
|
||||
*/
|
||||
clone () {
|
||||
const text = new YXmlText()
|
||||
text.applyDelta(this.toDelta())
|
||||
text.applyDelta(this.getContent())
|
||||
return text
|
||||
}
|
||||
|
||||
@@ -66,36 +68,38 @@ export class YXmlText extends YText {
|
||||
}
|
||||
|
||||
toString () {
|
||||
// @ts-ignore
|
||||
return this.toDelta().map(delta => {
|
||||
const nestedNodes = []
|
||||
for (const nodeName in delta.attributes) {
|
||||
const attrs = []
|
||||
for (const key in delta.attributes[nodeName]) {
|
||||
attrs.push({ key, value: delta.attributes[nodeName][key] })
|
||||
return this.getContent().ops.map(dop => {
|
||||
if (dop instanceof delta.InsertStringOp) {
|
||||
const nestedNodes = []
|
||||
for (const nodeName in dop.attributes) {
|
||||
const attrs = []
|
||||
for (const key in dop.attributes[nodeName]) {
|
||||
attrs.push({ key, value: dop.attributes[nodeName][key] })
|
||||
}
|
||||
// sort attributes to get a unique order
|
||||
attrs.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||
nestedNodes.push({ nodeName, attrs })
|
||||
}
|
||||
// sort attributes to get a unique order
|
||||
attrs.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||
nestedNodes.push({ nodeName, attrs })
|
||||
}
|
||||
// sort node order to get a unique order
|
||||
nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1)
|
||||
// now convert to dom string
|
||||
let str = ''
|
||||
for (let i = 0; i < nestedNodes.length; i++) {
|
||||
const node = nestedNodes[i]
|
||||
str += `<${node.nodeName}`
|
||||
for (let j = 0; j < node.attrs.length; j++) {
|
||||
const attr = node.attrs[j]
|
||||
str += ` ${attr.key}="${attr.value}"`
|
||||
// sort node order to get a unique order
|
||||
nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1)
|
||||
// now convert to dom string
|
||||
let str = ''
|
||||
for (let i = 0; i < nestedNodes.length; i++) {
|
||||
const node = nestedNodes[i]
|
||||
str += `<${node.nodeName}`
|
||||
for (let j = 0; j < node.attrs.length; j++) {
|
||||
const attr = node.attrs[j]
|
||||
str += ` ${attr.key}="${attr.value}"`
|
||||
}
|
||||
str += '>'
|
||||
}
|
||||
str += '>'
|
||||
str += dop.insert
|
||||
for (let i = nestedNodes.length - 1; i >= 0; i--) {
|
||||
str += `</${nestedNodes[i].nodeName}>`
|
||||
}
|
||||
return str
|
||||
}
|
||||
str += delta.insert
|
||||
for (let i = nestedNodes.length - 1; i >= 0; i--) {
|
||||
str += `</${nestedNodes[i].nodeName}>`
|
||||
}
|
||||
return str
|
||||
return ''
|
||||
}).join('')
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,14 @@ import {
|
||||
createDeleteSetFromStructStore,
|
||||
createIdMapFromIdSet,
|
||||
ContentDeleted,
|
||||
Doc, Item, AbstractContent, IdMap, // eslint-disable-line
|
||||
Snapshot, Doc, Item, AbstractContent, IdMap, // eslint-disable-line
|
||||
insertIntoIdMap,
|
||||
insertIntoIdSet,
|
||||
diffIdMap
|
||||
diffIdMap,
|
||||
createIdMap,
|
||||
createAttributionItem,
|
||||
mergeIdMaps,
|
||||
AttributionItem
|
||||
} from '../internals.js'
|
||||
|
||||
import * as error from 'lib0/error'
|
||||
@@ -90,6 +94,8 @@ export class AbstractAttributionManager {
|
||||
readContent (_contents, _item) {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
|
||||
destroy () {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +111,8 @@ export class TwosetAttributionManager {
|
||||
this.deletes = deletes
|
||||
}
|
||||
|
||||
destroy () {}
|
||||
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} contents
|
||||
* @param {Item} item
|
||||
@@ -131,6 +139,8 @@ export class TwosetAttributionManager {
|
||||
* @implements AbstractAttributionManager
|
||||
*/
|
||||
export class NoAttributionsManager {
|
||||
destroy () {}
|
||||
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} contents
|
||||
* @param {Item} item
|
||||
@@ -243,3 +253,59 @@ export class DiffAttributionManager {
|
||||
* @param {Doc} nextDoc
|
||||
*/
|
||||
export const createAttributionManagerFromDiff = (prevDoc, nextDoc) => new DiffAttributionManager(prevDoc, nextDoc)
|
||||
|
||||
/**
|
||||
* Intended for projects that used the v13 snapshot feature. With this AttributionManager you can
|
||||
* read content similar to the previous snapshot api. Requires that `ydoc.gc` is turned off.
|
||||
*
|
||||
* @implements AbstractAttributionManager
|
||||
*/
|
||||
export class SnapshotAttributionManager {
|
||||
/**
|
||||
* @param {Snapshot} prevSnapshot
|
||||
* @param {Snapshot} nextSnapshot
|
||||
*/
|
||||
constructor (prevSnapshot, nextSnapshot) {
|
||||
this.prevSnapshot = prevSnapshot
|
||||
this.nextSnapshot = nextSnapshot
|
||||
const inserts = createIdMap()
|
||||
const deletes = createIdMapFromIdSet(diffIdSet(nextSnapshot.ds, prevSnapshot.ds), [createAttributionItem('change', '')])
|
||||
nextSnapshot.sv.forEach((clock, client) => {
|
||||
inserts.add(client, 0, prevSnapshot.sv.get(client) || 0, [])
|
||||
inserts.add(client, prevSnapshot.sv.get(client) || 0, clock, [createAttributionItem('change', '')])
|
||||
})
|
||||
this.attrs = mergeIdMaps([diffIdMap(inserts, prevSnapshot.ds), deletes])
|
||||
}
|
||||
|
||||
destroy () { }
|
||||
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} contents
|
||||
* @param {Item} item
|
||||
*/
|
||||
readContent (contents, item) {
|
||||
if ((this.nextSnapshot.sv.get(item.id.client) ?? 0) <= item.id.clock) return // future item that should not be displayed
|
||||
const slice = this.attrs.slice(item.id, item.length)
|
||||
let content = slice.length === 1 ? item.content : item.content.copy()
|
||||
slice.forEach(s => {
|
||||
const deleted = this.nextSnapshot.ds.has(item.id.client, s.clock)
|
||||
const c = content
|
||||
if (s.len < c.getLength()) {
|
||||
content = c.splice(s.len)
|
||||
}
|
||||
if (!deleted || (s.attrs != null && s.attrs.length > 0)) {
|
||||
let attrsWithoutChange = s.attrs?.filter(attr => attr.name !== 'change') ?? null
|
||||
if (s.attrs?.length === 0) {
|
||||
attrsWithoutChange = null
|
||||
}
|
||||
contents.push(new AttributedContent(c, deleted, attrsWithoutChange))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Snapshot} prevSnapshot
|
||||
* @param {Snapshot} nextSnapshot
|
||||
*/
|
||||
export const createAttributionManagerFromSnapshots = (prevSnapshot, nextSnapshot = prevSnapshot) => new SnapshotAttributionManager(prevSnapshot, nextSnapshot)
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import * as object from 'lib0/object'
|
||||
import * as fun from 'lib0/function'
|
||||
import * as traits from 'lib0/traits'
|
||||
import * as error from 'lib0/error'
|
||||
|
||||
/**
|
||||
* @template {string|Array<any>|{[key: string]: any}} Content
|
||||
* @typedef {InsertOp<Content>|RetainOp|DeleteOp} DeltaOp
|
||||
* @template {any} ArrayContent
|
||||
* @template {{[key: string]: any}} Embeds
|
||||
* @typedef {InsertStringOp|InsertEmbedOp<Embeds>|InsertArrayOp<ArrayContent>|RetainOp|DeleteOp} DeltaOp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {{[key: string]: any}} Embeds
|
||||
* @typedef {InsertStringOp|InsertEmbedOp<Embeds>|RetainOp|DeleteOp} TextDeltaOp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {any} ArrayContent
|
||||
* @typedef {InsertArrayOp<ArrayContent>|RetainOp|DeleteOp} ArrayDeltaOp
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -16,11 +28,16 @@ import * as traits from 'lib0/traits'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {string|Array<any>|{[key: string]: any}} Content
|
||||
* @typedef {Array<DeltaJsonOp>} DeltaJson
|
||||
*/
|
||||
export class InsertOp {
|
||||
|
||||
/**
|
||||
* @typedef {{ insert: string|object, attributes?: { [key: string]: any }, attribution?: Attribution } | { delete: number } | { retain: number, attributes?: { [key:string]: any }, attribution?: Attribution }} DeltaJsonOp
|
||||
*/
|
||||
|
||||
export class InsertStringOp {
|
||||
/**
|
||||
* @param {Content} insert
|
||||
* @param {string} insert
|
||||
* @param {FormattingAttributes|null} attributes
|
||||
* @param {Attribution|null} attribution
|
||||
*/
|
||||
@@ -34,12 +51,83 @@ export class InsertOp {
|
||||
return (this.insert.constructor === Array || this.insert.constructor === String) ? this.insert.length : 1
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DeltaJsonOp}
|
||||
*/
|
||||
toJSON () {
|
||||
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InsertOp<Content>} other
|
||||
* @param {InsertStringOp} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {any} ArrayContent
|
||||
*/
|
||||
export class InsertArrayOp {
|
||||
/**
|
||||
* @param {Array<ArrayContent>} insert
|
||||
* @param {FormattingAttributes|null} attributes
|
||||
* @param {Attribution|null} attribution
|
||||
*/
|
||||
constructor (insert, attributes, attribution) {
|
||||
this.insert = insert
|
||||
this.attributes = attributes
|
||||
this.attribution = attribution
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this.insert.length
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DeltaJsonOp}
|
||||
*/
|
||||
toJSON () {
|
||||
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InsertArrayOp<ArrayContent>} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {{[key: string]: any}} Embeds
|
||||
*/
|
||||
export class InsertEmbedOp {
|
||||
/**
|
||||
* @param {Embeds} insert
|
||||
* @param {FormattingAttributes|null} attributes
|
||||
* @param {Attribution|null} attribution
|
||||
*/
|
||||
constructor (insert, attributes, attribution) {
|
||||
this.insert = insert
|
||||
this.attributes = attributes
|
||||
this.attribution = attribution
|
||||
}
|
||||
|
||||
get length () {
|
||||
return 1
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DeltaJsonOp}
|
||||
*/
|
||||
toJSON () {
|
||||
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : ({}), this.attribution ? { attribution: this.attribution } : ({}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InsertEmbedOp<Embeds>} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.attributes, other.attributes) && fun.equalityDeep(this.attribution, other.attribution)
|
||||
@@ -58,6 +146,9 @@ export class DeleteOp {
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DeltaJsonOp}
|
||||
*/
|
||||
toJSON () {
|
||||
return { delete: this.delete }
|
||||
}
|
||||
@@ -86,6 +177,9 @@ export class RetainOp {
|
||||
return this.retain
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {DeltaJsonOp}
|
||||
*/
|
||||
toJSON () {
|
||||
return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {})
|
||||
}
|
||||
@@ -98,25 +192,17 @@ export class RetainOp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Array<any>} ArrayDeltaContent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string | { [key: string]: any }} TextDeltaContent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ array: ArrayDeltaContent, text: TextDeltaContent, custom: string|Array<any>|{[key:string]:any}}} DeltaTypeMapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(TextDelta | ArrayDelta)} Delta
|
||||
* @typedef {(TextDelta<any> | ArrayDelta<any>)} Delta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {'array' | 'text' | 'custom'} Type
|
||||
* @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
|
||||
* @template {DeltaOp<any,any>} TDeltaOp
|
||||
*/
|
||||
export class AbstractDelta {
|
||||
/**
|
||||
@@ -125,26 +211,26 @@ export class AbstractDelta {
|
||||
constructor (type) {
|
||||
this.type = type
|
||||
/**
|
||||
* @type {Array<DeltaOp<Content>>}
|
||||
* @type {Array<TDeltaOp>}
|
||||
*/
|
||||
this.ops = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {DeltaTypeMapper[Type]} MContent
|
||||
* @param {(d:DeltaOp<Content>)=>DeltaOp<MContent>} f
|
||||
* @return {DeltaBuilder<Type, MContent>}
|
||||
* @template {(d:TDeltaOp) => DeltaOp<any,any>} Mapper
|
||||
* @param {Mapper} f
|
||||
* @return {DeltaBuilder<Type, Mapper extends (d:TDeltaOp) => infer OP ? OP : unknown>}
|
||||
*/
|
||||
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
|
||||
d.lastOp = d.ops[d.ops.length - 1] ?? null
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(d:DeltaOp<Content>,index:number)=>void} f
|
||||
* @param {(d:TDeltaOp,index:number)=>void} f
|
||||
*/
|
||||
forEach (f) {
|
||||
for (
|
||||
@@ -157,22 +243,25 @@ export class AbstractDelta {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractDelta<Type, Content>} other
|
||||
* @param {AbstractDelta<Type, TDeltaOp>} other
|
||||
* @return {boolean}
|
||||
*/
|
||||
equals (other) {
|
||||
return this[traits.EqualityTraitSymbol](other)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {DeltaJson}
|
||||
*/
|
||||
toJSON () {
|
||||
return { ops: this.ops.map(o => o.toJSON()) }
|
||||
return this.ops.map(o => o.toJSON())
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractDelta<Type,Content>} other
|
||||
* @param {AbstractDelta<Type,TDeltaOp>} other
|
||||
*/
|
||||
[traits.EqualityTraitSymbol] (other) {
|
||||
return this.type === other.type && fun.equalityDeep(this.ops, other.ops)
|
||||
return fun.equalityDeep(this.ops, other.ops)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,15 +273,14 @@ export class AbstractDelta {
|
||||
* @param {T | null} b
|
||||
*/
|
||||
const mergeAttrs = (a, b) => {
|
||||
const merged = a == null ? b : (b == null ? a : object.assign({}, a, b))
|
||||
if (merged == null || object.isEmpty(merged)) { return null }
|
||||
return merged
|
||||
const merged = object.isEmpty(a) ? b : (object.isEmpty(b) ? a : object.assign({}, a, b))
|
||||
return object.isEmpty(merged) ? null : merged
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {'array' | 'text' | 'custom'} [Type='custom']
|
||||
* @template {DeltaTypeMapper[Type]} [Content=DeltaTypeMapper[Type]]
|
||||
* @extends AbstractDelta<Type,Content>
|
||||
* @template {DeltaOp<any,any>} [TDeltaOp=DeltaOp<any,any>]
|
||||
* @extends AbstractDelta<Type,TDeltaOp>
|
||||
*/
|
||||
export class DeltaBuilder extends AbstractDelta {
|
||||
/**
|
||||
@@ -209,10 +297,9 @@ export class DeltaBuilder extends AbstractDelta {
|
||||
*/
|
||||
this.usedAttribution = null
|
||||
/**
|
||||
* @private
|
||||
* @type {DeltaOp<Content>?}
|
||||
* @type {TDeltaOp?}
|
||||
*/
|
||||
this._lastOp = null
|
||||
this.lastOp = null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,8 +307,44 @@ export class DeltaBuilder extends AbstractDelta {
|
||||
* @return {this}
|
||||
*/
|
||||
useAttributes (attributes) {
|
||||
if (this.usedAttributes === attributes) return this
|
||||
this.usedAttributes = attributes && (object.isEmpty(attributes) ? null : object.assign({}, attributes))
|
||||
this.usedAttributes = object.isEmpty(attributes) ? null : object.assign({}, attributes)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {any} value
|
||||
*/
|
||||
updateUsedAttributes (name, value) {
|
||||
if (value == null) {
|
||||
this.usedAttributes = object.assign({}, this.usedAttributes)
|
||||
delete this.usedAttributes?.[name]
|
||||
if (object.isEmpty(this.usedAttributes)) {
|
||||
this.usedAttributes = null
|
||||
}
|
||||
} else if (!fun.equalityDeep(this.usedAttributes?.[name], value)) {
|
||||
this.usedAttributes = object.assign({}, this.usedAttributes)
|
||||
this.usedAttributes[name] = value
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof Attribution} NAME
|
||||
* @param {NAME} name
|
||||
* @param {Attribution[NAME]?} value
|
||||
*/
|
||||
updateUsedAttribution (name, value) {
|
||||
if (value == null) {
|
||||
this.usedAttribution = object.assign({}, this.usedAttribution)
|
||||
delete this.usedAttribution?.[name]
|
||||
if (object.isEmpty(this.usedAttribution)) {
|
||||
this.usedAttribution = null
|
||||
}
|
||||
} else if (!fun.equalityDeep(this.usedAttribution?.[name], value)) {
|
||||
this.usedAttribution = object.assign({}, this.usedAttribution)
|
||||
this.usedAttribution[name] = value
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -229,32 +352,30 @@ export class DeltaBuilder extends AbstractDelta {
|
||||
* @param {Attribution?} attribution
|
||||
*/
|
||||
useAttribution (attribution) {
|
||||
if (this.usedAttribution === attribution) return this
|
||||
this.usedAttribution = attribution && (object.isEmpty(attribution) ? null : object.assign({}, attribution))
|
||||
this.usedAttribution = object.isEmpty(attribution) ? null : object.assign({}, attribution)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Content} insert
|
||||
* @param {(TDeltaOp extends TextDelta<infer Embeds> ? string | Embeds : never) | (TDeltaOp extends InsertArrayOp<infer Content> ? Array<Content> : never) } insert
|
||||
* @param {FormattingAttributes?} attributes
|
||||
* @param {Attribution?} attribution
|
||||
* @return {this}
|
||||
*/
|
||||
insert (insert, attributes = null, attribution = null) {
|
||||
const mergedAttributes = attributes == null ? this.usedAttributes : mergeAttrs(this.usedAttributes, attributes)
|
||||
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))) {
|
||||
const mergedAttributes = mergeAttrs(this.usedAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this.usedAttribution, attribution)
|
||||
if (((this.lastOp instanceof InsertStringOp && insert.constructor === String) || (this.lastOp instanceof InsertArrayOp && insert.constructor === Array)) && (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
|
||||
this._lastOp.insert += insert
|
||||
} else if (insert.constructor === Array && this._lastOp.insert.constructor === Array) {
|
||||
// @ts-ignore
|
||||
this._lastOp.insert.push(...insert)
|
||||
this.lastOp.insert += insert
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution))
|
||||
// @ts-ignore
|
||||
this.lastOp.insert.push(...insert)
|
||||
}
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new InsertOp(insert, mergedAttributes, mergedAttribution))
|
||||
const OpConstructor = /** @type {any} */ (insert.constructor === String ? InsertStringOp : (insert.constructor === Array ? InsertArrayOp : InsertEmbedOp))
|
||||
this.ops.push(this.lastOp = new OpConstructor(insert, object.isEmpty(mergedAttributes) ? null : mergedAttributes, object.isEmpty(mergedAttribution) ? null : mergedAttribution))
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -268,10 +389,11 @@ export class DeltaBuilder extends AbstractDelta {
|
||||
retain (retain, attributes = null, attribution = null) {
|
||||
const mergedAttributes = mergeAttrs(this.usedAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this.usedAttribution, attribution)
|
||||
if (this._lastOp instanceof RetainOp && fun.equalityDeep(mergedAttributes, this._lastOp.attributes) && fun.equalityDeep(mergedAttribution, this._lastOp.attribution)) {
|
||||
this._lastOp.retain += retain
|
||||
if (this.lastOp instanceof RetainOp && fun.equalityDeep(mergedAttributes, this.lastOp.attributes) && fun.equalityDeep(mergedAttribution, this.lastOp.attribution)) {
|
||||
this.lastOp.retain += retain
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new RetainOp(retain, mergedAttributes, mergedAttribution))
|
||||
// @ts-ignore
|
||||
this.ops.push(this.lastOp = new RetainOp(retain, mergedAttributes, mergedAttribution))
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -281,25 +403,30 @@ export class DeltaBuilder extends AbstractDelta {
|
||||
* @return {this}
|
||||
*/
|
||||
delete (len) {
|
||||
if (this._lastOp instanceof DeleteOp) {
|
||||
this._lastOp.delete += len
|
||||
if (this.lastOp instanceof DeleteOp) {
|
||||
this.lastOp.delete += len
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new DeleteOp(len))
|
||||
// @ts-ignore
|
||||
this.ops.push(this.lastOp = new DeleteOp(len))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {AbstractDelta<Type,Content>}
|
||||
* @return {AbstractDelta<Type,TDeltaOp>}
|
||||
*/
|
||||
done () {
|
||||
while (this.lastOp != null && this.lastOp instanceof RetainOp && this.lastOp.attributes === null) {
|
||||
this.ops.pop()
|
||||
this.lastOp = this.ops[this.ops.length - 1] ?? null
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {ArrayDeltaContent} [Content=ArrayDeltaContent]
|
||||
* @extends DeltaBuilder<'array',Content>
|
||||
* @template {any} ArrayContent
|
||||
* @extends DeltaBuilder<'array', ArrayDeltaOp<ArrayContent>>>
|
||||
*/
|
||||
export class ArrayDelta extends DeltaBuilder {
|
||||
constructor () {
|
||||
@@ -308,8 +435,8 @@ export class ArrayDelta extends DeltaBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {TextDeltaContent} [Content=TextDeltaContent]
|
||||
* @extends DeltaBuilder<'text',Content>
|
||||
* @template {{ [key:string]: any }} Embeds
|
||||
* @extends DeltaBuilder<'text',TextDeltaOp<Embeds>>
|
||||
*/
|
||||
export class TextDelta extends DeltaBuilder {
|
||||
constructor () {
|
||||
@@ -323,6 +450,28 @@ export class TextDelta extends DeltaBuilder {
|
||||
export const createTextDelta = () => new TextDelta()
|
||||
|
||||
/**
|
||||
* @return {ArrayDelta<ArrayDeltaContent>}
|
||||
* @return {ArrayDelta<any>}
|
||||
*/
|
||||
export const createArrayDelta = () => new ArrayDelta()
|
||||
|
||||
/**
|
||||
* @param {DeltaJson} ops
|
||||
* @param {'custom' | 'text' | 'array'} type
|
||||
*/
|
||||
export const fromJSON = (ops, type = 'custom') => {
|
||||
const d = new DeltaBuilder(type)
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
const op = /** @type {any} */ (ops[i])
|
||||
// @ts-ignore
|
||||
if (op.insert !== undefined) {
|
||||
d.insert(op.insert, op.attributes, op.attribution)
|
||||
} else if (op.retain !== undefined) {
|
||||
d.retain(op.retain, op.attributes ?? null, op.attribution ?? null)
|
||||
} else if (op.delete !== undefined) {
|
||||
d.delete(op.delete)
|
||||
} else {
|
||||
error.unexpectedCase()
|
||||
}
|
||||
}
|
||||
return d.done()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,14 @@ import * as set from 'lib0/set'
|
||||
import * as array from 'lib0/array'
|
||||
import * as error from 'lib0/error'
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/Delta.js').TextDelta} TextDelta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/Delta.js').Delta} Delta
|
||||
*/
|
||||
|
||||
const errorComputeChanges = 'You must not compute changes after the event-handler fired.'
|
||||
|
||||
/**
|
||||
@@ -42,7 +50,7 @@ export class YEvent {
|
||||
*/
|
||||
this._keys = null
|
||||
/**
|
||||
* @type {null | Array<{ insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
||||
* @type {TextDelta?}
|
||||
*/
|
||||
this._delta = null
|
||||
/**
|
||||
@@ -142,7 +150,7 @@ export class YEvent {
|
||||
* unexpected behavior (incorrect computation of deltas). A safe way to collect changes
|
||||
* is to store the `changes` or the `delta` object. Avoid storing the `transaction` object.
|
||||
*
|
||||
* @type {Array<{insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
||||
* @type {Delta}
|
||||
*/
|
||||
get delta () {
|
||||
return this.changes.delta
|
||||
@@ -166,7 +174,7 @@ export class YEvent {
|
||||
* unexpected behavior (incorrect computation of deltas). A safe way to collect changes
|
||||
* is to store the `changes` or the `delta` object. Avoid storing the `transaction` object.
|
||||
*
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Delta}}
|
||||
*/
|
||||
get changes () {
|
||||
let changes = this._changes
|
||||
|
||||
22
test.html
22
test.html
@@ -157,23 +157,23 @@
|
||||
"lib0/performance.js": "./node_modules/lib0/performance.js",
|
||||
"lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs",
|
||||
"lib0/performance": "./node_modules/lib0/performance.js",
|
||||
"y-protocols/package.json": "./node_modules/y-protocols/package.json",
|
||||
"y-protocols/sync.js": "./node_modules/y-protocols/sync.js",
|
||||
"y-protocols/dist/sync.cjs": "./node_modules/y-protocols/dist/sync.cjs",
|
||||
"y-protocols/sync": "./node_modules/y-protocols/sync.js",
|
||||
"y-protocols/awareness.js": "./node_modules/y-protocols/awareness.js",
|
||||
"y-protocols/dist/awareness.cjs": "./node_modules/y-protocols/dist/awareness.cjs",
|
||||
"y-protocols/awareness": "./node_modules/y-protocols/awareness.js",
|
||||
"y-protocols/auth.js": "./node_modules/y-protocols/auth.js",
|
||||
"y-protocols/dist/auth.cjs": "./node_modules/y-protocols/dist/auth.cjs",
|
||||
"y-protocols/auth": "./node_modules/y-protocols/auth.js"
|
||||
"@y/protocols/package.json": "./node_modules/@y/protocols/package.json",
|
||||
"@y/protocols/sync.js": "./node_modules/@y/protocols/sync.js",
|
||||
"@y/protocols/dist/sync.cjs": "./node_modules/@y/protocols/dist/sync.cjs",
|
||||
"@y/protocols/sync": "./node_modules/@y/protocols/sync.js",
|
||||
"@y/protocols/awareness.js": "./node_modules/@y/protocols/awareness.js",
|
||||
"@y/protocols/dist/awareness.cjs": "./node_modules/@y/protocols/dist/awareness.cjs",
|
||||
"@y/protocols/awareness": "./node_modules/@y/protocols/awareness.js",
|
||||
"@y/protocols/auth.js": "./node_modules/@y/protocols/auth.js",
|
||||
"@y/protocols/dist/auth.cjs": "./node_modules/@y/protocols/dist/auth.cjs",
|
||||
"@y/protocols/auth": "./node_modules/@y/protocols/auth.js"
|
||||
},
|
||||
"scopes": {
|
||||
"./node_modules/lib0/": {
|
||||
"isomorphic.js": "./node_modules/isomorphic.js/browser.mjs",
|
||||
"isomorphic.js/package.json": "./node_modules/isomorphic.js/package.json"
|
||||
},
|
||||
"./node_modules/y-protocols/": {
|
||||
"./node_modules/@y/protocols/": {
|
||||
"lib0/package.json": "./node_modules/lib0/package.json",
|
||||
"lib0": "./node_modules/lib0/index.js",
|
||||
"lib0/array.js": "./node_modules/lib0/array.js",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@ import * as delta from '../src/utils/Delta.js'
|
||||
*/
|
||||
export const testDelta = _tc => {
|
||||
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'] } }])
|
||||
t.compare(d.toJSON(), [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,5 +21,59 @@ export const testDeltaMerging = _tc => {
|
||||
.insert([1])
|
||||
.insert([2])
|
||||
.done()
|
||||
t.compare(d.toJSON().ops, [{ insert: 'helloworld' }, { insert: ' ', attributes: { italic: true } }, { insert: {} }, { insert: [1, 2] }])
|
||||
t.compare(d.toJSON(), [{ insert: 'helloworld' }, { insert: ' ', attributes: { italic: true } }, { insert: {} }, { insert: [1, 2] }])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testUseAttributes = _tc => {
|
||||
const d = delta.createTextDelta()
|
||||
.insert('a')
|
||||
.updateUsedAttributes('bold', true)
|
||||
.insert('b')
|
||||
.insert('c', { bold: 4 })
|
||||
.updateUsedAttributes('bold', null)
|
||||
.insert('d')
|
||||
.useAttributes({ italic: true })
|
||||
.insert('e')
|
||||
.useAttributes(null)
|
||||
.insert('f')
|
||||
.done()
|
||||
const d2 = delta.createTextDelta()
|
||||
.insert('a')
|
||||
.insert('b', { bold: true })
|
||||
.insert('c', { bold: 4 })
|
||||
.insert('d')
|
||||
.insert('e', { italic: true })
|
||||
.insert('f')
|
||||
.done()
|
||||
t.compare(d, d2)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testUseAttribution = _tc => {
|
||||
const d = delta.createTextDelta()
|
||||
.insert('a')
|
||||
.updateUsedAttribution('insert', ['me'])
|
||||
.insert('b')
|
||||
.insert('c', null, { insert: ['you'] })
|
||||
.updateUsedAttribution('insert', null)
|
||||
.insert('d')
|
||||
.useAttribution({ insert: ['me'] })
|
||||
.insert('e')
|
||||
.useAttribution(null)
|
||||
.insert('f')
|
||||
.done()
|
||||
const d2 = delta.createTextDelta()
|
||||
.insert('a')
|
||||
.insert('b', null, { insert: ['me'] })
|
||||
.insert('c', null, { insert: ['you'] })
|
||||
.insert('d')
|
||||
.insert('e', null, { insert: ['me'] })
|
||||
.insert('f')
|
||||
.done()
|
||||
t.compare(d, d2)
|
||||
}
|
||||
|
||||
@@ -67,31 +67,6 @@ export const testFindTypeInOtherDoc = _tc => {
|
||||
t.assert(findTypeInOtherYdoc(ytext, ydocClone) != null)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testOriginInTransaction = _tc => {
|
||||
const doc = new Y.Doc()
|
||||
const ytext = doc.getText()
|
||||
/**
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
const origins = []
|
||||
doc.on('afterTransaction', (tr) => {
|
||||
origins.push(tr.origin)
|
||||
if (origins.length <= 1) {
|
||||
ytext.toDelta(Y.snapshot(doc)) // adding a snapshot forces toDelta to create a cleanup transaction
|
||||
doc.transact(() => {
|
||||
ytext.insert(0, 'a')
|
||||
}, 'nested')
|
||||
}
|
||||
})
|
||||
doc.transact(() => {
|
||||
ytext.insert(0, '0')
|
||||
}, 'first')
|
||||
t.compareArrays(origins, ['first', 'cleanup', 'nested'])
|
||||
}
|
||||
|
||||
/**
|
||||
* Client id should be changed when an instance receives updates from another client using the same client id.
|
||||
*
|
||||
|
||||
@@ -465,7 +465,7 @@ export const compare = users => {
|
||||
const userArrayValues = users.map(u => u.getArray('array').toJSON())
|
||||
const userMapValues = users.map(u => u.getMap('map').toJSON())
|
||||
const userXmlValues = users.map(u => u.get('xml', Y.XmlElement).toString())
|
||||
const userTextValues = users.map(u => u.getText('text').toDelta())
|
||||
const userTextValues = users.map(u => u.getText('text').getContent())
|
||||
for (const u of users) {
|
||||
t.assert(u.store.pendingDs === null)
|
||||
t.assert(u.store.pendingStructs === null)
|
||||
@@ -490,7 +490,7 @@ export const compare = users => {
|
||||
t.compare(userArrayValues[i], userArrayValues[i + 1])
|
||||
t.compare(userMapValues[i], userMapValues[i + 1])
|
||||
t.compare(userXmlValues[i], userXmlValues[i + 1])
|
||||
t.compare(userTextValues[i].map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
|
||||
t.compare(userTextValues[i].ops.map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
|
||||
t.compare(userTextValues[i], userTextValues[i + 1], '', (_constructor, a, b) => {
|
||||
if (a instanceof Y.AbstractType) {
|
||||
t.compare(a.toJSON(), b.toJSON())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Y from '../src/index.js'
|
||||
import { init } from './testHelper.js' // eslint-disable-line
|
||||
import * as t from 'lib0/testing'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
|
||||
export const testInconsistentFormat = () => {
|
||||
/**
|
||||
@@ -10,7 +11,7 @@ export const testInconsistentFormat = () => {
|
||||
const content = /** @type {Y.XmlText} */ (ydoc.get('text', Y.XmlText))
|
||||
content.format(0, 6, { bold: null })
|
||||
content.format(6, 4, { type: 'text' })
|
||||
t.compare(content.toDelta(), [
|
||||
t.compare(content.getContent(), delta.fromJSON([
|
||||
{
|
||||
attributes: { type: 'text' },
|
||||
insert: 'Merge Test'
|
||||
@@ -19,11 +20,10 @@ export const testInconsistentFormat = () => {
|
||||
attributes: { type: 'text', italic: true },
|
||||
insert: ' After'
|
||||
}
|
||||
])
|
||||
]))
|
||||
}
|
||||
const initializeYDoc = () => {
|
||||
const yDoc = new Y.Doc({ gc: false })
|
||||
|
||||
const content = /** @type {Y.XmlText} */ (yDoc.get('text', Y.XmlText))
|
||||
content.insert(0, ' After', { type: 'text', italic: true })
|
||||
content.insert(0, 'Test', { type: 'text' })
|
||||
@@ -94,11 +94,11 @@ export const testUndoText = tc => {
|
||||
t.assert(text0.toString() === 'bcxyz')
|
||||
// test marks
|
||||
text0.format(1, 3, { bold: true })
|
||||
t.compare(text0.toDelta(), [{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])
|
||||
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
|
||||
undoManager.undo()
|
||||
t.compare(text0.toDelta(), [{ insert: 'bcxyz' }])
|
||||
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'bcxyz' }]))
|
||||
undoManager.redo()
|
||||
t.compare(text0.toDelta(), [{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }])
|
||||
t.compare(text0.getContent(), delta.fromJSON([{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -686,16 +686,16 @@ export const testUndoDeleteTextFormat = _tc => {
|
||||
undoManager.undo()
|
||||
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc))
|
||||
|
||||
const expect = [
|
||||
const expect = delta.fromJSON([
|
||||
{ insert: 'Attack ships ' },
|
||||
{
|
||||
insert: 'on fire',
|
||||
attributes: { bold: true }
|
||||
},
|
||||
{ insert: ' off the shoulder of Orion.' }
|
||||
]
|
||||
t.compare(text.toDelta(), expect)
|
||||
t.compare(text2.toDelta(), expect)
|
||||
])
|
||||
t.compare(text.getContent(), expect)
|
||||
t.compare(text2.getContent(), expect)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,7 +126,7 @@ export const testKeyEncoding = tc => {
|
||||
const update = Y.encodeStateAsUpdateV2(users[0])
|
||||
Y.applyUpdateV2(users[1], update)
|
||||
|
||||
t.compare(text1.toDelta(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }])
|
||||
t.compare(text1.getContent().toJSON(), [{ insert: 'c', attributes: { italic: true } }, { insert: 'b' }, { insert: 'a', attributes: { italic: true } }])
|
||||
|
||||
compare(users)
|
||||
}
|
||||
@@ -331,7 +331,7 @@ export const testObfuscateUpdates = _tc => {
|
||||
const omap = odoc.getMap('map')
|
||||
const oarray = odoc.getArray('array')
|
||||
// test ytext
|
||||
const delta = otext.toDelta()
|
||||
const delta = /** @type {Array<any>} */ (otext.getContent().toJSON())
|
||||
t.assert(delta.length === 2)
|
||||
t.assert(delta[0].insert !== 'text' && delta[0].insert.length === 4)
|
||||
t.assert(object.length(delta[0].attributes) === 1)
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as t from 'lib0/testing'
|
||||
import * as prng from 'lib0/prng'
|
||||
import * as math from 'lib0/math'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
import { createIdMapFromIdSet, noAttributionsManager, TwosetAttributionManager } from 'yjs/internals'
|
||||
import { createIdMapFromIdSet, noAttributionsManager, TwosetAttributionManager, createAttributionManagerFromSnapshots } from 'yjs/internals'
|
||||
|
||||
const { init, compare } = Y
|
||||
|
||||
@@ -232,185 +232,138 @@ export const testDeltaBug = _tc => {
|
||||
}
|
||||
]
|
||||
ytext.applyDelta(addingList)
|
||||
const result = ytext.toDelta()
|
||||
const expectedResult = [
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'table-col': {
|
||||
width: '150'
|
||||
}
|
||||
},
|
||||
insert: '\n\n\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-9144be72-e528-4f91-b0b2-82d20408e9ea',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-apba4k'
|
||||
},
|
||||
const result = ytext.getContent()
|
||||
const expectedResult = delta.createTextDelta()
|
||||
.insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' })
|
||||
.insert('\n\n\n', { 'table-col': { width: '150' } })
|
||||
.insert('\n', {
|
||||
'block-id': 'block-9144be72-e528-4f91-b0b2-82d20408e9ea',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-apba4k',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-apba4k'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-639adacb-1516-43ed-b272-937c55669a1c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-a8qf0r'
|
||||
},
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-apba4k',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-639adacb-1516-43ed-b272-937c55669a1c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-a8qf0r',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-a8qf0r'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-6302ca4a-73a3-4c25-8c1e-b542f048f1c6',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-oi9ikb'
|
||||
},
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-a8qf0r',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-6302ca4a-73a3-4c25-8c1e-b542f048f1c6',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-oi9ikb',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-oi9ikb'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-ceeddd05-330e-4f86-8017-4a3a060c4627',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-dt6ks2'
|
||||
},
|
||||
row: 'row-6kv2ls',
|
||||
cell: 'cell-oi9ikb',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-ceeddd05-330e-4f86-8017-4a3a060c4627',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-dt6ks2',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-dt6ks2'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-37b19322-cb57-4e6f-8fad-0d1401cae53f',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-qah2ay'
|
||||
},
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-dt6ks2',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-37b19322-cb57-4e6f-8fad-0d1401cae53f',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-qah2ay',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-qah2ay'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-468a69b5-9332-450b-9107-381d593de249',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-fpcz5a'
|
||||
},
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-qah2ay',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-468a69b5-9332-450b-9107-381d593de249',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-fpcz5a',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-fpcz5a'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-26b1d252-9b2e-4808-9b29-04e76696aa3c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-zrhylp'
|
||||
},
|
||||
row: 'row-d1sv2g',
|
||||
cell: 'cell-fpcz5a',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-26b1d252-9b2e-4808-9b29-04e76696aa3c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-zrhylp',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-zrhylp'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-6af97ba7-8cf9-497a-9365-7075b938837b',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-s1q9nt'
|
||||
},
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-zrhylp',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-6af97ba7-8cf9-497a-9365-7075b938837b',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-s1q9nt',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
cell: 'cell-s1q9nt'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
insert: '\n',
|
||||
// This attributes has only list and no table-cell-line
|
||||
attributes: {
|
||||
list: {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-20b0j9',
|
||||
list: 'bullet'
|
||||
},
|
||||
'block-id': 'block-107e273e-86bc-44fd-b0d7-41ab55aca484',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-s1q9nt',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
// This attributes has only list and no table-cell-line
|
||||
.insert('\n', {
|
||||
list: {
|
||||
rowspan: '1',
|
||||
colspan: '1',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-20b0j9',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
}
|
||||
},
|
||||
list: 'bullet'
|
||||
},
|
||||
'block-id': 'block-107e273e-86bc-44fd-b0d7-41ab55aca484',
|
||||
row: 'row-pflz90',
|
||||
cell: 'cell-20b0j9',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
})
|
||||
// No table-cell-line below here
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-38161f9c-6f6d-44c5-b086-54cc6490f1e3'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
insert: 'Content after table'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce'
|
||||
},
|
||||
insert: '\n'
|
||||
}
|
||||
]
|
||||
.insert('\n', {
|
||||
'block-id': 'block-38161f9c-6f6d-44c5-b086-54cc6490f1e3'
|
||||
})
|
||||
.insert('Content after table')
|
||||
.insert('\n', {
|
||||
'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce'
|
||||
})
|
||||
.done()
|
||||
t.compare(result, expectedResult)
|
||||
}
|
||||
|
||||
@@ -477,11 +430,7 @@ export const testDeltaBug2 = _tc => {
|
||||
insert: '\n',
|
||||
attributes: { 'block-id': 'block-8a1d2bb6-23c2-4bcf-af3c-3919ffea1697' }
|
||||
},
|
||||
{ insert: '\n\n', attributes: { 'table-col': { width: '150' } } },
|
||||
{
|
||||
insert: '\n',
|
||||
attributes: { 'table-col': { width: '150' } }
|
||||
},
|
||||
{ insert: '\n\n\n', attributes: { 'table-col': { width: '150' } } },
|
||||
{
|
||||
insert: '\n',
|
||||
attributes: {
|
||||
@@ -1640,8 +1589,8 @@ export const testDeltaBug2 = _tc => {
|
||||
}
|
||||
]
|
||||
ytext.applyDelta(changeEvent)
|
||||
const delta = ytext.toDelta()
|
||||
t.compare(delta[41], {
|
||||
const delta = ytext.getContent()
|
||||
t.compare(delta.ops[40].toJSON(), {
|
||||
insert: '\n',
|
||||
attributes: {
|
||||
'block-id': 'block-9d6566a1-be55-4e20-999a-b990bc15e143'
|
||||
@@ -1667,8 +1616,8 @@ export const testDeltaAfterConcurrentFormatting = tc => {
|
||||
*/
|
||||
const deltas = []
|
||||
text1.observe(event => {
|
||||
if (event.delta.length > 0) {
|
||||
deltas.push(event.delta)
|
||||
if (event.delta.ops.length > 0) {
|
||||
deltas.push(event.delta.toJSON())
|
||||
}
|
||||
})
|
||||
testConnector.flushAllMessages()
|
||||
@@ -1680,10 +1629,10 @@ export const testDeltaAfterConcurrentFormatting = tc => {
|
||||
*/
|
||||
export const testBasicInsertAndDelete = tc => {
|
||||
const { users, text0 } = init(tc, { users: 2 })
|
||||
let delta
|
||||
let eventDelta
|
||||
|
||||
text0.observe(event => {
|
||||
delta = event.delta
|
||||
eventDelta = event.delta
|
||||
})
|
||||
|
||||
text0.delete(0, 0)
|
||||
@@ -1691,21 +1640,21 @@ export const testBasicInsertAndDelete = tc => {
|
||||
|
||||
text0.insert(0, 'abc')
|
||||
t.assert(text0.toString() === 'abc', 'Basic insert works')
|
||||
t.compare(delta, [{ insert: 'abc' }])
|
||||
t.compare(eventDelta, delta.fromJSON([{ insert: 'abc' }]))
|
||||
|
||||
text0.delete(0, 1)
|
||||
t.assert(text0.toString() === 'bc', 'Basic delete works (position 0)')
|
||||
t.compare(delta, [{ delete: 1 }])
|
||||
t.compare(eventDelta, delta.fromJSON([{ delete: 1 }]))
|
||||
|
||||
text0.delete(1, 1)
|
||||
t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
|
||||
t.compare(delta, [{ retain: 1 }, { delete: 1 }])
|
||||
t.compare(eventDelta, delta.fromJSON([{ retain: 1 }, { delete: 1 }]))
|
||||
|
||||
users[0].transact(() => {
|
||||
text0.insert(0, '1')
|
||||
text0.delete(0, 1)
|
||||
})
|
||||
t.compare(delta, [])
|
||||
t.compare(eventDelta, delta.fromJSON([]))
|
||||
|
||||
compare(users)
|
||||
}
|
||||
@@ -1715,36 +1664,36 @@ export const testBasicInsertAndDelete = tc => {
|
||||
*/
|
||||
export const testBasicFormat = tc => {
|
||||
const { users, text0 } = init(tc, { users: 2 })
|
||||
let delta
|
||||
let eventDelta
|
||||
text0.observe(event => {
|
||||
delta = event.delta
|
||||
eventDelta = event.delta
|
||||
})
|
||||
text0.insert(0, 'abc', { bold: true })
|
||||
t.assert(text0.toString() === 'abc', 'Basic insert with attributes works')
|
||||
t.compare(text0.toDelta(), [{ insert: 'abc', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ insert: 'abc', attributes: { bold: true } }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('abc', { bold: true }).done())
|
||||
t.compare(eventDelta, delta.createTextDelta().insert('abc', { bold: true }))
|
||||
text0.delete(0, 1)
|
||||
t.assert(text0.toString() === 'bc', 'Basic delete on formatted works (position 0)')
|
||||
t.compare(text0.toDelta(), [{ insert: 'bc', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ delete: 1 }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('bc', { bold: true }))
|
||||
t.compare(eventDelta, delta.createTextDelta().delete(1))
|
||||
text0.delete(1, 1)
|
||||
t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
|
||||
t.compare(text0.toDelta(), [{ insert: 'b', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ retain: 1 }, { delete: 1 }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('b', { bold: true }))
|
||||
t.compare(eventDelta, delta.createTextDelta().retain(1).delete(1))
|
||||
text0.insert(0, 'z', { bold: true })
|
||||
t.assert(text0.toString() === 'zb')
|
||||
t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ insert: 'z', attributes: { bold: true } }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('zb', { bold: true }))
|
||||
t.compare(eventDelta, delta.createTextDelta().insert('z', { bold: true }))
|
||||
// @ts-ignore
|
||||
t.assert(text0._start.right.right.right.content.str === 'b', 'Does not insert duplicate attribute marker')
|
||||
text0.insert(0, 'y')
|
||||
t.assert(text0.toString() === 'yzb')
|
||||
t.compare(text0.toDelta(), [{ insert: 'y' }, { insert: 'zb', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ insert: 'y' }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('y').insert('zb', { bold: true }))
|
||||
t.compare(eventDelta, delta.createTextDelta().insert('y'))
|
||||
text0.format(0, 2, { bold: null })
|
||||
t.assert(text0.toString() === 'yzb')
|
||||
t.compare(text0.toDelta(), [{ insert: 'yz' }, { insert: 'b', attributes: { bold: true } }])
|
||||
t.compare(delta, [{ retain: 1 }, { retain: 1, attributes: { bold: null } }])
|
||||
t.compare(text0.getContent(), delta.createTextDelta().insert('yz').insert('b', { bold: true }))
|
||||
t.compare(eventDelta, delta.createTextDelta().retain(1).retain(1, { bold: null }))
|
||||
compare(users)
|
||||
}
|
||||
|
||||
@@ -1755,16 +1704,16 @@ export const testFalsyFormats = tc => {
|
||||
const { users, text0 } = init(tc, { users: 2 })
|
||||
let delta
|
||||
text0.observe(event => {
|
||||
delta = event.delta
|
||||
delta = event.delta.toJSON()
|
||||
})
|
||||
text0.insert(0, 'abcde', { falsy: false })
|
||||
t.compare(text0.toDelta(), [{ insert: 'abcde', attributes: { falsy: false } }])
|
||||
t.compare(text0.getContent().toJSON(), [{ insert: 'abcde', attributes: { falsy: false } }])
|
||||
t.compare(delta, [{ insert: 'abcde', attributes: { falsy: false } }])
|
||||
text0.format(1, 3, { falsy: true })
|
||||
t.compare(text0.toDelta(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
|
||||
t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
|
||||
t.compare(delta, [{ retain: 1 }, { retain: 3, attributes: { falsy: true } }])
|
||||
text0.format(2, 1, { falsy: false })
|
||||
t.compare(text0.toDelta(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
|
||||
t.compare(text0.getContent().toJSON(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
|
||||
t.compare(delta, [{ retain: 2 }, { retain: 1, attributes: { falsy: false } }])
|
||||
compare(users)
|
||||
}
|
||||
@@ -1783,7 +1732,7 @@ export const testMultilineFormat = _tc => {
|
||||
{ retain: 1 }, // newline character
|
||||
{ retain: 10, attributes: { bold: true } }
|
||||
])
|
||||
t.compare(testText.toDelta(), [
|
||||
t.compare(testText.getContent().toJSON(), [
|
||||
{ insert: 'Test', attributes: { bold: true } },
|
||||
{ insert: '\n' },
|
||||
{ insert: 'Multi-line', attributes: { bold: true } },
|
||||
@@ -1804,7 +1753,7 @@ export const testNotMergeEmptyLinesFormat = _tc => {
|
||||
{ insert: '\nText' },
|
||||
{ insert: '\n', attributes: { title: true } }
|
||||
])
|
||||
t.compare(testText.toDelta(), [
|
||||
t.compare(testText.getContent().toJSON(), [
|
||||
{ insert: 'Text' },
|
||||
{ insert: '\n', attributes: { title: true } },
|
||||
{ insert: '\nText' },
|
||||
@@ -1828,7 +1777,7 @@ export const testPreserveAttributesThroughDelete = _tc => {
|
||||
{ delete: 1 },
|
||||
{ retain: 1, attributes: { title: true } }
|
||||
])
|
||||
t.compare(testText.toDelta(), [
|
||||
t.compare(testText.getContent().toJSON(), [
|
||||
{ insert: 'Text' },
|
||||
{ insert: '\n', attributes: { title: true } }
|
||||
])
|
||||
@@ -1842,7 +1791,7 @@ export const testGetDeltaWithEmbeds = tc => {
|
||||
text0.applyDelta([{
|
||||
insert: { linebreak: 's' }
|
||||
}])
|
||||
t.compare(text0.toDelta(), [{
|
||||
t.compare(text0.getContent().toJSON(), [{
|
||||
insert: { linebreak: 's' }
|
||||
}])
|
||||
}
|
||||
@@ -1855,18 +1804,18 @@ export const testTypesAsEmbed = tc => {
|
||||
text0.applyDelta([{
|
||||
insert: new Y.Map([['key', 'val']])
|
||||
}])
|
||||
t.compare(text0.toDelta()[0].insert.toJSON(), { key: 'val' })
|
||||
t.compare(/** @type {delta.InsertOp<any>} */ (text0.getContent().ops[0]).insert.toJSON(), { key: 'val' })
|
||||
let firedEvent = false
|
||||
text1.observe(event => {
|
||||
const d = event.delta
|
||||
t.assert(d.length === 1)
|
||||
t.compare(d.map(x => /** @type {Y.AbstractType<any>} */ (x.insert).toJSON()), [{ key: 'val' }])
|
||||
t.assert(d.ops.length === 1)
|
||||
t.compare(d.ops.map(x => /** @type {any} */ (x).insert.toJSON()), [{ key: 'val' }])
|
||||
firedEvent = true
|
||||
})
|
||||
testConnector.flushAllMessages()
|
||||
const delta = text1.toDelta()
|
||||
const delta = text1.getContent().toJSON()
|
||||
t.assert(delta.length === 1)
|
||||
t.compare(delta[0].insert.toJSON(), { key: 'val' })
|
||||
t.compare(/** @type {any} */ (delta[0]).insert.toJSON(), { key: 'val' })
|
||||
t.assert(firedEvent, 'fired the event observer containing a Type-Embed')
|
||||
}
|
||||
|
||||
@@ -1898,18 +1847,13 @@ export const testSnapshot = tc => {
|
||||
}, {
|
||||
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: { type: 'added' } } }, { insert: 'b', attributes: { ychange: { type: 'removed' } } }, { insert: 'cd' }])
|
||||
const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1))
|
||||
t.compare(state1.toJSON(), [{ insert: 'abcd' }])
|
||||
const state2 = text0.getContent(createAttributionManagerFromSnapshots(snapshot2))
|
||||
t.compare(state2.toJSON(), [{ insert: 'axcd' }])
|
||||
const state2Diff = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot2)).toJSON()
|
||||
const expected = [{ insert: 'a' }, { insert: 'x', attribution: { insert: [] } }, { insert: 'b', attribution: { delete: [] } }, { insert: 'cd' }]
|
||||
t.compare(state2Diff, expected)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1928,8 +1872,8 @@ export const testSnapshotDeleteAfter = tc => {
|
||||
}, {
|
||||
insert: 'e'
|
||||
}])
|
||||
const state1 = text0.toDelta(snapshot1)
|
||||
t.compare(state1, [{ insert: 'abcd' }])
|
||||
const state1 = text0.getContent(createAttributionManagerFromSnapshots(snapshot1, snapshot1))
|
||||
t.compare(state1, delta.fromJSON([{ insert: 'abcd' }]))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1948,7 +1892,7 @@ export const testToDeltaEmbedAttributes = tc => {
|
||||
const { text0 } = init(tc, { users: 1 })
|
||||
text0.insert(0, 'ab', { bold: true })
|
||||
text0.insertEmbed(1, { image: 'imageSrc.png' }, { width: 100 })
|
||||
const delta0 = text0.toDelta()
|
||||
const delta0 = text0.getContent().toJSON()
|
||||
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }])
|
||||
}
|
||||
|
||||
@@ -1959,7 +1903,7 @@ export const testToDeltaEmbedNoAttributes = tc => {
|
||||
const { text0 } = init(tc, { users: 1 })
|
||||
text0.insert(0, 'ab', { bold: true })
|
||||
text0.insertEmbed(1, { image: 'imageSrc.png' })
|
||||
const delta0 = text0.toDelta()
|
||||
const delta0 = text0.getContent().toJSON()
|
||||
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' } }, { insert: 'b', attributes: { bold: true } }], 'toDelta does not set attributes key when no attributes are present')
|
||||
}
|
||||
|
||||
@@ -2000,7 +1944,7 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => {
|
||||
})
|
||||
testConnector.flushAllMessages()
|
||||
/**
|
||||
* @type {Array<any>}
|
||||
* @type {Array<delta.TextDelta>}
|
||||
*/
|
||||
const deltas = []
|
||||
text0.observe(event => {
|
||||
@@ -2011,9 +1955,9 @@ export const testFormattingDeltaUnnecessaryAttributeChange = tc => {
|
||||
})
|
||||
text1.format(0, 1, { LIST_STYLES: 'number' })
|
||||
testConnector.flushAllMessages()
|
||||
const filteredDeltas = deltas.filter(d => d.length > 0)
|
||||
const filteredDeltas = deltas.filter(d => d.ops.length > 0)
|
||||
t.assert(filteredDeltas.length === 2)
|
||||
t.compare(filteredDeltas[0], [
|
||||
t.compare(filteredDeltas[0].toJSON(), [
|
||||
{ retain: 1, attributes: { LIST_STYLES: 'number' } }
|
||||
])
|
||||
t.compare(filteredDeltas[0], filteredDeltas[1])
|
||||
@@ -2267,9 +2211,9 @@ export const testFormattingBug = async _tc => {
|
||||
{ insert: '\n', attributes: { url: 'http://docs.yjs.dev' } },
|
||||
{ insert: '\n', attributes: { url: 'http://example.com' } }
|
||||
]
|
||||
t.compare(text1.toDelta(), expectedResult)
|
||||
t.compare(text1.toDelta(), text2.toDelta())
|
||||
console.log(text1.toDelta())
|
||||
t.compare(text1.getContent().toJSON(), expectedResult)
|
||||
t.compare(text1.getContent().toJSON(), text2.getContent().toJSON())
|
||||
console.log(text1.getContent().toJSON())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2297,8 +2241,8 @@ export const testDeleteFormatting = _tc => {
|
||||
{ insert: 'on ', attributes: { bold: true } },
|
||||
{ insert: 'fire off the shoulder of Orion.' }
|
||||
]
|
||||
t.compare(text.toDelta(), expected)
|
||||
t.compare(text2.toDelta(), expected)
|
||||
t.compare(text.getContent().toJSON(), expected)
|
||||
t.compare(text2.getContent().toJSON(), expected)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2590,13 +2534,13 @@ const checkResult = result => {
|
||||
|
||||
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').getContent().toJSON().map(typeToObject)
|
||||
})
|
||||
t.measureTime('getContent(attributionManager) performance)', () => {
|
||||
result.users[i - 1].getText('text').getContent()
|
||||
})
|
||||
const p1 = result.users[i - 1].getText('text').toDelta().map(typeToObject)
|
||||
const p2 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||
const p1 = result.users[i - 1].getText('text').getContent().toJSON().map(typeToObject)
|
||||
const p2 = result.users[i].getText('text').getContent().toJSON().map(typeToObject)
|
||||
t.compare(p1, p2)
|
||||
}
|
||||
// Uncomment this to find formatting-cleanup issues
|
||||
@@ -2613,7 +2557,7 @@ const checkResult = result => {
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testAttributionManagerDefaultPerformance = tc => {
|
||||
const N = 10000
|
||||
const N = 100000
|
||||
const MaxDeletionLength = 5 // 25% chance of deletion
|
||||
const MaxInsertionLength = 5
|
||||
const ydoc = new Y.Doc()
|
||||
@@ -2634,12 +2578,7 @@ export const testAttributionManagerDefaultPerformance = tc => {
|
||||
const M = 100
|
||||
t.measureTime(`original toString perf <executed ${M} times>`, () => {
|
||||
for (let i = 0; i < M; i++) {
|
||||
ytext.toDelta()
|
||||
}
|
||||
})
|
||||
t.measureTime(`original toDelta perf <executed ${M} times>`, () => {
|
||||
for (let i = 0; i < M; i++) {
|
||||
ytext.toDelta()
|
||||
ytext.toString()
|
||||
}
|
||||
})
|
||||
t.measureTime(`getContent(attributionManager) performance <executed ${M} times>`, () => {
|
||||
|
||||
@@ -207,7 +207,7 @@ export const testFormattingBug = _tc => {
|
||||
{ insert: 'C', attributes: { em: {}, strong: {} } }
|
||||
]
|
||||
yxml.applyDelta(delta)
|
||||
t.compare(yxml.toDelta(), delta)
|
||||
t.compare(yxml.getContent().toJSON(), delta)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,8 +366,8 @@ export const testElementAttributedContentViaDiffer = _tc => {
|
||||
delta.createTextDelta().insert('bigworld')
|
||||
])
|
||||
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('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
|
||||
console.log('cs expec', JSON.stringify(expectedContent.toJSON(), null, 2))
|
||||
console.log('attributes', attributedContent.attributes)
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(attributedContent.attributes, { key: { prevValue: undefined, value: '42', attribution: null } })
|
||||
|
||||
Reference in New Issue
Block a user