Compare commits

..

7 Commits

Author SHA1 Message Date
Kevin Jahns
710ac31af3 13.5.52 2023-04-03 14:12:34 +02:00
Kevin Jahns
49f435284f lint 2023-04-03 14:10:26 +02:00
Kevin Jahns
ba96f2fe74 implement fix for #500. extends #515 2023-04-03 14:02:37 +02:00
Dominik Henneke
99bab4a1d8 Fix lint errors 2023-04-03 14:02:37 +02:00
Dominik Henneke
1674d3986d Restore deleted entries in a map 2023-04-03 14:02:37 +02:00
Kevin Jahns
dc3e99e6a1 Merge pull request #518 from WofWca/jsdoc-yarray
docs: fix JSDoc typo
2023-04-02 11:47:46 +02:00
WofWca
fb6664a2bc docs: fix JSDoc typo 2023-04-01 23:12:49 +08:00
6 changed files with 91 additions and 55 deletions

10
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.5.51", "version": "13.5.52",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "yjs", "name": "yjs",
"version": "13.5.51", "version": "13.5.52",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lib0": "^0.2.72" "lib0": "^0.2.72"
@@ -2481,9 +2481,9 @@
} }
}, },
"node_modules/lib0": { "node_modules/lib0": {
"version": "0.2.72", "version": "0.2.73",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.72.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.73.tgz",
"integrity": "sha512-JPnUxl15tO6jrBASQ92+uDyQzW4ISMhDORq6mLovBYxESEWQCj5SnC8oYkELboGbU1ZqCIEEDwCL6mYqWNzdOA==", "integrity": "sha512-aJJIElCLWnHMcYZPtsM07QoSfHwpxCy4VUzBYGXFYEmh/h2QS5uZNbCCfL0CqnkOE30b7Tp9DVfjXag+3qzZjQ==",
"dependencies": { "dependencies": {
"isomorphic.js": "^0.2.4" "isomorphic.js": "^0.2.4"
}, },

View File

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

View File

@@ -23,11 +23,12 @@ import {
readContentType, readContentType,
addChangedTypeToTransaction, addChangedTypeToTransaction,
isDeleted, isDeleted,
DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error' import * as error from 'lib0/error'
import * as binary from 'lib0/binary' import * as binary from 'lib0/binary'
import * as array from 'lib0/array'
/** /**
* @todo This should return several items * @todo This should return several items
@@ -120,6 +121,12 @@ export const splitItem = (transaction, leftItem, diff) => {
return rightItem return rightItem
} }
/**
* @param {Array<StackItem>} stack
* @param {ID} id
*/
const isDeletedByUndoStack = (stack, id) => array.some(stack, /** @param {StackItem} s */ s => isDeleted(s.deletions, id))
/** /**
* Redoes the effect of this operation. * Redoes the effect of this operation.
* *
@@ -128,12 +135,13 @@ export const splitItem = (transaction, leftItem, diff) => {
* @param {Set<Item>} redoitems * @param {Set<Item>} redoitems
* @param {DeleteSet} itemsToDelete * @param {DeleteSet} itemsToDelete
* @param {boolean} ignoreRemoteMapChanges * @param {boolean} ignoreRemoteMapChanges
* @param {import('../utils/UndoManager.js').UndoManager} um
* *
* @return {Item|null} * @return {Item|null}
* *
* @private * @private
*/ */
export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges) => { export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges, um) => {
const doc = transaction.doc const doc = transaction.doc
const store = doc.store const store = doc.store
const ownClientID = doc.clientID const ownClientID = doc.clientID
@@ -153,7 +161,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
// make sure that parent is redone // make sure that parent is redone
if (parentItem !== null && parentItem.deleted === true) { if (parentItem !== null && parentItem.deleted === true) {
// try to undo parent if it will be undone anyway // try to undo parent if it will be undone anyway
if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges) === null)) { if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges, um) === null)) {
return null return null
} }
while (parentItem.redone !== null) { while (parentItem.redone !== null) {
@@ -203,13 +211,10 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo
left = item left = item
// Iterate right while right is in itemsToDelete // Iterate right while right is in itemsToDelete
// If it is intended to delete right while item is redone, we can expect that item should replace right. // If it is intended to delete right while item is redone, we can expect that item should replace right.
while (left !== null && left.right !== null && isDeleted(itemsToDelete, left.right.id)) { while (left !== null && left.right !== null && (left.right.redone || isDeleted(itemsToDelete, left.right.id) || isDeletedByUndoStack(um.undoStack, left.right.id) || isDeletedByUndoStack(um.redoStack, left.right.id))) {
left = left.right left = left.right
} // follow redone
// follow redone while (left.redone) left = getItemCleanStart(transaction, left.redone)
// trace redone until parent matches
while (left !== null && left.redone !== null) {
left = getItemCleanStart(transaction, left.redone)
} }
if (left && left.right !== null) { if (left && left.right !== null) {
// It is not possible to redo this item because it conflicts with a // It is not possible to redo this item because it conflicts with a
@@ -756,48 +761,48 @@ export class AbstractContent {
} }
/** /**
* @param {number} offset * @param {number} _offset
* @return {AbstractContent} * @return {AbstractContent}
*/ */
splice (offset) { splice (_offset) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {AbstractContent} right * @param {AbstractContent} _right
* @return {boolean} * @return {boolean}
*/ */
mergeWith (right) { mergeWith (_right) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} _transaction
* @param {Item} item * @param {Item} _item
*/ */
integrate (transaction, item) { integrate (_transaction, _item) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} _transaction
*/ */
delete (transaction) { delete (_transaction) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {StructStore} store * @param {StructStore} _store
*/ */
gc (store) { gc (_store) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {UpdateEncoderV1 | UpdateEncoderV2} _encoder
* @param {number} offset * @param {number} _offset
*/ */
write (encoder, offset) { write (_encoder, _offset) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }

View File

@@ -244,7 +244,7 @@ export class YArray extends AbstractType {
} }
/** /**
* Executes a provided function on once on overy element of this YArray. * Executes a provided function once on overy element of this YArray.
* *
* @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray. * @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray.
*/ */

View File

@@ -10,14 +10,14 @@ import {
getItemCleanStart, getItemCleanStart,
isDeleted, isDeleted,
addToDeleteSet, addToDeleteSet,
Transaction, Doc, Item, GC, DeleteSet, AbstractType, YEvent // eslint-disable-line Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as time from 'lib0/time' import * as time from 'lib0/time'
import * as array from 'lib0/array' import * as array from 'lib0/array'
import { Observable } from 'lib0/observable' import { Observable } from 'lib0/observable'
class StackItem { export class StackItem {
/** /**
* @param {DeleteSet} deletions * @param {DeleteSet} deletions
* @param {DeleteSet} insertions * @param {DeleteSet} insertions
@@ -101,7 +101,7 @@ const popStackItem = (undoManager, stack, eventType) => {
} }
}) })
itemsToRedo.forEach(struct => { itemsToRedo.forEach(struct => {
performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions, undoManager.ignoreRemoteMapChanges) !== null || performedChange performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions, undoManager.ignoreRemoteMapChanges, undoManager) !== null || performedChange
}) })
// We want to delete in reverse order so that children are deleted before // We want to delete in reverse order so that children are deleted before
// parents, so we have more information available when items are filtered. // parents, so we have more information available when items are filtered.
@@ -158,7 +158,7 @@ export class UndoManager extends Observable {
*/ */
constructor (typeScope, { constructor (typeScope, {
captureTimeout = 500, captureTimeout = 500,
captureTransaction = tr => true, captureTransaction = _tr => true,
deleteFilter = () => true, deleteFilter = () => true,
trackedOrigins = new Set([null]), trackedOrigins = new Set([null]),
ignoreRemoteMapChanges = false, ignoreRemoteMapChanges = false,

View File

@@ -1,4 +1,4 @@
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line import { init } from './testHelper.js' // eslint-disable-line
import * as Y from '../src/index.js' import * as Y from '../src/index.js'
import * as t from 'lib0/testing' import * as t from 'lib0/testing'
@@ -64,9 +64,9 @@ export const testUndoText = tc => {
/** /**
* Test case to fix #241 * Test case to fix #241
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testEmptyTypeScope = tc => { export const testEmptyTypeScope = _tc => {
const ydoc = new Y.Doc() const ydoc = new Y.Doc()
const um = new Y.UndoManager([], { doc: ydoc }) const um = new Y.UndoManager([], { doc: ydoc })
const yarray = ydoc.getArray() const yarray = ydoc.getArray()
@@ -78,9 +78,9 @@ export const testEmptyTypeScope = tc => {
/** /**
* Test case to fix #241 * Test case to fix #241
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testDoubleUndo = tc => { export const testDoubleUndo = _tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const text = doc.getText() const text = doc.getText()
text.insert(0, '1221') text.insert(0, '1221')
@@ -316,9 +316,9 @@ export const testUndoDeleteFilter = tc => {
/** /**
* This issue has been reported in https://discuss.yjs.dev/t/undomanager-with-external-updates/454/6 * This issue has been reported in https://discuss.yjs.dev/t/undomanager-with-external-updates/454/6
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoUntilChangePerformed = tc => { export const testUndoUntilChangePerformed = _tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const doc2 = new Y.Doc() const doc2 = new Y.Doc()
doc.on('update', update => Y.applyUpdate(doc2, update)) doc.on('update', update => Y.applyUpdate(doc2, update))
@@ -347,9 +347,9 @@ export const testUndoUntilChangePerformed = tc => {
/** /**
* This issue has been reported in https://github.com/yjs/yjs/issues/317 * This issue has been reported in https://github.com/yjs/yjs/issues/317
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoNestedUndoIssue = tc => { export const testUndoNestedUndoIssue = _tc => {
const doc = new Y.Doc({ gc: false }) const doc = new Y.Doc({ gc: false })
const design = doc.getMap() const design = doc.getMap()
const undoManager = new Y.UndoManager(design, { captureTimeout: 0 }) const undoManager = new Y.UndoManager(design, { captureTimeout: 0 })
@@ -403,9 +403,9 @@ export const testUndoNestedUndoIssue = tc => {
/** /**
* This issue has been reported in https://github.com/yjs/yjs/issues/355 * This issue has been reported in https://github.com/yjs/yjs/issues/355
* *
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testConsecutiveRedoBug = tc => { export const testConsecutiveRedoBug = _tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const yRoot = doc.getMap() const yRoot = doc.getMap()
const undoMgr = new Y.UndoManager(yRoot) const undoMgr = new Y.UndoManager(yRoot)
@@ -454,9 +454,9 @@ export const testConsecutiveRedoBug = tc => {
/** /**
* This issue has been reported in https://github.com/yjs/yjs/issues/304 * This issue has been reported in https://github.com/yjs/yjs/issues/304
* *
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoXmlBug = tc => { export const testUndoXmlBug = _tc => {
const origin = 'origin' const origin = 'origin'
const doc = new Y.Doc() const doc = new Y.Doc()
const fragment = doc.getXmlFragment('t') const fragment = doc.getXmlFragment('t')
@@ -499,9 +499,9 @@ export const testUndoXmlBug = tc => {
/** /**
* This issue has been reported in https://github.com/yjs/yjs/issues/343 * This issue has been reported in https://github.com/yjs/yjs/issues/343
* *
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoBlockBug = tc => { export const testUndoBlockBug = _tc => {
const doc = new Y.Doc({ gc: false }) const doc = new Y.Doc({ gc: false })
const design = doc.getMap() const design = doc.getMap()
@@ -559,9 +559,9 @@ export const testUndoBlockBug = tc => {
* Undo text formatting delete should not corrupt peer state. * Undo text formatting delete should not corrupt peer state.
* *
* @see https://github.com/yjs/yjs/issues/392 * @see https://github.com/yjs/yjs/issues/392
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoDeleteTextFormat = tc => { export const testUndoDeleteTextFormat = _tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const text = doc.getText() const text = doc.getText()
text.insert(0, 'Attack ships on fire off the shoulder of Orion.') text.insert(0, 'Attack ships on fire off the shoulder of Orion.')
@@ -597,9 +597,9 @@ export const testUndoDeleteTextFormat = tc => {
* Undo text formatting delete should not corrupt peer state. * Undo text formatting delete should not corrupt peer state.
* *
* @see https://github.com/yjs/yjs/issues/392 * @see https://github.com/yjs/yjs/issues/392
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testBehaviorOfIgnoreremotemapchangesProperty = tc => { export const testBehaviorOfIgnoreremotemapchangesProperty = _tc => {
const doc = new Y.Doc() const doc = new Y.Doc()
const doc2 = new Y.Doc() const doc2 = new Y.Doc()
doc.on('update', update => Y.applyUpdate(doc2, update, doc)) doc.on('update', update => Y.applyUpdate(doc2, update, doc))
@@ -620,9 +620,9 @@ export const testBehaviorOfIgnoreremotemapchangesProperty = tc => {
* Special deletion case. * Special deletion case.
* *
* @see https://github.com/yjs/yjs/issues/447 * @see https://github.com/yjs/yjs/issues/447
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testSpecialDeletionCase = tc => { export const testSpecialDeletionCase = _tc => {
const origin = 'undoable' const origin = 'undoable'
const doc = new Y.Doc() const doc = new Y.Doc()
const fragment = doc.getXmlFragment() const fragment = doc.getXmlFragment()
@@ -644,3 +644,34 @@ export const testSpecialDeletionCase = tc => {
undoManager.undo() undoManager.undo()
t.compareStrings(fragment.toString(), '<test a="1" b="2"></test>') t.compareStrings(fragment.toString(), '<test a="1" b="2"></test>')
} }
/**
* Deleted entries in a map should be restored on undo.
*
* @see https://github.com/yjs/yjs/issues/500
* @param {t.TestCase} tc
*/
export const testUndoDeleteInMap = (tc) => {
const { map0 } = init(tc, { users: 3 })
const undoManager = new Y.UndoManager(map0, { captureTimeout: 0 })
map0.set('a', 'a')
map0.delete('a')
map0.set('a', 'b')
map0.delete('a')
map0.set('a', 'c')
map0.delete('a')
map0.set('a', 'd')
t.compare(map0.toJSON(), { a: 'd' })
undoManager.undo()
t.compare(map0.toJSON(), {})
undoManager.undo()
t.compare(map0.toJSON(), { a: 'c' })
undoManager.undo()
t.compare(map0.toJSON(), {})
undoManager.undo()
t.compare(map0.toJSON(), { a: 'b' })
undoManager.undo()
t.compare(map0.toJSON(), {})
undoManager.undo()
t.compare(map0.toJSON(), { a: 'a' })
}