Compare commits

..

9 Commits

Author SHA1 Message Date
Kevin Jahns
2e2710ded9 13.5.50 2023-03-11 09:15:11 +01:00
Kevin Jahns
227018f5c7 toDelta doesnt create transaction - fixes #506 2023-03-11 09:13:27 +01:00
Kevin Jahns
da8bacfc78 add tests for complex Y.Text deltas 2023-03-10 12:53:48 +01:00
Kevin Jahns
92bad63145 add docs: tr.changes should only be computed during the event 2023-03-09 18:44:43 +01:00
Kevin Jahns
52ff230dd1 13.5.49 2023-03-09 13:59:08 +01:00
Kevin Jahns
fe48efe64f fix generating too many cleanup transactions. closes #506 2023-03-09 13:45:13 +01:00
Kevin Jahns
7e40fc442d 13.5.48 2023-03-02 19:50:34 +01:00
Kevin Jahns
035e350062 optimize formatting cleanup 2023-03-02 19:48:00 +01:00
Kevin Jahns
bf338d8040 fix attribute update issue - fixes #503 2023-03-02 19:08:01 +01:00
8 changed files with 1365 additions and 41 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "yjs",
"version": "13.5.47",
"version": "13.5.50",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "yjs",
"version": "13.5.47",
"version": "13.5.50",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.49"

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.5.47",
"version": "13.5.50",
"description": "Shared Editing Library",
"main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs",

View File

@@ -363,19 +363,26 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
* @function
*/
const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => {
let end = curr
const endAttributes = map.copy(currAttributes)
/**
* @type {Item|null}
*/
let end = start
/**
* @type {Map<string,ContentFormat>}
*/
const endFormats = map.create()
while (end && (!end.countable || end.deleted)) {
if (!end.deleted && end.content.constructor === ContentFormat) {
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
const cf = /** @type {ContentFormat} */ (end.content)
endFormats.set(cf.key, cf)
}
end = end.right
}
let cleanups = 0
let reachedEndOfCurr = false
let reachedCurr = false
while (start !== end) {
if (curr === start) {
reachedEndOfCurr = true
reachedCurr = true
}
if (!start.deleted) {
const content = start.content
@@ -383,11 +390,11 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt
case ContentFormat: {
const { key, value } = /** @type {ContentFormat} */ (content)
const startAttrValue = startAttributes.get(key) || null
if ((endAttributes.get(key) || null) !== value || startAttrValue === value) {
if (endFormats.get(key) !== content || startAttrValue === value) {
// Either this format is overwritten or it is not necessary because the attribute already existed.
start.delete(transaction)
cleanups++
if (!reachedEndOfCurr && (currAttributes.get(key) || null) === value && (startAttributes.get(key) || null) !== value) {
if (!reachedCurr && (currAttributes.get(key) || null) === value && startAttrValue !== value) {
if (startAttrValue === null) {
currAttributes.delete(key)
} else {
@@ -395,6 +402,9 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt
}
}
}
if (!reachedCurr && !start.deleted) {
updateCurrentAttributes(currAttributes, /** @type {ContentFormat} */ (content))
}
break
}
}
@@ -1008,15 +1018,7 @@ export class YText extends AbstractType {
str = ''
}
}
// snapshots are merged again after the transaction, so we need to keep the
// transalive until we are done
transact(doc, transaction => {
if (snapshot) {
splitSnapshotAffectedStructs(transaction, snapshot)
}
if (prevSnapshot) {
splitSnapshotAffectedStructs(transaction, prevSnapshot)
}
const computeDelta = () => {
while (n !== null) {
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
switch (n.content.constructor) {
@@ -1069,7 +1071,22 @@ export class YText extends AbstractType {
n = n.right
}
packStr()
}, 'cleanup')
}
if (snapshot || prevSnapshot) {
// snapshots are merged again after the transaction, so we need to keep the
// transaction alive until we are done
transact(doc, transaction => {
if (snapshot) {
splitSnapshotAffectedStructs(transaction, snapshot)
}
if (prevSnapshot) {
splitSnapshotAffectedStructs(transaction, prevSnapshot)
}
computeDelta()
}, 'cleanup')
} else {
computeDelta()
}
return ops
}

View File

@@ -156,13 +156,15 @@ export class Doc extends Observable {
* that happened inside of the transaction are sent as one message to the
* other peers.
*
* @param {function(Transaction):void} f The function that should be executed as a transaction
* @template T
* @param {function(Transaction):T} f The function that should be executed as a transaction
* @param {any} [origin] Origin of who started the transaction. Will be stored on transaction.origin
* @return T
*
* @public
*/
transact (f, origin = null) {
transact(this, f, origin)
return transact(this, f, origin)
}
/**

View File

@@ -376,15 +376,21 @@ const cleanupTransactions = (transactionCleanups, i) => {
/**
* Implements the functionality of `y.transact(()=>{..})`
*
* @template T
* @param {Doc} doc
* @param {function(Transaction):void} f
* @param {function(Transaction):T} f
* @param {any} [origin=true]
* @return {T}
*
* @function
*/
export const transact = (doc, f, origin = null, local = true) => {
const transactionCleanups = doc._transactionCleanups
let initialCall = false
/**
* @type {any}
*/
let result = null
if (doc._transaction === null) {
initialCall = true
doc._transaction = new Transaction(doc, origin, local)
@@ -395,7 +401,7 @@ export const transact = (doc, f, origin = null, local = true) => {
doc.emit('beforeTransaction', [doc._transaction, doc])
}
try {
f(doc._transaction)
result = f(doc._transaction)
} finally {
if (initialCall) {
const finishCleanup = doc._transaction === transactionCleanups[0]
@@ -413,4 +419,5 @@ export const transact = (doc, f, origin = null, local = true) => {
}
}
}
return result
}

View File

@@ -130,6 +130,11 @@ 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.
*
* @type {Array<{insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
*/
get delta () {
@@ -149,6 +154,11 @@ 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.
*
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
*/
get changes () {

View File

@@ -2,6 +2,24 @@
import * as Y from '../src/index.js'
import * as t from 'lib0/testing'
/**
* @param {t.TestCase} _tc
*/
export const testAfterTransactionRecursion = _tc => {
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment('')
ydoc.on('afterTransaction', tr => {
if (tr.origin === 'test') {
yxml.toJSON()
}
})
ydoc.transact(_tr => {
for (let i = 0; i < 15000; i++) {
yxml.push([new Y.XmlText('a')])
}
}, 'test')
}
/**
* @param {t.TestCase} _tc
*/
@@ -15,7 +33,7 @@ export const testOriginInTransaction = _tc => {
doc.on('afterTransaction', (tr) => {
origins.push(tr.origin)
if (origins.length <= 1) {
ytext.toDelta()
ytext.toDelta(Y.snapshot(doc)) // adding a snapshot forces toDelta to create a cleanup transaction
doc.transact(() => {
ytext.insert(0, 'a')
}, 'nested')

File diff suppressed because it is too large Load Diff