From 1cb52dc863e6fb6a7d87a775d9f7273b4404dbb7 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 21 Jan 2024 11:27:12 +0100 Subject: [PATCH] fix Y.Text formatting issue - closes #606 --- README.md | 13 +++++++++-- src/internals.js | 1 - src/structs/AbstractStruct.js | 1 - src/structs/ContentDeleted.js | 1 - src/structs/ContentDoc.js | 1 - src/structs/ContentEmbed.js | 1 - src/structs/ContentFormat.js | 1 - src/structs/ContentType.js | 1 - src/structs/GC.js | 1 - src/structs/Item.js | 1 - src/structs/Skip.js | 1 - src/types/AbstractType.js | 1 - src/types/YMap.js | 1 - src/types/YText.js | 22 +++++++++--------- src/types/YXmlEvent.js | 1 - src/types/YXmlHook.js | 1 - src/types/YXmlText.js | 1 - src/utils/AbstractConnector.js | 1 - src/utils/DeleteSet.js | 1 - src/utils/ID.js | 1 - src/utils/PermanentUserData.js | 1 - src/utils/RelativePosition.js | 1 - src/utils/Snapshot.js | 1 - src/utils/StructStore.js | 1 - src/utils/Transaction.js | 1 - src/utils/UpdateEncoder.js | 1 - src/utils/YEvent.js | 1 - src/utils/encoding.js | 1 - src/utils/isParentOf.js | 1 - src/utils/logging.js | 1 - src/utils/updates.js | 1 - tests/compatibility.tests.js | 1 - tests/doc.tests.js | 1 - tests/relativePositions.tests.js | 1 - tests/testHelper.js | 1 - tests/undo-redo.tests.js | 40 ++++++++++++++++++++++++++++++++ 36 files changed, 62 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 7c6552ae..6f85686b 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,17 @@ on Yjs. [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%2 ## Professional Support -* [Support Contract with the Maintainer](https://github.com/sponsors/dmonad) - By contributing financially to the open-source Yjs project, you can receive professional support directly from the author. This includes the opportunity for weekly video calls to discuss your specific challenges. -* [Synergy Codes](https://synergycodes.com/yjs-services/) - Specializing in consulting and developing real-time collaborative editing solutions for visual apps, Synergy Codes focuses on interactive diagrams, complex graphs, charts, and various data visualization types. Their expertise empowers developers to build engaging and interactive visual experiences leveraging the power of Yjs. See their work in action at [Visual Collaboration Showcase](https://yjs-diagram.synergy.codes/). +* [Support Contract with the Maintainer](https://github.com/sponsors/dmonad) - +By contributing financially to the open-source Yjs project, you can receive +professional support directly from the author. This includes the opportunity for +weekly video calls to discuss your specific challenges. +* [Synergy Codes](https://synergycodes.com/yjs-services/) - Specializing in +consulting and developing real-time collaborative editing solutions for visual +apps, Synergy Codes focuses on interactive diagrams, complex graphs, charts, and +various data visualization types. Their expertise empowers developers to build +engaging and interactive visual experiences leveraging the power of Yjs. See +their work in action at [Visual Collaboration +Showcase](https://yjs-diagram.synergy.codes/). ## Who is using Yjs diff --git a/src/internals.js b/src/internals.js index bc386f0a..cb2fcac8 100644 --- a/src/internals.js +++ b/src/internals.js @@ -1,4 +1,3 @@ - export * from './utils/AbstractConnector.js' export * from './utils/DeleteSet.js' export * from './utils/Doc.js' diff --git a/src/structs/AbstractStruct.js b/src/structs/AbstractStruct.js index 38457aef..ad000053 100644 --- a/src/structs/AbstractStruct.js +++ b/src/structs/AbstractStruct.js @@ -1,4 +1,3 @@ - import { UpdateEncoderV1, UpdateEncoderV2, ID, Transaction // eslint-disable-line } from '../internals.js' diff --git a/src/structs/ContentDeleted.js b/src/structs/ContentDeleted.js index 7225e1f6..917ba247 100644 --- a/src/structs/ContentDeleted.js +++ b/src/structs/ContentDeleted.js @@ -1,4 +1,3 @@ - import { addToDeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line diff --git a/src/structs/ContentDoc.js b/src/structs/ContentDoc.js index 2c3bf8a6..15836f51 100644 --- a/src/structs/ContentDoc.js +++ b/src/structs/ContentDoc.js @@ -1,4 +1,3 @@ - import { Doc, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item // eslint-disable-line } from '../internals.js' diff --git a/src/structs/ContentEmbed.js b/src/structs/ContentEmbed.js index a64c53fa..46fba375 100644 --- a/src/structs/ContentEmbed.js +++ b/src/structs/ContentEmbed.js @@ -1,4 +1,3 @@ - import { UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line } from '../internals.js' diff --git a/src/structs/ContentFormat.js b/src/structs/ContentFormat.js index dbc06a53..eb2bd0ee 100644 --- a/src/structs/ContentFormat.js +++ b/src/structs/ContentFormat.js @@ -1,4 +1,3 @@ - import { YText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line } from '../internals.js' diff --git a/src/structs/ContentType.js b/src/structs/ContentType.js index e9c11de1..630efeb3 100644 --- a/src/structs/ContentType.js +++ b/src/structs/ContentType.js @@ -1,4 +1,3 @@ - import { readYArray, readYMap, diff --git a/src/structs/GC.js b/src/structs/GC.js index 42d71f73..3c7cec0c 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -1,4 +1,3 @@ - import { AbstractStruct, addStruct, diff --git a/src/structs/Item.js b/src/structs/Item.js index c14778b3..1b7ba939 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -1,4 +1,3 @@ - import { GC, getState, diff --git a/src/structs/Skip.js b/src/structs/Skip.js index 3db2399d..3f7caafa 100644 --- a/src/structs/Skip.js +++ b/src/structs/Skip.js @@ -1,4 +1,3 @@ - import { AbstractStruct, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, ID // eslint-disable-line diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index 8aef5dc4..4cc1bf8a 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -1,4 +1,3 @@ - import { removeEventHandlerListener, callEventHandlerListeners, diff --git a/src/types/YMap.js b/src/types/YMap.js index 3e1a975e..855ccb4f 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -1,4 +1,3 @@ - /** * @module YMap */ diff --git a/src/types/YText.js b/src/types/YText.js index 399a6ff3..c02ae6e5 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -1,4 +1,3 @@ - /** * @module YText */ @@ -118,14 +117,15 @@ const findNextPosition = (transaction, pos, count) => { * @param {Transaction} transaction * @param {AbstractType} parent * @param {number} index + * @param {boolean} useSearchMarker * @return {ItemTextListPosition} * * @private * @function */ -const findPosition = (transaction, parent, index) => { +const findPosition = (transaction, parent, index, useSearchMarker) => { const currentAttributes = new Map() - const marker = findMarker(parent, index) + const marker = useSearchMarker ? findMarker(parent, index) : null if (marker) { const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes) return findNextPosition(transaction, pos, index - marker.index) @@ -1120,7 +1120,7 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) + const pos = findPosition(transaction, this, index, !attributes) if (!attributes) { attributes = {} // @ts-ignore @@ -1138,20 +1138,20 @@ export class YText extends AbstractType { * * @param {number} index The index to insert the embed at. * @param {Object | AbstractType} embed The Object that represents the embed. - * @param {TextAttributes} attributes Attribute information to apply on the + * @param {TextAttributes} [attributes] Attribute information to apply on the * embed * * @public */ - insertEmbed (index, embed, attributes = {}) { + insertEmbed (index, embed, attributes) { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) - insertText(transaction, this, pos, embed, attributes) + const pos = findPosition(transaction, this, index, !attributes) + insertText(transaction, this, pos, embed, attributes || {}) }) } else { - /** @type {Array} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes)) + /** @type {Array} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes || {})) } } @@ -1170,7 +1170,7 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - deleteText(transaction, findPosition(transaction, this, index), length) + deleteText(transaction, findPosition(transaction, this, index, true), length) }) } else { /** @type {Array} */ (this._pending).push(() => this.delete(index, length)) @@ -1194,7 +1194,7 @@ export class YText extends AbstractType { const y = this.doc if (y !== null) { transact(y, transaction => { - const pos = findPosition(transaction, this, index) + const pos = findPosition(transaction, this, index, false) if (pos.right === null) { return } diff --git a/src/types/YXmlEvent.js b/src/types/YXmlEvent.js index 3c2566ed..022b72d5 100644 --- a/src/types/YXmlEvent.js +++ b/src/types/YXmlEvent.js @@ -1,4 +1,3 @@ - import { YEvent, YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line diff --git a/src/types/YXmlHook.js b/src/types/YXmlHook.js index be8c759b..c5b5ed6d 100644 --- a/src/types/YXmlHook.js +++ b/src/types/YXmlHook.js @@ -1,4 +1,3 @@ - import { YMap, YXmlHookRefID, diff --git a/src/types/YXmlText.js b/src/types/YXmlText.js index 470ce70f..413b247c 100644 --- a/src/types/YXmlText.js +++ b/src/types/YXmlText.js @@ -1,4 +1,3 @@ - import { YText, YXmlTextRefID, diff --git a/src/utils/AbstractConnector.js b/src/utils/AbstractConnector.js index ecf76a3b..5f544684 100644 --- a/src/utils/AbstractConnector.js +++ b/src/utils/AbstractConnector.js @@ -1,4 +1,3 @@ - import { Observable } from 'lib0/observable' import { diff --git a/src/utils/DeleteSet.js b/src/utils/DeleteSet.js index d3b3ad75..fe07b7ce 100644 --- a/src/utils/DeleteSet.js +++ b/src/utils/DeleteSet.js @@ -1,4 +1,3 @@ - import { findIndexSS, getState, diff --git a/src/utils/ID.js b/src/utils/ID.js index 225ee5b4..b0cabd8f 100644 --- a/src/utils/ID.js +++ b/src/utils/ID.js @@ -1,4 +1,3 @@ - import { AbstractType } from '../internals.js' // eslint-disable-line import * as decoding from 'lib0/decoding' diff --git a/src/utils/PermanentUserData.js b/src/utils/PermanentUserData.js index d9e44f12..80b71259 100644 --- a/src/utils/PermanentUserData.js +++ b/src/utils/PermanentUserData.js @@ -1,4 +1,3 @@ - import { YArray, YMap, diff --git a/src/utils/RelativePosition.js b/src/utils/RelativePosition.js index 614c0bc5..a1b4356b 100644 --- a/src/utils/RelativePosition.js +++ b/src/utils/RelativePosition.js @@ -1,4 +1,3 @@ - import { writeID, readID, diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js index dfd82c86..777cd39d 100644 --- a/src/utils/Snapshot.js +++ b/src/utils/Snapshot.js @@ -1,4 +1,3 @@ - import { isDeleted, createDeleteSetFromStructStore, diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 7a2e256c..55a85178 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -1,4 +1,3 @@ - import { GC, splitItem, diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 5b93369f..c5931ab4 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -1,4 +1,3 @@ - import { getState, writeStructsFromTransaction, diff --git a/src/utils/UpdateEncoder.js b/src/utils/UpdateEncoder.js index e8c5d06c..8cf30381 100644 --- a/src/utils/UpdateEncoder.js +++ b/src/utils/UpdateEncoder.js @@ -1,4 +1,3 @@ - import * as error from 'lib0/error' import * as encoding from 'lib0/encoding' diff --git a/src/utils/YEvent.js b/src/utils/YEvent.js index b47d8c9a..13174148 100644 --- a/src/utils/YEvent.js +++ b/src/utils/YEvent.js @@ -1,4 +1,3 @@ - import { isDeleted, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line diff --git a/src/utils/encoding.js b/src/utils/encoding.js index a86f7b42..2277a140 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -1,4 +1,3 @@ - /** * @module encoding */ diff --git a/src/utils/isParentOf.js b/src/utils/isParentOf.js index d3012e24..d8f5a613 100644 --- a/src/utils/isParentOf.js +++ b/src/utils/isParentOf.js @@ -1,4 +1,3 @@ - import { AbstractType, Item } from '../internals.js' // eslint-disable-line /** diff --git a/src/utils/logging.js b/src/utils/logging.js index 37709763..989ac488 100644 --- a/src/utils/logging.js +++ b/src/utils/logging.js @@ -1,4 +1,3 @@ - import { AbstractType // eslint-disable-line } from '../internals.js' diff --git a/src/utils/updates.js b/src/utils/updates.js index c64ce355..fc40cd57 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -1,4 +1,3 @@ - import * as binary from 'lib0/binary' import * as decoding from 'lib0/decoding' import * as encoding from 'lib0/encoding' diff --git a/tests/compatibility.tests.js b/tests/compatibility.tests.js index 3a7ad4fe..fc71364c 100644 --- a/tests/compatibility.tests.js +++ b/tests/compatibility.tests.js @@ -1,4 +1,3 @@ - /** * Testing if encoding/decoding compatibility and integration compatiblity is given. * We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches. diff --git a/tests/doc.tests.js b/tests/doc.tests.js index 60524936..bb94819d 100644 --- a/tests/doc.tests.js +++ b/tests/doc.tests.js @@ -1,4 +1,3 @@ - import * as Y from '../src/index.js' import * as t from 'lib0/testing' diff --git a/tests/relativePositions.tests.js b/tests/relativePositions.tests.js index 817b9805..93fec230 100644 --- a/tests/relativePositions.tests.js +++ b/tests/relativePositions.tests.js @@ -1,4 +1,3 @@ - import * as Y from '../src/index.js' import * as t from 'lib0/testing' diff --git a/tests/testHelper.js b/tests/testHelper.js index f1ff4756..c74b7438 100644 --- a/tests/testHelper.js +++ b/tests/testHelper.js @@ -1,4 +1,3 @@ - import * as t from 'lib0/testing' import * as prng from 'lib0/prng' import * as encoding from 'lib0/encoding' diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 1844af86..f1dbf428 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -3,6 +3,46 @@ import { init } from './testHelper.js' // eslint-disable-line import * as Y from '../src/index.js' import * as t from 'lib0/testing' +export const testInconsistentFormat = () => { + /** + * @param {Y.Doc} ydoc + */ + const testYjsMerge = ydoc => { + const content = /** @type {Y.XmlText} */ (ydoc.get('text', Y.XmlText)) + content.format(0, 6, { bold: null }) + content.format(6, 4, { type: 'text' }) + t.compare(content.toDelta(), [ + { + attributes: { type: 'text' }, + insert: 'Merge Test' + }, + { + attributes: { type: 'text', italic: true }, + insert: ' After' + } + ]) + } + const initializeYDoc = () => { + const yDoc = new Y.Doc({ gc: false }) + + const content = /** @type {Y.XmlText} */ (yDoc.get('text', Y.XmlText)) + content.insert(0, ' After', { type: 'text', italic: true }) + content.insert(0, 'Test', { type: 'text' }) + content.insert(0, 'Merge ', { type: 'text', bold: true }) + return yDoc + } + { + const yDoc = initializeYDoc() + testYjsMerge(yDoc) + } + { + const initialYDoc = initializeYDoc() + const yDoc = new Y.Doc({ gc: false }) + Y.applyUpdate(yDoc, Y.encodeStateAsUpdate(initialYDoc)) + testYjsMerge(yDoc) + } +} + /** * @param {t.TestCase} tc */