diff --git a/src/types/YText.js b/src/types/YText.js index d963c0b0..fd2660bf 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -387,6 +387,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => { * @function */ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => { + if (!transaction.doc.cleanupFormatting) return 0 /** * @type {Item|null} */ @@ -417,6 +418,7 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt if (endFormats.get(key) !== content || startAttrValue === value) { // Either this format is overwritten or it is not necessary because the attribute already existed. start.delete(transaction) + transaction.cleanUps.add(start.id.client, start.id.clock, start.length) cleanups++ if (!reachedCurr && (currAttributes.get(key) ?? null) === value && startAttrValue !== value) { if (startAttrValue === null) { @@ -443,6 +445,7 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt * @param {Item | null} item */ const cleanupContextlessFormattingGap = (transaction, item) => { + if (!transaction.doc.cleanupFormatting) return 0 // iterate until item.right is null or content while (item && item.right && (item.right.deleted || !item.right.countable)) { item = item.right @@ -454,6 +457,7 @@ const cleanupContextlessFormattingGap = (transaction, item) => { const key = /** @type {ContentFormat} */ (item.content).key if (attrs.has(key)) { item.delete(transaction) + transaction.cleanUps.add(item.id.client, item.id.clock, item.length) } else { attrs.add(key) } @@ -475,6 +479,7 @@ const cleanupContextlessFormattingGap = (transaction, item) => { * @return {number} How many formatting attributes have been cleaned up. */ export const cleanupYTextFormatting = type => { + if (!type.doc?.cleanupFormatting) return 0 let res = 0 transact(/** @type {Doc} */ (type.doc), transaction => { let start = /** @type {Item} */ (type._start) diff --git a/src/utils/Doc.js b/src/utils/Doc.js index 83c65f3a..958c2230 100644 --- a/src/utils/Doc.js +++ b/src/utils/Doc.js @@ -33,6 +33,9 @@ export const generateNewClientId = random.uint32 * @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well. * @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. * @property {boolean} [DocOpts.shouldLoad] Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() + * @property {boolean} [DocOpts.isSuggestionDoc] Set to true if this document merely suggests + * changes. If this flag is not set in a suggestion document, automatic formatting changes will be + * displayed as suggestions, which might not be intended. */ /** @@ -59,13 +62,15 @@ export class Doc extends ObservableV2 { /** * @param {DocOpts} opts configuration */ - constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true } = {}) { + constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true, isSuggestionDoc = true } = {}) { super() this.gc = gc this.gcFilter = gcFilter this.clientID = generateNewClientId() this.guid = guid this.collectionid = collectionid + this.isSuggestionDoc = isSuggestionDoc + this.cleanupFormatting = !isSuggestionDoc /** * @type {Map>>} */ @@ -350,9 +355,10 @@ export class Doc extends ObservableV2 { /** * @param {Doc} ydoc + * @param {DocOpts} [opts] */ -export const cloneDoc = ydoc => { - const clone = new Doc() +export const cloneDoc = (ydoc, opts) => { + const clone = new Doc(opts) applyUpdate(clone, encodeStateAsUpdate(ydoc)) return clone } diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 5f935437..6f1acf38 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -62,6 +62,11 @@ export class Transaction { * Describes the set of deleted items by ids */ this.deleteSet = createIdSet() + /** + * Describes the set of items that are cleaned up / deleted by ids. It is a subset of + * this.deleteSet + */ + this.cleanUps = createIdSet() /** * Describes the set of inserted items by ids */ @@ -354,7 +359,7 @@ const cleanupTransactions = (transactionCleanups, i) => { }) fs.push(() => doc.emit('afterTransaction', [transaction, doc])) callAll(fs, []) - if (transaction._needFormattingCleanup) { + if (transaction._needFormattingCleanup && doc.cleanupFormatting) { cleanupYTextAfterTransaction(transaction) } } finally {