fix Y.Text formatting issue - closes #606

This commit is contained in:
Kevin Jahns 2024-01-21 11:27:12 +01:00
parent 7a8ca6eaa5
commit 1cb52dc863
36 changed files with 62 additions and 46 deletions

View File

@ -34,8 +34,17 @@ on Yjs. [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%2
## Professional Support ## 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. * [Support Contract with the Maintainer](https://github.com/sponsors/dmonad) -
* [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/). 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 ## Who is using Yjs

View File

@ -1,4 +1,3 @@
export * from './utils/AbstractConnector.js' export * from './utils/AbstractConnector.js'
export * from './utils/DeleteSet.js' export * from './utils/DeleteSet.js'
export * from './utils/Doc.js' export * from './utils/Doc.js'

View File

@ -1,4 +1,3 @@
import { import {
UpdateEncoderV1, UpdateEncoderV2, ID, Transaction // eslint-disable-line UpdateEncoderV1, UpdateEncoderV2, ID, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'

View File

@ -1,4 +1,3 @@
import { import {
addToDeleteSet, addToDeleteSet,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line

View File

@ -1,4 +1,3 @@
import { import {
Doc, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item // eslint-disable-line Doc, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'

View File

@ -1,4 +1,3 @@
import { import {
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'

View File

@ -1,4 +1,3 @@
import { import {
YText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line YText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'

View File

@ -1,4 +1,3 @@
import { import {
readYArray, readYArray,
readYMap, readYMap,

View File

@ -1,4 +1,3 @@
import { import {
AbstractStruct, AbstractStruct,
addStruct, addStruct,

View File

@ -1,4 +1,3 @@
import { import {
GC, GC,
getState, getState,

View File

@ -1,4 +1,3 @@
import { import {
AbstractStruct, AbstractStruct,
UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, ID // eslint-disable-line UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, ID // eslint-disable-line

View File

@ -1,4 +1,3 @@
import { import {
removeEventHandlerListener, removeEventHandlerListener,
callEventHandlerListeners, callEventHandlerListeners,

View File

@ -1,4 +1,3 @@
/** /**
* @module YMap * @module YMap
*/ */

View File

@ -1,4 +1,3 @@
/** /**
* @module YText * @module YText
*/ */
@ -118,14 +117,15 @@ const findNextPosition = (transaction, pos, count) => {
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {number} index * @param {number} index
* @param {boolean} useSearchMarker
* @return {ItemTextListPosition} * @return {ItemTextListPosition}
* *
* @private * @private
* @function * @function
*/ */
const findPosition = (transaction, parent, index) => { const findPosition = (transaction, parent, index, useSearchMarker) => {
const currentAttributes = new Map() const currentAttributes = new Map()
const marker = findMarker(parent, index) const marker = useSearchMarker ? findMarker(parent, index) : null
if (marker) { if (marker) {
const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes) const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes)
return findNextPosition(transaction, pos, index - marker.index) return findNextPosition(transaction, pos, index - marker.index)
@ -1120,7 +1120,7 @@ export class YText extends AbstractType {
const y = this.doc const y = this.doc
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
const pos = findPosition(transaction, this, index) const pos = findPosition(transaction, this, index, !attributes)
if (!attributes) { if (!attributes) {
attributes = {} attributes = {}
// @ts-ignore // @ts-ignore
@ -1138,20 +1138,20 @@ export class YText extends AbstractType {
* *
* @param {number} index The index to insert the embed at. * @param {number} index The index to insert the embed at.
* @param {Object | AbstractType<any>} embed The Object that represents the embed. * @param {Object | AbstractType<any>} 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 * embed
* *
* @public * @public
*/ */
insertEmbed (index, embed, attributes = {}) { insertEmbed (index, embed, attributes) {
const y = this.doc const y = this.doc
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
const pos = findPosition(transaction, this, index) const pos = findPosition(transaction, this, index, !attributes)
insertText(transaction, this, pos, embed, attributes) insertText(transaction, this, pos, embed, attributes || {})
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes)) /** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes || {}))
} }
} }
@ -1170,7 +1170,7 @@ export class YText extends AbstractType {
const y = this.doc const y = this.doc
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
deleteText(transaction, findPosition(transaction, this, index), length) deleteText(transaction, findPosition(transaction, this, index, true), length)
}) })
} else { } else {
/** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length)) /** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length))
@ -1194,7 +1194,7 @@ export class YText extends AbstractType {
const y = this.doc const y = this.doc
if (y !== null) { if (y !== null) {
transact(y, transaction => { transact(y, transaction => {
const pos = findPosition(transaction, this, index) const pos = findPosition(transaction, this, index, false)
if (pos.right === null) { if (pos.right === null) {
return return
} }

View File

@ -1,4 +1,3 @@
import { import {
YEvent, YEvent,
YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line

View File

@ -1,4 +1,3 @@
import { import {
YMap, YMap,
YXmlHookRefID, YXmlHookRefID,

View File

@ -1,4 +1,3 @@
import { import {
YText, YText,
YXmlTextRefID, YXmlTextRefID,

View File

@ -1,4 +1,3 @@
import { Observable } from 'lib0/observable' import { Observable } from 'lib0/observable'
import { import {

View File

@ -1,4 +1,3 @@
import { import {
findIndexSS, findIndexSS,
getState, getState,

View File

@ -1,4 +1,3 @@
import { AbstractType } from '../internals.js' // eslint-disable-line import { AbstractType } from '../internals.js' // eslint-disable-line
import * as decoding from 'lib0/decoding' import * as decoding from 'lib0/decoding'

View File

@ -1,4 +1,3 @@
import { import {
YArray, YArray,
YMap, YMap,

View File

@ -1,4 +1,3 @@
import { import {
writeID, writeID,
readID, readID,

View File

@ -1,4 +1,3 @@
import { import {
isDeleted, isDeleted,
createDeleteSetFromStructStore, createDeleteSetFromStructStore,

View File

@ -1,4 +1,3 @@
import { import {
GC, GC,
splitItem, splitItem,

View File

@ -1,4 +1,3 @@
import { import {
getState, getState,
writeStructsFromTransaction, writeStructsFromTransaction,

View File

@ -1,4 +1,3 @@
import * as error from 'lib0/error' import * as error from 'lib0/error'
import * as encoding from 'lib0/encoding' import * as encoding from 'lib0/encoding'

View File

@ -1,4 +1,3 @@
import { import {
isDeleted, isDeleted,
Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line

View File

@ -1,4 +1,3 @@
/** /**
* @module encoding * @module encoding
*/ */

View File

@ -1,4 +1,3 @@
import { AbstractType, Item } from '../internals.js' // eslint-disable-line import { AbstractType, Item } from '../internals.js' // eslint-disable-line
/** /**

View File

@ -1,4 +1,3 @@
import { import {
AbstractType // eslint-disable-line AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'

View File

@ -1,4 +1,3 @@
import * as binary from 'lib0/binary' import * as binary from 'lib0/binary'
import * as decoding from 'lib0/decoding' import * as decoding from 'lib0/decoding'
import * as encoding from 'lib0/encoding' import * as encoding from 'lib0/encoding'

View File

@ -1,4 +1,3 @@
/** /**
* Testing if encoding/decoding compatibility and integration compatiblity is given. * 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. * We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches.

View File

@ -1,4 +1,3 @@
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'

View File

@ -1,4 +1,3 @@
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'

View File

@ -1,4 +1,3 @@
import * as t from 'lib0/testing' import * as t from 'lib0/testing'
import * as prng from 'lib0/prng' import * as prng from 'lib0/prng'
import * as encoding from 'lib0/encoding' import * as encoding from 'lib0/encoding'

View File

@ -3,6 +3,46 @@ 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'
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 * @param {t.TestCase} tc
*/ */