tfrere HF Staff commited on
Commit
ade54e4
·
1 Parent(s): edbe0c1

fix: resolve TipTap v3 collaboration crash

Browse files

- Replace @tiptap /extension-collaboration-cursor v2 with custom
CollaborationCursorV3 using yCursorPlugin from @tiptap /y-tiptap
(same ySyncPluginKey as Collaboration v3)
- Simplify CollaborationUndo to read UndoManager from Collaboration v3's
yUndoPlugin instead of creating its own
- Fix BubbleMenu import: @tiptap /react/menus (v3 API change)
- Install @floating-ui/dom, remove y-prosemirror and .npmrc
- Fix backend Image import to named export

Made-with: Cursor

backend/src/publisher/extensions.ts CHANGED
@@ -11,7 +11,7 @@ import { Node, mergeAttributes, type Extensions } from "@tiptap/core";
11
  import StarterKit from "@tiptap/starter-kit";
12
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
13
  import Mathematics from "@tiptap/extension-mathematics";
14
- import Image from "@tiptap/extension-image";
15
  import { Table } from "@tiptap/extension-table";
16
  import { TableRow } from "@tiptap/extension-table-row";
17
  import { TableCell } from "@tiptap/extension-table-cell";
@@ -305,6 +305,7 @@ export function getServerExtensions(): Extensions {
305
  return [
306
  StarterKit.configure({
307
  codeBlock: false,
 
308
  link: { openOnClick: false },
309
  } as any),
310
  CodeBlockLowlight.configure({ lowlight }),
 
11
  import StarterKit from "@tiptap/starter-kit";
12
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
13
  import Mathematics from "@tiptap/extension-mathematics";
14
+ import { Image } from "@tiptap/extension-image";
15
  import { Table } from "@tiptap/extension-table";
16
  import { TableRow } from "@tiptap/extension-table-row";
17
  import { TableCell } from "@tiptap/extension-table-cell";
 
305
  return [
306
  StarterKit.configure({
307
  codeBlock: false,
308
+ undoRedo: false,
309
  link: { openOnClick: false },
310
  } as any),
311
  CodeBlockLowlight.configure({ lowlight }),
frontend/.npmrc DELETED
@@ -1 +0,0 @@
1
- legacy-peer-deps=true
 
 
frontend/package-lock.json CHANGED
@@ -11,13 +11,12 @@
11
  "@ai-sdk/react": "^3.0.160",
12
  "@emotion/react": "^11.14.0",
13
  "@emotion/styled": "^11.14.1",
 
14
  "@mui/icons-material": "^9.0.0",
15
  "@mui/material": "^9.0.0",
16
  "@tiptap/core": "^3.22.3",
17
- "@tiptap/extension-bubble-menu": "^3.22.3",
18
  "@tiptap/extension-code-block-lowlight": "^3.22.3",
19
  "@tiptap/extension-collaboration": "^3.22.3",
20
- "@tiptap/extension-collaboration-cursor": "^2.26.2",
21
  "@tiptap/extension-image": "^3.22.3",
22
  "@tiptap/extension-link": "^3.22.3",
23
  "@tiptap/extension-mathematics": "^3.22.3",
@@ -39,7 +38,6 @@
39
  "react": "^18.3.0",
40
  "react-dom": "^18.3.0",
41
  "tippy.js": "^6.3.7",
42
- "y-prosemirror": "^1.2.0",
43
  "y-websocket": "^2.0.0",
44
  "yjs": "^13.6.0",
45
  "zod": "^4.3.6"
@@ -145,6 +143,7 @@
145
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
146
  "dev": true,
147
  "license": "MIT",
 
148
  "dependencies": {
149
  "@babel/code-frame": "^7.29.0",
150
  "@babel/generator": "^7.29.0",
@@ -461,6 +460,7 @@
461
  "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
462
  "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
463
  "license": "MIT",
 
464
  "dependencies": {
465
  "@babel/runtime": "^7.18.3",
466
  "@emotion/babel-plugin": "^11.13.5",
@@ -504,6 +504,7 @@
504
  "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
505
  "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
506
  "license": "MIT",
 
507
  "dependencies": {
508
  "@babel/runtime": "^7.18.3",
509
  "@emotion/babel-plugin": "^11.13.5",
@@ -1005,6 +1006,7 @@
1005
  "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
1006
  "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
1007
  "license": "MIT",
 
1008
  "dependencies": {
1009
  "@floating-ui/core": "^1.7.5",
1010
  "@floating-ui/utils": "^0.2.11"
@@ -1103,6 +1105,7 @@
1103
  "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz",
1104
  "integrity": "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==",
1105
  "license": "MIT",
 
1106
  "dependencies": {
1107
  "@babel/runtime": "^7.29.2",
1108
  "@mui/core-downloads-tracker": "^9.0.0",
@@ -1688,6 +1691,7 @@
1688
  "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz",
1689
  "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==",
1690
  "license": "MIT",
 
1691
  "funding": {
1692
  "type": "github",
1693
  "url": "https://github.com/sponsors/ueberdosis"
@@ -1727,6 +1731,7 @@
1727
  "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.3.tgz",
1728
  "integrity": "sha512-Y6zQjh0ypDg32HWgICEvmPSKjGLr39k3aDxxt/H0uQEZSfw4smT0hxUyyyjVjx68C6t6MTnwdfz0hPI5lL68vQ==",
1729
  "license": "MIT",
 
1730
  "dependencies": {
1731
  "@floating-ui/dom": "^1.0.0"
1732
  },
@@ -1770,6 +1775,7 @@
1770
  "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz",
1771
  "integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==",
1772
  "license": "MIT",
 
1773
  "funding": {
1774
  "type": "github",
1775
  "url": "https://github.com/sponsors/ueberdosis"
@@ -1812,20 +1818,6 @@
1812
  "yjs": "^13"
1813
  }
1814
  },
1815
- "node_modules/@tiptap/extension-collaboration-cursor": {
1816
- "version": "2.26.2",
1817
- "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.26.2.tgz",
1818
- "integrity": "sha512-FdRb27mZ5Kr18hN6cbfBj1e9F0DOoHB1Gv3IYeic+g4h1C9BjDVMN0+JRBQc+4lamNA8TsHO0oKWRwaPe4sSlA==",
1819
- "license": "MIT",
1820
- "funding": {
1821
- "type": "github",
1822
- "url": "https://github.com/sponsors/ueberdosis"
1823
- },
1824
- "peerDependencies": {
1825
- "@tiptap/core": "^2.7.0",
1826
- "y-prosemirror": "^1.2.11"
1827
- }
1828
- },
1829
  "node_modules/@tiptap/extension-document": {
1830
  "version": "3.22.3",
1831
  "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz",
@@ -1969,6 +1961,7 @@
1969
  "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz",
1970
  "integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==",
1971
  "license": "MIT",
 
1972
  "funding": {
1973
  "type": "github",
1974
  "url": "https://github.com/sponsors/ueberdosis"
@@ -2076,6 +2069,7 @@
2076
  "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.22.3.tgz",
2077
  "integrity": "sha512-inbQSusJad7H0T++L1APg/anfL5d15cNGp2YG3vwo6TQr71nn2c9pepvmz3xuAIt8eygZDRba+4GT/COP1f9QA==",
2078
  "license": "MIT",
 
2079
  "funding": {
2080
  "type": "github",
2081
  "url": "https://github.com/sponsors/ueberdosis"
@@ -2155,6 +2149,7 @@
2155
  "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz",
2156
  "integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==",
2157
  "license": "MIT",
 
2158
  "funding": {
2159
  "type": "github",
2160
  "url": "https://github.com/sponsors/ueberdosis"
@@ -2169,6 +2164,7 @@
2169
  "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz",
2170
  "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==",
2171
  "license": "MIT",
 
2172
  "dependencies": {
2173
  "prosemirror-changeset": "^2.3.0",
2174
  "prosemirror-collab": "^1.3.1",
@@ -2276,6 +2272,7 @@
2276
  "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.3.tgz",
2277
  "integrity": "sha512-8UvuV4lTisCE9cMTc/X8kRyTn9edUO7Kball0I6wb17VwZSjNDfh/YKtP4O5vcPawEzFHQIvZGq/k1h37kAf0w==",
2278
  "license": "MIT",
 
2279
  "dependencies": {
2280
  "lib0": "^0.2.100"
2281
  },
@@ -2396,8 +2393,8 @@
2396
  "version": "18.3.28",
2397
  "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
2398
  "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
2399
- "dev": true,
2400
  "license": "MIT",
 
2401
  "dependencies": {
2402
  "@types/prop-types": "*",
2403
  "csstype": "^3.2.2"
@@ -2407,8 +2404,8 @@
2407
  "version": "18.3.7",
2408
  "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
2409
  "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
2410
- "dev": true,
2411
  "license": "MIT",
 
2412
  "peerDependencies": {
2413
  "@types/react": "^18.0.0"
2414
  }
@@ -2582,6 +2579,7 @@
2582
  }
2583
  ],
2584
  "license": "MIT",
 
2585
  "dependencies": {
2586
  "baseline-browser-mapping": "^2.10.12",
2587
  "caniuse-lite": "^1.0.30001782",
@@ -2691,6 +2689,15 @@
2691
  "node": ">=10"
2692
  }
2693
  },
 
 
 
 
 
 
 
 
 
2694
  "node_modules/crelt": {
2695
  "version": "1.0.6",
2696
  "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
@@ -2991,6 +2998,7 @@
2991
  "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
2992
  "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
2993
  "license": "BSD-3-Clause",
 
2994
  "engines": {
2995
  "node": ">=12.0.0"
2996
  }
@@ -3144,6 +3152,7 @@
3144
  "https://github.com/sponsors/katex"
3145
  ],
3146
  "license": "MIT",
 
3147
  "dependencies": {
3148
  "commander": "^8.3.0"
3149
  },
@@ -3366,6 +3375,7 @@
3366
  "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
3367
  "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
3368
  "license": "MIT",
 
3369
  "dependencies": {
3370
  "@types/hast": "^3.0.0",
3371
  "devlop": "^1.0.0",
@@ -3539,6 +3549,7 @@
3539
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
3540
  "dev": true,
3541
  "license": "MIT",
 
3542
  "engines": {
3543
  "node": ">=12"
3544
  },
@@ -3704,6 +3715,7 @@
3704
  "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
3705
  "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
3706
  "license": "MIT",
 
3707
  "dependencies": {
3708
  "orderedmap": "^2.0.0"
3709
  }
@@ -3733,6 +3745,7 @@
3733
  "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
3734
  "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
3735
  "license": "MIT",
 
3736
  "dependencies": {
3737
  "prosemirror-model": "^1.0.0",
3738
  "prosemirror-transform": "^1.0.0",
@@ -3781,6 +3794,7 @@
3781
  "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
3782
  "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
3783
  "license": "MIT",
 
3784
  "dependencies": {
3785
  "prosemirror-model": "^1.20.0",
3786
  "prosemirror-state": "^1.0.0",
@@ -3808,6 +3822,7 @@
3808
  "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
3809
  "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
3810
  "license": "MIT",
 
3811
  "dependencies": {
3812
  "loose-envify": "^1.1.0"
3813
  },
@@ -3820,6 +3835,7 @@
3820
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
3821
  "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
3822
  "license": "MIT",
 
3823
  "dependencies": {
3824
  "loose-envify": "^1.1.0",
3825
  "scheduler": "^0.23.2"
@@ -4153,6 +4169,7 @@
4153
  "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
4154
  "dev": true,
4155
  "license": "MIT",
 
4156
  "dependencies": {
4157
  "esbuild": "^0.25.0",
4158
  "fdir": "^6.4.4",
@@ -4266,35 +4283,12 @@
4266
  "yjs": "^13.0.0"
4267
  }
4268
  },
4269
- "node_modules/y-prosemirror": {
4270
- "version": "1.3.7",
4271
- "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.3.7.tgz",
4272
- "integrity": "sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==",
4273
- "license": "MIT",
4274
- "dependencies": {
4275
- "lib0": "^0.2.109"
4276
- },
4277
- "engines": {
4278
- "node": ">=16.0.0",
4279
- "npm": ">=8.0.0"
4280
- },
4281
- "funding": {
4282
- "type": "GitHub Sponsors ❤",
4283
- "url": "https://github.com/sponsors/dmonad"
4284
- },
4285
- "peerDependencies": {
4286
- "prosemirror-model": "^1.7.1",
4287
- "prosemirror-state": "^1.2.3",
4288
- "prosemirror-view": "^1.9.10",
4289
- "y-protocols": "^1.0.1",
4290
- "yjs": "^13.5.38"
4291
- }
4292
- },
4293
  "node_modules/y-protocols": {
4294
  "version": "1.0.7",
4295
  "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz",
4296
  "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==",
4297
  "license": "MIT",
 
4298
  "dependencies": {
4299
  "lib0": "^0.2.85"
4300
  },
@@ -4347,20 +4341,12 @@
4347
  "dev": true,
4348
  "license": "ISC"
4349
  },
4350
- "node_modules/yaml": {
4351
- "version": "1.10.3",
4352
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
4353
- "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
4354
- "license": "ISC",
4355
- "engines": {
4356
- "node": ">= 6"
4357
- }
4358
- },
4359
  "node_modules/yjs": {
4360
  "version": "13.6.30",
4361
  "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.30.tgz",
4362
  "integrity": "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==",
4363
  "license": "MIT",
 
4364
  "dependencies": {
4365
  "lib0": "^0.2.99"
4366
  },
@@ -4378,6 +4364,7 @@
4378
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
4379
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
4380
  "license": "MIT",
 
4381
  "funding": {
4382
  "url": "https://github.com/sponsors/colinhacks"
4383
  }
 
11
  "@ai-sdk/react": "^3.0.160",
12
  "@emotion/react": "^11.14.0",
13
  "@emotion/styled": "^11.14.1",
14
+ "@floating-ui/dom": "^1.7.6",
15
  "@mui/icons-material": "^9.0.0",
16
  "@mui/material": "^9.0.0",
17
  "@tiptap/core": "^3.22.3",
 
18
  "@tiptap/extension-code-block-lowlight": "^3.22.3",
19
  "@tiptap/extension-collaboration": "^3.22.3",
 
20
  "@tiptap/extension-image": "^3.22.3",
21
  "@tiptap/extension-link": "^3.22.3",
22
  "@tiptap/extension-mathematics": "^3.22.3",
 
38
  "react": "^18.3.0",
39
  "react-dom": "^18.3.0",
40
  "tippy.js": "^6.3.7",
 
41
  "y-websocket": "^2.0.0",
42
  "yjs": "^13.6.0",
43
  "zod": "^4.3.6"
 
143
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
144
  "dev": true,
145
  "license": "MIT",
146
+ "peer": true,
147
  "dependencies": {
148
  "@babel/code-frame": "^7.29.0",
149
  "@babel/generator": "^7.29.0",
 
460
  "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
461
  "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
462
  "license": "MIT",
463
+ "peer": true,
464
  "dependencies": {
465
  "@babel/runtime": "^7.18.3",
466
  "@emotion/babel-plugin": "^11.13.5",
 
504
  "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
505
  "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
506
  "license": "MIT",
507
+ "peer": true,
508
  "dependencies": {
509
  "@babel/runtime": "^7.18.3",
510
  "@emotion/babel-plugin": "^11.13.5",
 
1006
  "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
1007
  "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
1008
  "license": "MIT",
1009
+ "peer": true,
1010
  "dependencies": {
1011
  "@floating-ui/core": "^1.7.5",
1012
  "@floating-ui/utils": "^0.2.11"
 
1105
  "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz",
1106
  "integrity": "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==",
1107
  "license": "MIT",
1108
+ "peer": true,
1109
  "dependencies": {
1110
  "@babel/runtime": "^7.29.2",
1111
  "@mui/core-downloads-tracker": "^9.0.0",
 
1691
  "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz",
1692
  "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==",
1693
  "license": "MIT",
1694
+ "peer": true,
1695
  "funding": {
1696
  "type": "github",
1697
  "url": "https://github.com/sponsors/ueberdosis"
 
1731
  "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.3.tgz",
1732
  "integrity": "sha512-Y6zQjh0ypDg32HWgICEvmPSKjGLr39k3aDxxt/H0uQEZSfw4smT0hxUyyyjVjx68C6t6MTnwdfz0hPI5lL68vQ==",
1733
  "license": "MIT",
1734
+ "optional": true,
1735
  "dependencies": {
1736
  "@floating-ui/dom": "^1.0.0"
1737
  },
 
1775
  "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz",
1776
  "integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==",
1777
  "license": "MIT",
1778
+ "peer": true,
1779
  "funding": {
1780
  "type": "github",
1781
  "url": "https://github.com/sponsors/ueberdosis"
 
1818
  "yjs": "^13"
1819
  }
1820
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1821
  "node_modules/@tiptap/extension-document": {
1822
  "version": "3.22.3",
1823
  "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz",
 
1961
  "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz",
1962
  "integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==",
1963
  "license": "MIT",
1964
+ "peer": true,
1965
  "funding": {
1966
  "type": "github",
1967
  "url": "https://github.com/sponsors/ueberdosis"
 
2069
  "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.22.3.tgz",
2070
  "integrity": "sha512-inbQSusJad7H0T++L1APg/anfL5d15cNGp2YG3vwo6TQr71nn2c9pepvmz3xuAIt8eygZDRba+4GT/COP1f9QA==",
2071
  "license": "MIT",
2072
+ "peer": true,
2073
  "funding": {
2074
  "type": "github",
2075
  "url": "https://github.com/sponsors/ueberdosis"
 
2149
  "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz",
2150
  "integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==",
2151
  "license": "MIT",
2152
+ "peer": true,
2153
  "funding": {
2154
  "type": "github",
2155
  "url": "https://github.com/sponsors/ueberdosis"
 
2164
  "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz",
2165
  "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==",
2166
  "license": "MIT",
2167
+ "peer": true,
2168
  "dependencies": {
2169
  "prosemirror-changeset": "^2.3.0",
2170
  "prosemirror-collab": "^1.3.1",
 
2272
  "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.3.tgz",
2273
  "integrity": "sha512-8UvuV4lTisCE9cMTc/X8kRyTn9edUO7Kball0I6wb17VwZSjNDfh/YKtP4O5vcPawEzFHQIvZGq/k1h37kAf0w==",
2274
  "license": "MIT",
2275
+ "peer": true,
2276
  "dependencies": {
2277
  "lib0": "^0.2.100"
2278
  },
 
2393
  "version": "18.3.28",
2394
  "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
2395
  "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
 
2396
  "license": "MIT",
2397
+ "peer": true,
2398
  "dependencies": {
2399
  "@types/prop-types": "*",
2400
  "csstype": "^3.2.2"
 
2404
  "version": "18.3.7",
2405
  "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
2406
  "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
 
2407
  "license": "MIT",
2408
+ "peer": true,
2409
  "peerDependencies": {
2410
  "@types/react": "^18.0.0"
2411
  }
 
2579
  }
2580
  ],
2581
  "license": "MIT",
2582
+ "peer": true,
2583
  "dependencies": {
2584
  "baseline-browser-mapping": "^2.10.12",
2585
  "caniuse-lite": "^1.0.30001782",
 
2689
  "node": ">=10"
2690
  }
2691
  },
2692
+ "node_modules/cosmiconfig/node_modules/yaml": {
2693
+ "version": "1.10.3",
2694
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
2695
+ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
2696
+ "license": "ISC",
2697
+ "engines": {
2698
+ "node": ">= 6"
2699
+ }
2700
+ },
2701
  "node_modules/crelt": {
2702
  "version": "1.0.6",
2703
  "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
 
2998
  "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
2999
  "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
3000
  "license": "BSD-3-Clause",
3001
+ "peer": true,
3002
  "engines": {
3003
  "node": ">=12.0.0"
3004
  }
 
3152
  "https://github.com/sponsors/katex"
3153
  ],
3154
  "license": "MIT",
3155
+ "peer": true,
3156
  "dependencies": {
3157
  "commander": "^8.3.0"
3158
  },
 
3375
  "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
3376
  "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
3377
  "license": "MIT",
3378
+ "peer": true,
3379
  "dependencies": {
3380
  "@types/hast": "^3.0.0",
3381
  "devlop": "^1.0.0",
 
3549
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
3550
  "dev": true,
3551
  "license": "MIT",
3552
+ "peer": true,
3553
  "engines": {
3554
  "node": ">=12"
3555
  },
 
3715
  "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
3716
  "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
3717
  "license": "MIT",
3718
+ "peer": true,
3719
  "dependencies": {
3720
  "orderedmap": "^2.0.0"
3721
  }
 
3745
  "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
3746
  "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
3747
  "license": "MIT",
3748
+ "peer": true,
3749
  "dependencies": {
3750
  "prosemirror-model": "^1.0.0",
3751
  "prosemirror-transform": "^1.0.0",
 
3794
  "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
3795
  "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
3796
  "license": "MIT",
3797
+ "peer": true,
3798
  "dependencies": {
3799
  "prosemirror-model": "^1.20.0",
3800
  "prosemirror-state": "^1.0.0",
 
3822
  "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
3823
  "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
3824
  "license": "MIT",
3825
+ "peer": true,
3826
  "dependencies": {
3827
  "loose-envify": "^1.1.0"
3828
  },
 
3835
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
3836
  "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
3837
  "license": "MIT",
3838
+ "peer": true,
3839
  "dependencies": {
3840
  "loose-envify": "^1.1.0",
3841
  "scheduler": "^0.23.2"
 
4169
  "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
4170
  "dev": true,
4171
  "license": "MIT",
4172
+ "peer": true,
4173
  "dependencies": {
4174
  "esbuild": "^0.25.0",
4175
  "fdir": "^6.4.4",
 
4283
  "yjs": "^13.0.0"
4284
  }
4285
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4286
  "node_modules/y-protocols": {
4287
  "version": "1.0.7",
4288
  "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz",
4289
  "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==",
4290
  "license": "MIT",
4291
+ "peer": true,
4292
  "dependencies": {
4293
  "lib0": "^0.2.85"
4294
  },
 
4341
  "dev": true,
4342
  "license": "ISC"
4343
  },
 
 
 
 
 
 
 
 
 
4344
  "node_modules/yjs": {
4345
  "version": "13.6.30",
4346
  "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.30.tgz",
4347
  "integrity": "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==",
4348
  "license": "MIT",
4349
+ "peer": true,
4350
  "dependencies": {
4351
  "lib0": "^0.2.99"
4352
  },
 
4364
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
4365
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
4366
  "license": "MIT",
4367
+ "peer": true,
4368
  "funding": {
4369
  "url": "https://github.com/sponsors/colinhacks"
4370
  }
frontend/package.json CHANGED
@@ -12,13 +12,12 @@
12
  "@ai-sdk/react": "^3.0.160",
13
  "@emotion/react": "^11.14.0",
14
  "@emotion/styled": "^11.14.1",
 
15
  "@mui/icons-material": "^9.0.0",
16
  "@mui/material": "^9.0.0",
17
  "@tiptap/core": "^3.22.3",
18
- "@tiptap/extension-bubble-menu": "^3.22.3",
19
  "@tiptap/extension-code-block-lowlight": "^3.22.3",
20
  "@tiptap/extension-collaboration": "^3.22.3",
21
- "@tiptap/extension-collaboration-cursor": "^2.26.2",
22
  "@tiptap/extension-image": "^3.22.3",
23
  "@tiptap/extension-link": "^3.22.3",
24
  "@tiptap/extension-mathematics": "^3.22.3",
@@ -40,7 +39,6 @@
40
  "react": "^18.3.0",
41
  "react-dom": "^18.3.0",
42
  "tippy.js": "^6.3.7",
43
- "y-prosemirror": "^1.2.0",
44
  "y-websocket": "^2.0.0",
45
  "yjs": "^13.6.0",
46
  "zod": "^4.3.6"
 
12
  "@ai-sdk/react": "^3.0.160",
13
  "@emotion/react": "^11.14.0",
14
  "@emotion/styled": "^11.14.1",
15
+ "@floating-ui/dom": "^1.7.6",
16
  "@mui/icons-material": "^9.0.0",
17
  "@mui/material": "^9.0.0",
18
  "@tiptap/core": "^3.22.3",
 
19
  "@tiptap/extension-code-block-lowlight": "^3.22.3",
20
  "@tiptap/extension-collaboration": "^3.22.3",
 
21
  "@tiptap/extension-image": "^3.22.3",
22
  "@tiptap/extension-link": "^3.22.3",
23
  "@tiptap/extension-mathematics": "^3.22.3",
 
39
  "react": "^18.3.0",
40
  "react-dom": "^18.3.0",
41
  "tippy.js": "^6.3.7",
 
42
  "y-websocket": "^2.0.0",
43
  "yjs": "^13.6.0",
44
  "zod": "^4.3.6"
frontend/src/editor/BubbleToolbar.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { BubbleMenu } from "@tiptap/extension-bubble-menu";
2
  import type { Editor } from "@tiptap/core";
3
  import { Box, IconButton, Divider, Tooltip } from "@mui/material";
4
  import FormatBoldIcon from "@mui/icons-material/FormatBold";
@@ -59,7 +59,7 @@ export function BubbleToolbar({ editor, onAddComment }: BubbleToolbarProps) {
59
  return (
60
  <BubbleMenu
61
  editor={editor}
62
- tippyOptions={{ duration: 150, placement: "top" }}
63
  >
64
  <Box
65
  sx={{
 
1
+ import { BubbleMenu } from "@tiptap/react/menus";
2
  import type { Editor } from "@tiptap/core";
3
  import { Box, IconButton, Divider, Tooltip } from "@mui/material";
4
  import FormatBoldIcon from "@mui/icons-material/FormatBold";
 
59
  return (
60
  <BubbleMenu
61
  editor={editor}
62
+ options={{ placement: "top", offset: 6 }}
63
  >
64
  <Box
65
  sx={{
frontend/src/editor/Editor.tsx CHANGED
@@ -2,7 +2,7 @@ import { useEditor, EditorContent } from "@tiptap/react";
2
  import { Editor as TiptapEditor } from "@tiptap/core";
3
  import StarterKit from "@tiptap/starter-kit";
4
  import Collaboration from "@tiptap/extension-collaboration";
5
- import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
6
  import Placeholder from "@tiptap/extension-placeholder";
7
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
8
  // Link is included in StarterKit v3, configured via StarterKit options
@@ -66,7 +66,6 @@ export function Editor({
66
  onAddComment,
67
  }: EditorProps) {
68
  const ydoc = useMemo(() => new Y.Doc(), []);
69
- const fragment = useMemo(() => ydoc.getXmlFragment("default"), [ydoc]);
70
  const undoManagerCallbackRef = useRef(onUndoManagerReady);
71
  undoManagerCallbackRef.current = onUndoManagerReady;
72
 
@@ -163,7 +162,7 @@ export function Editor({
163
  Collaboration.configure({
164
  document: ydoc,
165
  }),
166
- CollaborationCursor.configure({
167
  provider,
168
  user,
169
  }),
@@ -189,13 +188,12 @@ export function Editor({
189
  ...COMPONENTS.filter((d) => d.kind === "wrapper").map(createWrapperExtension),
190
  ...COMPONENTS.filter((d) => d.kind === "atomic").map(createAtomicExtension),
191
  CollaborationUndo.configure({
192
- fragment,
193
  onUndoManagerReady: (um) => undoManagerCallbackRef.current?.(um),
194
  }),
195
  Comment,
196
  ],
197
  },
198
- [ydoc, provider, fragment],
199
  );
200
 
201
  const seededRef = useRef(false);
 
2
  import { Editor as TiptapEditor } from "@tiptap/core";
3
  import StarterKit from "@tiptap/starter-kit";
4
  import Collaboration from "@tiptap/extension-collaboration";
5
+ import { CollaborationCursorV3 } from "./extensions/collaboration-cursor-v3";
6
  import Placeholder from "@tiptap/extension-placeholder";
7
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
8
  // Link is included in StarterKit v3, configured via StarterKit options
 
66
  onAddComment,
67
  }: EditorProps) {
68
  const ydoc = useMemo(() => new Y.Doc(), []);
 
69
  const undoManagerCallbackRef = useRef(onUndoManagerReady);
70
  undoManagerCallbackRef.current = onUndoManagerReady;
71
 
 
162
  Collaboration.configure({
163
  document: ydoc,
164
  }),
165
+ CollaborationCursorV3.configure({
166
  provider,
167
  user,
168
  }),
 
188
  ...COMPONENTS.filter((d) => d.kind === "wrapper").map(createWrapperExtension),
189
  ...COMPONENTS.filter((d) => d.kind === "atomic").map(createAtomicExtension),
190
  CollaborationUndo.configure({
 
191
  onUndoManagerReady: (um) => undoManagerCallbackRef.current?.(um),
192
  }),
193
  Comment,
194
  ],
195
  },
196
+ [ydoc, provider],
197
  );
198
 
199
  const seededRef = useRef(false);
frontend/src/editor/extensions/collaboration-cursor-v3.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Extension } from "@tiptap/core";
2
+ import { yCursorPlugin, defaultCursorBuilder, defaultSelectionBuilder } from "@tiptap/y-tiptap";
3
+
4
+ export interface CollaborationCursorOptions {
5
+ provider: any;
6
+ user: { name: string; color: string };
7
+ render?: (user: { name: string; color: string }) => HTMLElement;
8
+ selectionRender?: typeof defaultSelectionBuilder;
9
+ }
10
+
11
+ const awarenessStatesToArray = (states: Map<number, any>) => {
12
+ return Array.from(states.entries()).map(([key, value]) => ({
13
+ clientId: key,
14
+ ...value.user,
15
+ }));
16
+ };
17
+
18
+ export const CollaborationCursorV3 = Extension.create<CollaborationCursorOptions>({
19
+ name: "collaborationCursor",
20
+ priority: 999,
21
+
22
+ addOptions() {
23
+ return {
24
+ provider: null,
25
+ user: { name: "Anonymous", color: "#aaaaaa" },
26
+ render: (user: { name: string; color: string }) => {
27
+ const cursor = document.createElement("span");
28
+ cursor.classList.add("collaboration-cursor__caret");
29
+ cursor.setAttribute("style", `border-color: ${user.color}`);
30
+ const label = document.createElement("div");
31
+ label.classList.add("collaboration-cursor__label");
32
+ label.setAttribute("style", `background-color: ${user.color}`);
33
+ label.insertBefore(document.createTextNode(user.name), null);
34
+ cursor.insertBefore(label, null);
35
+ return cursor;
36
+ },
37
+ selectionRender: defaultSelectionBuilder,
38
+ };
39
+ },
40
+
41
+ addStorage() {
42
+ return { users: [] as any[] };
43
+ },
44
+
45
+ onCreate() {
46
+ if (!this.options.provider) {
47
+ throw new Error('The "provider" option is required for CollaborationCursorV3');
48
+ }
49
+ },
50
+
51
+ addCommands() {
52
+ return {
53
+ updateUser:
54
+ (attributes: Record<string, any>) =>
55
+ () => {
56
+ this.options.user = attributes as any;
57
+ this.options.provider.awareness.setLocalStateField("user", this.options.user);
58
+ return true;
59
+ },
60
+ };
61
+ },
62
+
63
+ addProseMirrorPlugins() {
64
+ this.options.provider.awareness.setLocalStateField("user", this.options.user);
65
+ this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states);
66
+ this.options.provider.awareness.on("update", () => {
67
+ this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states);
68
+ });
69
+
70
+ return [
71
+ yCursorPlugin(
72
+ this.options.provider.awareness,
73
+ {
74
+ cursorBuilder: this.options.render ?? defaultCursorBuilder,
75
+ selectionBuilder: this.options.selectionRender ?? defaultSelectionBuilder,
76
+ },
77
+ ),
78
+ ];
79
+ },
80
+ });
frontend/src/editor/extensions/collaboration-undo.ts CHANGED
@@ -1,35 +1,22 @@
1
  import { Extension } from "@tiptap/core";
2
  import { UndoManager } from "yjs";
3
- import type { XmlFragment } from "yjs";
4
-
5
- declare module "@tiptap/core" {
6
- interface Commands<ReturnType> {
7
- collaborationUndo: {
8
- undo: () => ReturnType;
9
- redo: () => ReturnType;
10
- };
11
- }
12
- }
13
 
14
  export interface CollaborationUndoOptions {
15
- fragment: XmlFragment;
16
  onUndoManagerReady?: (manager: UndoManager) => void;
17
  }
18
 
19
  /**
20
- * Wraps Y.UndoManager as a TipTap extension, replacing the disabled
21
- * ProseMirror history with collaborative undo/redo.
22
- *
23
- * Agent edits can be grouped by calling undoManager.stopCapturing()
24
- * before and after a batch of changes - everything between two
25
- * stopCapturing() calls is merged into a single undo step.
26
  */
27
  export const CollaborationUndo = Extension.create<CollaborationUndoOptions>({
28
- name: "collaborationUndo",
29
 
30
  addOptions() {
31
  return {
32
- fragment: null as unknown as XmlFragment,
33
  onUndoManagerReady: undefined,
34
  };
35
  },
@@ -41,47 +28,11 @@ export const CollaborationUndo = Extension.create<CollaborationUndoOptions>({
41
  },
42
 
43
  onCreate() {
44
- const um = new UndoManager(this.options.fragment, {
45
- captureTimeout: 500,
46
- });
47
- this.storage.undoManager = um;
48
- this.options.onUndoManagerReady?.(um);
49
- },
50
-
51
- onDestroy() {
52
- this.storage.undoManager?.destroy();
53
- },
54
-
55
- addCommands() {
56
- return {
57
- undo:
58
- () =>
59
- () => {
60
- const um = this.storage.undoManager;
61
- if (um && um.undoStack.length > 0) {
62
- um.undo();
63
- return true;
64
- }
65
- return false;
66
- },
67
- redo:
68
- () =>
69
- () => {
70
- const um = this.storage.undoManager;
71
- if (um && um.redoStack.length > 0) {
72
- um.redo();
73
- return true;
74
- }
75
- return false;
76
- },
77
- };
78
- },
79
-
80
- addKeyboardShortcuts() {
81
- return {
82
- "Mod-z": () => this.editor.commands.undo(),
83
- "Mod-Shift-z": () => this.editor.commands.redo(),
84
- "Mod-y": () => this.editor.commands.redo(),
85
- };
86
  },
87
  });
 
1
  import { Extension } from "@tiptap/core";
2
  import { UndoManager } from "yjs";
3
+ import { yUndoPluginKey } from "@tiptap/y-tiptap";
 
 
 
 
 
 
 
 
 
4
 
5
  export interface CollaborationUndoOptions {
 
6
  onUndoManagerReady?: (manager: UndoManager) => void;
7
  }
8
 
9
  /**
10
+ * Thin extension that exposes the UndoManager created by @tiptap/extension-collaboration v3.
11
+ * The actual undo/redo commands and shortcuts are handled by the Collaboration extension itself.
12
+ * This extension only reads the UndoManager from the yUndoPlugin state and
13
+ * exposes it via onUndoManagerReady for external consumers (e.g. agent chat batching).
 
 
14
  */
15
  export const CollaborationUndo = Extension.create<CollaborationUndoOptions>({
16
+ name: "collaborationUndoBridge",
17
 
18
  addOptions() {
19
  return {
 
20
  onUndoManagerReady: undefined,
21
  };
22
  },
 
28
  },
29
 
30
  onCreate() {
31
+ const state = this.editor.state;
32
+ const undoState = yUndoPluginKey.getState(state);
33
+ if (undoState?.undoManager) {
34
+ this.storage.undoManager = undoState.undoManager;
35
+ this.options.onUndoManagerReady?.(undoState.undoManager);
36
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  },
38
  });