From dc70054cafa6a179f89d71494bf352df91da2df0 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Wed, 21 Aug 2024 11:23:10 +0200 Subject: [PATCH] trackDetachedComments as separate extension GitOrigin-RevId: 8039de3f9766b072e9bb2170b50e683073105748 --- .../source-editor/extensions/index.ts | 2 + .../source-editor/extensions/ranges.ts | 89 +-------------- .../source-editor/extensions/track-changes.ts | 88 +-------------- .../extensions/track-detached-comments.ts | 105 ++++++++++++++++++ 4 files changed, 109 insertions(+), 175 deletions(-) create mode 100644 services/web/frontend/js/features/source-editor/extensions/track-detached-comments.ts diff --git a/services/web/frontend/js/features/source-editor/extensions/index.ts b/services/web/frontend/js/features/source-editor/extensions/index.ts index b1d7f393f2..cb86ab8b85 100644 --- a/services/web/frontend/js/features/source-editor/extensions/index.ts +++ b/services/web/frontend/js/features/source-editor/extensions/index.ts @@ -51,6 +51,7 @@ import { fileTreeItemDrop } from './file-tree-item-drop' import { mathPreview } from './math-preview' import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { ranges } from './ranges' +import { trackDetachedComments } from './track-detached-comments' const moduleExtensions: Array<() => Extension> = importOverleafModules( 'sourceEditorExtensions' @@ -129,6 +130,7 @@ export const createExtensions = (options: Record): Extension[] => [ isSplitTestEnabled('review-panel-redesign') ? ranges(options.currentDoc, options.changeManager) : trackChanges(options.currentDoc, options.changeManager), + trackDetachedComments(options.currentDoc), visual(options.visual), mathPreview(options.settings.mathPreview), toolbarPanel(), diff --git a/services/web/frontend/js/features/source-editor/extensions/ranges.ts b/services/web/frontend/js/features/source-editor/extensions/ranges.ts index 4674656276..8f8146597e 100644 --- a/services/web/frontend/js/features/source-editor/extensions/ranges.ts +++ b/services/web/frontend/js/features/source-editor/extensions/ranges.ts @@ -1,11 +1,4 @@ -import { - EditorState, - RangeSet, - StateEffect, - StateField, - Transaction, - TransactionSpec, -} from '@codemirror/state' +import { EditorState, StateEffect, TransactionSpec } from '@codemirror/state' import { Decoration, type DecorationSet, @@ -14,14 +7,6 @@ import { ViewPlugin, WidgetType, } from '@codemirror/view' -import { - findCommentsInCut, - findDetachedCommentsInChanges, - restoreCommentsOnPaste, - restoreDetachedComments, - StoredComment, -} from './changes/comments' -import { invertedEffects } from '@codemirror/commands' import { Change, DeleteOperation, @@ -58,17 +43,6 @@ export const updateRanges = (data: RangesData): TransactionSpec => { const clearChangesEffect = StateEffect.define() const buildChangesEffect = StateEffect.define() -const restoreDetachedCommentsEffect = StateEffect.define>({ - map: (value, mapping) => { - return value - .update({ - filter: (from, to) => { - return from <= mapping.length && to <= mapping.length - }, - }) - .map(mapping) - }, -}) type Options = { currentDoc: DocumentContainer @@ -85,68 +59,7 @@ export const ranges = ( { currentDoc, loadingThreads, ranges, threads }: Options, changeManager?: ChangeManager ) => { - // A state field that stored any comments found within the ranges of a "cut" transaction, - // to be restored when pasting matching text. - const cutCommentsState = StateField.define({ - create: () => { - return [] - }, - update: (value, transaction) => { - if (transaction.annotation(Transaction.remote)) { - return value - } - - if (!transaction.docChanged) { - return value - } - - if (transaction.isUserEvent('delete.cut')) { - return findCommentsInCut(currentDoc, transaction) - } - - if (transaction.isUserEvent('input.paste')) { - restoreCommentsOnPaste(currentDoc, transaction, value) - return [] - } - - return value - }, - }) - return [ - // attach any comments detached by the transaction as an inverted effect, to be applied on undo - invertedEffects.of(transaction => { - if ( - transaction.docChanged && - !transaction.annotation(Transaction.remote) - ) { - const detachedComments = findDetachedCommentsInChanges( - currentDoc, - transaction - ) - if (detachedComments.size) { - return [restoreDetachedCommentsEffect.of(detachedComments)] - } - } - return [] - }), - - // restore any detached comments on undo - EditorState.transactionExtender.of(transaction => { - for (const effect of transaction.effects) { - if (effect.is(restoreDetachedCommentsEffect)) { - // send the comments to the ShareJS doc - restoreDetachedComments(currentDoc, transaction, effect.value) - - // return a transaction spec to rebuild the change markers - return buildChangeMarkers() - } - } - return null - }), - - cutCommentsState, - // initialize/destroy the change manager, and handle any updates changeManager ? ViewPlugin.define(() => { diff --git a/services/web/frontend/js/features/source-editor/extensions/track-changes.ts b/services/web/frontend/js/features/source-editor/extensions/track-changes.ts index 461b8f78ca..ccb9be5cd3 100644 --- a/services/web/frontend/js/features/source-editor/extensions/track-changes.ts +++ b/services/web/frontend/js/features/source-editor/extensions/track-changes.ts @@ -1,10 +1,4 @@ -import { - EditorState, - RangeSet, - StateEffect, - StateField, - Transaction, -} from '@codemirror/state' +import { StateEffect } from '@codemirror/state' import { Decoration, type DecorationSet, @@ -13,14 +7,6 @@ import { ViewPlugin, WidgetType, } from '@codemirror/view' -import { - findCommentsInCut, - findDetachedCommentsInChanges, - restoreCommentsOnPaste, - restoreDetachedComments, - StoredComment, -} from './changes/comments' -import { invertedEffects } from '@codemirror/commands' import { Change, DeleteOperation } from '../../../../../types/change' import { ChangeManager } from './changes/change-manager' import { debugConsole } from '@/utils/debugging' @@ -32,17 +18,6 @@ import { const clearChangesEffect = StateEffect.define() const buildChangesEffect = StateEffect.define() -const restoreDetachedCommentsEffect = StateEffect.define>({ - map: (value, mapping) => { - return value - .update({ - filter: (from, to) => { - return from <= mapping.length && to <= mapping.length - }, - }) - .map(mapping) - }, -}) type Options = { currentDoc: DocumentContainer @@ -57,68 +32,7 @@ export const trackChanges = ( { currentDoc, loadingThreads }: Options, changeManager: ChangeManager ) => { - // A state field that stored any comments found within the ranges of a "cut" transaction, - // to be restored when pasting matching text. - const cutCommentsState = StateField.define({ - create: () => { - return [] - }, - update: (value, transaction) => { - if (transaction.annotation(Transaction.remote)) { - return value - } - - if (!transaction.docChanged) { - return value - } - - if (transaction.isUserEvent('delete.cut')) { - return findCommentsInCut(currentDoc, transaction) - } - - if (transaction.isUserEvent('input.paste')) { - restoreCommentsOnPaste(currentDoc, transaction, value) - return [] - } - - return value - }, - }) - return [ - // attach any comments detached by the transaction as an inverted effect, to be applied on undo - invertedEffects.of(transaction => { - if ( - transaction.docChanged && - !transaction.annotation(Transaction.remote) - ) { - const detachedComments = findDetachedCommentsInChanges( - currentDoc, - transaction - ) - if (detachedComments.size) { - return [restoreDetachedCommentsEffect.of(detachedComments)] - } - } - return [] - }), - - // restore any detached comments on undo - EditorState.transactionExtender.of(transaction => { - for (const effect of transaction.effects) { - if (effect.is(restoreDetachedCommentsEffect)) { - // send the comments to the ShareJS doc - restoreDetachedComments(currentDoc, transaction, effect.value) - - // return a transaction spec to rebuild the change markers - return buildChangeMarkers() - } - } - return null - }), - - cutCommentsState, - // initialize/destroy the change manager, and handle any updates ViewPlugin.define(() => { changeManager.initialize() diff --git a/services/web/frontend/js/features/source-editor/extensions/track-detached-comments.ts b/services/web/frontend/js/features/source-editor/extensions/track-detached-comments.ts new file mode 100644 index 0000000000..560287521a --- /dev/null +++ b/services/web/frontend/js/features/source-editor/extensions/track-detached-comments.ts @@ -0,0 +1,105 @@ +import { + EditorState, + RangeSet, + StateEffect, + StateField, + Transaction, +} from '@codemirror/state' +import { + findCommentsInCut, + findDetachedCommentsInChanges, + restoreCommentsOnPaste, + restoreDetachedComments, + StoredComment, +} from './changes/comments' +import { invertedEffects } from '@codemirror/commands' +import { DocumentContainer } from '@/features/ide-react/editor/document-container' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' +import { buildChangeMarkers } from './track-changes' + +const restoreDetachedCommentsEffect = StateEffect.define>({ + map: (value, mapping) => { + return value + .update({ + filter: (from, to) => { + return from <= mapping.length && to <= mapping.length + }, + }) + .map(mapping) + }, +}) + +/** + * A custom extension that detects detached comments when a comment is cut and pasted, + * or when a deleted comment is undone + */ +export const trackDetachedComments = ({ + currentDoc, +}: { + currentDoc: DocumentContainer +}) => { + // A state field that stored any comments found within the ranges of a "cut" transaction, + // to be restored when pasting matching text. + const cutCommentsState = StateField.define({ + create: () => { + return [] + }, + update: (value, transaction) => { + if (transaction.annotation(Transaction.remote)) { + return value + } + + if (!transaction.docChanged) { + return value + } + + if (transaction.isUserEvent('delete.cut')) { + return findCommentsInCut(currentDoc, transaction) + } + + if (transaction.isUserEvent('input.paste')) { + restoreCommentsOnPaste(currentDoc, transaction, value) + return [] + } + + return value + }, + }) + + return [ + // attach any comments detached by the transaction as an inverted effect, to be applied on undo + invertedEffects.of(transaction => { + if ( + transaction.docChanged && + !transaction.annotation(Transaction.remote) + ) { + const detachedComments = findDetachedCommentsInChanges( + currentDoc, + transaction + ) + if (detachedComments.size) { + return [restoreDetachedCommentsEffect.of(detachedComments)] + } + } + return [] + }), + + // restore any detached comments on undo + EditorState.transactionExtender.of(transaction => { + for (const effect of transaction.effects) { + if (effect.is(restoreDetachedCommentsEffect)) { + // send the comments to the ShareJS doc + restoreDetachedComments(currentDoc, transaction, effect.value) + + if (isSplitTestEnabled('review-panel-redesign')) { + // return a transaction spec to rebuild the change markers + return buildChangeMarkers() + } + } + } + return null + }), + + cutCommentsState, + ] +}