@liveblocks/react-tiptap 3.18.4 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/LiveblocksExtension.cjs +2 -1
  2. package/dist/LiveblocksExtension.cjs.map +1 -1
  3. package/dist/LiveblocksExtension.js +2 -1
  4. package/dist/LiveblocksExtension.js.map +1 -1
  5. package/dist/ai/AiExtension.cjs.map +1 -1
  6. package/dist/ai/AiExtension.js.map +1 -1
  7. package/dist/collaboration/collaboration.cjs +10 -12
  8. package/dist/collaboration/collaboration.cjs.map +1 -1
  9. package/dist/collaboration/collaboration.js +10 -12
  10. package/dist/collaboration/collaboration.js.map +1 -1
  11. package/dist/comments/AnchoredThreads.cjs +12 -7
  12. package/dist/comments/AnchoredThreads.cjs.map +1 -1
  13. package/dist/comments/AnchoredThreads.js +12 -7
  14. package/dist/comments/AnchoredThreads.js.map +1 -1
  15. package/dist/comments/CommentsExtension.cjs +102 -120
  16. package/dist/comments/CommentsExtension.cjs.map +1 -1
  17. package/dist/comments/CommentsExtension.js +103 -120
  18. package/dist/comments/CommentsExtension.js.map +1 -1
  19. package/dist/comments/FloatingThreads.cjs +61 -42
  20. package/dist/comments/FloatingThreads.cjs.map +1 -1
  21. package/dist/comments/FloatingThreads.js +62 -43
  22. package/dist/comments/FloatingThreads.js.map +1 -1
  23. package/dist/toolbar/Toolbar.cjs +1 -1
  24. package/dist/toolbar/Toolbar.cjs.map +1 -1
  25. package/dist/toolbar/Toolbar.js +1 -1
  26. package/dist/toolbar/Toolbar.js.map +1 -1
  27. package/dist/types.cjs +1 -1
  28. package/dist/types.cjs.map +1 -1
  29. package/dist/types.js +1 -1
  30. package/dist/types.js.map +1 -1
  31. package/dist/utils.cjs +19 -0
  32. package/dist/utils.cjs.map +1 -1
  33. package/dist/utils.js +18 -1
  34. package/dist/utils.js.map +1 -1
  35. package/dist/version.cjs +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +6 -6
@@ -1,11 +1,42 @@
1
+ import { shallow } from '@liveblocks/core';
1
2
  import { Mark, mergeAttributes, Extension } from '@tiptap/core';
2
3
  import { Fragment, Slice } from '@tiptap/pm/model';
3
4
  import { PluginKey, Plugin } from '@tiptap/pm/state';
4
5
  import { Decoration, DecorationSet } from '@tiptap/pm/view';
5
6
  import { ySyncPluginKey } from 'y-prosemirror';
6
- import { LIVEBLOCKS_COMMENT_MARK_TYPE, THREADS_PLUGIN_KEY, ThreadPluginActions, THREADS_ACTIVE_SELECTION_PLUGIN } from '../types.js';
7
+ import { THREADS_PLUGIN_KEY, ThreadPluginActions, LIVEBLOCKS_COMMENT_MARK_TYPE, THREADS_ACTIVE_SELECTION_PLUGIN } from '../types.js';
8
+ import { areSetsEqual } from '../utils.js';
7
9
 
8
10
  const FILTERED_THREADS_PLUGIN_KEY = new PluginKey();
11
+ function getFilteredThreads(state) {
12
+ return FILTERED_THREADS_PLUGIN_KEY.getState(state)?.filteredThreads;
13
+ }
14
+ function getVisibleThreadIdsFromMarks(marks, markType, filteredThreads) {
15
+ const ids = /* @__PURE__ */ new Set();
16
+ for (const mark of marks) {
17
+ if (mark.type !== markType || mark.attrs.orphan) continue;
18
+ const threadId = mark.attrs.threadId;
19
+ if (!threadId) continue;
20
+ if (filteredThreads && !filteredThreads.has(threadId)) continue;
21
+ ids.add(threadId);
22
+ }
23
+ return [...ids];
24
+ }
25
+ function getVisibleThreadIdsAtPos(state, $pos, markType) {
26
+ return getVisibleThreadIdsFromMarks(
27
+ $pos.marks(),
28
+ markType,
29
+ getFilteredThreads(state)
30
+ );
31
+ }
32
+ function dispatchSetActiveThreadIds(view, ids) {
33
+ view.dispatch(
34
+ view.state.tr.setMeta(THREADS_PLUGIN_KEY, {
35
+ name: ThreadPluginActions.SET_ACTIVE_THREAD_IDS,
36
+ data: ids
37
+ })
38
+ );
39
+ }
9
40
  const Comment = Mark.create({
10
41
  name: LIVEBLOCKS_COMMENT_MARK_TYPE,
11
42
  excludes: "",
@@ -64,50 +95,45 @@ const Comment = Mark.create({
64
95
  * This plugin tracks the (first) position of each thread mark in the doc and creates a decoration for the selected thread
65
96
  */
66
97
  addProseMirrorPlugins() {
67
- const updateState = (doc, selectedThreadId) => {
98
+ const updateState = (doc, activeThreadIds, { scroll }) => {
68
99
  const threadPositions = /* @__PURE__ */ new Map();
69
100
  const decorations = [];
101
+ const activeSet = new Set(activeThreadIds);
70
102
  doc.descendants((node, pos) => {
71
- node.marks.forEach((mark) => {
72
- if (mark.type === this.type) {
73
- const thisThreadId = mark.attrs.threadId;
74
- if (!thisThreadId) {
75
- return;
76
- }
77
- const from = pos;
78
- const to = from + node.nodeSize;
79
- const currentPosition = threadPositions.get(thisThreadId) ?? {
80
- from: Infinity,
81
- to: 0
82
- };
83
- threadPositions.set(thisThreadId, {
84
- from: Math.min(from, currentPosition.from),
85
- to: Math.max(to, currentPosition.to)
86
- });
87
- if (selectedThreadId === thisThreadId) {
88
- decorations.push(
89
- Decoration.inline(from, to, {
90
- class: "lb-root lb-tiptap-thread-mark-selected"
91
- })
92
- );
93
- const decoration = this.editor.view.dom.querySelector(
94
- `.lb-tiptap-thread-mark[data-lb-thread-id="${thisThreadId}"]`
95
- );
96
- if (decoration) {
97
- decoration.scrollIntoView({
98
- behavior: "smooth",
99
- block: "nearest"
100
- });
101
- }
102
- }
103
+ for (const mark of node.marks) {
104
+ if (mark.type !== this.type) continue;
105
+ const threadId = mark.attrs.threadId;
106
+ if (!threadId) continue;
107
+ const from = pos;
108
+ const to = from + node.nodeSize;
109
+ const current = threadPositions.get(threadId) ?? {
110
+ from: Infinity,
111
+ to: 0
112
+ };
113
+ threadPositions.set(threadId, {
114
+ from: Math.min(from, current.from),
115
+ to: Math.max(to, current.to)
116
+ });
117
+ if (activeSet.has(threadId)) {
118
+ decorations.push(
119
+ Decoration.inline(from, to, {
120
+ class: "lb-root lb-tiptap-thread-mark-selected"
121
+ })
122
+ );
103
123
  }
104
- });
124
+ }
105
125
  });
126
+ if (scroll && activeThreadIds.length > 0) {
127
+ const [scrollTargetId] = activeThreadIds;
128
+ const element = this.editor.view.dom.querySelector(
129
+ `.lb-tiptap-thread-mark[data-lb-thread-id="${scrollTargetId}"]`
130
+ );
131
+ element?.scrollIntoView({ behavior: "smooth", block: "nearest" });
132
+ }
106
133
  return {
107
134
  decorations: DecorationSet.create(doc, decorations),
108
- selectedThreadId,
109
- threadPositions,
110
- selectedThreadPos: selectedThreadId !== null ? threadPositions.get(selectedThreadId)?.to ?? null : null
135
+ activeThreadIds,
136
+ threadPositions
111
137
  };
112
138
  };
113
139
  const stripCommentMarks = (slice) => {
@@ -149,8 +175,7 @@ const Comment = Mark.create({
149
175
  init() {
150
176
  return {
151
177
  threadPositions: /* @__PURE__ */ new Map(),
152
- selectedThreadId: null,
153
- selectedThreadPos: null,
178
+ activeThreadIds: [],
154
179
  decorations: DecorationSet.empty
155
180
  };
156
181
  },
@@ -160,10 +185,16 @@ const Comment = Mark.create({
160
185
  return state;
161
186
  }
162
187
  if (!action) {
163
- return updateState(tr.doc, state.selectedThreadId);
188
+ return updateState(tr.doc, state.activeThreadIds, {
189
+ scroll: false
190
+ });
164
191
  }
165
- if (action.name === ThreadPluginActions.SET_SELECTED_THREAD_ID && state.selectedThreadId !== action.data) {
166
- return updateState(tr.doc, action.data);
192
+ if (action.name === ThreadPluginActions.SET_ACTIVE_THREAD_IDS) {
193
+ const idsChanged = !shallow(action.data, state.activeThreadIds);
194
+ if (!tr.docChanged && !idsChanged) {
195
+ return state;
196
+ }
197
+ return updateState(tr.doc, action.data, { scroll: idsChanged });
167
198
  }
168
199
  return state;
169
200
  }
@@ -173,38 +204,10 @@ const Comment = Mark.create({
173
204
  return THREADS_PLUGIN_KEY.getState(state)?.decorations ?? DecorationSet.empty;
174
205
  },
175
206
  handleClick: (view, pos, event) => {
176
- if (event.button !== 0) {
177
- return;
178
- }
179
- const selectThread = (threadId2) => {
180
- view.dispatch(
181
- view.state.tr.setMeta(THREADS_PLUGIN_KEY, {
182
- name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
183
- data: threadId2
184
- })
185
- );
186
- };
187
- const node = view.state.doc.nodeAt(pos);
188
- if (!node) {
189
- selectThread(null);
190
- return;
191
- }
192
- const commentMark = node.marks.find(
193
- (mark) => mark.type === this.type && !mark.attrs.orphan
194
- );
195
- if (!commentMark) {
196
- selectThread(null);
197
- return;
198
- }
199
- const threadId = commentMark?.attrs.threadId;
200
- const filtered = FILTERED_THREADS_PLUGIN_KEY.getState(
201
- view.state
202
- )?.filteredThreads;
203
- if (threadId && filtered && !filtered.has(threadId)) {
204
- selectThread(null);
205
- return;
206
- }
207
- selectThread(threadId ?? null);
207
+ if (event.button !== 0) return;
208
+ const $pos = view.state.doc.resolve(pos);
209
+ const ids = getVisibleThreadIdsAtPos(view.state, $pos, this.type);
210
+ dispatchSetActiveThreadIds(view, ids);
208
211
  }
209
212
  }
210
213
  })
@@ -228,12 +231,7 @@ const CommentsExtension = Extension.create({
228
231
  if (this.editor.state.selection.empty) {
229
232
  return false;
230
233
  }
231
- this.editor.view.dispatch(
232
- this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {
233
- name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
234
- data: null
235
- })
236
- );
234
+ dispatchSetActiveThreadIds(this.editor.view, []);
237
235
  this.storage.pendingComment = true;
238
236
  return true;
239
237
  },
@@ -242,24 +240,9 @@ const CommentsExtension = Extension.create({
242
240
  return true;
243
241
  },
244
242
  selectThread: (id) => () => {
245
- const filtered = FILTERED_THREADS_PLUGIN_KEY.getState(
246
- this.editor.state
247
- )?.filteredThreads;
248
- if (id && filtered && !filtered.has(id)) {
249
- this.editor.view.dispatch(
250
- this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {
251
- name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
252
- data: null
253
- })
254
- );
255
- return true;
256
- }
257
- this.editor.view.dispatch(
258
- this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {
259
- name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
260
- data: id
261
- })
262
- );
243
+ const filtered = getFilteredThreads(this.editor.state);
244
+ const nextIds = id === null || filtered && !filtered.has(id) ? [] : [id];
245
+ dispatchSetActiveThreadIds(this.editor.view, nextIds);
263
246
  return true;
264
247
  },
265
248
  addComment: (id) => ({ commands }) => {
@@ -273,10 +256,21 @@ const CommentsExtension = Extension.create({
273
256
  };
274
257
  },
275
258
  onSelectionUpdate({ transaction }) {
276
- if (!this.storage.pendingComment || transaction.getMeta(ySyncPluginKey)) {
277
- return;
259
+ if (this.storage.pendingComment && !transaction.getMeta(ySyncPluginKey)) {
260
+ this.storage.pendingComment = false;
278
261
  }
279
- this.storage.pendingComment = false;
262
+ if (this.storage.pendingComment) return;
263
+ const { state } = this.editor;
264
+ const markType = state.schema.marks[LIVEBLOCKS_COMMENT_MARK_TYPE];
265
+ if (!markType) return;
266
+ const ids = getVisibleThreadIdsAtPos(
267
+ state,
268
+ state.selection.$from,
269
+ markType
270
+ );
271
+ const current = THREADS_PLUGIN_KEY.getState(state)?.activeThreadIds ?? [];
272
+ if (shallow(ids, current)) return;
273
+ dispatchSetActiveThreadIds(this.editor.view, ids);
280
274
  },
281
275
  addProseMirrorPlugins() {
282
276
  return [
@@ -340,16 +334,12 @@ const CommentsExtension = Extension.create({
340
334
  )?.filteredThreads;
341
335
  if (!areSetsEqual(prev, curr) || view2.state.doc !== prevState.doc) {
342
336
  syncDom();
343
- const selected = THREADS_PLUGIN_KEY.getState(
344
- view2.state
345
- )?.selectedThreadId;
346
- if (selected && curr && !curr.has(selected)) {
347
- view2.dispatch(
348
- view2.state.tr.setMeta(THREADS_PLUGIN_KEY, {
349
- name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
350
- data: null
351
- })
352
- );
337
+ const active = THREADS_PLUGIN_KEY.getState(view2.state)?.activeThreadIds ?? [];
338
+ if (active.length && curr) {
339
+ const next = active.filter((id) => curr.has(id));
340
+ if (next.length !== active.length) {
341
+ dispatchSetActiveThreadIds(view2, next);
342
+ }
353
343
  }
354
344
  }
355
345
  }
@@ -359,13 +349,6 @@ const CommentsExtension = Extension.create({
359
349
  ];
360
350
  }
361
351
  });
362
- function areSetsEqual(a, b) {
363
- if (a === b) return true;
364
- if (!a || !b) return false;
365
- if (a.size !== b.size) return false;
366
- for (const v of a) if (!b.has(v)) return false;
367
- return true;
368
- }
369
352
 
370
- export { CommentsExtension, FILTERED_THREADS_PLUGIN_KEY, areSetsEqual };
353
+ export { CommentsExtension, FILTERED_THREADS_PLUGIN_KEY };
371
354
  //# sourceMappingURL=CommentsExtension.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentsExtension.js","sources":["../../src/comments/CommentsExtension.ts"],"sourcesContent":["import { Extension, Mark, mergeAttributes } from \"@tiptap/core\";\nimport type { Node } from \"@tiptap/pm/model\";\nimport { Fragment, Slice } from \"@tiptap/pm/model\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport { ySyncPluginKey } from \"y-prosemirror\";\n\nimport type { CommentsExtensionStorage, ThreadPluginState } from \"../types\";\nimport {\n LIVEBLOCKS_COMMENT_MARK_TYPE,\n ThreadPluginActions,\n THREADS_ACTIVE_SELECTION_PLUGIN,\n THREADS_PLUGIN_KEY,\n} from \"../types\";\n\ntype ThreadPluginAction = {\n name: ThreadPluginActions;\n data: string | null;\n};\n\nexport const FILTERED_THREADS_PLUGIN_KEY = new PluginKey<{\n filteredThreads?: Set<string>;\n}>();\n\n/**\n * Known issues: Overlapping marks are merged when reloading the doc. May be related:\n * https://github.com/ueberdosis/tiptap/issues/4339\n * https://github.com/yjs/y-prosemirror/issues/47\n */\nconst Comment = Mark.create({\n name: LIVEBLOCKS_COMMENT_MARK_TYPE,\n excludes: \"\",\n inclusive: false,\n keepOnSplit: true,\n parseHTML: () => {\n return [\n {\n tag: \"span\",\n getAttrs: (node) =>\n node.getAttribute(\"data-lb-thread-id\") !== null && null,\n },\n ];\n },\n addAttributes() {\n // Return an object with attribute configuration\n return {\n orphan: {\n parseHTML: (element) => !!element.getAttribute(\"data-orphan\"),\n renderHTML: (attributes) => {\n return (attributes as { orphan: boolean }).orphan\n ? {\n \"data-orphan\": \"true\",\n }\n : {};\n },\n default: false,\n },\n threadId: {\n parseHTML: (element) => element.getAttribute(\"data-lb-thread-id\"),\n renderHTML: (attributes) => {\n return {\n \"data-lb-thread-id\": (attributes as { threadId: string }).threadId,\n };\n },\n default: \"\",\n },\n };\n },\n\n renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, any> }) {\n const filteredThreads = this.editor\n ? FILTERED_THREADS_PLUGIN_KEY.getState(this.editor.state)?.filteredThreads\n : undefined;\n const threadId = (HTMLAttributes as { [\"data-lb-thread-id\"]: string })[\n \"data-lb-thread-id\"\n ];\n if (filteredThreads && !filteredThreads.has(threadId)) {\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n class: \"lb-root lb-tiptap-thread-mark\",\n \"data-hidden\": \"\",\n }),\n ];\n }\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n class: \"lb-root lb-tiptap-thread-mark\",\n }),\n ];\n },\n\n /**\n * This plugin tracks the (first) position of each thread mark in the doc and creates a decoration for the selected thread\n */\n addProseMirrorPlugins() {\n const updateState = (doc: Node, selectedThreadId: string | null) => {\n const threadPositions = new Map<string, { from: number; to: number }>();\n const decorations: Decoration[] = [];\n // find all thread marks and store their position + create decoration for selected thread\n doc.descendants((node, pos) => {\n node.marks.forEach((mark) => {\n if (mark.type === this.type) {\n const thisThreadId = (\n mark.attrs as { threadId: string | undefined }\n ).threadId;\n if (!thisThreadId) {\n return;\n }\n const from = pos;\n const to = from + node.nodeSize;\n\n // FloatingThreads component uses \"to\" as the position, so always store the largest \"to\" found\n // AnchoredThreads component uses \"from\" as the position, so always store the smallest \"from\" found\n const currentPosition = threadPositions.get(thisThreadId) ?? {\n from: Infinity,\n to: 0,\n };\n threadPositions.set(thisThreadId, {\n from: Math.min(from, currentPosition.from),\n to: Math.max(to, currentPosition.to),\n });\n\n if (selectedThreadId === thisThreadId) {\n decorations.push(\n Decoration.inline(from, to, {\n class: \"lb-root lb-tiptap-thread-mark-selected\",\n })\n );\n\n const decoration = this.editor.view.dom.querySelector(\n `.lb-tiptap-thread-mark[data-lb-thread-id=\"${thisThreadId}\"]`\n );\n\n if (decoration) {\n decoration.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n });\n }\n }\n }\n });\n });\n return {\n decorations: DecorationSet.create(doc, decorations),\n selectedThreadId,\n threadPositions,\n selectedThreadPos:\n selectedThreadId !== null\n ? (threadPositions.get(selectedThreadId)?.to ?? null)\n : null,\n };\n };\n\n // Recursively walks a Fragment and removes only this extension's comment mark from every node it finds.\n const stripCommentMarks = (slice: Slice): Slice => {\n const stripFragment = (fragment: Fragment): Fragment => {\n let changed = false;\n const nodes: Node[] = [];\n\n fragment.forEach((node) => {\n // Filter out this extension's comment mark from the node's marks so that it is not copied to the clipboard\n const nextMarks = node.marks.filter(\n (mark) => mark.type !== this.type\n );\n const marksChanged = nextMarks.length !== node.marks.length;\n\n // Recursively strip comment marks from child content (e.g. inline content inside paragraphs, list items)\n const nextContent =\n node.content.childCount > 0\n ? stripFragment(node.content)\n : node.content;\n const contentChanged = nextContent !== node.content;\n\n if (marksChanged || contentChanged) {\n changed = true;\n nodes.push(\n node.isText\n ? node.mark(nextMarks)\n : node.type.create(node.attrs, nextContent, nextMarks)\n );\n } else {\n nodes.push(node);\n }\n });\n\n return changed ? Fragment.fromArray(nodes) : fragment;\n };\n\n const content = stripFragment(slice.content);\n return content === slice.content\n ? slice\n : new Slice(content, slice.openStart, slice.openEnd);\n };\n\n return [\n new Plugin({\n key: new PluginKey(\"lb-comment-clipboard\"),\n props: {\n transformCopied: (slice) => stripCommentMarks(slice),\n transformPasted: (slice) => stripCommentMarks(slice),\n },\n }),\n new Plugin({\n key: THREADS_PLUGIN_KEY,\n state: {\n init() {\n return {\n threadPositions: new Map<string, { from: number; to: number }>(),\n selectedThreadId: null,\n selectedThreadPos: null,\n decorations: DecorationSet.empty,\n } as ThreadPluginState;\n },\n apply(tr, state) {\n const action = tr.getMeta(THREADS_PLUGIN_KEY) as ThreadPluginAction;\n if (!tr.docChanged && !action) {\n return state;\n }\n\n if (!action) {\n // Doc changed, but no action, just update rects\n return updateState(tr.doc, state.selectedThreadId);\n }\n // handle actions, possibly support more actions\n if (\n action.name === ThreadPluginActions.SET_SELECTED_THREAD_ID &&\n state.selectedThreadId !== action.data\n ) {\n return updateState(tr.doc, action.data);\n }\n\n return state;\n },\n },\n props: {\n decorations: (state) => {\n return (\n THREADS_PLUGIN_KEY.getState(state)?.decorations ??\n DecorationSet.empty\n );\n },\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return;\n }\n\n const selectThread = (threadId: string | null) => {\n view.dispatch(\n view.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: threadId,\n })\n );\n };\n\n const node = view.state.doc.nodeAt(pos);\n if (!node) {\n selectThread(null);\n return;\n }\n const commentMark = node.marks.find(\n (mark) => mark.type === this.type && !mark.attrs.orphan\n );\n // nothing to select\n if (!commentMark) {\n selectThread(null);\n return;\n }\n const threadId = commentMark?.attrs.threadId as string | undefined;\n\n const filtered = FILTERED_THREADS_PLUGIN_KEY.getState(\n view.state\n )?.filteredThreads;\n if (threadId && filtered && !filtered.has(threadId)) {\n selectThread(null);\n return;\n }\n\n selectThread(threadId ?? null);\n },\n },\n }),\n ];\n },\n});\n\nexport const CommentsExtension = Extension.create<\n { filteredThreads?: Set<string> },\n CommentsExtensionStorage\n>({\n name: \"liveblocksComments\",\n priority: 95,\n addExtensions() {\n return [Comment];\n },\n\n addStorage() {\n return {\n pendingComment: false,\n };\n },\n\n addCommands() {\n return {\n addPendingComment: () => () => {\n if (this.editor.state.selection.empty) {\n return false;\n }\n // unselect any open threads\n this.editor.view.dispatch(\n this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: null,\n })\n );\n this.storage.pendingComment = true;\n return true;\n },\n closePendingComment: () => () => {\n this.storage.pendingComment = false;\n return true;\n },\n selectThread: (id: string | null) => () => {\n const filtered = FILTERED_THREADS_PLUGIN_KEY.getState(\n this.editor.state\n )?.filteredThreads;\n if (id && filtered && !filtered.has(id)) {\n this.editor.view.dispatch(\n this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: null,\n })\n );\n return true;\n }\n\n this.editor.view.dispatch(\n this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: id,\n })\n );\n return true;\n },\n addComment:\n (id: string) =>\n ({ commands }) => {\n if (\n !this.storage.pendingComment ||\n this.editor.state.selection.empty\n ) {\n return false;\n }\n commands.setMark(LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });\n this.storage.pendingComment = false;\n return true;\n },\n };\n },\n onSelectionUpdate(\n this: { storage: CommentsExtensionStorage }, // NOTE: there are more types here I didn't override, this gets removed after submitting PR to tiptap\n { transaction }: { transaction: Transaction } // TODO: remove this after submitting PR to tiptap\n ) {\n // ignore changes made by yjs\n if (!this.storage.pendingComment || transaction.getMeta(ySyncPluginKey)) {\n return;\n }\n // if selection changes, hide the composer. We could keep the composer open and move it to the new selection?\n this.storage.pendingComment = false;\n },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: THREADS_ACTIVE_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc, selection }) => {\n if (!this.storage.pendingComment) {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = selection;\n const decorations: Decoration[] = [\n Decoration.inline(from, to, {\n class: \"lb-root lb-selection lb-tiptap-active-selection\",\n }),\n ];\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n new Plugin({\n key: FILTERED_THREADS_PLUGIN_KEY,\n state: {\n init: () => ({\n filteredThreads: this.options.filteredThreads,\n }),\n apply(tr, value) {\n const meta = tr.getMeta(FILTERED_THREADS_PLUGIN_KEY) as\n | { filteredThreads?: Set<string> }\n | undefined;\n if (meta?.filteredThreads) {\n return { filteredThreads: meta.filteredThreads };\n }\n return value;\n },\n },\n view: (view) => {\n const syncDom = () => {\n const filteredThreads = FILTERED_THREADS_PLUGIN_KEY.getState(\n view.state\n )?.filteredThreads;\n\n // Toggle attribute for all comment-mark spans\n const els = view.dom.querySelectorAll<HTMLElement>(\n \"span.lb-tiptap-thread-mark[data-lb-thread-id]\"\n );\n els.forEach((el) => {\n const id = el.getAttribute(\"data-lb-thread-id\");\n if (!id) return;\n if (!filteredThreads || filteredThreads.has(id)) {\n el.removeAttribute(\"data-hidden\");\n } else {\n el.setAttribute(\"data-hidden\", \"\");\n }\n });\n };\n\n queueMicrotask(syncDom);\n\n return {\n update: (view, prevState) => {\n const curr = FILTERED_THREADS_PLUGIN_KEY.getState(\n view.state\n )?.filteredThreads;\n const prev =\n FILTERED_THREADS_PLUGIN_KEY.getState(\n prevState\n )?.filteredThreads;\n\n if (\n !areSetsEqual(prev, curr) ||\n view.state.doc !== prevState.doc\n ) {\n syncDom();\n\n const selected = THREADS_PLUGIN_KEY.getState(\n view.state\n )?.selectedThreadId;\n if (selected && curr && !curr.has(selected)) {\n view.dispatch(\n view.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_SELECTED_THREAD_ID,\n data: null,\n })\n );\n }\n }\n },\n };\n },\n }),\n ];\n },\n});\n\nexport function areSetsEqual(a?: Set<string>, b?: Set<string>): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n"],"names":["threadId","view"],"mappings":"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"}
1
+ {"version":3,"file":"CommentsExtension.js","sources":["../../src/comments/CommentsExtension.ts"],"sourcesContent":["import { shallow } from \"@liveblocks/core\";\nimport { type Editor, Extension, Mark, mergeAttributes } from \"@tiptap/core\";\nimport type {\n Mark as ProseMirrorMark,\n MarkType,\n Node,\n ResolvedPos,\n} from \"@tiptap/pm/model\";\nimport { Fragment, Slice } from \"@tiptap/pm/model\";\nimport type { EditorState, Transaction } from \"@tiptap/pm/state\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { EditorView } from \"@tiptap/pm/view\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport { ySyncPluginKey } from \"y-prosemirror\";\n\nimport type { CommentsExtensionStorage, ThreadPluginState } from \"../types\";\nimport {\n LIVEBLOCKS_COMMENT_MARK_TYPE,\n ThreadPluginActions,\n THREADS_ACTIVE_SELECTION_PLUGIN,\n THREADS_PLUGIN_KEY,\n} from \"../types\";\nimport { areSetsEqual } from \"../utils\";\n\ntype ThreadPluginAction = {\n name: ThreadPluginActions;\n data: string[];\n};\n\nexport const FILTERED_THREADS_PLUGIN_KEY = new PluginKey<{\n filteredThreads?: Set<string>;\n}>();\n\nfunction getFilteredThreads(state: EditorState): Set<string> | undefined {\n return FILTERED_THREADS_PLUGIN_KEY.getState(state)?.filteredThreads;\n}\n\nfunction getVisibleThreadIdsFromMarks(\n marks: readonly ProseMirrorMark[],\n markType: MarkType,\n filteredThreads: Set<string> | undefined\n): string[] {\n const ids = new Set<string>();\n for (const mark of marks) {\n if (mark.type !== markType || mark.attrs.orphan) continue;\n const threadId = mark.attrs.threadId as string | undefined;\n if (!threadId) continue;\n if (filteredThreads && !filteredThreads.has(threadId)) continue;\n ids.add(threadId);\n }\n return [...ids];\n}\n\nfunction getVisibleThreadIdsAtPos(\n state: EditorState,\n $pos: ResolvedPos,\n markType: MarkType\n): string[] {\n return getVisibleThreadIdsFromMarks(\n $pos.marks(),\n markType,\n getFilteredThreads(state)\n );\n}\n\nfunction dispatchSetActiveThreadIds(view: EditorView, ids: string[]): void {\n view.dispatch(\n view.state.tr.setMeta(THREADS_PLUGIN_KEY, {\n name: ThreadPluginActions.SET_ACTIVE_THREAD_IDS,\n data: ids,\n } satisfies ThreadPluginAction)\n );\n}\n\nconst Comment = Mark.create({\n name: LIVEBLOCKS_COMMENT_MARK_TYPE,\n excludes: \"\",\n inclusive: false,\n keepOnSplit: true,\n parseHTML: () => {\n return [\n {\n tag: \"span\",\n getAttrs: (node) =>\n node.getAttribute(\"data-lb-thread-id\") !== null && null,\n },\n ];\n },\n addAttributes() {\n // Return an object with attribute configuration\n return {\n orphan: {\n parseHTML: (element) => !!element.getAttribute(\"data-orphan\"),\n renderHTML: (attributes) => {\n return (attributes as { orphan: boolean }).orphan\n ? {\n \"data-orphan\": \"true\",\n }\n : {};\n },\n default: false,\n },\n threadId: {\n parseHTML: (element) => element.getAttribute(\"data-lb-thread-id\"),\n renderHTML: (attributes) => {\n return {\n \"data-lb-thread-id\": (attributes as { threadId: string }).threadId,\n };\n },\n default: \"\",\n },\n };\n },\n\n renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, any> }) {\n const filteredThreads = this.editor\n ? FILTERED_THREADS_PLUGIN_KEY.getState(this.editor.state)?.filteredThreads\n : undefined;\n const threadId = (HTMLAttributes as { [\"data-lb-thread-id\"]: string })[\n \"data-lb-thread-id\"\n ];\n if (filteredThreads && !filteredThreads.has(threadId)) {\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n class: \"lb-root lb-tiptap-thread-mark\",\n \"data-hidden\": \"\",\n }),\n ];\n }\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n class: \"lb-root lb-tiptap-thread-mark\",\n }),\n ];\n },\n\n /**\n * This plugin tracks the (first) position of each thread mark in the doc and creates a decoration for the selected thread\n */\n addProseMirrorPlugins() {\n const updateState = (\n doc: Node,\n activeThreadIds: string[],\n { scroll }: { scroll: boolean }\n ): ThreadPluginState => {\n const threadPositions = new Map<string, { from: number; to: number }>();\n const decorations: Decoration[] = [];\n const activeSet = new Set(activeThreadIds);\n\n doc.descendants((node, pos) => {\n for (const mark of node.marks) {\n if (mark.type !== this.type) continue;\n\n const threadId = (mark.attrs as { threadId?: string }).threadId;\n if (!threadId) continue;\n\n const from = pos;\n const to = from + node.nodeSize;\n\n // FloatingThreads component uses \"to\" as the position, so we always store the largest \"to\" found.\n // AnchoredThreads component uses \"from\" as the position, so we always store the smallest \"from\" found.\n const current = threadPositions.get(threadId) ?? {\n from: Infinity,\n to: 0,\n };\n threadPositions.set(threadId, {\n from: Math.min(from, current.from),\n to: Math.max(to, current.to),\n });\n\n if (activeSet.has(threadId)) {\n decorations.push(\n Decoration.inline(from, to, {\n class: \"lb-root lb-tiptap-thread-mark-selected\",\n })\n );\n }\n }\n });\n\n // Only scroll when the active selection explicitly changes.\n if (scroll && activeThreadIds.length > 0) {\n const [scrollTargetId] = activeThreadIds;\n const element = this.editor.view.dom.querySelector(\n `.lb-tiptap-thread-mark[data-lb-thread-id=\"${scrollTargetId}\"]`\n );\n element?.scrollIntoView({ behavior: \"smooth\", block: \"nearest\" });\n }\n\n return {\n decorations: DecorationSet.create(doc, decorations),\n activeThreadIds,\n threadPositions,\n };\n };\n\n // Recursively walks a Fragment and removes only this extension's comment mark from every node it finds.\n const stripCommentMarks = (slice: Slice): Slice => {\n const stripFragment = (fragment: Fragment): Fragment => {\n let changed = false;\n const nodes: Node[] = [];\n\n fragment.forEach((node) => {\n // Filter out this extension's comment mark from the node's marks so that it is not copied to the clipboard\n const nextMarks = node.marks.filter(\n (mark) => mark.type !== this.type\n );\n const marksChanged = nextMarks.length !== node.marks.length;\n\n // Recursively strip comment marks from child content (e.g. inline content inside paragraphs, list items)\n const nextContent =\n node.content.childCount > 0\n ? stripFragment(node.content)\n : node.content;\n const contentChanged = nextContent !== node.content;\n\n if (marksChanged || contentChanged) {\n changed = true;\n nodes.push(\n node.isText\n ? node.mark(nextMarks)\n : node.type.create(node.attrs, nextContent, nextMarks)\n );\n } else {\n nodes.push(node);\n }\n });\n\n return changed ? Fragment.fromArray(nodes) : fragment;\n };\n\n const content = stripFragment(slice.content);\n return content === slice.content\n ? slice\n : new Slice(content, slice.openStart, slice.openEnd);\n };\n\n return [\n new Plugin({\n key: new PluginKey(\"lb-comment-clipboard\"),\n props: {\n transformCopied: (slice) => stripCommentMarks(slice),\n transformPasted: (slice) => stripCommentMarks(slice),\n },\n }),\n new Plugin({\n key: THREADS_PLUGIN_KEY,\n state: {\n init(): ThreadPluginState {\n return {\n threadPositions: new Map(),\n activeThreadIds: [],\n decorations: DecorationSet.empty,\n };\n },\n apply(tr, state) {\n const action = tr.getMeta(THREADS_PLUGIN_KEY) as\n | ThreadPluginAction\n | undefined;\n\n if (!tr.docChanged && !action) {\n return state;\n }\n\n if (!action) {\n return updateState(tr.doc, state.activeThreadIds, {\n scroll: false,\n });\n }\n\n if (action.name === ThreadPluginActions.SET_ACTIVE_THREAD_IDS) {\n const idsChanged = !shallow(action.data, state.activeThreadIds);\n if (!tr.docChanged && !idsChanged) {\n return state;\n }\n return updateState(tr.doc, action.data, { scroll: idsChanged });\n }\n\n return state;\n },\n },\n props: {\n decorations: (state) => {\n return (\n THREADS_PLUGIN_KEY.getState(state)?.decorations ??\n DecorationSet.empty\n );\n },\n handleClick: (view, pos, event) => {\n if (event.button !== 0) return;\n\n const $pos = view.state.doc.resolve(pos);\n const ids = getVisibleThreadIdsAtPos(view.state, $pos, this.type);\n dispatchSetActiveThreadIds(view, ids);\n },\n },\n }),\n ];\n },\n});\n\nexport const CommentsExtension = Extension.create<\n { filteredThreads?: Set<string> },\n CommentsExtensionStorage\n>({\n name: \"liveblocksComments\",\n priority: 95,\n addExtensions() {\n return [Comment];\n },\n\n addStorage() {\n return {\n pendingComment: false,\n };\n },\n\n addCommands() {\n return {\n addPendingComment: () => () => {\n if (this.editor.state.selection.empty) {\n return false;\n }\n // Unselect any open threads.\n dispatchSetActiveThreadIds(this.editor.view, []);\n this.storage.pendingComment = true;\n return true;\n },\n closePendingComment: () => () => {\n this.storage.pendingComment = false;\n return true;\n },\n selectThread: (id: string | null) => () => {\n // If the target thread is filtered out, clear the active selection\n // instead of selecting an invisible thread.\n const filtered = getFilteredThreads(this.editor.state);\n const nextIds =\n id === null || (filtered && !filtered.has(id)) ? [] : [id];\n\n dispatchSetActiveThreadIds(this.editor.view, nextIds);\n return true;\n },\n addComment:\n (id: string) =>\n ({ commands }) => {\n if (\n !this.storage.pendingComment ||\n this.editor.state.selection.empty\n ) {\n return false;\n }\n commands.setMark(LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });\n this.storage.pendingComment = false;\n return true;\n },\n };\n },\n onSelectionUpdate(\n this: { storage: CommentsExtensionStorage; editor: Editor },\n { transaction }: { transaction: Transaction }\n ) {\n // Close any pending composer when the user moves the selection locally\n // (but ignore remote Yjs-driven selection changes).\n if (this.storage.pendingComment && !transaction.getMeta(ySyncPluginKey)) {\n this.storage.pendingComment = false;\n }\n\n if (this.storage.pendingComment) return;\n\n const { state } = this.editor;\n const markType = state.schema.marks[LIVEBLOCKS_COMMENT_MARK_TYPE];\n if (!markType) return;\n\n const ids = getVisibleThreadIdsAtPos(\n state,\n state.selection.$from,\n markType\n );\n const current = THREADS_PLUGIN_KEY.getState(state)?.activeThreadIds ?? [];\n if (shallow(ids, current)) return;\n\n dispatchSetActiveThreadIds(this.editor.view, ids);\n },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: THREADS_ACTIVE_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc, selection }) => {\n if (!this.storage.pendingComment) {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = selection;\n const decorations: Decoration[] = [\n Decoration.inline(from, to, {\n class: \"lb-root lb-selection lb-tiptap-active-selection\",\n }),\n ];\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n new Plugin({\n key: FILTERED_THREADS_PLUGIN_KEY,\n state: {\n init: () => ({\n filteredThreads: this.options.filteredThreads,\n }),\n apply(tr, value) {\n const meta = tr.getMeta(FILTERED_THREADS_PLUGIN_KEY) as\n | { filteredThreads?: Set<string> }\n | undefined;\n if (meta?.filteredThreads) {\n return { filteredThreads: meta.filteredThreads };\n }\n return value;\n },\n },\n view: (view) => {\n const syncDom = () => {\n const filteredThreads = FILTERED_THREADS_PLUGIN_KEY.getState(\n view.state\n )?.filteredThreads;\n\n // Toggle attribute for all comment-mark spans\n const els = view.dom.querySelectorAll<HTMLElement>(\n \"span.lb-tiptap-thread-mark[data-lb-thread-id]\"\n );\n els.forEach((el) => {\n const id = el.getAttribute(\"data-lb-thread-id\");\n if (!id) return;\n if (!filteredThreads || filteredThreads.has(id)) {\n el.removeAttribute(\"data-hidden\");\n } else {\n el.setAttribute(\"data-hidden\", \"\");\n }\n });\n };\n\n queueMicrotask(syncDom);\n\n return {\n update: (view, prevState) => {\n const curr = FILTERED_THREADS_PLUGIN_KEY.getState(\n view.state\n )?.filteredThreads;\n const prev =\n FILTERED_THREADS_PLUGIN_KEY.getState(\n prevState\n )?.filteredThreads;\n\n if (\n !areSetsEqual(prev, curr) ||\n view.state.doc !== prevState.doc\n ) {\n syncDom();\n\n const active =\n THREADS_PLUGIN_KEY.getState(view.state)?.activeThreadIds ??\n [];\n\n if (active.length && curr) {\n const next = active.filter((id) => curr.has(id));\n if (next.length !== active.length) {\n dispatchSetActiveThreadIds(view, next);\n }\n }\n }\n },\n };\n },\n }),\n ];\n },\n});\n"],"names":["view"],"mappings":"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"}
@@ -2,12 +2,14 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var reactDom = require('@floating-ui/react-dom');
5
+ var core = require('@liveblocks/core');
5
6
  var _private$1 = require('@liveblocks/react/_private');
6
7
  var reactUi = require('@liveblocks/react-ui');
7
8
  var _private = require('@liveblocks/react-ui/_private');
8
9
  var react = require('@tiptap/react');
9
10
  var react$1 = require('react');
10
11
  var types = require('../types.cjs');
12
+ var utils = require('../utils.cjs');
11
13
 
12
14
  function FloatingThreads({
13
15
  threads,
@@ -16,57 +18,84 @@ function FloatingThreads({
16
18
  ...props
17
19
  }) {
18
20
  const Thread = _private.useStableComponent(components?.Thread, reactUi.Thread);
19
- const { pluginState } = react.useEditorState({
21
+ const activeThreadIds = react.useEditorState({
20
22
  editor,
21
23
  selector: (ctx) => {
22
- if (!ctx?.editor?.state) return { pluginState: void 0 };
24
+ if (!ctx?.editor?.state) {
25
+ return void 0;
26
+ }
23
27
  const state = types.THREADS_PLUGIN_KEY.getState(ctx.editor.state);
24
- return {
25
- pluginState: state
26
- };
28
+ return state?.activeThreadIds;
27
29
  },
28
30
  equalityFn: (prev, next) => {
29
31
  if (!prev || !next) return false;
30
- return prev.pluginState?.selectedThreadPos === next.pluginState?.selectedThreadPos && prev.pluginState?.selectedThreadId === next.pluginState?.selectedThreadId;
32
+ return core.shallow(prev, next);
31
33
  }
32
- }) ?? { pluginState: void 0 };
33
- const [activeThread, setActiveThread] = react$1.useState(null);
34
- react$1.useEffect(() => {
35
- if (!editor || !pluginState) {
36
- setActiveThread(null);
34
+ }) ?? void 0;
35
+ const [range, setRange] = react$1.useState(null);
36
+ const handleUpdateRange = react$1.useCallback(() => {
37
+ if (!editor || !editor.view || editor.view.isDestroyed || !activeThreadIds) {
38
+ setRange(null);
37
39
  return;
38
40
  }
39
- const { selectedThreadId, selectedThreadPos } = pluginState;
40
- if (selectedThreadId === null || selectedThreadPos === null) {
41
- setActiveThread(null);
41
+ if (activeThreadIds.length === 0) {
42
+ setRange(null);
42
43
  return;
43
44
  }
44
- const active = (threads ?? []).find(
45
- (thread) => selectedThreadId === thread.id
45
+ const activeThreads = (threads ?? []).filter(
46
+ (thread) => activeThreadIds.includes(thread.id) && !thread.resolved
46
47
  );
47
- setActiveThread(active ?? null);
48
- }, [editor, pluginState, threads]);
48
+ if (activeThreads.length === 0) {
49
+ setRange(null);
50
+ return;
51
+ }
52
+ const elements = /* @__PURE__ */ new Set();
53
+ for (const id of activeThreadIds) {
54
+ const els = editor.view.dom.querySelectorAll(
55
+ `span.lb-tiptap-thread-mark[data-lb-thread-id="${id}"]`
56
+ );
57
+ els.forEach((el) => elements.add(el));
58
+ }
59
+ const sorted = Array.from(elements).sort(utils.compareDocumentPosition);
60
+ if (sorted.length === 0) {
61
+ setRange(null);
62
+ return;
63
+ }
64
+ const domRange = document.createRange();
65
+ domRange.setStartBefore(sorted[0]);
66
+ domRange.setEndAfter(sorted[sorted.length - 1]);
67
+ setRange({ range: domRange, threads: activeThreads });
68
+ }, [editor, activeThreadIds, threads]);
69
+ react$1.useEffect(() => {
70
+ if (!editor) return;
71
+ editor.on("transaction", handleUpdateRange);
72
+ return () => {
73
+ editor.off("transaction", handleUpdateRange);
74
+ };
75
+ }, [editor, handleUpdateRange]);
76
+ _private$1.useLayoutEffect(handleUpdateRange, [handleUpdateRange]);
49
77
  const handleEscapeKeydown = react$1.useCallback(() => {
50
- if (!editor || activeThread === null) return false;
78
+ if (!editor || range === null) return false;
51
79
  editor.commands.selectThread(null);
52
80
  return true;
53
- }, [activeThread, editor]);
54
- if (!activeThread || !editor || activeThread.resolved) return null;
55
- return /* @__PURE__ */ jsxRuntime.jsx(FloatingThreadPortal, { thread: activeThread, editor, ...props, children: activeThread && /* @__PURE__ */ jsxRuntime.jsx(
81
+ }, [editor, range]);
82
+ if (range === null) {
83
+ return null;
84
+ }
85
+ return /* @__PURE__ */ jsxRuntime.jsx(FloatingThreadPortal, { range: range.range, ...props, children: range.threads.map((thread) => /* @__PURE__ */ jsxRuntime.jsx(
56
86
  ThreadWrapper,
57
87
  {
58
- thread: activeThread,
88
+ thread,
59
89
  Thread,
60
90
  onEscapeKeydown: handleEscapeKeydown,
61
91
  className: "lb-tiptap-floating-threads-thread"
62
92
  },
63
- activeThread.id
64
- ) });
93
+ thread.id
94
+ )) });
65
95
  }
66
96
  const FLOATING_THREAD_COLLISION_PADDING = 10;
67
97
  function FloatingThreadPortal({
68
- editor,
69
- thread,
98
+ range,
70
99
  children,
71
100
  className,
72
101
  style,
@@ -108,21 +137,11 @@ function FloatingThreadPortal({
108
137
  });
109
138
  }
110
139
  });
111
- const updateRef = react$1.useCallback(() => {
112
- const el = editor.view.dom.querySelector(
113
- `[data-lb-thread-id="${thread.id}"]`
114
- );
115
- if (el) {
116
- setReference(el);
117
- }
118
- }, [setReference, editor, thread.id]);
119
- react$1.useEffect(() => {
120
- editor.on("transaction", updateRef);
121
- return () => {
122
- editor.off("transaction", updateRef);
123
- };
124
- }, [editor, updateRef]);
125
- _private$1.useLayoutEffect(updateRef, [updateRef]);
140
+ _private$1.useLayoutEffect(() => {
141
+ setReference({
142
+ getBoundingClientRect: () => range.getBoundingClientRect()
143
+ });
144
+ }, [setReference, range]);
126
145
  return /* @__PURE__ */ jsxRuntime.jsx(_private.Portal, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
127
146
  "div",
128
147
  {
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingThreads.cjs","sources":["../../src/comments/FloatingThreads.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n hide,\n limitShift,\n offset,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { BaseMetadata, DCM, DTM, ThreadData } from \"@liveblocks/core\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n Thread as DefaultThread,\n type ThreadProps,\n} from \"@liveblocks/react-ui\";\nimport { cn, Portal, useStableComponent } from \"@liveblocks/react-ui/_private\";\nimport { type Editor, useEditorState } from \"@tiptap/react\";\nimport {\n type ComponentType,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useState,\n} from \"react\";\n\nimport { THREADS_PLUGIN_KEY } from \"../types\";\n\ntype FloatingThreadsComponents = {\n Thread: ComponentType<ThreadProps>;\n};\n\nexport interface FloatingThreadsProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * The threads to display.\n */\n threads: ThreadData<TM, CM>[];\n\n /**\n * Override the component's components.\n */\n components?: Partial<FloatingThreadsComponents>;\n\n /**\n * The Tiptap editor.\n */\n editor: Editor | null;\n}\n\nexport function FloatingThreads({\n threads,\n components,\n editor,\n ...props\n}: FloatingThreadsProps) {\n const Thread = useStableComponent(components?.Thread, DefaultThread);\n\n const { pluginState } = useEditorState({\n editor,\n selector: (ctx) => {\n if (!ctx?.editor?.state) return { pluginState: undefined };\n const state = THREADS_PLUGIN_KEY.getState(ctx.editor.state);\n return {\n pluginState: state,\n };\n },\n equalityFn: (prev, next) => {\n if (!prev || !next) return false;\n return (\n prev.pluginState?.selectedThreadPos ===\n next.pluginState?.selectedThreadPos &&\n prev.pluginState?.selectedThreadId ===\n next.pluginState?.selectedThreadId\n );\n },\n }) ?? { pluginState: undefined };\n\n const [activeThread, setActiveThread] = useState<ThreadData | null>(null);\n\n useEffect(() => {\n if (!editor || !pluginState) {\n setActiveThread(null);\n return;\n }\n const { selectedThreadId, selectedThreadPos } = pluginState;\n if (selectedThreadId === null || selectedThreadPos === null) {\n setActiveThread(null);\n return;\n }\n const active = (threads ?? []).find(\n (thread) => selectedThreadId === thread.id\n );\n setActiveThread(active ?? null);\n }, [editor, pluginState, threads]);\n\n const handleEscapeKeydown = useCallback((): boolean => {\n if (!editor || activeThread === null) return false;\n editor.commands.selectThread(null);\n return true;\n }, [activeThread, editor]);\n\n if (!activeThread || !editor || activeThread.resolved) return null;\n\n return (\n <FloatingThreadPortal thread={activeThread} editor={editor} {...props}>\n {activeThread && (\n <ThreadWrapper\n key={activeThread.id}\n thread={activeThread}\n Thread={Thread}\n onEscapeKeydown={handleEscapeKeydown}\n className=\"lb-tiptap-floating-threads-thread\"\n />\n )}\n </FloatingThreadPortal>\n );\n}\n\ninterface FloatingThreadPortalProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\"\n> {\n thread: ThreadData;\n editor: Editor;\n children: ReactNode;\n}\n\nexport const FLOATING_THREAD_COLLISION_PADDING = 10;\n\nfunction FloatingThreadPortal({\n editor,\n thread,\n children,\n className,\n style,\n ...props\n}: FloatingThreadPortalProps) {\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"absolute\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),\n shift({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n const updateRef = useCallback(() => {\n const el = editor.view.dom.querySelector(\n `[data-lb-thread-id=\"${thread.id}\"]`\n );\n if (el) {\n setReference(el);\n }\n }, [setReference, editor, thread.id]);\n\n // Remote cursor updates and other edits can cause the ref to break\n useEffect(() => {\n editor.on(\"transaction\", updateRef);\n return () => {\n editor.off(\"transaction\", updateRef);\n };\n }, [editor, updateRef]);\n\n useLayoutEffect(updateRef, [updateRef]);\n\n return (\n <Portal asChild>\n <div\n ref={setFloating}\n {...props}\n style={{\n ...style,\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}\n className={cn(\n \"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads\",\n className\n )}\n >\n {children}\n </div>\n </Portal>\n );\n}\n\ninterface ThreadWrapperProps extends ThreadProps {\n thread: ThreadData;\n Thread: ComponentType<ThreadProps>;\n onEscapeKeydown: () => void;\n}\n\nfunction ThreadWrapper({\n thread,\n Thread,\n onEscapeKeydown,\n onKeyDown,\n ...threadProps\n}: ThreadWrapperProps) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n // TODO: Add ability to preventDefault on keydown to override the default behavior, e.g. to show an alert dialog\n if (event.key === \"Escape\") {\n onEscapeKeydown();\n }\n },\n [onEscapeKeydown, onKeyDown]\n );\n\n return <Thread thread={thread} onKeyDown={handleKeyDown} {...threadProps} />;\n}\n"],"names":["useStableComponent","DefaultThread","useEditorState","THREADS_PLUGIN_KEY","useState","useEffect","useCallback","jsx","useFloating","flip","offset","hide","shift","limitShift","size","autoUpdate","useLayoutEffect","Portal","cn"],"mappings":"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"}
1
+ {"version":3,"file":"FloatingThreads.cjs","sources":["../../src/comments/FloatingThreads.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n hide,\n limitShift,\n offset,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport {\n type BaseMetadata,\n type DCM,\n type DTM,\n shallow,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n Thread as DefaultThread,\n type ThreadProps,\n} from \"@liveblocks/react-ui\";\nimport { cn, Portal, useStableComponent } from \"@liveblocks/react-ui/_private\";\nimport { type Editor, useEditorState } from \"@tiptap/react\";\nimport {\n type ComponentType,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useState,\n} from \"react\";\n\nimport { THREADS_PLUGIN_KEY } from \"../types\";\nimport { compareDocumentPosition } from \"../utils\";\n\ntype FloatingThreadsComponents = {\n Thread: ComponentType<ThreadProps>;\n};\n\nexport interface FloatingThreadsProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * The threads to display.\n */\n threads: ThreadData<TM, CM>[];\n\n /**\n * Override the component's components.\n */\n components?: Partial<FloatingThreadsComponents>;\n\n /**\n * The Tiptap editor.\n */\n editor: Editor | null;\n}\n\nexport function FloatingThreads({\n threads,\n components,\n editor,\n ...props\n}: FloatingThreadsProps) {\n const Thread = useStableComponent(components?.Thread, DefaultThread);\n\n const activeThreadIds =\n useEditorState({\n editor,\n selector: (ctx) => {\n if (!ctx?.editor?.state) {\n return undefined;\n }\n\n const state = THREADS_PLUGIN_KEY.getState(ctx.editor.state);\n\n return state?.activeThreadIds;\n },\n equalityFn: (prev, next) => {\n if (!prev || !next) return false;\n return shallow(prev, next);\n },\n }) ?? undefined;\n\n const [range, setRange] = useState<{\n range: Range;\n threads: ThreadData[];\n } | null>(null);\n\n const handleUpdateRange = useCallback(() => {\n if (\n !editor ||\n !editor.view ||\n editor.view.isDestroyed ||\n !activeThreadIds\n ) {\n setRange(null);\n return;\n }\n\n if (activeThreadIds.length === 0) {\n setRange(null);\n return;\n }\n\n const activeThreads = (threads ?? []).filter(\n (thread) => activeThreadIds.includes(thread.id) && !thread.resolved\n );\n if (activeThreads.length === 0) {\n setRange(null);\n return;\n }\n\n // A thread mark can be split across multiple DOM elements (e.g. when\n // overlapping with another mark), so we collect every matching element and\n // build a DOM range spanning from the first to the last one.\n const elements = new Set<HTMLElement>();\n for (const id of activeThreadIds) {\n const els = editor.view.dom.querySelectorAll<HTMLElement>(\n `span.lb-tiptap-thread-mark[data-lb-thread-id=\"${id}\"]`\n );\n els.forEach((el) => elements.add(el));\n }\n\n const sorted = Array.from(elements).sort(compareDocumentPosition);\n if (sorted.length === 0) {\n setRange(null);\n return;\n }\n\n const domRange = document.createRange();\n domRange.setStartBefore(sorted[0]);\n domRange.setEndAfter(sorted[sorted.length - 1]);\n setRange({ range: domRange, threads: activeThreads });\n }, [editor, activeThreadIds, threads]);\n\n // Remote cursor updates and other edits can shift the underlying DOM\n // elements, so we recompute the range on every change.\n useEffect(() => {\n if (!editor) return;\n editor.on(\"transaction\", handleUpdateRange);\n return () => {\n editor.off(\"transaction\", handleUpdateRange);\n };\n }, [editor, handleUpdateRange]);\n\n useLayoutEffect(handleUpdateRange, [handleUpdateRange]);\n\n const handleEscapeKeydown = useCallback((): boolean => {\n if (!editor || range === null) return false;\n editor.commands.selectThread(null);\n return true;\n }, [editor, range]);\n\n if (range === null) {\n return null;\n }\n\n return (\n <FloatingThreadPortal range={range.range} {...props}>\n {range.threads.map((thread) => (\n <ThreadWrapper\n key={thread.id}\n thread={thread}\n Thread={Thread}\n onEscapeKeydown={handleEscapeKeydown}\n className=\"lb-tiptap-floating-threads-thread\"\n />\n ))}\n </FloatingThreadPortal>\n );\n}\n\ninterface FloatingThreadPortalProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\"\n> {\n range: Range;\n children: ReactNode;\n}\n\nexport const FLOATING_THREAD_COLLISION_PADDING = 10;\n\nfunction FloatingThreadPortal({\n range,\n children,\n className,\n style,\n ...props\n}: FloatingThreadPortalProps) {\n const {\n refs: { setReference, setFloating },\n strategy,\n x,\n y,\n } = useFloating({\n strategy: \"absolute\",\n placement: \"bottom\",\n middleware: [\n flip({ padding: FLOATING_THREAD_COLLISION_PADDING, crossAxis: false }),\n offset(10),\n hide({ padding: FLOATING_THREAD_COLLISION_PADDING }),\n shift({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n limiter: limitShift(),\n }),\n size({\n padding: FLOATING_THREAD_COLLISION_PADDING,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-tiptap-floating-threads-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: (...args) => {\n return autoUpdate(...args, {\n animationFrame: true,\n });\n },\n });\n\n useLayoutEffect(() => {\n setReference({\n getBoundingClientRect: () => range.getBoundingClientRect(),\n });\n }, [setReference, range]);\n\n return (\n <Portal asChild>\n <div\n ref={setFloating}\n {...props}\n style={{\n ...style,\n position: strategy,\n top: 0,\n left: 0,\n transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,\n minWidth: \"max-content\",\n }}\n className={cn(\n \"lb-root lb-portal lb-elevation lb-tiptap-floating lb-tiptap-floating-threads\",\n className\n )}\n >\n {children}\n </div>\n </Portal>\n );\n}\n\ninterface ThreadWrapperProps extends ThreadProps {\n thread: ThreadData;\n Thread: ComponentType<ThreadProps>;\n onEscapeKeydown: () => void;\n}\n\nfunction ThreadWrapper({\n thread,\n Thread,\n onEscapeKeydown,\n onKeyDown,\n ...threadProps\n}: ThreadWrapperProps) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n // TODO: Add ability to preventDefault on keydown to override the default behavior, e.g. to show an alert dialog\n if (event.key === \"Escape\") {\n onEscapeKeydown();\n }\n },\n [onEscapeKeydown, onKeyDown]\n );\n\n return <Thread thread={thread} onKeyDown={handleKeyDown} {...threadProps} />;\n}\n"],"names":["useStableComponent","DefaultThread","useEditorState","THREADS_PLUGIN_KEY","shallow","useState","useCallback","compareDocumentPosition","useEffect","useLayoutEffect","jsx","useFloating","flip","offset","hide","shift","limitShift","size","autoUpdate","Portal","cn"],"mappings":"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"}