track-detached-comments.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import {
  2. EditorState,
  3. RangeSet,
  4. StateEffect,
  5. StateField,
  6. Transaction,
  7. } from '@codemirror/state'
  8. import {
  9. findCommentsInCut,
  10. findDetachedCommentsInChanges,
  11. restoreCommentsOnPaste,
  12. restoreDetachedComments,
  13. StoredComment,
  14. } from './changes/comments'
  15. import { invertedEffects } from '@codemirror/commands'
  16. import { DocumentContainer } from '@/features/ide-react/editor/document-container'
  17. import { isSplitTestEnabled } from '@/utils/splitTestUtils'
  18. import { buildChangeMarkers } from './track-changes'
  19. const restoreDetachedCommentsEffect = StateEffect.define<RangeSet<any>>({
  20. map: (value, mapping) => {
  21. return value
  22. .update({
  23. filter: (from, to) => {
  24. return from <= mapping.length && to <= mapping.length
  25. },
  26. })
  27. .map(mapping)
  28. },
  29. })
  30. /**
  31. * A custom extension that detects detached comments when a comment is cut and pasted,
  32. * or when a deleted comment is undone
  33. */
  34. export const trackDetachedComments = ({
  35. currentDoc,
  36. }: {
  37. currentDoc: DocumentContainer
  38. }) => {
  39. // A state field that stored any comments found within the ranges of a "cut" transaction,
  40. // to be restored when pasting matching text.
  41. const cutCommentsState = StateField.define<StoredComment[]>({
  42. create: () => {
  43. return []
  44. },
  45. update: (value, transaction) => {
  46. if (transaction.annotation(Transaction.remote)) {
  47. return value
  48. }
  49. if (!transaction.docChanged) {
  50. return value
  51. }
  52. if (transaction.isUserEvent('delete.cut')) {
  53. return findCommentsInCut(currentDoc, transaction)
  54. }
  55. if (transaction.isUserEvent('input.paste')) {
  56. restoreCommentsOnPaste(currentDoc, transaction, value)
  57. return []
  58. }
  59. return value
  60. },
  61. })
  62. return [
  63. // attach any comments detached by the transaction as an inverted effect, to be applied on undo
  64. invertedEffects.of(transaction => {
  65. if (
  66. transaction.docChanged &&
  67. !transaction.annotation(Transaction.remote)
  68. ) {
  69. const detachedComments = findDetachedCommentsInChanges(
  70. currentDoc,
  71. transaction
  72. )
  73. if (detachedComments.size) {
  74. return [restoreDetachedCommentsEffect.of(detachedComments)]
  75. }
  76. }
  77. return []
  78. }),
  79. // restore any detached comments on undo
  80. EditorState.transactionExtender.of(transaction => {
  81. for (const effect of transaction.effects) {
  82. if (effect.is(restoreDetachedCommentsEffect)) {
  83. // send the comments to the ShareJS doc
  84. restoreDetachedComments(currentDoc, transaction, effect.value)
  85. if (isSplitTestEnabled('review-panel-redesign')) {
  86. // return a transaction spec to rebuild the change markers
  87. return buildChangeMarkers()
  88. }
  89. }
  90. }
  91. return null
  92. }),
  93. cutCommentsState,
  94. ]
  95. }