diff --git a/src/types/YText.js b/src/types/YText.js index 328795e0..c0339003 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -338,14 +338,16 @@ const formatText = (transaction, parent, currPos, length, attributes) => { * * @param {Transaction} transaction * @param {Item} start - * @param {Item|null} end exclusive end, automatically iterates to the next Content Item + * @param {Item|null} curr exclusive end, automatically iterates to the next Content Item * @param {Map} startAttributes - * @param {Map} endAttributes This attribute is modified! + * @param {Map} currAttributes * @return {number} The amount of formatting Items deleted. * * @function */ -const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => { +const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => { + let end = curr + const endAttributes = map.copy(currAttributes) while (end && (!end.countable || end.deleted)) { if (!end.deleted && end.content.constructor === ContentFormat) { updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content)) @@ -353,7 +355,11 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri end = end.right } let cleanups = 0 + let reachedEndOfCurr = false while (start !== end) { + if (curr === start) { + reachedEndOfCurr = true + } if (!start.deleted) { const content = start.content switch (content.constructor) { @@ -363,6 +369,9 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri // 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) { + currAttributes.delete(key) + } } break } @@ -465,7 +474,7 @@ const deleteText = (transaction, currPos, length) => { currPos.forward() } if (start) { - cleanupFormattingGap(transaction, start, currPos.right, startAttrs, map.copy(currPos.currentAttributes)) + cleanupFormattingGap(transaction, start, currPos.right, startAttrs, currPos.currentAttributes) } const parent = /** @type {AbstractType} */ (/** @type {Item} */ (currPos.left || currPos.right).parent) if (parent._searchMarker) { diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index 2a555237..7a8c448a 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -138,6 +138,28 @@ export const testNotMergeEmptyLinesFormat = tc => { ]) } +/** + * @param {t.TestCase} tc + */ +export const testPreserveAttributesThroughDelete = tc => { + const ydoc = new Y.Doc() + const testText = ydoc.getText('test') + testText.applyDelta([ + { insert: 'Text' }, + { insert: '\n', attributes: { title: true } }, + { insert: '\n' } + ]) + testText.applyDelta([ + { retain: 4 }, + { delete: 1 }, + { retain: 1, attributes: { title: true } } + ]) + t.compare(testText.toDelta(), [ + { insert: 'Text' }, + { insert: '\n', attributes: { title: true } } + ]) +} + /** * @param {t.TestCase} tc */