diff --git a/src/types/YText.js b/src/types/YText.js index 053f23a2..f9d36394 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -1016,11 +1016,12 @@ export class YText extends AbstractType { let attributions = null if (attrs != null) { attributions = {} + attributions.changeType = deleted ? 'delete' : 'insert' attrs.forEach(attr => { switch (attr.name) { - case '_insertedBy': - case '_deletedBy': - case '_suggestedBy': { + case 'insertedBy': + case 'deletedBy': + case 'suggestedBy': { const as = /** @type {any} */ (attributions) const ls = as[attr.name] = as[attr.name] ?? [] ls.push(attr.val) @@ -1046,10 +1047,15 @@ export class YText extends AbstractType { } case ContentFormat: if (attributions != null) { - attributions.formattedBy = (deleted ? attributions.deletedBy : attributions.insertedBy) ?? [] - delete attributions.deletedBy - delete attributions.insertedBy - d.useAttribution(attributions) + if (deleted) { + d.useAttribution(null) + } else { + attributions.formattedBy = (deleted ? attributions.deletedBy : attributions.insertedBy) ?? [] + attributions.changeType = 'format' + delete attributions.deletedBy + delete attributions.insertedBy + d.useAttribution(attributions) + } } break } diff --git a/src/utils/Delta.js b/src/utils/Delta.js index 60f12c4b..c1c5b525 100644 --- a/src/utils/Delta.js +++ b/src/utils/Delta.js @@ -106,22 +106,22 @@ export class DeltaBuilder extends Delta { } /** - * @param {FormattingAttributes} attributes + * @param {FormattingAttributes?} attributes * @return {this} */ useAttributes (attributes) { if (this._useAttributes === attributes) return this - this._useAttributes = object.assign({}, attributes) + this._useAttributes = attributes && object.assign({}, attributes) if (this._lastOp?.constructor !== DeleteOp) this._lastOp = null return this } /** - * @param {Attribution} attribution + * @param {Attribution?} attribution */ useAttribution (attribution) { if (this._useAttribution === attribution) return this - this._useAttribution = object.assign({}, attribution) + this._useAttribution = attribution && object.assign({}, attribution) if (this._lastOp?.constructor !== DeleteOp) this._lastOp = null return this } diff --git a/src/utils/IdMap.js b/src/utils/IdMap.js index 0e0d3197..522fb81a 100644 --- a/src/utils/IdMap.js +++ b/src/utils/IdMap.js @@ -110,6 +110,16 @@ export class AttrRange { * @typedef {{ clock: number, len: number, attrs: Array>? }} MaybeAttrRange */ +/** + * @template Attrs + * + * @param {number} clock + * @param {number} len + * @param {Array>?} attrs + * @return {MaybeAttrRange} + */ +export const createMaybeAttrRange = (clock, len, attrs) => new AttrRange(clock, len, /** @type {any} */ (attrs)) + /** * @template Attrs */ @@ -238,21 +248,18 @@ export const mergeIdMaps = ams => { if (!merged.clients.has(client)) { // Write all missing keys from current set and all following. // If merged already contains `client` current ds has already been added. - const ids = rangesLeft.getIds().slice() + let ids = rangesLeft.getIds().slice() for (let i = amsI + 1; i < ams.length; i++) { const nextIds = ams[i].clients.get(client) if (nextIds) { array.appendTo(ids, nextIds.getIds()) } } - ids.forEach(id => { - // @ts-ignore - id.attrs = id.attrs.map(attr => - map.setIfUndefined(attrMapper, attr, () => - _ensureAttrs(merged, [attr])[0] - ) + ids = ids.map(id => new AttrRange(id.clock, id.len, id.attrs.map(attr => + map.setIfUndefined(attrMapper, attr, () => + _ensureAttrs(merged, [attr])[0] ) - }) + ))) merged.clients.set(client, new AttrRanges(ids)) } }) @@ -323,7 +330,7 @@ export class IdMap { if (r.len <= 0) break const prevEnd = prev != null ? prev.clock + prev.len : index if (prevEnd < index) { - res.push(/** @type {MaybeAttrRange} */ (new AttrRange(prevEnd, index - prevEnd, /** @type {any} */ (null)))) + res.push(createMaybeAttrRange(prevEnd, index - prevEnd, null)) } prev = r res.push(r) @@ -335,10 +342,10 @@ export class IdMap { const last = res[res.length - 1] const end = last.clock + last.len if (end < id.clock + len) { - res.push(new AttrRange(end, id.clock + len - end, [])) + res.push(createMaybeAttrRange(end, id.clock + len - end, null)) } } else { - res.push(new AttrRange(id.clock, len, [])) + res.push(createMaybeAttrRange(id.clock, len, null)) } return res } diff --git a/tests/IdMap.tests.js b/tests/IdMap.tests.js index 68a595ca..79809e2e 100644 --- a/tests/IdMap.tests.js +++ b/tests/IdMap.tests.js @@ -1,6 +1,6 @@ import * as t from 'lib0/testing' import * as idmap from '../src/utils/IdMap.js' -import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution } from './testHelper.js' +import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution, validateIdMap } from './testHelper.js' import * as YY from '../src/internals.js' /** @@ -93,13 +93,11 @@ export const testRepeatMergingMultipleIdMaps = tc => { const oneHas = sets.some(ids => ids.has(new ID(iclient, iclock))) t.assert(mergedHas === oneHas) const mergedAttrs = merged.slice(new ID(iclient, iclock), 1) - if (mergedAttrs) { - mergedAttrs.forEach(a => { - if (a.attrs != null) { - composed.add(iclient, a.clock, a.len, a.attrs) - } - }) - } + mergedAttrs.forEach(a => { + if (a.attrs != null) { + composed.add(iclient, a.clock, a.len, a.attrs) + } + }) } } compareIdmaps(merged, composed)