mirror of
https://github.com/yjs/yjs.git
synced 2026-02-24 04:01:14 +01:00
[wip] refactor for lib0/delta v2
This commit is contained in:
@@ -743,8 +743,6 @@ or any of its children.
|
||||
<dd>Clone this type into a fresh Yjs type.</dd>
|
||||
<b><code>toArray():Array<Y.XmlElement|Y.XmlText></code></b>
|
||||
<dd>Copies the children to a new Array.</dd>
|
||||
<b><code>toDOM():DocumentFragment</code></b>
|
||||
<dd>Transforms this type and all children to new DOM elements.</dd>
|
||||
<b><code>toString():string</code></b>
|
||||
<dd>Get the XML serialization of all descendants.</dd>
|
||||
<b><code>toJSON():string</code></b>
|
||||
@@ -818,8 +816,6 @@ content and be actually XML compliant.
|
||||
<dd>Clone this type into a fresh Yjs type.</dd>
|
||||
<b><code>toArray():Array<Y.XmlElement|Y.XmlText></code></b>
|
||||
<dd>Copies the children to a new Array.</dd>
|
||||
<b><code>toDOM():Element</code></b>
|
||||
<dd>Transforms this type and all children to a new DOM element.</dd>
|
||||
<b><code>toString():string</code></b>
|
||||
<dd>Get the XML serialization of all descendants.</dd>
|
||||
<b><code>toJSON():string</code></b>
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
"rollup": "^4.37.0",
|
||||
"standard": "^16.0.4",
|
||||
"tui-jsdoc-template": "^1.2.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.3",
|
||||
"yjs": "."
|
||||
},
|
||||
"engines": {
|
||||
@@ -5292,9 +5292,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -88,15 +88,15 @@
|
||||
"lib0": "^0.2.114"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@y/protocols": "^1.0.6-1",
|
||||
"@types/node": "^22.14.1",
|
||||
"@y/protocols": "^1.0.6-1",
|
||||
"concurrently": "^3.6.1",
|
||||
"jsdoc": "^3.6.7",
|
||||
"markdownlint-cli": "^0.41.0",
|
||||
"rollup": "^4.37.0",
|
||||
"standard": "^16.0.4",
|
||||
"tui-jsdoc-template": "^1.2.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.3",
|
||||
"yjs": "."
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -10,10 +10,6 @@ export {
|
||||
YXmlHook as XmlHook,
|
||||
YXmlElement as XmlElement,
|
||||
YXmlFragment as XmlFragment,
|
||||
YXmlEvent,
|
||||
YMapEvent,
|
||||
YArrayEvent,
|
||||
YTextEvent,
|
||||
YEvent,
|
||||
Item,
|
||||
AbstractStruct,
|
||||
@@ -74,7 +70,6 @@ export {
|
||||
relativePositionToJSON,
|
||||
isParentOf,
|
||||
equalSnapshots,
|
||||
PermanentUserData, // @TODO experimental
|
||||
tryGc,
|
||||
transact,
|
||||
AbstractConnector,
|
||||
|
||||
@@ -8,7 +8,6 @@ export * from './utils/EventHandler.js'
|
||||
export * from './utils/ID.js'
|
||||
export * from './utils/isParentOf.js'
|
||||
export * from './utils/logging.js'
|
||||
export * from './utils/PermanentUserData.js'
|
||||
export * from './utils/RelativePosition.js'
|
||||
export * from './utils/Snapshot.js'
|
||||
export * from './utils/StructStore.js'
|
||||
@@ -19,7 +18,6 @@ export * from './utils/YEvent.js'
|
||||
export * from './utils/StructSet.js'
|
||||
export * from './utils/IdMap.js'
|
||||
export * from './utils/AttributionManager.js'
|
||||
export * from './utils/Delta.js'
|
||||
|
||||
export * from './types/AbstractType.js'
|
||||
export * from './types/YArray.js'
|
||||
@@ -27,7 +25,6 @@ export * from './types/YMap.js'
|
||||
export * from './types/YText.js'
|
||||
export * from './types/YXmlFragment.js'
|
||||
export * from './types/YXmlElement.js'
|
||||
export * from './types/YXmlEvent.js'
|
||||
export * from './types/YXmlHook.js'
|
||||
export * from './types/YXmlText.js'
|
||||
|
||||
|
||||
@@ -6,13 +6,17 @@ import {
|
||||
readYXmlFragment,
|
||||
readYXmlHook,
|
||||
readYXmlText,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, AbstractType // eslint-disable-line
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YType} YType_CT
|
||||
*/
|
||||
|
||||
import * as error from 'lib0/error'
|
||||
|
||||
/**
|
||||
* @type {Array<function(UpdateDecoderV1 | UpdateDecoderV2):AbstractType<any>>}
|
||||
* @type {Array<(decoder: UpdateDecoderV1 | UpdateDecoderV2)=>(import('../utils/types.js').YType)>}
|
||||
* @private
|
||||
*/
|
||||
export const typeRefs = [
|
||||
@@ -38,11 +42,11 @@ export const YXmlTextRefID = 6
|
||||
*/
|
||||
export class ContentType {
|
||||
/**
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {YType_CT} type
|
||||
*/
|
||||
constructor (type) {
|
||||
/**
|
||||
* @type {AbstractType<any>}
|
||||
* @type {YType_CT}
|
||||
*/
|
||||
this.type = type
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ import * as error from 'lib0/error'
|
||||
import * as binary from 'lib0/binary'
|
||||
import * as array from 'lib0/array'
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YType} YType__
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo This should return several items
|
||||
*
|
||||
@@ -68,7 +72,7 @@ export const followRedone = (store, id) => {
|
||||
export const keepItem = (item, keep) => {
|
||||
while (item !== null && item.keep !== keep) {
|
||||
item.keep = keep
|
||||
item = /** @type {AbstractType<any>} */ (item.parent)._item
|
||||
item = /** @type {YType__} */ (item.parent)._item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +119,7 @@ export const splitItem = (transaction, leftItem, diff) => {
|
||||
transaction._mergeStructs.push(rightItem)
|
||||
// update parent._map
|
||||
if (rightItem.parentSub !== null && rightItem.right === null) {
|
||||
/** @type {AbstractType<any>} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
|
||||
/** @type {YType__} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem)
|
||||
}
|
||||
} else {
|
||||
rightItem.left = null
|
||||
@@ -173,7 +177,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
|
||||
if (redone !== null) {
|
||||
return getItemCleanStart(transaction, redone)
|
||||
}
|
||||
let parentItem = /** @type {AbstractType<any>} */ (item.parent)._item
|
||||
let parentItem = /** @type {YType__} */ (item.parent)._item
|
||||
/**
|
||||
* @type {Item|null}
|
||||
*/
|
||||
@@ -192,7 +196,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
|
||||
parentItem = getItemCleanStart(transaction, parentItem.redone)
|
||||
}
|
||||
}
|
||||
const parentType = parentItem === null ? /** @type {AbstractType<any>} */ (item.parent) : /** @type {ContentType} */ (parentItem.content).type
|
||||
/**
|
||||
* @type {YType__}
|
||||
*/
|
||||
const parentType = /** @type {YType__} */ (parentItem === null ? item.parent : /** @type {ContentType} */ (parentItem.content).type)
|
||||
|
||||
if (item.parentSub === null) {
|
||||
// Is an array item. Insert at the old position
|
||||
@@ -205,10 +212,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
|
||||
*/
|
||||
let leftTrace = left
|
||||
// trace redone until parent matches
|
||||
while (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item !== parentItem) {
|
||||
while (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item !== parentItem) {
|
||||
leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
|
||||
}
|
||||
if (leftTrace !== null && /** @type {AbstractType<any>} */ (leftTrace.parent)._item === parentItem) {
|
||||
if (leftTrace !== null && /** @type {YType__} */ (leftTrace.parent)._item === parentItem) {
|
||||
left = leftTrace
|
||||
break
|
||||
}
|
||||
@@ -220,10 +227,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
|
||||
*/
|
||||
let rightTrace = right
|
||||
// trace redone until parent matches
|
||||
while (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item !== parentItem) {
|
||||
while (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item !== parentItem) {
|
||||
rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
|
||||
}
|
||||
if (rightTrace !== null && /** @type {AbstractType<any>} */ (rightTrace.parent)._item === parentItem) {
|
||||
if (rightTrace !== null && /** @type {YType__} */ (rightTrace.parent)._item === parentItem) {
|
||||
right = rightTrace
|
||||
break
|
||||
}
|
||||
@@ -275,7 +282,7 @@ export class Item extends AbstractStruct {
|
||||
* @param {ID | null} origin
|
||||
* @param {Item | null} right
|
||||
* @param {ID | null} rightOrigin
|
||||
* @param {AbstractType<any>|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
|
||||
* @param {YType__|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
|
||||
* @param {string | null} parentSub
|
||||
* @param {AbstractContent} content
|
||||
*/
|
||||
@@ -302,7 +309,7 @@ export class Item extends AbstractStruct {
|
||||
*/
|
||||
this.rightOrigin = rightOrigin
|
||||
/**
|
||||
* @type {AbstractType<any>|ID|null}
|
||||
* @type {YType__|ID|null}
|
||||
*/
|
||||
this.parent = parent
|
||||
/**
|
||||
@@ -541,7 +548,7 @@ export class Item extends AbstractStruct {
|
||||
addStruct(transaction.doc.store, this)
|
||||
this.content.integrate(transaction, this)
|
||||
// add parent to transaction.changed
|
||||
addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
|
||||
addChangedTypeToTransaction(transaction, /** @type {import('../utils/types.js').YType} */ (this.parent), this.parentSub)
|
||||
if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
|
||||
// delete if parent is deleted or if this is not the current attribute value of parent
|
||||
this.delete(transaction)
|
||||
@@ -635,7 +642,7 @@ export class Item extends AbstractStruct {
|
||||
*/
|
||||
delete (transaction) {
|
||||
if (!this.deleted) {
|
||||
const parent = /** @type {AbstractType<any>} */ (this.parent)
|
||||
const parent = /** @type {import('../utils/types.js').YType} */ (this.parent)
|
||||
// adjust the length of parent
|
||||
if (this.countable && this.parentSub === null) {
|
||||
parent._length -= this.length
|
||||
|
||||
@@ -8,18 +8,32 @@ import {
|
||||
ContentType,
|
||||
createID,
|
||||
ContentAny,
|
||||
ContentFormat,
|
||||
ContentBinary,
|
||||
ContentJSON,
|
||||
ContentDeleted,
|
||||
ContentString,
|
||||
ContentEmbed,
|
||||
getItemCleanStart,
|
||||
noAttributionsManager,
|
||||
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, createAttributionFromAttributionItems, AbstractAttributionManager, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
import * as delta from 'lib0/delta'
|
||||
import * as array from 'lib0/array'
|
||||
import * as map from 'lib0/map'
|
||||
import * as iterator from 'lib0/iterator'
|
||||
import * as error from 'lib0/error'
|
||||
import * as math from 'lib0/math'
|
||||
import * as log from 'lib0/logging'
|
||||
import * as object from 'lib0/object'
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YType} YType_
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YValue} _YValue
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
|
||||
@@ -98,7 +112,7 @@ const markPosition = (searchMarker, p, index) => {
|
||||
*
|
||||
* This function always returns a refreshed marker (updated timestamp)
|
||||
*
|
||||
* @param {AbstractType<any>} yarray
|
||||
* @param {import('../utils/types.js').YType} yarray
|
||||
* @param {number} index
|
||||
*/
|
||||
export const findMarker = (yarray, index) => {
|
||||
@@ -219,7 +233,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
|
||||
/**
|
||||
* Accumulate all (list) children of a type and return them as an Array.
|
||||
*
|
||||
* @param {AbstractType<any>} t
|
||||
* @param {AbstractType} t
|
||||
* @return {Array<Item>}
|
||||
*/
|
||||
export const getTypeChildren = t => {
|
||||
@@ -237,10 +251,9 @@ export const getTypeChildren = t => {
|
||||
* Call event listeners with an event. This will also add an event to all
|
||||
* parents (for `.observeDeep` handlers).
|
||||
*
|
||||
* @template EventType
|
||||
* @param {AbstractType<EventType>} type
|
||||
* @param {import('../utils/types.js').YType} type
|
||||
* @param {Transaction} transaction
|
||||
* @param {EventType} event
|
||||
* @param {YEvent<any>} event
|
||||
*/
|
||||
export const callTypeObservers = (type, transaction, event) => {
|
||||
const changedType = type
|
||||
@@ -251,17 +264,15 @@ export const callTypeObservers = (type, transaction, event) => {
|
||||
if (type._item === null) {
|
||||
break
|
||||
}
|
||||
type = /** @type {AbstractType<any>} */ (type._item.parent)
|
||||
type = /** @type {import('../utils/types.js').YType} */ (type._item.parent)
|
||||
}
|
||||
callEventHandlerListeners(changedType._eH, event, transaction)
|
||||
callEventHandlerListeners(/** @type {any} */ (changedType._eH), event, transaction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Yjs Type class
|
||||
*
|
||||
* @template EventType
|
||||
* @template {import('../utils/Delta.js').Delta} [EventDelta=any]
|
||||
* @template {import('../utils/Delta.js').Delta} [EventDeltaDeep=any]
|
||||
* @template {delta.Delta<any,any,any,any,any>} [EventDelta=delta.Delta<any,any,any,any,any>]
|
||||
* @template {YType_} [Self=any]
|
||||
*/
|
||||
export class AbstractType {
|
||||
constructor () {
|
||||
@@ -284,7 +295,7 @@ export class AbstractType {
|
||||
this._length = 0
|
||||
/**
|
||||
* Event handlers
|
||||
* @type {EventHandler<EventType,Transaction>}
|
||||
* @type {EventHandler<YEvent<Self>,Transaction>}
|
||||
*/
|
||||
this._eH = createEventHandler()
|
||||
/**
|
||||
@@ -299,10 +310,18 @@ export class AbstractType {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {AbstractType<any,any>|null}
|
||||
* Returns a fresh delta that can be used to change this YType.
|
||||
* @type {EventDelta}
|
||||
*/
|
||||
get change () {
|
||||
return /** @type {any} */ (delta.create())
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import('../utils/types.js').YType|null}
|
||||
*/
|
||||
get parent () {
|
||||
return this._item ? /** @type {AbstractType<any,any>} */ (this._item.parent) : null
|
||||
return /** @type {import('../utils/types.js').YType} */ (this._item ? this._item.parent : null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,10 +340,11 @@ export class AbstractType {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {AbstractType<EventType,any>}
|
||||
* @return {Self}
|
||||
*/
|
||||
_copy () {
|
||||
throw error.methodUnimplemented()
|
||||
// @ts-ignore
|
||||
return new this.constructor()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,9 +352,10 @@ export class AbstractType {
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {AbstractType<EventType,any>}
|
||||
* @return {Self}
|
||||
*/
|
||||
clone () {
|
||||
// @todo remove this method from othern types by doing `_copy().apply(this.getContent())`
|
||||
throw error.methodUnimplemented()
|
||||
}
|
||||
|
||||
@@ -370,7 +391,7 @@ export class AbstractType {
|
||||
/**
|
||||
* Observe all events that are created on this type.
|
||||
*
|
||||
* @param {function(EventType, Transaction):void} f Observer function
|
||||
* @param {(target: YEvent<Self>, tr: Transaction) => void} f Observer function
|
||||
*/
|
||||
observe (f) {
|
||||
addEventHandlerListener(this._eH, f)
|
||||
@@ -388,7 +409,7 @@ export class AbstractType {
|
||||
/**
|
||||
* Unregister an observer function.
|
||||
*
|
||||
* @param {function(EventType,Transaction):void} f Observer function
|
||||
* @param {(type:YEvent<Self>,tr:Transaction)=>void} f Observer function
|
||||
*/
|
||||
unobserve (f) {
|
||||
removeEventHandlerListener(this._eH, f)
|
||||
@@ -410,24 +431,284 @@ export class AbstractType {
|
||||
toJSON () {}
|
||||
|
||||
/**
|
||||
* @param {AbstractAttributionManager} _am
|
||||
* @return {EventDelta}
|
||||
* 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
|
||||
* @param {Object} [opts]
|
||||
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
|
||||
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
|
||||
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
|
||||
* @param {Set<string>?} [opts.renderAttrs] - if true, retain rendered+attributed deletes only
|
||||
* @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only
|
||||
* @return {EventDelta} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (_am) {
|
||||
error.methodUnimplemented()
|
||||
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true } = {}) {
|
||||
/**
|
||||
* @type {EventDelta}
|
||||
*/
|
||||
const d = /** @type {any} */ (delta.create())
|
||||
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am)
|
||||
if (renderChildren) {
|
||||
/**
|
||||
* @type {delta.FormattingAttributes}
|
||||
*/
|
||||
let currentAttributes = {} // saves all current attributes for insert
|
||||
let usingCurrentAttributes = false
|
||||
/**
|
||||
* @type {delta.FormattingAttributes}
|
||||
*/
|
||||
let changedAttributes = {} // saves changed attributes for retain
|
||||
let usingChangedAttributes = false
|
||||
/**
|
||||
* Logic for formatting attribute attribution
|
||||
* Everything that comes after an formatting attribute is formatted by the user that created it.
|
||||
* Two exceptions:
|
||||
* - the user resets formatting to the previously known formatting that is not attributed
|
||||
* - the user deletes a formatting attribute and hence restores the previously known formatting
|
||||
* that is not attributed.
|
||||
* @type {delta.FormattingAttributes}
|
||||
*/
|
||||
const previousUnattributedAttributes = {} // contains previously known unattributed formatting
|
||||
/**
|
||||
* @type {delta.FormattingAttributes}
|
||||
*/
|
||||
const previousAttributes = {} // The value before changes
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
const cs = []
|
||||
for (let item = this._start; item !== null; cs.length = 0) {
|
||||
if (itemsToRender != null) {
|
||||
for (; item !== null && cs.length < 50; item = item.right) {
|
||||
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
||||
let itemContent = rslice.length > 1 ? item.content.copy() : item.content
|
||||
for (let ir = 0; ir < rslice.length; ir++) {
|
||||
const idrange = rslice[ir]
|
||||
const content = itemContent
|
||||
if (ir !== rslice.length - 1) {
|
||||
itemContent = itemContent.splice(idrange.len)
|
||||
}
|
||||
am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (; item !== null && cs.length < 50; item = item.right) {
|
||||
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
const c = cs[i]
|
||||
// render (attributed) content even if it was deleted
|
||||
const renderContent = c.render && (!c.deleted || c.attrs != null)
|
||||
// content that was just deleted. It is not rendered as an insertion, because it doesn't
|
||||
// have any attributes.
|
||||
const renderDelete = c.render && c.deleted
|
||||
// existing content that should be retained, only adding changed attributes
|
||||
const retainContent = !c.render && (!c.deleted || c.attrs != null)
|
||||
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
|
||||
switch (c.content.constructor) {
|
||||
case ContentDeleted: {
|
||||
if (renderDelete) d.delete(c.content.getLength())
|
||||
break
|
||||
}
|
||||
case ContentString:
|
||||
if (renderContent) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
if (c.deleted ? retainDeletes : retainInserts) {
|
||||
d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {})
|
||||
} else {
|
||||
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
|
||||
}
|
||||
} else if (renderDelete) {
|
||||
d.delete(c.content.getLength())
|
||||
} else if (retainContent) {
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(c.content.getLength())
|
||||
}
|
||||
break
|
||||
case ContentEmbed:
|
||||
case ContentAny:
|
||||
case ContentJSON:
|
||||
case ContentType:
|
||||
case ContentBinary:
|
||||
if (renderContent) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
if (c.deleted ? retainDeletes : retainInserts) {
|
||||
d.retain(c.content.getLength(), null, attribution ?? {})
|
||||
} else {
|
||||
d.insert(c.content.getContent(), null, attribution)
|
||||
}
|
||||
} else if (renderDelete) {
|
||||
d.delete(1)
|
||||
} else if (retainContent) {
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(1)
|
||||
}
|
||||
break
|
||||
case ContentFormat: {
|
||||
const { key, value } = /** @type {ContentFormat} */ (c.content)
|
||||
const currAttrVal = currentAttributes[key] ?? null
|
||||
if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) {
|
||||
previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal
|
||||
}
|
||||
// @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
|
||||
// # Update Attributes
|
||||
if (renderContent || renderDelete) {
|
||||
// create fresh references
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
if (usingChangedAttributes) {
|
||||
usingChangedAttributes = false
|
||||
changedAttributes = object.assign({}, changedAttributes)
|
||||
}
|
||||
}
|
||||
if (renderContent || renderDelete) {
|
||||
if (c.deleted) {
|
||||
// content was deleted, but is possibly attributed
|
||||
if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed
|
||||
if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
|
||||
delete changedAttributes[key]
|
||||
} else {
|
||||
changedAttributes[key] = currAttrVal
|
||||
}
|
||||
// current attributes doesn't change
|
||||
previousAttributes[key] = value
|
||||
}
|
||||
} else { // !c.deleted
|
||||
// content was inserted, and is possibly attributed
|
||||
if (equalAttrs(value, currAttrVal)) {
|
||||
// item.delete(transaction)
|
||||
} else if (equalAttrs(value, previousAttributes[key] ?? null)) {
|
||||
delete changedAttributes[key]
|
||||
} else {
|
||||
changedAttributes[key] = value
|
||||
}
|
||||
if (value == null) {
|
||||
delete currentAttributes[key]
|
||||
} else {
|
||||
currentAttributes[key] = value
|
||||
}
|
||||
}
|
||||
} else if (retainContent && !c.deleted) {
|
||||
// fresh reference to currentAttributes only
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
if (usingChangedAttributes && changedAttributes[key] !== undefined) {
|
||||
usingChangedAttributes = false
|
||||
changedAttributes = object.assign({}, changedAttributes)
|
||||
}
|
||||
if (value == null) {
|
||||
delete currentAttributes[key]
|
||||
} else {
|
||||
currentAttributes[key] = value
|
||||
}
|
||||
delete changedAttributes[key]
|
||||
previousAttributes[key] = value
|
||||
}
|
||||
// # Update Attributions
|
||||
if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) {
|
||||
/**
|
||||
* @type {import('../utils/AttributionManager.js').Attribution}
|
||||
*/
|
||||
const formattingAttribution = object.assign({}, d.usedAttribution)
|
||||
const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {}))
|
||||
if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
|
||||
// an unattributed formatting attribute was found or an attributed formatting
|
||||
// attribute was found that resets to the previous status
|
||||
delete changedAttributedAttributes[key]
|
||||
delete previousUnattributedAttributes[key]
|
||||
} else {
|
||||
const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
|
||||
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
|
||||
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
||||
if (attributedAt) formattingAttribution.attributedAt = attributedAt
|
||||
}
|
||||
if (object.isEmpty(changedAttributedAttributes)) {
|
||||
d.useAttribution(null)
|
||||
} else if (attribution != null) {
|
||||
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
||||
if (attributedAt != null) formattingAttribution.attributedAt = attributedAt
|
||||
d.useAttribution(formattingAttribution)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractAttributionManager} _am
|
||||
* @return {EventDeltaDeep}
|
||||
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
||||
* attributions.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {ToDeepEventDelta<EventDelta>}
|
||||
*/
|
||||
getContentDeep (_am) {
|
||||
error.methodUnimplemented()
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
const d = this.getContent(am)
|
||||
d.children.forEach(op => {
|
||||
if (op instanceof delta.InsertOp) {
|
||||
op.insert = /** @type {any} */ (op.insert.map(ins =>
|
||||
ins instanceof AbstractType
|
||||
// @ts-ignore
|
||||
? ins.getContentDeep(am)
|
||||
: ins)
|
||||
)
|
||||
}
|
||||
})
|
||||
d.attrs.forEach((op) => {
|
||||
if (delta.$insertOp.check(op) && op.value instanceof AbstractType) {
|
||||
op.value = op.value.getContentDeep(am)
|
||||
}
|
||||
})
|
||||
return /** @type {any} */ (d)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {any} a
|
||||
* @param {any} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
|
||||
|
||||
/**
|
||||
* @template {delta.Delta<any,any,any,any,any>} D
|
||||
* @typedef {D extends delta.Delta<infer N,infer Attrs,infer Cs,infer Text,any>
|
||||
* ? delta.Delta<
|
||||
* N,
|
||||
* { [K in keyof Attrs]: TypeToDelta<Attrs[K]> },
|
||||
* TypeToDelta<Cs>,
|
||||
* Text
|
||||
* >
|
||||
* : D
|
||||
* } ToDeepEventDelta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {any} T
|
||||
* @typedef {(Extract<T,AbstractType<any>> extends AbstractType<infer D> ? (unknown extends D ? never : ToDeepEventDelta<D>) : never) | Exclude<T,AbstractType<any>>} TypeToDelta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @return {Array<any>}
|
||||
@@ -465,7 +746,7 @@ export const typeListSlice = (type, start, end) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {import('../utils/types.js').YType} type
|
||||
* @return {Array<any>}
|
||||
*
|
||||
* @private
|
||||
@@ -488,23 +769,23 @@ export const typeListToArray = type => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo this can be removed as this can be replaced by a generic function
|
||||
* 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, .. }`.
|
||||
*
|
||||
* @template {delta.ArrayDelta<any,any>} TypeDelta
|
||||
* @param {AbstractType<any,TypeDelta,any>} type
|
||||
* @template {delta.Delta<any,any,any,any>} TypeDelta
|
||||
* @param {TypeDelta} d
|
||||
* @param {import('../utils/types.js').YType} type
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {TypeDelta}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const typeListGetContent = (type, am) => {
|
||||
export const typeListGetContent = (d, type, am) => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
const d = delta.createArrayDelta()
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
@@ -526,11 +807,10 @@ export const typeListGetContent = (type, am) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
return /** @type {TypeDelta} */ (d.done())
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {Array<any>}
|
||||
*
|
||||
@@ -555,7 +835,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
|
||||
/**
|
||||
* Executes a provided function on once on every element of this YArray.
|
||||
*
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {function(any,number,any):void} f A function to execute on every element of this YArray.
|
||||
*
|
||||
* @private
|
||||
@@ -578,8 +858,8 @@ export const typeListForEach = (type, f) => {
|
||||
|
||||
/**
|
||||
* @template C,R
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {function(C,number,AbstractType<any,any>):R} f
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {function(C,number,AbstractType<any>):R} f
|
||||
* @return {Array<R>}
|
||||
*
|
||||
* @private
|
||||
@@ -597,7 +877,7 @@ export const typeListMap = (type, f) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {AbstractType} type
|
||||
* @return {IterableIterator<any>}
|
||||
*
|
||||
* @private
|
||||
@@ -649,8 +929,8 @@ export const typeListCreateIterator = type => {
|
||||
* Executes a provided function on once on every element of this YArray.
|
||||
* Operates on a snapshotted state of the document.
|
||||
*
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {function(any,number,AbstractType<any,any>):void} f A function to execute on every element of this YArray.
|
||||
* @param {AbstractType} type
|
||||
* @param {function(any,number,AbstractType):void} f A function to execute on every element of this YArray.
|
||||
* @param {Snapshot} snapshot
|
||||
*
|
||||
* @private
|
||||
@@ -671,7 +951,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any,any>} type
|
||||
* @param {import('../utils/types.js').YType} type
|
||||
* @param {number} index
|
||||
* @return {any}
|
||||
*
|
||||
@@ -698,9 +978,9 @@ export const typeListGet = (type, index) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {Item?} referenceItem
|
||||
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
|
||||
* @param {Array<_YValue>} content
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
@@ -750,7 +1030,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
||||
break
|
||||
default:
|
||||
if (c instanceof AbstractType) {
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
|
||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(/** @type {any} */ (c)))
|
||||
left.integrate(transaction, 0)
|
||||
} else {
|
||||
throw new Error('Unexpected content type in insert operation')
|
||||
@@ -766,7 +1046,7 @@ const lengthExceeded = () => error.create('Length exceeded!')
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {number} index
|
||||
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
|
||||
*
|
||||
@@ -819,7 +1099,7 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
||||
* the search marker.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
|
||||
*
|
||||
* @private
|
||||
@@ -839,7 +1119,7 @@ export const typeListPushGenerics = (transaction, parent, content) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {number} index
|
||||
* @param {number} length
|
||||
*
|
||||
@@ -886,7 +1166,7 @@ export const typeListDelete = (transaction, parent, index, length) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {string} key
|
||||
*
|
||||
* @private
|
||||
@@ -901,9 +1181,9 @@ export const typeMapDelete = (transaction, parent, key) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any,any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {string} key
|
||||
* @param {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any,any>} value
|
||||
* @param {_YValue} value
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
@@ -934,7 +1214,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
||||
break
|
||||
default:
|
||||
if (value instanceof AbstractType) {
|
||||
content = new ContentType(value)
|
||||
content = new ContentType(/** @type {any} */ (value))
|
||||
} else {
|
||||
throw new Error('Unexpected content type')
|
||||
}
|
||||
@@ -944,7 +1224,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {YType_} parent
|
||||
* @param {string} key
|
||||
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
||||
*
|
||||
@@ -985,17 +1265,23 @@ export const typeMapGetAll = (parent) => {
|
||||
* 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>} parent
|
||||
* @template {delta.Delta<any,any,any,any>} TypeDelta
|
||||
* @param {TypeDelta} d
|
||||
* @param {YType_} parent
|
||||
* @param {Set<string>?} attrsToRender
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const typeMapGetDelta = (parent, am) => {
|
||||
const mapdelta = /** @type {delta.MapDeltaBuilder<{ [key:string]: MapType }>} */ (delta.createMapDelta())
|
||||
export const typeMapGetDelta = (d, parent, attrsToRender, am) => {
|
||||
parent.doc ?? warnPrematureAccess()
|
||||
parent._map.forEach((item, key) => {
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
* @param {string} key
|
||||
*/
|
||||
const renderAttrs = (item, key) => {
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
@@ -1005,7 +1291,7 @@ export const typeMapGetDelta = (parent, am) => {
|
||||
const c = array.last(content.getContent())
|
||||
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
||||
if (deleted) {
|
||||
mapdelta.delete(key, c, attribution)
|
||||
d.unset(key, attribution, c)
|
||||
} else {
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
@@ -1027,10 +1313,14 @@ export const typeMapGetDelta = (parent, am) => {
|
||||
}
|
||||
}
|
||||
const prevValue = cs.length > 0 ? array.last(cs[0].content.getContent()) : undefined
|
||||
mapdelta.set(key, c, prevValue, attribution)
|
||||
d.set(key, c, attribution, prevValue)
|
||||
}
|
||||
})
|
||||
return mapdelta
|
||||
}
|
||||
if (attrsToRender == null) {
|
||||
parent._map.forEach(renderAttrs)
|
||||
} else {
|
||||
attrsToRender.forEach(key => renderAttrs(/** @type {Item} */ (parent._map.get(key)), key))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,28 +23,12 @@ import {
|
||||
AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
/**
|
||||
*
|
||||
* @template Content
|
||||
* @template {import('../internals.js').Delta|undefined} Modifiers
|
||||
* @typedef {import('../internals.js').ArrayDelta<Content,Modifiers>} ArrayDelta
|
||||
*/
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YArray
|
||||
* @template {import('../utils/types.js').YValue} T
|
||||
* @extends YEvent<YArray<T>>
|
||||
*/
|
||||
export class YArrayEvent extends YEvent {}
|
||||
import * as delta from 'lib0/delta' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* A shared Array implementation.
|
||||
* @template {import('../utils/types.js').YValue} T
|
||||
* @template {ArrayDelta<T,undefined>} [TypeDelta=ArrayDelta<T,undefined>]
|
||||
* @template {T extends AbstractType<any,any,infer DeepD> ? ArrayDelta<Exclude<T,AbstractType<any>>|DeepD,DeepD> : ArrayDelta<T,undefined>} [EventDeltaDeep=T extends AbstractType<any,any,infer DeepD> ? ArrayDelta<Exclude<T,AbstractType<any>>|DeepD,DeepD> : ArrayDelta<T,undefined>]
|
||||
* @extends AbstractType<YArrayEvent<T>,TypeDelta,EventDeltaDeep>
|
||||
* @extends {AbstractType<delta.ArrayDelta<T>,YArray<T>>}
|
||||
* @implements {Iterable<T>}
|
||||
*/
|
||||
export class YArray extends AbstractType {
|
||||
@@ -84,7 +68,7 @@ export class YArray extends AbstractType {
|
||||
* * Observer functions are fired
|
||||
*
|
||||
* @param {Doc} y The Yjs instance
|
||||
* @param {Item} item
|
||||
* @param {Item?} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
@@ -92,13 +76,6 @@ export class YArray extends AbstractType {
|
||||
this._prelimContent = null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {YArray<T>}
|
||||
*/
|
||||
_copy () {
|
||||
return new YArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of this data type that can be included somewhere else.
|
||||
*
|
||||
@@ -108,11 +85,12 @@ export class YArray extends AbstractType {
|
||||
*/
|
||||
clone () {
|
||||
/**
|
||||
* @type {YArray<T>}
|
||||
* @type {this}
|
||||
*/
|
||||
const arr = new YArray()
|
||||
const arr = /** @type {this} */ (new YArray())
|
||||
arr.insert(0, this.toArray().map(el =>
|
||||
el instanceof AbstractType ? /** @type {typeof el} */ (el.clone()) : el
|
||||
// @ts-ignore
|
||||
el instanceof AbstractType ? /** @type {any} */ (el.clone()) : el
|
||||
))
|
||||
return arr
|
||||
}
|
||||
@@ -130,7 +108,7 @@ export class YArray extends AbstractType {
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
super._callObserver(transaction, parentSubs)
|
||||
callTypeObservers(this, transaction, new YArrayEvent(this, transaction))
|
||||
callTypeObservers(this, transaction, new YEvent(this, transaction, null))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,16 +206,12 @@ export class YArray extends AbstractType {
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {EventDeltaDeep} The Delta representation of this type.
|
||||
* @return {delta.ArrayDelta<import('./AbstractType.js').TypeToDelta<T>>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
return /** @type {any} */ (this.getContent(am).map(d => /** @type {any} */ (
|
||||
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 super.getContentDeep(am)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,12 +222,14 @@ export class YArray extends AbstractType {
|
||||
* attribution `{ isDeleted: true, .. }`.
|
||||
*
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {TypeDelta} The Delta representation of this type.
|
||||
* @return {delta.ArrayDelta<T>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am = noAttributionsManager) {
|
||||
return typeListGetContent(this, am)
|
||||
const d = this.change
|
||||
typeListGetContent(d, this, am)
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,6 +292,7 @@ export class YArray extends AbstractType {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
|
||||
@@ -13,35 +13,18 @@ import {
|
||||
YMapRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
typeMapGetDelta,
|
||||
warnPrematureAccess,
|
||||
MapDelta, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as iterator from 'lib0/iterator'
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends YEvent<YMap<T>>
|
||||
* Event that describes the changes on a YMap.
|
||||
*/
|
||||
export class YMapEvent extends YEvent {
|
||||
/**
|
||||
* @param {YMap<T>} ymap The YArray that changed.
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<any>} subs The keys that changed.
|
||||
*/
|
||||
constructor (ymap, transaction, subs) {
|
||||
super(ymap, transaction)
|
||||
this.keysChanged = subs
|
||||
}
|
||||
}
|
||||
import * as delta from 'lib0/delta' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* @template MapType
|
||||
* A shared Map implementation.
|
||||
*
|
||||
* @extends AbstractType<YMapEvent<MapType>>
|
||||
* @extends AbstractType<delta.MapDelta<{[K in string]:MapType}>>
|
||||
* @implements {Iterable<[string, MapType]>}
|
||||
*/
|
||||
export class YMap extends AbstractType {
|
||||
@@ -72,7 +55,7 @@ export class YMap extends AbstractType {
|
||||
* * Observer functions are fired
|
||||
*
|
||||
* @param {Doc} y The Yjs instance
|
||||
* @param {Item} item
|
||||
* @param {Item?} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
@@ -82,25 +65,15 @@ export class YMap extends AbstractType {
|
||||
this._prelimContent = null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {YMap<MapType>}
|
||||
*/
|
||||
_copy () {
|
||||
return new YMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of this data type that can be included somewhere else.
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {YMap<MapType>}
|
||||
* @return {this}
|
||||
*/
|
||||
clone () {
|
||||
/**
|
||||
* @type {YMap<MapType>}
|
||||
*/
|
||||
const map = new YMap()
|
||||
const map = this._copy()
|
||||
this.forEach((value, key) => {
|
||||
map.set(key, value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value)
|
||||
})
|
||||
@@ -114,7 +87,7 @@ export class YMap extends AbstractType {
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs))
|
||||
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,22 +160,6 @@ export class YMap extends AbstractType {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {MapDelta<{[key:string]: MapType},undefined>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am) {
|
||||
return typeMapGetDelta(this, am)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Iterator of [key, value] pairs
|
||||
*
|
||||
@@ -291,6 +248,7 @@ export class YMap extends AbstractType {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
|
||||
@@ -25,27 +25,15 @@ import {
|
||||
ContentType,
|
||||
warnPrematureAccess,
|
||||
noAttributionsManager, AbstractAttributionManager, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, Transaction, // eslint-disable-line
|
||||
createAttributionFromAttributionItems,
|
||||
mergeIdSets,
|
||||
diffIdSet,
|
||||
createIdSet,
|
||||
ContentDeleted
|
||||
equalAttrs
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
import * as math from 'lib0/math'
|
||||
import * as traits from 'lib0/traits'
|
||||
import * as object from 'lib0/object'
|
||||
import * as map from 'lib0/map'
|
||||
import * as error from 'lib0/error'
|
||||
|
||||
/**
|
||||
* @param {any} a
|
||||
* @param {any} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
|
||||
import * as delta from 'lib0/delta'
|
||||
|
||||
export class ItemTextListPosition {
|
||||
/**
|
||||
@@ -86,7 +74,7 @@ export class ItemTextListPosition {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {number} length
|
||||
* @param {Object<string,any>} attributes
|
||||
*
|
||||
@@ -214,7 +202,7 @@ const findNextPosition = (transaction, pos, count) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {number} index
|
||||
* @param {boolean} useSearchMarker
|
||||
* @return {ItemTextListPosition}
|
||||
@@ -238,7 +226,7 @@ const findPosition = (transaction, parent, index, useSearchMarker) => {
|
||||
* Negate applied formats
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {ItemTextListPosition} currPos
|
||||
* @param {Map<string,any>} negatedAttributes
|
||||
*
|
||||
@@ -311,7 +299,7 @@ const minimizeAttributeChanges = (currPos, attributes) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {ItemTextListPosition} currPos
|
||||
* @param {Object<string,any>} attributes
|
||||
* @return {Map<string,any>}
|
||||
@@ -341,9 +329,9 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {ItemTextListPosition} currPos
|
||||
* @param {string|object|AbstractType<any>} text
|
||||
* @param {string|object|import('../utils/types.js').YType} text
|
||||
* @param {Object<string,any>} attributes
|
||||
*
|
||||
* @private
|
||||
@@ -638,82 +626,6 @@ const deleteText = (transaction, currPos, length) => {
|
||||
* @typedef {Object} TextAttributes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {{ [key:string]: any } | AbstractType<any> } TextEmbeds
|
||||
* @extends YEvent<YText<any>>
|
||||
* Event that describes the changes on a YText type.
|
||||
*/
|
||||
export class YTextEvent extends YEvent {
|
||||
/**
|
||||
* @param {YText<TextEmbeds>} ytext
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<any>} subs The keys that changed
|
||||
*/
|
||||
constructor (ytext, transaction, subs) {
|
||||
super(ytext, transaction)
|
||||
/**
|
||||
* Whether the children changed.
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.childListChanged = false
|
||||
/**
|
||||
* Set of all changed attributes.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
this.keysChanged = new Set()
|
||||
subs.forEach((sub) => {
|
||||
if (sub === null) {
|
||||
this.childListChanged = true
|
||||
} else {
|
||||
this.keysChanged.add(sub)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds,undefined>}}
|
||||
*/
|
||||
get changes () {
|
||||
if (this._changes === null) {
|
||||
/**
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:delta.TextDelta<TextEmbeds,undefined>}}
|
||||
*/
|
||||
const changes = {
|
||||
keys: this.keys,
|
||||
delta: this.delta,
|
||||
added: new Set(),
|
||||
deleted: new Set()
|
||||
}
|
||||
this._changes = changes
|
||||
}
|
||||
return /** @type {any} */ (this._changes)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {import('../utils/Delta.js').TextDelta<TextEmbeds,undefined>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getDelta (am = noAttributionsManager) {
|
||||
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
|
||||
return this.target.getContent(am, { itemsToRender, retainDeletes: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the changes in the delta format.
|
||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
||||
*
|
||||
* @type {delta.TextDelta<TextEmbeds,undefined>}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
get delta () {
|
||||
return this._delta ?? (this._delta = this.getDelta())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type that represents text with formatting information.
|
||||
*
|
||||
@@ -721,8 +633,8 @@ export class YTextEvent extends YEvent {
|
||||
* block formats (format information on a paragraph), embeds (complex elements
|
||||
* like pictures and videos), and text formats (**bold**, *italic*).
|
||||
*
|
||||
* @template {{ [key:string]:any } | AbstractType<any>} [Embeds={ [key:string]:any } | AbstractType<any>]
|
||||
* @extends AbstractType<YTextEvent<Embeds>>
|
||||
* @template {{ [key:string]:any } | import('../utils/types.js').YType} [Embeds={ [key:string]:any } | import('../utils/types.js').YType]
|
||||
* @extends {AbstractType<delta.TextDelta<Embeds>>}
|
||||
*/
|
||||
export class YText extends AbstractType {
|
||||
/**
|
||||
@@ -758,7 +670,7 @@ export class YText extends AbstractType {
|
||||
|
||||
/**
|
||||
* @param {Doc} y
|
||||
* @param {Item} item
|
||||
* @param {Item?} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
@@ -770,13 +682,6 @@ export class YText extends AbstractType {
|
||||
this._pending = null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {YText<Embeds>}
|
||||
*/
|
||||
_copy () {
|
||||
return new YText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of this data type that can be included somewhere else.
|
||||
*
|
||||
@@ -788,7 +693,7 @@ export class YText extends AbstractType {
|
||||
/**
|
||||
* @type {YText<Embeds>}
|
||||
*/
|
||||
const text = new YText()
|
||||
const text = /** @type {any} */ (new YText())
|
||||
text.applyDelta(this.getContent())
|
||||
return text
|
||||
}
|
||||
@@ -801,8 +706,8 @@ export class YText extends AbstractType {
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
super._callObserver(transaction, parentSubs)
|
||||
const event = new YTextEvent(this, transaction, parentSubs)
|
||||
callTypeObservers(this, transaction, event)
|
||||
const event = new YEvent(/** @type {YText<any>} */ (this), transaction, parentSubs)
|
||||
callTypeObservers(/** @type {YText<any>} */ (this), transaction, event)
|
||||
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
||||
if (!transaction.local && this._hasFormatting) {
|
||||
transaction._needFormattingCleanup = true
|
||||
@@ -843,272 +748,32 @@ export class YText extends AbstractType {
|
||||
/**
|
||||
* Apply a {@link Delta} on this shared YText type.
|
||||
*
|
||||
* @param {Array<any> | delta.TextDelta<Embeds,undefined>} delta The changes to apply on this element.
|
||||
* @param {delta.TextDelta<Embeds>} d The changes to apply on this element.
|
||||
* @param {AbstractAttributionManager} am
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
applyDelta (delta, am = noAttributionsManager) {
|
||||
applyDelta (d, am = noAttributionsManager) {
|
||||
if (this.doc !== null) {
|
||||
transact(this.doc, transaction => {
|
||||
const deltaOps = /** @type {Array<any>} */ (/** @type {delta.TextDelta<any,undefined>} */ (delta).ops instanceof Array ? /** @type {delta.TextDelta<any,undefined>} */ (delta).ops : delta)
|
||||
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), am)
|
||||
for (let i = 0; i < deltaOps.length; i++) {
|
||||
const op = deltaOps[i]
|
||||
if (op.insert !== undefined) {
|
||||
for (const op of d.children) {
|
||||
if (delta.$insertOp.check(op)) {
|
||||
if (op.insert.length > 0 || typeof op.insert !== 'string') {
|
||||
insertText(transaction, this, currPos, op.insert, op.attributes || {})
|
||||
insertText(transaction, this, currPos, op.insert, op.format || {})
|
||||
}
|
||||
} else if (op.retain !== undefined) {
|
||||
currPos.formatText(transaction, this, op.retain, op.attributes || {})
|
||||
} else if (op.delete !== undefined) {
|
||||
} else if (delta.$retainOp.check(op)) {
|
||||
currPos.formatText(transaction, this, op.retain, op.format || {})
|
||||
} else if (delta.$deleteOp.check(op)) {
|
||||
deleteText(transaction, currPos, op.delete)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
/** @type {Array<function>} */ (this._pending).push(() => this.applyDelta(delta))
|
||||
/** @type {Array<function>} */ (this._pending).push(() => this.applyDelta(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<Embeds extends import('./AbstractType.js').AbstractType<infer SubEvent> ? SubEvent : Embeds, undefined>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContentDeep (am = noAttributionsManager) {
|
||||
return this.getContent(am).map(d =>
|
||||
d instanceof delta.InsertEmbedOp && d.insert instanceof AbstractType
|
||||
? new delta.InsertEmbedOp(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
|
||||
* @param {Object} [opts]
|
||||
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
|
||||
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
|
||||
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
|
||||
* @return {import('../utils/Delta.js').TextDelta<Embeds,undefined>} The Delta representation of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am = noAttributionsManager, { itemsToRender = null, retainInserts = false, retainDeletes = false } = {}) {
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').TextDeltaBuilder<Embeds>}
|
||||
*/
|
||||
const d = delta.createTextDelta()
|
||||
/**
|
||||
* @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
|
||||
/**
|
||||
* Logic for formatting attribute attribution
|
||||
* Everything that comes after an formatting attribute is formatted by the user that created it.
|
||||
* Two exceptions:
|
||||
* - the user resets formatting to the previously known formatting that is not attributed
|
||||
* - the user deletes a formatting attribute and hence restores the previously known formatting
|
||||
* that is not attributed.
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
const previousUnattributedAttributes = {} // contains previously known unattributed formatting
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').FormattingAttributes}
|
||||
*/
|
||||
const previousAttributes = {} // The value before changes
|
||||
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
const cs = []
|
||||
for (let item = this._start; item !== null; cs.length = 0) {
|
||||
if (itemsToRender != null) {
|
||||
for (; item !== null && cs.length < 50; item = item.right) {
|
||||
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
||||
let itemContent = rslice.length > 1 ? item.content.copy() : item.content
|
||||
for (let ir = 0; ir < rslice.length; ir++) {
|
||||
const idrange = rslice[ir]
|
||||
const content = itemContent
|
||||
if (ir !== rslice.length - 1) {
|
||||
itemContent = itemContent.splice(idrange.len)
|
||||
}
|
||||
am.readContent(cs, item.id.client, idrange.clock, item.deleted, content, idrange.exists ? 2 : 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (; item !== null && cs.length < 50; item = item.right) {
|
||||
am.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
const c = cs[i]
|
||||
// render (attributed) content even if it was deleted
|
||||
const renderContent = c.render && (!c.deleted || c.attrs != null)
|
||||
// content that was just deleted. It is not rendered as an insertion, because it doesn't
|
||||
// have any attributes.
|
||||
const renderDelete = c.render && c.deleted
|
||||
// existing content that should be retained, only adding changed attributes
|
||||
const retainContent = !c.render && (!c.deleted || c.attrs != null)
|
||||
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : null
|
||||
switch (c.content.constructor) {
|
||||
case ContentDeleted: {
|
||||
if (renderDelete) d.delete(c.content.getLength())
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed:
|
||||
if (renderContent) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
if (c.deleted ? retainDeletes : retainInserts) {
|
||||
d.retain(c.content.getLength(), null, attribution ?? {})
|
||||
} else {
|
||||
d.insert(c.content.getContent()[0], null, attribution)
|
||||
}
|
||||
} else if (renderDelete) {
|
||||
d.delete(1)
|
||||
} else if (retainContent) {
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(1)
|
||||
}
|
||||
break
|
||||
case ContentString:
|
||||
if (renderContent) {
|
||||
d.usedAttributes = currentAttributes
|
||||
usingCurrentAttributes = true
|
||||
if (c.deleted ? retainDeletes : retainInserts) {
|
||||
d.retain(/** @type {ContentString} */ (c.content).str.length, null, attribution ?? {})
|
||||
} else {
|
||||
d.insert(/** @type {ContentString} */ (c.content).str, null, attribution)
|
||||
}
|
||||
} else if (renderDelete) {
|
||||
d.delete(c.content.getLength())
|
||||
} else if (retainContent) {
|
||||
d.usedAttributes = changedAttributes
|
||||
usingChangedAttributes = true
|
||||
d.retain(c.content.getLength())
|
||||
}
|
||||
break
|
||||
case ContentFormat: {
|
||||
const { key, value } = /** @type {ContentFormat} */ (c.content)
|
||||
const currAttrVal = currentAttributes[key] ?? null
|
||||
if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedAttributes, key))) {
|
||||
previousUnattributedAttributes[key] = c.deleted ? value : currAttrVal
|
||||
}
|
||||
// @todo write a function "updateCurrentAttributes" and "updateChangedAttributes"
|
||||
// # Update Attributes
|
||||
if (renderContent || renderDelete) {
|
||||
// create fresh references
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
if (usingChangedAttributes) {
|
||||
usingChangedAttributes = false
|
||||
changedAttributes = object.assign({}, changedAttributes)
|
||||
}
|
||||
}
|
||||
if (renderContent || renderDelete) {
|
||||
if (c.deleted) {
|
||||
// content was deleted, but is possibly attributed
|
||||
if (!equalAttrs(value, currAttrVal)) { // do nothing if nothing changed
|
||||
if (equalAttrs(currAttrVal, previousAttributes[key] ?? null) && changedAttributes[key] !== undefined) {
|
||||
delete changedAttributes[key]
|
||||
} else {
|
||||
changedAttributes[key] = currAttrVal
|
||||
}
|
||||
// current attributes doesn't change
|
||||
previousAttributes[key] = value
|
||||
}
|
||||
} else { // !c.deleted
|
||||
// content was inserted, and is possibly attributed
|
||||
if (equalAttrs(value, currAttrVal)) {
|
||||
// item.delete(transaction)
|
||||
} else if (equalAttrs(value, previousAttributes[key] ?? null)) {
|
||||
delete changedAttributes[key]
|
||||
} else {
|
||||
changedAttributes[key] = value
|
||||
}
|
||||
if (value == null) {
|
||||
delete currentAttributes[key]
|
||||
} else {
|
||||
currentAttributes[key] = value
|
||||
}
|
||||
}
|
||||
} else if (retainContent && !c.deleted) {
|
||||
// fresh reference to currentAttributes only
|
||||
if (usingCurrentAttributes) {
|
||||
currentAttributes = object.assign({}, currentAttributes)
|
||||
usingCurrentAttributes = false
|
||||
}
|
||||
if (usingChangedAttributes && changedAttributes[key] !== undefined) {
|
||||
usingChangedAttributes = false
|
||||
changedAttributes = object.assign({}, changedAttributes)
|
||||
}
|
||||
if (value == null) {
|
||||
delete currentAttributes[key]
|
||||
} else {
|
||||
currentAttributes[key] = value
|
||||
}
|
||||
delete changedAttributes[key]
|
||||
previousAttributes[key] = value
|
||||
}
|
||||
// # Update Attributions
|
||||
if (attribution != null || object.hasProperty(previousUnattributedAttributes, key)) {
|
||||
/**
|
||||
* @type {import('../utils/AttributionManager.js').Attribution}
|
||||
*/
|
||||
const formattingAttribution = object.assign({}, d.usedAttribution)
|
||||
const changedAttributedAttributes = /** @type {{ [key: string]: Array<any> }} */ (formattingAttribution.attributes = object.assign({}, formattingAttribution.attributes ?? {}))
|
||||
if (attribution == null || equalAttrs(previousUnattributedAttributes[key], currentAttributes[key] ?? null)) {
|
||||
// an unattributed formatting attribute was found or an attributed formatting
|
||||
// attribute was found that resets to the previous status
|
||||
delete changedAttributedAttributes[key]
|
||||
delete previousUnattributedAttributes[key]
|
||||
} else {
|
||||
const by = changedAttributedAttributes[key] = (changedAttributedAttributes[key]?.slice() ?? [])
|
||||
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
|
||||
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
||||
if (attributedAt) formattingAttribution.attributedAt = attributedAt
|
||||
}
|
||||
if (object.isEmpty(changedAttributedAttributes)) {
|
||||
d.useAttribution(null)
|
||||
} else if (attribution != null) {
|
||||
const attributedAt = (c.deleted ? attribution.deletedAt : attribution.insertedAt)
|
||||
if (attributedAt != null) formattingAttribution.attributedAt = attributedAt
|
||||
d.useAttribution(formattingAttribution)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @todo! fix the typings here
|
||||
return /** @type {any} */ (d.done())
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text at a given index.
|
||||
*
|
||||
@@ -1295,7 +960,7 @@ export class YText extends AbstractType {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {YText}
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
|
||||
@@ -9,15 +9,10 @@ import {
|
||||
typeMapGet,
|
||||
typeMapGetAll,
|
||||
typeMapGetAllSnapshot,
|
||||
typeListForEach,
|
||||
YXmlElementRefID,
|
||||
typeMapGetDelta,
|
||||
noAttributionsManager,
|
||||
AbstractAttributionManager, Snapshot, YXmlText, ContentType, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item, // eslint-disable-line
|
||||
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
|
||||
*/
|
||||
@@ -29,7 +24,9 @@ import * as delta from '../utils/Delta.js'
|
||||
* * An YXmlElement has attributes (key value pairs)
|
||||
* * An YXmlElement has childElements that must inherit from YXmlElement
|
||||
*
|
||||
* @template {{ [key: string]: ValueTypes }} [KV={ [key: string]: string }]
|
||||
* @template {{ [key: string]: ValueTypes }} [Attrs={ [key: string]: string }]
|
||||
* @template {any} [Children=any]
|
||||
* @extends YXmlFragment<Children,Attrs>
|
||||
*/
|
||||
export class YXmlElement extends YXmlFragment {
|
||||
constructor (nodeName = 'UNDEFINED') {
|
||||
@@ -65,7 +62,7 @@ export class YXmlElement extends YXmlFragment {
|
||||
* * Observer functions are fired
|
||||
*
|
||||
* @param {Doc} y The Yjs instance
|
||||
* @param {Item} item
|
||||
* @param {Item?} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
@@ -78,10 +75,10 @@ export class YXmlElement extends YXmlFragment {
|
||||
/**
|
||||
* Creates an Item with the same effect as this Item (without position effect)
|
||||
*
|
||||
* @return {YXmlElement}
|
||||
* @return {this}
|
||||
*/
|
||||
_copy () {
|
||||
return new YXmlElement(this.nodeName)
|
||||
return /** @type {any} */ (new YXmlElement(this.nodeName))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,13 +86,10 @@ export class YXmlElement extends YXmlFragment {
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {YXmlElement<KV>}
|
||||
* @return {this}
|
||||
*/
|
||||
clone () {
|
||||
/**
|
||||
* @type {YXmlElement<KV>}
|
||||
*/
|
||||
const el = new YXmlElement(this.nodeName)
|
||||
const el = this._copy()
|
||||
const attrs = this.getAttributes()
|
||||
object.forEach(attrs, (value, key) => {
|
||||
if (typeof value === 'string') {
|
||||
@@ -154,17 +148,17 @@ export class YXmlElement extends YXmlFragment {
|
||||
/**
|
||||
* Sets or updates an attribute.
|
||||
*
|
||||
* @template {keyof KV & string} KEY
|
||||
* @template {keyof Attrs & string} KEY
|
||||
*
|
||||
* @param {KEY} attributeName The attribute name that is to be set.
|
||||
* @param {KV[KEY]} attributeValue The attribute value that is to be set.
|
||||
* @param {Attrs[KEY]} attributeValue The attribute value that is to be set.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
setAttribute (attributeName, attributeValue) {
|
||||
if (this.doc !== null) {
|
||||
transact(this.doc, transaction => {
|
||||
typeMapSet(transaction, this, attributeName, attributeValue)
|
||||
typeMapSet(transaction, this, attributeName, /** @type {any} */ (attributeValue))
|
||||
})
|
||||
} else {
|
||||
/** @type {Map<string, any>} */ (this._prelimAttrs).set(attributeName, attributeValue)
|
||||
@@ -174,11 +168,11 @@ export class YXmlElement extends YXmlFragment {
|
||||
/**
|
||||
* Returns an attribute value that belongs to the attribute name.
|
||||
*
|
||||
* @template {keyof KV & string} KEY
|
||||
* @template {keyof Attrs & string} KEY
|
||||
*
|
||||
* @param {KEY} attributeName The attribute name that identifies the
|
||||
* queried value.
|
||||
* @return {KV[KEY]|undefined} The queried attribute value.
|
||||
* @return {Attrs[KEY]|undefined} The queried attribute value.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -202,7 +196,7 @@ export class YXmlElement extends YXmlFragment {
|
||||
* Returns all attribute name/value pairs in a JSON Object.
|
||||
*
|
||||
* @param {Snapshot} [snapshot]
|
||||
* @return {{ [Key in Extract<keyof KV,string>]?: KV[Key]}} A JSON Object that describes the attributes.
|
||||
* @return {{ [Key in Extract<keyof Attrs,string>]?: Attrs[Key]}} A JSON Object that describes the attributes.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -210,93 +204,6 @@ 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.ArrayDeltaBuilder<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.InsertArrayOp && d.insert instanceof Array)
|
||||
? new delta.InsertArrayOp(d.insert.map(e => e instanceof AbstractType ? /** @type {delta.ArrayDeltaBuilder<Array<any>>} */ (e.getContentDeep(am)) : e), d.attributes, d.attribution)
|
||||
: d
|
||||
))
|
||||
/**
|
||||
* @todo there is a Attributes type and a DeepAttributes type.
|
||||
* @type {delta.MapDeltaBuilder<any,any>}
|
||||
*/
|
||||
const attributes = delta.createMapDelta()
|
||||
origAttributes.forEach(
|
||||
null,
|
||||
(insertOp, key) => {
|
||||
if (insertOp.value instanceof AbstractType) {
|
||||
attributes.set(key, insertOp.value.getContentDeep(am), null, insertOp.attribution)
|
||||
} else {
|
||||
attributes.set(key, insertOp.value, undefined, insertOp.attribution)
|
||||
}
|
||||
}
|
||||
)
|
||||
return delta.createXmlDelta(this.nodeName, children, attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {import('../internals.js').AbstractAttributionManager} am
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getContent (am = noAttributionsManager) {
|
||||
const { children } = super.getContent(am)
|
||||
const attributes = typeMapGetDelta(this, am)
|
||||
return new delta.XmlDelta(this.nodeName, children, attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Dom Element that mirrors this YXmlElement.
|
||||
*
|
||||
* @param {Document} [_document=document] The document object (you must define
|
||||
* this when calling this method in
|
||||
* nodejs)
|
||||
* @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
|
||||
* are presented in the DOM
|
||||
* @param {any} [binding] You should not set this property. This is
|
||||
* used if DomBinding wants to create a
|
||||
* association to the created DOM type.
|
||||
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toDOM (_document = document, hooks = {}, binding) {
|
||||
const dom = _document.createElement(this.nodeName)
|
||||
const attrs = this.getAttributes()
|
||||
for (const key in attrs) {
|
||||
const value = attrs[key]
|
||||
if (typeof value === 'string') {
|
||||
dom.setAttribute(key, value)
|
||||
}
|
||||
}
|
||||
typeListForEach(this, yxml => {
|
||||
dom.appendChild(yxml.toDOM(_document, hooks, binding))
|
||||
})
|
||||
if (binding !== undefined) {
|
||||
binding._createAssociation(dom, this)
|
||||
}
|
||||
return dom
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the properties of this type to binary and write it to an
|
||||
* BinaryEncoder.
|
||||
@@ -313,7 +220,7 @@ export class YXmlElement extends YXmlFragment {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||
* @return {YXmlElement}
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @function
|
||||
*/
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import {
|
||||
YEvent,
|
||||
YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
/**
|
||||
* @extends YEvent<YXmlElement|YXmlText|YXmlFragment>
|
||||
* An Event that describes changes on a YXml Element or Yxml Fragment
|
||||
*/
|
||||
export class YXmlEvent extends YEvent {
|
||||
/**
|
||||
* @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created.
|
||||
* @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
|
||||
* child list changed.
|
||||
* @param {Transaction} transaction The transaction instance with which the
|
||||
* change was created.
|
||||
*/
|
||||
constructor (target, subs, transaction) {
|
||||
super(target, transaction)
|
||||
/**
|
||||
* Whether the children changed.
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.childListChanged = false
|
||||
/**
|
||||
* Set of all changed attributes.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
this.attributesChanged = new Set()
|
||||
subs.forEach((sub) => {
|
||||
if (sub === null) {
|
||||
this.childListChanged = true
|
||||
} else {
|
||||
this.attributesChanged.add(sub)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
YXmlEvent,
|
||||
YXmlElement,
|
||||
YEvent,
|
||||
AbstractType,
|
||||
typeListMap,
|
||||
typeListForEach,
|
||||
@@ -18,14 +17,11 @@ import {
|
||||
typeListGet,
|
||||
typeListSlice,
|
||||
warnPrematureAccess,
|
||||
noAttributionsManager,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, // eslint-disable-line
|
||||
typeListGetContent
|
||||
YXmlElement, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
import * as delta from 'lib0/delta' // eslint-disable-line
|
||||
import * as error from 'lib0/error'
|
||||
import * as array from 'lib0/array'
|
||||
|
||||
/**
|
||||
* Define the elements to which a set of CSS queries apply.
|
||||
@@ -48,83 +44,6 @@ import * as array from 'lib0/array'
|
||||
* @return {boolean} Whether to include the Dom node in the YXmlElement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
|
||||
* position within them.
|
||||
*
|
||||
* Can be created with {@link YXmlFragment#createTreeWalker}
|
||||
*
|
||||
* @public
|
||||
* @implements {Iterable<YXmlElement|YXmlText|YXmlElement|YXmlHook>}
|
||||
*/
|
||||
export class YXmlTreeWalker {
|
||||
/**
|
||||
* @param {YXmlFragment | YXmlElement} root
|
||||
* @param {function(AbstractType<any>):boolean} [f]
|
||||
*/
|
||||
constructor (root, f = () => true) {
|
||||
this._filter = f
|
||||
this._root = root
|
||||
/**
|
||||
* @type {Item}
|
||||
*/
|
||||
this._currentNode = /** @type {Item} */ (root._start)
|
||||
this._firstCall = true
|
||||
root.doc ?? warnPrematureAccess()
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next node.
|
||||
*
|
||||
* @return {IteratorResult<YXmlElement|YXmlText|YXmlHook>} The next node.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
next () {
|
||||
/**
|
||||
* @type {Item|null}
|
||||
*/
|
||||
let n = this._currentNode
|
||||
let type = n && n.content && /** @type {any} */ (n.content).type
|
||||
if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item
|
||||
do {
|
||||
type = /** @type {any} */ (n.content).type
|
||||
if (!n.deleted && (type.constructor === YXmlElement || type.constructor === YXmlFragment) && type._start !== null) {
|
||||
// walk down in the tree
|
||||
n = type._start
|
||||
} else {
|
||||
// walk right or up in the tree
|
||||
while (n !== null) {
|
||||
/**
|
||||
* @type {Item | null}
|
||||
*/
|
||||
const nxt = n.next
|
||||
if (nxt !== null) {
|
||||
n = nxt
|
||||
break
|
||||
} else if (n.parent === this._root) {
|
||||
n = null
|
||||
} else {
|
||||
n = /** @type {AbstractType<any>} */ (n.parent)._item
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (n !== null && (n.deleted || !this._filter(/** @type {ContentType} */ (n.content).type)))
|
||||
}
|
||||
this._firstCall = false
|
||||
if (n === null) {
|
||||
// @ts-ignore
|
||||
return { value: undefined, done: true }
|
||||
}
|
||||
this._currentNode = n
|
||||
return { value: /** @type {any} */ (n.content).type, done: false }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
|
||||
* A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
|
||||
@@ -132,7 +51,9 @@ export class YXmlTreeWalker {
|
||||
* element - in this case the attributes and the nodeName are not shared.
|
||||
*
|
||||
* @public
|
||||
* @extends AbstractType<YXmlEvent>
|
||||
* @template {any} [Children=any]
|
||||
* @template {{[K in string]:any}} [Attrs={}]
|
||||
* @extends AbstractType<delta.Delta<any,Attrs,Children,any>>
|
||||
*/
|
||||
export class YXmlFragment extends AbstractType {
|
||||
constructor () {
|
||||
@@ -159,7 +80,7 @@ export class YXmlFragment extends AbstractType {
|
||||
* * Observer functions are fired
|
||||
*
|
||||
* @param {Doc} y The Yjs instance
|
||||
* @param {Item} item
|
||||
* @param {Item?} item
|
||||
*/
|
||||
_integrate (y, item) {
|
||||
super._integrate(y, item)
|
||||
@@ -167,20 +88,15 @@ export class YXmlFragment extends AbstractType {
|
||||
this._prelimContent = null
|
||||
}
|
||||
|
||||
_copy () {
|
||||
return new YXmlFragment()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of this data type that can be included somewhere else.
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {YXmlFragment}
|
||||
* @return {this}
|
||||
*/
|
||||
clone () {
|
||||
const el = new YXmlFragment()
|
||||
// @ts-ignore
|
||||
const el = this._copy()
|
||||
el.insert(0, this.toArray().map(item => item instanceof AbstractType ? item.clone() : item))
|
||||
return el
|
||||
}
|
||||
@@ -190,71 +106,6 @@ export class YXmlFragment extends AbstractType {
|
||||
return this._prelimContent === null ? this._length : this._prelimContent.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subtree of childNodes.
|
||||
*
|
||||
* @example
|
||||
* const walker = elem.createTreeWalker(dom => dom.nodeName === 'div')
|
||||
* for (let node in walker) {
|
||||
* // `node` is a div node
|
||||
* nop(node)
|
||||
* }
|
||||
*
|
||||
* @param {function(AbstractType<any>):boolean} filter Function that is called on each child element and
|
||||
* returns a Boolean indicating whether the child
|
||||
* is to be included in the subtree.
|
||||
* @return {YXmlTreeWalker} A subtree and a position within it.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
createTreeWalker (filter) {
|
||||
return new YXmlTreeWalker(this, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first YXmlElement that matches the query.
|
||||
* Similar to DOM's {@link querySelector}.
|
||||
*
|
||||
* Query support:
|
||||
* - tagname
|
||||
* TODO:
|
||||
* - id
|
||||
* - attribute
|
||||
*
|
||||
* @param {CSS_Selector} query The query on the children.
|
||||
* @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
querySelector (query) {
|
||||
query = query.toUpperCase()
|
||||
// @ts-ignore
|
||||
const iterator = new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query)
|
||||
const next = iterator.next()
|
||||
if (next.done) {
|
||||
return null
|
||||
} else {
|
||||
return next.value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all YXmlElements that match the query.
|
||||
* Similar to Dom's {@link querySelectorAll}.
|
||||
*
|
||||
* @todo Does not yet support all queries. Currently only query by tagName.
|
||||
*
|
||||
* @param {CSS_Selector} query The query on the children
|
||||
* @return {Array<YXmlElement|YXmlText|YXmlHook|null>} The elements that match this query.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
querySelectorAll (query) {
|
||||
query = query.toUpperCase()
|
||||
// @ts-ignore
|
||||
return array.from(new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates YXmlEvent and calls observers.
|
||||
*
|
||||
@@ -262,7 +113,7 @@ export class YXmlFragment extends AbstractType {
|
||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||
*/
|
||||
_callObserver (transaction, parentSubs) {
|
||||
callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction))
|
||||
callTypeObservers(this, transaction, new YEvent(this, transaction, parentSubs))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,32 +132,6 @@ export class YXmlFragment extends AbstractType {
|
||||
return this.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Dom Element that mirrors this YXmlElement.
|
||||
*
|
||||
* @param {Document} [_document=document] The document object (you must define
|
||||
* this when calling this method in
|
||||
* nodejs)
|
||||
* @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
|
||||
* are presented in the DOM
|
||||
* @param {any} [binding] You should not set this property. This is
|
||||
* used if DomBinding wants to create a
|
||||
* association to the created DOM type.
|
||||
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toDOM (_document = document, hooks = {}, binding) {
|
||||
const fragment = _document.createDocumentFragment()
|
||||
if (binding !== undefined) {
|
||||
binding._createAssociation(fragment, this)
|
||||
}
|
||||
typeListForEach(this, xmlType => {
|
||||
fragment.insertBefore(xmlType.toDOM(_document, hooks, binding), null)
|
||||
})
|
||||
return fragment
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new content at an index.
|
||||
*
|
||||
@@ -315,7 +140,7 @@ export class YXmlFragment extends AbstractType {
|
||||
* xml.insert(0, [new Y.XmlText('text')])
|
||||
*
|
||||
* @param {number} index The index to insert content at
|
||||
* @param {Array<YXmlElement|YXmlText>} content The array of content
|
||||
* @param {Array<YXmlElement|YXmlText|YXmlHook>} content The array of content
|
||||
*/
|
||||
insert (index, content) {
|
||||
if (this.doc !== null) {
|
||||
@@ -380,36 +205,6 @@ export class YXmlFragment extends AbstractType {
|
||||
return typeListToArray(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the attributed content using the attribution manager.
|
||||
*
|
||||
* @param {import('../internals.js').AbstractAttributionManager} am
|
||||
* @return {{ children: import('../utils/Delta.js').ArrayDeltaBuilderBuilder<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').ArrayDeltaBuilderBuilder<Array<import('./AbstractType.js').YXmlDeepContent>> }}
|
||||
*/
|
||||
getContentDeep (am) {
|
||||
const { children: origChildren } = this.getContent(am)
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').ArrayDeltaBuilderBuilder<Array<import('./AbstractType.js').YXmlDeepContent>>}
|
||||
*/
|
||||
const children = origChildren.map(d => /** @type {any} */ (
|
||||
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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content to this YArray.
|
||||
*
|
||||
@@ -474,7 +269,7 @@ export class YXmlFragment extends AbstractType {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {YXmlFragment}
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
|
||||
@@ -22,10 +22,10 @@ export class YXmlHook extends YMap {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Item with the same effect as this Item (without position effect)
|
||||
* @return {this}
|
||||
*/
|
||||
_copy () {
|
||||
return new YXmlHook(this.hookName)
|
||||
return /** @type {this} */ (new YXmlHook(this.hookName))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,46 +33,16 @@ export class YXmlHook extends YMap {
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {YXmlHook}
|
||||
* @return {this}
|
||||
*/
|
||||
clone () {
|
||||
const el = new YXmlHook(this.hookName)
|
||||
const el = this._copy()
|
||||
this.forEach((value, key) => {
|
||||
el.set(key, value)
|
||||
})
|
||||
return el
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Dom Element that mirrors this YXmlElement.
|
||||
*
|
||||
* @param {Document} [_document=document] The document object (you must define
|
||||
* this when calling this method in
|
||||
* nodejs)
|
||||
* @param {Object.<string, any>} [hooks] Optional property to customize how hooks
|
||||
* are presented in the DOM
|
||||
* @param {any} [binding] You should not set this property. This is
|
||||
* used if DomBinding wants to create a
|
||||
* association to the created DOM type
|
||||
* @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toDOM (_document = document, hooks = {}, binding) {
|
||||
const hook = hooks[this.hookName]
|
||||
let dom
|
||||
if (hook !== undefined) {
|
||||
dom = hook.createDom(this)
|
||||
} else {
|
||||
dom = document.createElement(this.hookName)
|
||||
}
|
||||
dom.setAttribute('data-yjs-hook', this.hookName)
|
||||
if (binding !== undefined) {
|
||||
binding._createAssociation(dom, this)
|
||||
}
|
||||
return dom
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the properties of this type to binary and write it to an
|
||||
* BinaryEncoder.
|
||||
@@ -89,7 +59,7 @@ export class YXmlHook extends YMap {
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||
* @return {YXmlHook}
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
ContentType, YXmlElement, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as delta from '../utils/Delta.js'
|
||||
|
||||
/**
|
||||
* @todo can we deprecate this?
|
||||
*
|
||||
* Represents text in a Dom Element. In the future this type will also handle
|
||||
* simple formatting information like bold and italic.
|
||||
* @extends YText
|
||||
*/
|
||||
export class YXmlText extends YText {
|
||||
/**
|
||||
@@ -27,82 +28,19 @@ export class YXmlText extends YText {
|
||||
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
|
||||
}
|
||||
|
||||
_copy () {
|
||||
return new YXmlText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of this data type that can be included somewhere else.
|
||||
*
|
||||
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
||||
*
|
||||
* @return {YXmlText}
|
||||
* @return {this}
|
||||
*/
|
||||
clone () {
|
||||
const text = new YXmlText()
|
||||
const text = /** @type {this} */ (this._copy())
|
||||
text.applyDelta(this.getContent())
|
||||
return text
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Dom Element that mirrors this YXmlText.
|
||||
*
|
||||
* @param {Document} [_document=document] The document object (you must define
|
||||
* this when calling this method in
|
||||
* nodejs)
|
||||
* @param {Object<string, any>} [hooks] Optional property to customize how hooks
|
||||
* are presented in the DOM
|
||||
* @param {any} [binding] You should not set this property. This is
|
||||
* used if DomBinding wants to create a
|
||||
* association to the created DOM type.
|
||||
* @return {Text} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
toDOM (_document = document, hooks, binding) {
|
||||
const dom = _document.createTextNode(this.toString())
|
||||
if (binding !== undefined) {
|
||||
binding._createAssociation(dom, this)
|
||||
}
|
||||
return dom
|
||||
}
|
||||
|
||||
toString () {
|
||||
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 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 += dop.insert
|
||||
for (let i = nestedNodes.length - 1; i >= 0; i--) {
|
||||
str += `</${nestedNodes[i].nodeName}>`
|
||||
}
|
||||
return str
|
||||
}
|
||||
return ''
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
@@ -119,10 +57,10 @@ export class YXmlText extends YText {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||
* @return {YXmlText}
|
||||
* @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
|
||||
* @return {import('../utils/types.js').YType}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const readYXmlText = decoder => new YXmlText()
|
||||
export const readYXmlText = _decoder => new YXmlText()
|
||||
|
||||
@@ -33,13 +33,13 @@ import { ObservableV2 } from 'lib0/observable'
|
||||
import * as encoding from 'lib0/encoding'
|
||||
import * as s from 'lib0/schema'
|
||||
|
||||
export const attributionJsonSchema = s.object({
|
||||
insert: s.array(s.string).optional,
|
||||
insertedAt: s.number.optional,
|
||||
delete: s.array(s.string).optional,
|
||||
deletedAt: s.number.optional,
|
||||
attributes: s.record(s.string, s.array(s.string)).optional,
|
||||
attributedAt: s.number.optional
|
||||
export const attributionJsonSchema = s.$object({
|
||||
insert: s.$array(s.$string).optional,
|
||||
insertedAt: s.$number.optional,
|
||||
delete: s.$array(s.$string).optional,
|
||||
deletedAt: s.$number.optional,
|
||||
attributes: s.$record(s.$string, s.$array(s.$string)).optional,
|
||||
attributedAt: s.$number.optional
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => {
|
||||
*/
|
||||
const attribution = {}
|
||||
if (deleted) {
|
||||
attribution.delete = s.array(s.string).ensure([])
|
||||
attribution.delete = s.$array(s.$string).cast([])
|
||||
} else {
|
||||
attribution.insert = []
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export const createAttributionFromAttributionItems = (attrs, deleted) => {
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'insert':
|
||||
case 'delete': {
|
||||
const as = /** @type {import('../utils/Delta.js').Attribution_} */ (attribution)
|
||||
const as = /** @type {import('lib0/delta').Attribution} */ (attribution)
|
||||
const ls = as[attr.name] = as[attr.name] ?? []
|
||||
ls.push(attr.val)
|
||||
break
|
||||
|
||||
1007
src/utils/Delta.js
1007
src/utils/Delta.js
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import {
|
||||
YXmlFragment,
|
||||
transact,
|
||||
applyUpdate,
|
||||
ContentDoc, Item, Transaction, YEvent, // eslint-disable-line
|
||||
ContentDoc, Item, Transaction, // eslint-disable-line
|
||||
encodeStateAsUpdate
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -24,6 +24,13 @@ import * as promise from 'lib0/promise'
|
||||
|
||||
export const generateNewClientId = random.uint32
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YTypeConstructors} YTypeConstructors
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('../utils/types.js').YType} YType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DocOpts
|
||||
* @property {boolean} [DocOpts.gc=true] Disable garbage collection (default: gc=true)
|
||||
@@ -72,7 +79,7 @@ export class Doc extends ObservableV2 {
|
||||
this.isSuggestionDoc = isSuggestionDoc
|
||||
this.cleanupFormatting = !isSuggestionDoc
|
||||
/**
|
||||
* @type {Map<string, AbstractType<YEvent<any>>>}
|
||||
* @type {Map<string, YType>}
|
||||
*/
|
||||
this.share = new Map()
|
||||
this.store = new StructStore()
|
||||
@@ -205,7 +212,7 @@ export class Doc extends ObservableV2 {
|
||||
* Define all types right after the Y.Doc instance is created and store them in a separate object.
|
||||
* Also use the typed methods `getText(name)`, `getArray(name)`, ..
|
||||
*
|
||||
* @template {typeof AbstractType<any>} Type
|
||||
* @template {YTypeConstructors} TypeC
|
||||
* @example
|
||||
* const ydoc = new Y.Doc(..)
|
||||
* const appState = {
|
||||
@@ -214,8 +221,8 @@ export class Doc extends ObservableV2 {
|
||||
* }
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Type} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ...
|
||||
* @return {InstanceType<Type>} The created type. Constructed with TypeConstructor
|
||||
* @param {TypeC} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ...
|
||||
* @return {InstanceType<TypeC>} The created type. Constructed with TypeConstructor
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -227,6 +234,7 @@ export class Doc extends ObservableV2 {
|
||||
return t
|
||||
})
|
||||
const Constr = type.constructor
|
||||
// @ts-ignore
|
||||
if (TypeConstructor !== AbstractType && Constr !== TypeConstructor) {
|
||||
if (Constr === AbstractType) {
|
||||
// @ts-ignore
|
||||
@@ -245,12 +253,12 @@ export class Doc extends ObservableV2 {
|
||||
t._length = type._length
|
||||
this.share.set(name, t)
|
||||
t._integrate(this, null)
|
||||
return /** @type {InstanceType<Type>} */ (t)
|
||||
return /** @type {InstanceType<TypeC>} */ (t)
|
||||
} else {
|
||||
throw new Error(`Type with the name ${name} has already been defined with a different constructor`)
|
||||
}
|
||||
}
|
||||
return /** @type {InstanceType<Type>} */ (type)
|
||||
return /** @type {InstanceType<TypeC>} */ (type)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,7 +269,7 @@ export class Doc extends ObservableV2 {
|
||||
* @public
|
||||
*/
|
||||
getArray (name = '') {
|
||||
return /** @type {YArray<T>} */ (this.get(name, YArray))
|
||||
return /** @type {YArray<any>} */ (this.get(name, YArray))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import {
|
||||
YArray,
|
||||
YMap,
|
||||
readIdSet,
|
||||
writeIdSet,
|
||||
createIdSet,
|
||||
mergeIdSets,
|
||||
IdSetEncoderV1, DSDecoderV1, ID, IdSet, YArrayEvent, Transaction, Doc // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as decoding from 'lib0/decoding'
|
||||
|
||||
export class PermanentUserData {
|
||||
/**
|
||||
* @param {Doc} doc
|
||||
* @param {YMap<any>} [storeType]
|
||||
*/
|
||||
constructor (doc, storeType = doc.getMap('users')) {
|
||||
/**
|
||||
* @type {Map<string,IdSet>}
|
||||
*/
|
||||
const dss = new Map()
|
||||
this.yusers = storeType
|
||||
this.doc = doc
|
||||
/**
|
||||
* Maps from clientid to userDescription
|
||||
*
|
||||
* @type {Map<number,string>}
|
||||
*/
|
||||
this.clients = new Map()
|
||||
this.dss = dss
|
||||
/**
|
||||
* @param {YMap<any>} user
|
||||
* @param {string} userDescription
|
||||
*/
|
||||
const initUser = (user, userDescription) => {
|
||||
/**
|
||||
* @type {YArray<Uint8Array>}
|
||||
*/
|
||||
const ds = user.get('ds')
|
||||
const ids = user.get('ids')
|
||||
const addClientId = /** @param {number} clientid */ clientid => this.clients.set(clientid, userDescription)
|
||||
ds.observe(/** @param {YArrayEvent<any>} event */ event => {
|
||||
event.changes.added.forEach(item => {
|
||||
item.content.getContent().forEach(encodedDs => {
|
||||
if (encodedDs instanceof Uint8Array) {
|
||||
this.dss.set(userDescription, mergeIdSets([this.dss.get(userDescription) || createIdSet(), readIdSet(new DSDecoderV1(decoding.createDecoder(encodedDs)))]))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
this.dss.set(userDescription, mergeIdSets(ds.map(encodedDs => readIdSet(new DSDecoderV1(decoding.createDecoder(encodedDs))))))
|
||||
ids.observe(/** @param {YArrayEvent<any>} event */ event =>
|
||||
event.changes.added.forEach(item => item.content.getContent().forEach(addClientId))
|
||||
)
|
||||
ids.forEach(addClientId)
|
||||
}
|
||||
// observe users
|
||||
storeType.observe(event => {
|
||||
event.keysChanged.forEach(userDescription =>
|
||||
initUser(storeType.get(userDescription), userDescription)
|
||||
)
|
||||
})
|
||||
// add initial data
|
||||
storeType.forEach(initUser)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Doc} doc
|
||||
* @param {number} clientid
|
||||
* @param {string} userDescription
|
||||
* @param {Object} conf
|
||||
* @param {function(Transaction, IdSet):boolean} [conf.filter]
|
||||
*/
|
||||
setUserMapping (doc, clientid, userDescription, { filter = () => true } = {}) {
|
||||
const users = this.yusers
|
||||
let user = users.get(userDescription)
|
||||
if (!user) {
|
||||
user = new YMap()
|
||||
user.set('ids', new YArray())
|
||||
user.set('ds', new YArray())
|
||||
users.set(userDescription, user)
|
||||
}
|
||||
user.get('ids').push([clientid])
|
||||
users.observe(_event => {
|
||||
setTimeout(() => {
|
||||
const userOverwrite = users.get(userDescription)
|
||||
if (userOverwrite !== user) {
|
||||
// user was overwritten, port all data over to the next user object
|
||||
// @todo Experiment with Y.Sets here
|
||||
user = userOverwrite
|
||||
// @todo iterate over old type
|
||||
this.clients.forEach((_userDescription, clientid) => {
|
||||
if (userDescription === _userDescription) {
|
||||
user.get('ids').push([clientid])
|
||||
}
|
||||
})
|
||||
const encoder = new IdSetEncoderV1()
|
||||
const ds = this.dss.get(userDescription)
|
||||
if (ds) {
|
||||
writeIdSet(encoder, ds)
|
||||
user.get('ds').push([encoder.toUint8Array()])
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
|
||||
setTimeout(() => {
|
||||
const yds = user.get('ds')
|
||||
const ds = transaction.deleteSet
|
||||
if (transaction.local && ds.clients.size > 0 && filter(transaction, ds)) {
|
||||
const encoder = new IdSetEncoderV1()
|
||||
writeIdSet(encoder, ds)
|
||||
yds.push([encoder.toUint8Array()])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} clientid
|
||||
* @return {any}
|
||||
*/
|
||||
getUserByClientId (clientid) {
|
||||
return this.clients.get(clientid) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ID} id
|
||||
* @return {string | null}
|
||||
*/
|
||||
getUserByDeletedId (id) {
|
||||
for (const [userDescription, ds] of this.dss.entries()) {
|
||||
if (ds.hasId(id)) {
|
||||
return userDescription
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export const createRelativePosition = (type, item, assoc) => {
|
||||
/**
|
||||
* Create a relativePosition based on a absolute position.
|
||||
*
|
||||
* @param {AbstractType<any>} type The base type (e.g. YText or YArray).
|
||||
* @param {AbstractType} type The base type (e.g. YText or YArray).
|
||||
* @param {number} index The absolute position.
|
||||
* @param {number} [assoc]
|
||||
* @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
|
||||
|
||||
@@ -84,13 +84,13 @@ export class Transaction {
|
||||
* All types that were directly modified (property added or child
|
||||
* inserted/deleted). New types are not included in this Set.
|
||||
* Maps from type to parentSubs (`item.parentSub = null` for YArray)
|
||||
* @type {Map<AbstractType<YEvent<any>>,Set<String|null>>}
|
||||
* @type {Map<import('../utils/types.js').YType,Set<String|null>>}
|
||||
*/
|
||||
this.changed = new Map()
|
||||
/**
|
||||
* Stores the events for the types that observe also child elements.
|
||||
* It is mainly used by `observeDeep`.
|
||||
* @type {Map<AbstractType<YEvent<any>>,Array<YEvent<any>>>}
|
||||
* @type {Map<import('../utils/types.js').YType,Array<YEvent<any>>>}
|
||||
*/
|
||||
this.changedParentTypes = new Map()
|
||||
/**
|
||||
@@ -198,7 +198,7 @@ export const nextID = transaction => {
|
||||
* did not change, it was just added and we should not fire events for `type`.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<YEvent<any>>} type
|
||||
* @param {import('../utils/types.js').YType} type
|
||||
* @param {string|null} parentSub
|
||||
*/
|
||||
export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
|
||||
|
||||
@@ -37,7 +37,7 @@ export class StackItem {
|
||||
*/
|
||||
const clearUndoManagerStackItem = (tr, um, stackItem) => {
|
||||
iterateStructsByIdSet(tr, stackItem.deletions, item => {
|
||||
if (item instanceof Item && um.scope.some(type => type === tr.doc || isParentOf(/** @type {AbstractType<any>} */ (type), item))) {
|
||||
if (item instanceof Item && um.scope.some(type => type === tr.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), item))) {
|
||||
keepItem(item, false)
|
||||
}
|
||||
})
|
||||
@@ -79,7 +79,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
}
|
||||
struct = item
|
||||
}
|
||||
if (!struct.deleted && scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), /** @type {Item} */ (struct)))) {
|
||||
if (!struct.deleted && scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), /** @type {Item} */ (struct)))) {
|
||||
itemsToDelete.push(struct)
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
iterateStructsByIdSet(transaction, stackItem.deletions, struct => {
|
||||
if (
|
||||
struct instanceof Item &&
|
||||
scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), struct)) &&
|
||||
scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), struct)) &&
|
||||
// Never redo structs in stackItem.insertions because they were created and deleted in the same capture interval.
|
||||
!stackItem.insertions.hasId(struct.id)
|
||||
) {
|
||||
@@ -143,7 +143,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
* @property {StackItem} StackItemEvent.stackItem
|
||||
* @property {any} StackItemEvent.origin
|
||||
* @property {'undo'|'redo'} StackItemEvent.type
|
||||
* @property {Map<AbstractType<YEvent<any>>,Array<YEvent<any>>>} StackItemEvent.changedParentTypes
|
||||
* @property {Map<import('../utils/types.js').YType,Array<YEvent<any>>>} StackItemEvent.changedParentTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -157,7 +157,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
*/
|
||||
export class UndoManager extends ObservableV2 {
|
||||
/**
|
||||
* @param {Doc|AbstractType<any>|Array<AbstractType<any>>} typeScope Limits the scope of the UndoManager. If this is set to a ydoc instance, all changes on that ydoc will be undone. If set to a specific type, only changes on that type or its children will be undone. Also accepts an array of types.
|
||||
* @param {Doc|import('../utils/types.js').YType|Array<import('../utils/types.js').YType>} typeScope Limits the scope of the UndoManager. If this is set to a ydoc instance, all changes on that ydoc will be undone. If set to a specific type, only changes on that type or its children will be undone. Also accepts an array of types.
|
||||
* @param {UndoManagerOptions} options
|
||||
*/
|
||||
constructor (typeScope, {
|
||||
@@ -170,7 +170,7 @@ export class UndoManager extends ObservableV2 {
|
||||
} = {}) {
|
||||
super()
|
||||
/**
|
||||
* @type {Array<AbstractType<any> | Doc>}
|
||||
* @type {Array<import('../utils/types.js').YType | Doc>}
|
||||
*/
|
||||
this.scope = []
|
||||
this.doc = doc
|
||||
@@ -210,7 +210,7 @@ export class UndoManager extends ObservableV2 {
|
||||
// Only track certain transactions
|
||||
if (
|
||||
!this.captureTransaction(transaction) ||
|
||||
!this.scope.some(type => transaction.changedParentTypes.has(/** @type {AbstractType<any>} */ (type)) || type === this.doc) ||
|
||||
!this.scope.some(type => transaction.changedParentTypes.has(/** @type {import('../utils/types.js').YType} */ (type)) || type === this.doc) ||
|
||||
(!this.trackedOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedOrigins.has(transaction.origin.constructor)))
|
||||
) {
|
||||
return
|
||||
@@ -242,7 +242,7 @@ export class UndoManager extends ObservableV2 {
|
||||
}
|
||||
// make sure that deleted structs are not gc'd
|
||||
iterateStructsByIdSet(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => {
|
||||
if (item instanceof Item && this.scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), item))) {
|
||||
if (item instanceof Item && this.scope.some(type => type === transaction.doc || isParentOf(/** @type {import('../utils/types.js').YType} */ (type), item))) {
|
||||
keepItem(item, true)
|
||||
}
|
||||
})
|
||||
@@ -265,7 +265,7 @@ export class UndoManager extends ObservableV2 {
|
||||
/**
|
||||
* Extend the scope.
|
||||
*
|
||||
* @param {Array<AbstractType<any> | Doc> | AbstractType<any> | Doc} ytypes
|
||||
* @param {Array<import('../utils/types.js').YType | Doc> | import('../utils/types.js').YType | Doc} ytypes
|
||||
*/
|
||||
addToScope (ytypes) {
|
||||
const tmpSet = new Set(this.scope)
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
import {
|
||||
TextDeltaBuilder, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||
diffIdSet,
|
||||
mergeIdSets,
|
||||
noAttributionsManager,
|
||||
AbstractAttributionManager, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as set from 'lib0/set'
|
||||
import * as array from 'lib0/array'
|
||||
import * as error from 'lib0/error'
|
||||
import * as delta from 'lib0/delta' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* @typedef {import('./types.js').YType} _YType
|
||||
*/
|
||||
|
||||
const errorComputeChanges = 'You must not compute changes after the event-handler fired.'
|
||||
|
||||
/**
|
||||
* @template {AbstractType<any>} T
|
||||
* @template {_YType} Target
|
||||
* YEvent describes the changes on a YType.
|
||||
*/
|
||||
export class YEvent {
|
||||
/**
|
||||
* @param {T} target The changed type.
|
||||
* @param {Target} target The changed type.
|
||||
* @param {Transaction} transaction
|
||||
* @param {Set<any>?} subs The keys that changed
|
||||
*/
|
||||
constructor (target, transaction) {
|
||||
constructor (target, transaction, subs) {
|
||||
/**
|
||||
* The type on which this event was created on.
|
||||
* @type {T}
|
||||
* @type {Target}
|
||||
*/
|
||||
this.target = target
|
||||
/**
|
||||
* The current target on which the observe callback is called.
|
||||
* @type {AbstractType<any>}
|
||||
* @type {_YType}
|
||||
*/
|
||||
this.currentTarget = target
|
||||
/**
|
||||
@@ -42,13 +50,31 @@ export class YEvent {
|
||||
*/
|
||||
this._keys = null
|
||||
/**
|
||||
* @type {import('./Delta.js').TextDelta<any,undefined>?}
|
||||
* @type {(Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>)|null}
|
||||
*/
|
||||
this._delta = null
|
||||
/**
|
||||
* @type {Array<string|number>|null}
|
||||
*/
|
||||
this._path = null
|
||||
/**
|
||||
* Whether the children changed.
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.childListChanged = false
|
||||
/**
|
||||
* Set of all changed attributes.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
this.keysChanged = new Set()
|
||||
subs?.forEach((sub) => {
|
||||
if (sub === null) {
|
||||
this.childListChanged = true
|
||||
} else {
|
||||
this.keysChanged.add(sub)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +116,7 @@ export class YEvent {
|
||||
}
|
||||
const keys = new Map()
|
||||
const target = this.target
|
||||
// @ts-ignore
|
||||
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||
changed.forEach(key => {
|
||||
if (key !== null) {
|
||||
@@ -136,18 +163,6 @@ export class YEvent {
|
||||
return this._keys
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a computed property. Note that this can only be safely computed during the
|
||||
* event call. Computing this property after other changes happened might result in
|
||||
* 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 {import('./Delta.js').Delta}
|
||||
*/
|
||||
get delta () {
|
||||
return this.changes.delta
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a struct is added by this event.
|
||||
*
|
||||
@@ -161,77 +176,25 @@ export class YEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a computed property. Note that this can only be safely computed during the
|
||||
* event call. Computing this property after other changes happened might result in
|
||||
* 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.
|
||||
* @param {AbstractAttributionManager} am
|
||||
* @return {Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any>} The Delta representation of this type.
|
||||
*
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:import('./Delta.js').Delta}}
|
||||
* @public
|
||||
*/
|
||||
get changes () {
|
||||
let changes = this._changes
|
||||
if (changes === null) {
|
||||
if (this.transaction.doc._transactionCleanups.length === 0) {
|
||||
throw error.create(errorComputeChanges)
|
||||
}
|
||||
const target = this.target
|
||||
const added = set.create()
|
||||
const deleted = set.create()
|
||||
/**
|
||||
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
||||
*/
|
||||
const delta = []
|
||||
changes = {
|
||||
added,
|
||||
deleted,
|
||||
delta,
|
||||
keys: this.keys
|
||||
}
|
||||
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||
if (changed.has(null)) {
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
let lastOp = null
|
||||
const packOp = () => {
|
||||
if (lastOp) {
|
||||
delta.push(lastOp)
|
||||
}
|
||||
}
|
||||
for (let item = target._start; item !== null; item = item.right) {
|
||||
if (item.deleted) {
|
||||
if (this.deletes(item) && !this.adds(item)) {
|
||||
if (lastOp === null || lastOp.delete === undefined) {
|
||||
packOp()
|
||||
lastOp = { delete: 0 }
|
||||
}
|
||||
lastOp.delete += item.length
|
||||
deleted.add(item)
|
||||
} // else nop
|
||||
} else {
|
||||
if (this.adds(item)) {
|
||||
if (lastOp === null || lastOp.insert === undefined) {
|
||||
packOp()
|
||||
lastOp = { insert: [] }
|
||||
}
|
||||
lastOp.insert = lastOp.insert.concat(item.content.getContent())
|
||||
added.add(item)
|
||||
} else {
|
||||
if (lastOp === null || lastOp.retain === undefined) {
|
||||
packOp()
|
||||
lastOp = { retain: 0 }
|
||||
}
|
||||
lastOp.retain += item.length
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastOp !== null && lastOp.retain === undefined) {
|
||||
packOp()
|
||||
}
|
||||
}
|
||||
this._changes = changes
|
||||
}
|
||||
return /** @type {any} */ (changes)
|
||||
getDelta (am = noAttributionsManager) {
|
||||
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
|
||||
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: this.childListChanged }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the changes in the delta format.
|
||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
||||
*
|
||||
* @type {Target extends AbstractType<infer D,any> ? D : delta.Delta<any,any,any,any,any>} The Delta representation of this type.
|
||||
* @public
|
||||
*/
|
||||
get delta () {
|
||||
return /** @type {any} */ (this._delta ?? (this._delta = this.getDelta()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +208,8 @@ export class YEvent {
|
||||
* console.log(path) // might look like => [2, 'key1']
|
||||
* child === type.get(path[0]).get(path[1])
|
||||
*
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {AbstractType<any>} child target
|
||||
* @param {_YType} parent
|
||||
* @param {_YType} child target
|
||||
* @return {Array<string|number>} Path to the target
|
||||
*
|
||||
* @private
|
||||
@@ -261,7 +224,7 @@ const getPathTo = (parent, child) => {
|
||||
} else {
|
||||
// parent is array-ish
|
||||
let i = 0
|
||||
let c = /** @type {AbstractType<any>} */ (child._item.parent)._start
|
||||
let c = /** @type {import('../utils/types.js').YType} */ (child._item.parent)._start
|
||||
while (c !== child._item && c !== null) {
|
||||
if (!c.deleted && c.countable) {
|
||||
i += c.length
|
||||
@@ -270,7 +233,7 @@ const getPathTo = (parent, child) => {
|
||||
}
|
||||
path.unshift(i)
|
||||
}
|
||||
child = /** @type {AbstractType<any>} */ (child._item.parent)
|
||||
child = /** @type {_YType} */ (child._item.parent)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -501,6 +501,9 @@ export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map())
|
||||
export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8Array([0]), encoder = new UpdateEncoderV2()) => {
|
||||
const targetStateVector = decodeStateVector(encodedTargetStateVector)
|
||||
writeStateAsUpdate(encoder, doc, targetStateVector)
|
||||
/**
|
||||
* @type {Uint8Array<ArrayBufferLike>[]}
|
||||
*/
|
||||
const updates = [encoder.toUint8Array()]
|
||||
// also add the pending updates (if there are any)
|
||||
if (doc.store.pendingDs) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AbstractType, Item } from '../internals.js' // eslint-disable-line
|
||||
/**
|
||||
* Check if `parent` is a parent of `child`.
|
||||
*
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {import('../utils/types.js').YType} parent
|
||||
* @param {Item|null} child
|
||||
* @return {Boolean} Whether `parent` is a parent of `child`.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,27 @@
|
||||
* |import('../index.js').Map<any>
|
||||
* |import('../index.js').Text<any>
|
||||
* |import('../index.js').XmlElement<any>
|
||||
* |import('../index.js').XmlFragment
|
||||
* |import('../index.js').XmlFragment<any>
|
||||
* |import('../index.js').XmlText
|
||||
* |import('../index.js').XmlHook} YValue
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/YArray.js').YArray<any>
|
||||
* | import('../types/YMap.js').YMap<any>
|
||||
* | import('../types/YText.js').YText<any>
|
||||
* | import('../types/YXmlFragment.js').YXmlFragment<any,any>
|
||||
* | import('../types/YXmlElement.js').YXmlElement<any,any>
|
||||
* | import('../types/YXmlHook.js').YXmlHook
|
||||
* | import('../types/YXmlText.js').YXmlText} YType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {typeof import('../types/YArray.js').YArray<any>
|
||||
* | typeof import('../types/YMap.js').YMap<any>
|
||||
* | typeof import('../types/YText.js').YText<any>
|
||||
* | typeof import('../types/YXmlFragment.js').YXmlFragment<any,any>
|
||||
* | typeof import('../types/YXmlElement.js').YXmlElement<any,any>
|
||||
* | typeof import('../types/YXmlHook.js').YXmlHook
|
||||
* | typeof import('../types/YXmlText.js').YXmlText} YTypeConstructors
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import * as Y from '../src/index.js'
|
||||
import * as t from 'lib0/testing'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
import * as delta from 'lib0/delta'
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
@@ -40,11 +40,11 @@ export const testAttributedEvents = _tc => {
|
||||
})
|
||||
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
|
||||
const c1 = ytext.getContent(am)
|
||||
t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] }))
|
||||
t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] }))
|
||||
let calledObserver = false
|
||||
ytext.observe(event => {
|
||||
const d = event.getDelta(am)
|
||||
t.compare(d, delta.createTextDelta().retain(11).insert('!', null, { insert: [] }))
|
||||
t.compare(d, delta.text().retain(11).insert('!', null, { insert: [] }))
|
||||
calledObserver = true
|
||||
})
|
||||
ytext.insert(11, '!')
|
||||
@@ -64,8 +64,8 @@ export const testInsertionsMindingAttributedContent = _tc => {
|
||||
})
|
||||
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
|
||||
const c1 = ytext.getContent(am)
|
||||
t.compare(c1, delta.createTextDelta().insert('hello ').insert('world', null, { delete: [] }))
|
||||
ytext.applyDelta(delta.createTextDelta().retain(11).insert('content'), am)
|
||||
t.compare(c1, delta.text().insert('hello ').insert('world', null, { delete: [] }))
|
||||
ytext.applyDelta(delta.text().retain(11).insert('content'), am)
|
||||
t.assert(ytext.toString() === 'hello content')
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export const testInsertionsIntoAttributedContent = _tc => {
|
||||
})
|
||||
const am = Y.createAttributionManagerFromDiff(v1, ydoc)
|
||||
const c1 = ytext.getContent(am)
|
||||
t.compare(c1, delta.createTextDelta().insert('hello ').insert('word', null, { insert: [] }))
|
||||
ytext.applyDelta(delta.createTextDelta().retain(9).insert('l'), am)
|
||||
t.compare(c1, delta.text().insert('hello ').insert('word', null, { insert: [] }))
|
||||
ytext.applyDelta(delta.text().retain(9).insert('l'), am)
|
||||
t.assert(ytext.toString() === 'hello world')
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,227 +0,0 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
import * as Y from 'yjs'
|
||||
import * as schema from 'lib0/schema'
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
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(), [{ insert: 'hello ' }, { insert: 'world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { insert: ['tester'] } }])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testDeltaMerging = _tc => {
|
||||
const d = delta.createTextDelta()
|
||||
.insert('hello')
|
||||
.insert('world')
|
||||
.insert(' ', { italic: true })
|
||||
.insert({})
|
||||
.insert([1])
|
||||
.insert([2])
|
||||
.done()
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testMapDelta = _tc => {
|
||||
const d = /** @type {delta.MapDeltaBuilder<{ key: string, v: number, over: string }>} */ (delta.createMapDelta())
|
||||
d.set('key', 'value')
|
||||
.useAttribution({ delete: ['me'] })
|
||||
.delete('v', 94)
|
||||
.useAttribution(null)
|
||||
.set('over', 'andout', 'i existed before')
|
||||
.done()
|
||||
t.compare(d.toJSON(), {
|
||||
key: { type: 'insert', value: 'value', prevValue: undefined, attribution: null },
|
||||
v: { type: 'delete', prevValue: 94, attribution: { delete: ['me'] } },
|
||||
over: { type: 'insert', value: 'andout', prevValue: 'i existed before', attribution: null }
|
||||
})
|
||||
t.compare(d.origin, null)
|
||||
t.compare(d.remote, false)
|
||||
t.compare(d.isDiff, true)
|
||||
d.forEach((change, key) => {
|
||||
if (key === 'v') {
|
||||
t.assert(d.get(key)?.prevValue === 94) // should know that value is number
|
||||
t.assert(change.prevValue === 94)
|
||||
} else if (key === 'key') {
|
||||
t.assert(d.get(key)?.value === 'value') // show know that value is a string
|
||||
t.assert(change.value === 'value')
|
||||
} else if (key === 'over') {
|
||||
t.assert(change.value === 'andout')
|
||||
} else {
|
||||
throw new Error()
|
||||
}
|
||||
})
|
||||
for (const [key, change] of d) {
|
||||
if (key === 'v') {
|
||||
t.assert(d.get(key)?.prevValue === 94)
|
||||
t.assert(change.prevValue === 94) // should know that value is number
|
||||
} else if (key === 'key') {
|
||||
t.assert(change.value === 'value') // should know that value is string
|
||||
} else if (key === 'over') {
|
||||
t.assert(change.value === 'andout')
|
||||
} else {
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testXmlDelta = _tc => {
|
||||
const d = /** @type {delta.XmlDelta<string, string, { a: 1 }>} */ (delta.createXmlDelta())
|
||||
d.children.insert(['hi'])
|
||||
d.attributes.set('a', 1)
|
||||
d.attributes.delete('a', 1)
|
||||
/**
|
||||
* @type {Array<Array<string>| number>}
|
||||
*/
|
||||
const arr = []
|
||||
d.children.forEach(
|
||||
(op, index) => {
|
||||
if (op instanceof delta.InsertArrayOp) {
|
||||
arr.push(op.insert, index)
|
||||
}
|
||||
},
|
||||
(op, index) => {
|
||||
arr.push(op.insert, index)
|
||||
},
|
||||
(op, _index) => {
|
||||
arr.push(op.retain)
|
||||
},
|
||||
(op, _index) => {
|
||||
arr.push(op.delete)
|
||||
}
|
||||
)
|
||||
t.compare(arr, [['hi'], 0, ['hi'], 0])
|
||||
const x = d.done()
|
||||
console.log(x)
|
||||
}
|
||||
|
||||
const textDeltaSchema = schema.object({
|
||||
ops: schema.array(
|
||||
schema.any
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testTextModifyingDelta = _tc => {
|
||||
const d = /** @type {delta.TextDelta<Y.Map<any>|Y.Array<any>,undefined>} */ (delta.createTextDelta().insert('hi').insert(new Y.Map()).done())
|
||||
schema.assert(d, textDeltaSchema)
|
||||
console.log(d)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testYtypeDeltaTypings = _tc => {
|
||||
const ydoc = new Y.Doc({ gc: false })
|
||||
{
|
||||
const yarray = /** @type {Y.Array<Y.Text|number>} */ (ydoc.getArray('numbers'))
|
||||
const content = yarray.getContent()
|
||||
content.forEach(
|
||||
op => {
|
||||
schema.union(
|
||||
schema.constructedBy(delta.InsertArrayOp),
|
||||
schema.constructedBy(delta.RetainOp),
|
||||
schema.constructedBy(delta.DeleteOp)
|
||||
).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.InsertArrayOp).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.RetainOp).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.DeleteOp).ensure(op)
|
||||
}
|
||||
)
|
||||
const cdeep = yarray.getContentDeep()
|
||||
cdeep.forEach(
|
||||
op => {
|
||||
schema.union(
|
||||
schema.constructedBy(delta.InsertArrayOp),
|
||||
schema.constructedBy(delta.RetainOp),
|
||||
schema.constructedBy(delta.DeleteOp),
|
||||
schema.constructedBy(delta.ModifyOp)
|
||||
).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.InsertArrayOp).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.RetainOp).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.DeleteOp).ensure(op)
|
||||
},
|
||||
op => {
|
||||
schema.constructedBy(delta.ModifyOp).ensure(op)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import * as promise from 'lib0/promise'
|
||||
|
||||
import {
|
||||
contentRefs,
|
||||
@@ -11,11 +10,7 @@ import {
|
||||
readContentType,
|
||||
readContentFormat,
|
||||
readContentAny,
|
||||
readContentDoc,
|
||||
Doc,
|
||||
PermanentUserData,
|
||||
encodeStateAsUpdate,
|
||||
applyUpdate
|
||||
readContentDoc
|
||||
} from '../src/internals.js'
|
||||
|
||||
import * as Y from '../src/index.js'
|
||||
@@ -37,34 +32,6 @@ export const testStructReferences = tc => {
|
||||
// contentRefs[10] is reserved for Skip structs
|
||||
}
|
||||
|
||||
/**
|
||||
* There is some custom encoding/decoding happening in PermanentUserData.
|
||||
* This is why it landed here.
|
||||
*
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testPermanentUserData = async tc => {
|
||||
const ydoc1 = new Doc()
|
||||
const ydoc2 = new Doc()
|
||||
const pd1 = new PermanentUserData(ydoc1)
|
||||
const pd2 = new PermanentUserData(ydoc2)
|
||||
pd1.setUserMapping(ydoc1, ydoc1.clientID, 'user a')
|
||||
pd2.setUserMapping(ydoc2, ydoc2.clientID, 'user b')
|
||||
ydoc1.getText().insert(0, 'xhi')
|
||||
ydoc1.getText().delete(0, 1)
|
||||
ydoc2.getText().insert(0, 'hxxi')
|
||||
ydoc2.getText().delete(1, 2)
|
||||
await promise.wait(10)
|
||||
applyUpdate(ydoc2, encodeStateAsUpdate(ydoc1))
|
||||
applyUpdate(ydoc1, encodeStateAsUpdate(ydoc2))
|
||||
|
||||
// now sync a third doc with same name as doc1 and then create PermanentUserData
|
||||
const ydoc3 = new Doc()
|
||||
applyUpdate(ydoc3, encodeStateAsUpdate(ydoc1))
|
||||
const pd3 = new PermanentUserData(ydoc3)
|
||||
pd3.setUserMapping(ydoc3, ydoc3.clientID, 'user a')
|
||||
}
|
||||
|
||||
/**
|
||||
* Reported here: https://github.com/yjs/yjs/issues/308
|
||||
* @param {t.TestCase} tc
|
||||
|
||||
@@ -11,7 +11,6 @@ import * as doc from './doc.tests.js'
|
||||
import * as snapshot from './snapshot.tests.js'
|
||||
import * as updates from './updates.tests.js'
|
||||
import * as relativePositions from './relativePositions.tests.js'
|
||||
import * as delta from './delta.tests.js'
|
||||
import * as idset from './IdSet.tests.js'
|
||||
import * as idmap from './IdMap.tests.js'
|
||||
import * as attribution from './attribution.tests.js'
|
||||
@@ -25,7 +24,7 @@ if (isBrowser) {
|
||||
}
|
||||
|
||||
const tests = {
|
||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, delta, idset, idmap, attribution
|
||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, idset, idmap, attribution
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Y from './testHelper.js'
|
||||
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 * as delta from 'lib0/delta'
|
||||
import { createIdMapFromIdSet, noAttributionsManager, TwosetAttributionManager, createAttributionManagerFromSnapshots } from 'yjs/internals'
|
||||
|
||||
const { init, compare } = Y
|
||||
@@ -13,22 +13,19 @@ const { init, compare } = Y
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testDeltaBug = _tc => {
|
||||
const initialDelta = [{
|
||||
attributes: {
|
||||
'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
const initialDelta = delta.create()
|
||||
.insert('\n', {
|
||||
attributes: {
|
||||
'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087'
|
||||
},
|
||||
insert: '\n'
|
||||
})
|
||||
.insert('\n\n\n', {
|
||||
'table-col': {
|
||||
width: '150'
|
||||
}
|
||||
},
|
||||
insert: '\n\n\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-9144be72-e528-4f91-b0b2-82d20408e9ea',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -40,11 +37,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-apba4k',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-639adacb-1516-43ed-b272-937c55669a1c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -56,11 +50,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-a8qf0r',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-6302ca4a-73a3-4c25-8c1e-b542f048f1c6',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -72,11 +63,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-oi9ikb',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-ceeddd05-330e-4f86-8017-4a3a060c4627',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -88,11 +76,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-dt6ks2',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-37b19322-cb57-4e6f-8fad-0d1401cae53f',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -104,11 +89,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-qah2ay',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-468a69b5-9332-450b-9107-381d593de249',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -120,11 +102,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-fpcz5a',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-26b1d252-9b2e-4808-9b29-04e76696aa3c',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -136,11 +115,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-zrhylp',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-6af97ba7-8cf9-497a-9365-7075b938837b',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -152,11 +128,8 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-s1q9nt',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-107e273e-86bc-44fd-b0d7-41ab55aca484',
|
||||
'table-cell-line': {
|
||||
rowspan: '1',
|
||||
@@ -168,56 +141,22 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-20b0j9',
|
||||
rowspan: '1',
|
||||
colspan: '1'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('\n', {
|
||||
'block-id': 'block-38161f9c-6f6d-44c5-b086-54cc6490f1e3'
|
||||
},
|
||||
insert: '\n'
|
||||
},
|
||||
{
|
||||
insert: 'Content after table'
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
})
|
||||
.insert('Content after table')
|
||||
.insert('\n', {
|
||||
'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce'
|
||||
},
|
||||
insert: '\n'
|
||||
}
|
||||
]
|
||||
})
|
||||
const ydoc1 = new Y.Doc()
|
||||
const ytext = ydoc1.getText()
|
||||
ytext.applyDelta(initialDelta)
|
||||
const addingDash = [
|
||||
{
|
||||
retain: 12
|
||||
},
|
||||
{
|
||||
insert: '-'
|
||||
}
|
||||
]
|
||||
const addingDash = delta.create().retain(12).insert('-')
|
||||
ytext.applyDelta(addingDash)
|
||||
const addingSpace = [
|
||||
{
|
||||
retain: 13
|
||||
},
|
||||
{
|
||||
insert: ' '
|
||||
}
|
||||
]
|
||||
const addingSpace = delta.create().retain(13).insert(' ')
|
||||
ytext.applyDelta(addingSpace)
|
||||
const addingList = [
|
||||
{
|
||||
retain: 12
|
||||
},
|
||||
{
|
||||
delete: 2
|
||||
},
|
||||
{
|
||||
retain: 1,
|
||||
attributes: {
|
||||
const addingList = delta.create().retain(12).delete(2).retain(1, {
|
||||
// Clear table line attribute
|
||||
'table-cell-line': null,
|
||||
// Add list attribute in place of table-cell-line
|
||||
@@ -228,15 +167,10 @@ export const testDeltaBug = _tc => {
|
||||
cell: 'cell-20b0j9',
|
||||
list: 'bullet'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
ytext.applyDelta(addingList)
|
||||
const result = ytext.getContent()
|
||||
/**
|
||||
* @type {delta.TextDelta<any,any>}
|
||||
*/
|
||||
const expectedResult = delta.createTextDelta()
|
||||
const expectedResult = delta.text()
|
||||
.insert('\n', { 'block-id': 'block-28eea923-9cbb-4b6f-a950-cf7fd82bc087' })
|
||||
.insert('\n\n\n', { 'table-col': { width: '150' } })
|
||||
.insert('\n', {
|
||||
@@ -366,7 +300,6 @@ export const testDeltaBug = _tc => {
|
||||
.insert('\n', {
|
||||
'block-id': 'block-15630542-ef45-412d-9415-88f0052238ce'
|
||||
})
|
||||
.done()
|
||||
t.compare(result, expectedResult)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Y from '../src/index.js'
|
||||
import { init, compare } from './testHelper.js'
|
||||
import * as t from 'lib0/testing'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
import * as delta from 'lib0/delta'
|
||||
|
||||
export const testCustomTypings = () => {
|
||||
const ydoc = new Y.Doc()
|
||||
@@ -99,25 +99,6 @@ export const testEvents = tc => {
|
||||
compare(users)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testTreewalker = tc => {
|
||||
const { users, xml0 } = init(tc, { users: 3 })
|
||||
const paragraph1 = new Y.XmlElement('p')
|
||||
const paragraph2 = new Y.XmlElement('p')
|
||||
const text1 = new Y.XmlText('init')
|
||||
const text2 = new Y.XmlText('text')
|
||||
paragraph1.insert(0, [text1, text2])
|
||||
xml0.insert(0, [paragraph1, paragraph2, new Y.XmlElement('img')])
|
||||
const allParagraphs = xml0.querySelectorAll('p')
|
||||
t.assert(allParagraphs.length === 2, 'found exactly two paragraphs')
|
||||
t.assert(allParagraphs[0] === paragraph1, 'querySelectorAll found paragraph1')
|
||||
t.assert(allParagraphs[1] === paragraph2, 'querySelectorAll found paragraph2')
|
||||
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
|
||||
compare(users)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
@@ -125,7 +106,7 @@ export const testYtextAttributes = _tc => {
|
||||
const ydoc = new Y.Doc()
|
||||
const ytext = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText))
|
||||
ytext.observe(event => {
|
||||
t.compare(event.changes.keys.get('test'), { action: 'add', oldValue: undefined })
|
||||
t.assert(event.delta.attrs.get('test')?.type === 'insert')
|
||||
})
|
||||
ytext.setAttribute('test', 42)
|
||||
t.compare(ytext.getAttribute('test'), 42)
|
||||
@@ -201,13 +182,12 @@ export const testClone = _tc => {
|
||||
export const testFormattingBug = _tc => {
|
||||
const ydoc = new Y.Doc()
|
||||
const yxml = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText))
|
||||
const delta = [
|
||||
{ insert: 'A', attributes: { em: {}, strong: {} } },
|
||||
{ insert: 'B', attributes: { em: {} } },
|
||||
{ insert: 'C', attributes: { em: {}, strong: {} } }
|
||||
]
|
||||
yxml.applyDelta(delta)
|
||||
t.compare(yxml.getContent().toJSON(), delta)
|
||||
const q = delta.create()
|
||||
.insert('A', { em: {}, strong: {} })
|
||||
.insert('B', { em: {} })
|
||||
.insert('C', { em: {}, strong: {} })
|
||||
yxml.applyDelta(q)
|
||||
t.compare(yxml.getContent(), q)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,11 +223,11 @@ export const testFragmentAttributedContent = _tc => {
|
||||
yfragment.delete(0, 1)
|
||||
yfragment.insert(1, [elem3])
|
||||
})
|
||||
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const expectedContent = delta.create().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const attributedContent = yfragment.getContent(attributionManager)
|
||||
console.log(attributedContent.children.toJSON())
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(elem1.getContent(attributionManager).toJSON(), delta.createTextDelta().insert('hello', null, { delete: [] }).done().toJSON())
|
||||
console.log(attributedContent.toJSON())
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(elem1.getContent(attributionManager).toJSON(), delta.create().insert('hello', null, { delete: [] }).toJSON())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -272,29 +252,29 @@ export const testElementAttributedContent = _tc => {
|
||||
yelement.insert(1, [elem3])
|
||||
yelement.setAttribute('key', '42')
|
||||
})
|
||||
const expectedContent = delta.createArrayDelta().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const expectedContent = delta.create().insert([elem1], null, { delete: [] }).insert([elem2]).insert([elem3], null, { insert: [] })
|
||||
const attributedContent = yelement.getContent(attributionManager)
|
||||
console.log('children', attributedContent.children.toJSON())
|
||||
console.log('attributes', attributedContent.attributes)
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
console.log('children', attributedContent.toJSON())
|
||||
console.log('attributes', attributedContent)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.group('test getContentDeep', () => {
|
||||
const expectedContent = delta.createArrayDelta().insert(
|
||||
[delta.createTextDelta().insert('hello', null, { delete: [] })],
|
||||
const expectedContent = delta.create().insert(
|
||||
[delta.text().insert('hello', null, { delete: [] })],
|
||||
null,
|
||||
{ delete: [] }
|
||||
).insert([delta.createXmlDelta('span')])
|
||||
).insert([delta.create('span')])
|
||||
.insert([
|
||||
delta.createTextDelta().insert('world', null, { insert: [] })
|
||||
delta.text().insert('world', null, { insert: [] })
|
||||
], null, { insert: [] })
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
|
||||
console.log('children', JSON.stringify(attributedContent.toJSON().children, 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, /** @type {delta.MapDeltaBuilder<any>} */ (delta.createMapDelta()).set('key', '42', undefined, { insert: [] }))
|
||||
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.nodeName === 'UNDEFINED')
|
||||
console.log('attributes', attributedContent.toJSON().attrs)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent, /** @type {delta.MapDelta<any>} */ (delta.map()).set('key', '42', { insert: [] }))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.name === 'UNDEFINED')
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -316,64 +296,64 @@ export const testElementAttributedContentViaDiffer = _tc => {
|
||||
yelement.setAttribute('key', '42')
|
||||
})
|
||||
const attributionManager = Y.createAttributionManagerFromDiff(ydocV1, ydoc)
|
||||
const expectedContent = delta.createArrayDelta().insert([delta.createTextDelta().insert('hello')], null, { delete: [] }).insert([elem2.getContentDeep()]).insert([delta.createTextDelta().insert('world', null, { insert: [] })], null, { insert: [] })
|
||||
const expectedContent = delta.create().insert([delta.create().insert('hello')], null, { delete: [] }).insert([elem2.getContentDeep()]).insert([delta.create().insert('world', null, { insert: [] })], null, { insert: [] })
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', attributedContent.children.toJSON())
|
||||
console.log('attributes', attributedContent.attributes)
|
||||
t.compare(attributedContent.children.toJSON(), expectedContent.toJSON())
|
||||
t.assert(attributedContent.children.equals(expectedContent))
|
||||
t.compare(attributedContent.attributes.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
console.log('children', attributedContent.toJSON().children)
|
||||
console.log('attributes', attributedContent.toJSON().attrs)
|
||||
t.compare(attributedContent.toJSON(), expectedContent.toJSON())
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.group('test getContentDeep', () => {
|
||||
const expectedContent = delta.createArrayDelta().insert(
|
||||
[delta.createTextDelta().insert('hello')],
|
||||
const expectedContent = delta.create().insert(
|
||||
[delta.create().insert('hello')],
|
||||
null,
|
||||
{ delete: [] }
|
||||
).insert([delta.createXmlDelta('span')])
|
||||
).insert([delta.create('span')])
|
||||
.insert([
|
||||
delta.createTextDelta().insert('world', null, { insert: [] })
|
||||
delta.create().insert('world', null, { insert: [] })
|
||||
], null, { insert: [] })
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
|
||||
console.log('children', JSON.stringify(attributedContent.toJSON().children, 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.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.nodeName === 'UNDEFINED')
|
||||
console.log('attributes', attributedContent.toJSON().attrs)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.name === 'UNDEFINED')
|
||||
})
|
||||
ydoc.transact(() => {
|
||||
elem3.insert(0, 'big')
|
||||
})
|
||||
t.group('test getContentDeep after some more updates', () => {
|
||||
t.info('expecting diffingAttributionManager to auto update itself')
|
||||
const expectedContent = delta.createArrayDelta().insert(
|
||||
[delta.createTextDelta().insert('hello')],
|
||||
const expectedContent = delta.create().insert(
|
||||
[delta.create().insert('hello')],
|
||||
null,
|
||||
{ delete: [] }
|
||||
).insert([delta.createXmlDelta('span')])
|
||||
).insert([delta.create('span')])
|
||||
.insert([
|
||||
delta.createTextDelta().insert('bigworld', null, { insert: [] })
|
||||
delta.create().insert('bigworld', null, { insert: [] })
|
||||
], null, { insert: [] })
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
|
||||
console.log('children', JSON.stringify(attributedContent.toJSON().children, 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.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.nodeName === 'UNDEFINED')
|
||||
console.log('attributes', attributedContent.toJSON().attrs)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42', attribution: { insert: [] } } })
|
||||
t.assert(attributedContent.name === 'UNDEFINED')
|
||||
})
|
||||
Y.applyUpdate(ydocV1, Y.encodeStateAsUpdate(ydoc))
|
||||
t.group('test getContentDeep both docs synced', () => {
|
||||
t.info('expecting diffingAttributionManager to auto update itself')
|
||||
const expectedContent = delta.createArrayDelta().insert([delta.createXmlDelta('span')]).insert([
|
||||
delta.createTextDelta().insert('bigworld')
|
||||
const expectedContent = delta.create().insert([delta.create('span')]).insert([
|
||||
delta.create().insert('bigworld')
|
||||
])
|
||||
const attributedContent = yelement.getContentDeep(attributionManager)
|
||||
console.log('children', JSON.stringify(attributedContent.children.toJSON(), null, 2))
|
||||
console.log('children', JSON.stringify(attributedContent.toJSON().children, 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.toJSON(), { key: { type: 'insert', prevValue: undefined, value: '42', attribution: null } })
|
||||
t.assert(attributedContent.nodeName === 'UNDEFINED')
|
||||
console.log('attributes', attributedContent.toJSON().attrs)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
t.compare(attributedContent.toJSON().attrs, { key: { type: 'insert', prevValue: undefined, value: '42' } })
|
||||
t.assert(attributedContent.name === 'UNDEFINED')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
"yjs/testHelper": ["./tests/testHelper.js"]
|
||||
}
|
||||
},
|
||||
"include": ["./src/**/*.js", "./tests/**/*.js"]
|
||||
"include": ["./src/**/*.js", "./tests/**/*.js"],
|
||||
"exclude": ["../lib0/**"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user