@liveblocks/react-tiptap 0.0.1 → 2.8.3-tiptap1

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 (69) hide show
  1. package/dist/LiveblocksExtension.js +144 -0
  2. package/dist/LiveblocksExtension.js.map +1 -0
  3. package/dist/LiveblocksExtension.mjs +142 -0
  4. package/dist/LiveblocksExtension.mjs.map +1 -0
  5. package/dist/classnames.js +8 -0
  6. package/dist/classnames.js.map +1 -0
  7. package/dist/classnames.mjs +6 -0
  8. package/dist/classnames.mjs.map +1 -0
  9. package/dist/comments/AnchoredThreads.js +178 -0
  10. package/dist/comments/AnchoredThreads.js.map +1 -0
  11. package/dist/comments/AnchoredThreads.mjs +176 -0
  12. package/dist/comments/AnchoredThreads.mjs.map +1 -0
  13. package/dist/comments/CommentsExtension.js +207 -0
  14. package/dist/comments/CommentsExtension.js.map +1 -0
  15. package/dist/comments/CommentsExtension.mjs +205 -0
  16. package/dist/comments/CommentsExtension.mjs.map +1 -0
  17. package/dist/comments/FloatingComposer.js +103 -0
  18. package/dist/comments/FloatingComposer.js.map +1 -0
  19. package/dist/comments/FloatingComposer.mjs +100 -0
  20. package/dist/comments/FloatingComposer.mjs.map +1 -0
  21. package/dist/comments/FloatingThreads.js +154 -0
  22. package/dist/comments/FloatingThreads.js.map +1 -0
  23. package/dist/comments/FloatingThreads.mjs +151 -0
  24. package/dist/comments/FloatingThreads.mjs.map +1 -0
  25. package/dist/index.d.mts +65 -0
  26. package/dist/index.d.ts +65 -0
  27. package/dist/index.js +18 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.mjs +10 -0
  30. package/dist/index.mjs.map +1 -0
  31. package/dist/mentions/Avatar.js +53 -0
  32. package/dist/mentions/Avatar.js.map +1 -0
  33. package/dist/mentions/Avatar.mjs +51 -0
  34. package/dist/mentions/Avatar.mjs.map +1 -0
  35. package/dist/mentions/Mention.js +24 -0
  36. package/dist/mentions/Mention.js.map +1 -0
  37. package/dist/mentions/Mention.mjs +22 -0
  38. package/dist/mentions/Mention.mjs.map +1 -0
  39. package/dist/mentions/MentionExtension.js +221 -0
  40. package/dist/mentions/MentionExtension.js.map +1 -0
  41. package/dist/mentions/MentionExtension.mjs +219 -0
  42. package/dist/mentions/MentionExtension.mjs.map +1 -0
  43. package/dist/mentions/MentionsList.js +123 -0
  44. package/dist/mentions/MentionsList.js.map +1 -0
  45. package/dist/mentions/MentionsList.mjs +119 -0
  46. package/dist/mentions/MentionsList.mjs.map +1 -0
  47. package/dist/types.js +33 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/types.mjs +24 -0
  50. package/dist/types.mjs.map +1 -0
  51. package/dist/utils.js +47 -0
  52. package/dist/utils.js.map +1 -0
  53. package/dist/utils.mjs +43 -0
  54. package/dist/utils.mjs.map +1 -0
  55. package/dist/version-history/HistoryVersionPreview.js +79 -0
  56. package/dist/version-history/HistoryVersionPreview.js.map +1 -0
  57. package/dist/version-history/HistoryVersionPreview.mjs +77 -0
  58. package/dist/version-history/HistoryVersionPreview.mjs.map +1 -0
  59. package/dist/version.js +10 -0
  60. package/dist/version.js.map +1 -0
  61. package/dist/version.mjs +6 -0
  62. package/dist/version.mjs.map +1 -0
  63. package/package.json +77 -1
  64. package/src/styles/constants.css +9 -0
  65. package/src/styles/index.css +247 -0
  66. package/src/styles/utils.css +6 -0
  67. package/styles.css +1 -0
  68. package/styles.css.d.ts +1 -0
  69. package/styles.css.map +1 -0
@@ -0,0 +1,176 @@
1
+ import { Thread } from '@liveblocks/react-ui';
2
+ import React, { useRef, useState, useCallback, useEffect, useLayoutEffect } from 'react';
3
+ import { classNames } from '../classnames.mjs';
4
+ import { THREADS_PLUGIN_KEY } from '../types.mjs';
5
+ import { getRectFromCoords } from '../utils.mjs';
6
+
7
+ const DEFAULT_GAP = 20;
8
+ const DEFAULT_ACTIVE_THREAD_OFFSET = -12;
9
+ const GAP = `var(--lb-tiptap-anchored-threads-gap, ${DEFAULT_GAP}px)`;
10
+ const ACTIVE_THREAD_OFFSET = `var(--lb-tiptap-anchored-threads-active-thread-offset, ${DEFAULT_ACTIVE_THREAD_OFFSET}px)`;
11
+ function AnchoredThreads({
12
+ threads,
13
+ components,
14
+ className,
15
+ style,
16
+ editor,
17
+ ...props
18
+ }) {
19
+ const Thread$1 = components?.Thread ?? Thread;
20
+ const containerRef = useRef(null);
21
+ const [orderedThreads, setOrderedThreads] = useState([]);
22
+ const [elements, setElements] = useState(/* @__PURE__ */ new Map());
23
+ const [positions, setPositions] = useState(/* @__PURE__ */ new Map());
24
+ const pluginState = editor ? THREADS_PLUGIN_KEY.getState(editor.state) : null;
25
+ const handlePositionThreads = useCallback(() => {
26
+ const container = containerRef.current;
27
+ if (container === null || !editor)
28
+ return;
29
+ const activeIndex = orderedThreads.findIndex(
30
+ ({ thread }) => thread.id === pluginState?.selectedThreadId
31
+ );
32
+ const ascending = activeIndex !== -1 ? orderedThreads.slice(activeIndex) : orderedThreads;
33
+ const descending = activeIndex !== -1 ? orderedThreads.slice(0, activeIndex) : [];
34
+ const newPositions = /* @__PURE__ */ new Map();
35
+ for (const { thread, position } of ascending) {
36
+ const coords = editor.view.coordsAtPos(Math.min(position.from, editor.view.state.doc.content.size - 1));
37
+ const rect = getRectFromCoords(coords);
38
+ let top = rect.top - container.getBoundingClientRect().top;
39
+ for (const [id, position2] of newPositions) {
40
+ const el = elements.get(id);
41
+ if (el === void 0)
42
+ continue;
43
+ if (top >= position2 && top <= position2 + el.getBoundingClientRect().height) {
44
+ top = position2 + el.getBoundingClientRect().height;
45
+ }
46
+ }
47
+ newPositions.set(thread.id, top);
48
+ }
49
+ for (const { thread, position } of descending.reverse()) {
50
+ const coords = editor.view.coordsAtPos(position.from);
51
+ const rect = getRectFromCoords(coords);
52
+ const el = elements.get(thread.id);
53
+ if (el === void 0)
54
+ continue;
55
+ let top = rect.top - container.getBoundingClientRect().top;
56
+ for (const [, position2] of newPositions) {
57
+ if (top >= position2 - el.getBoundingClientRect().height) {
58
+ top = position2 - el.getBoundingClientRect().height;
59
+ }
60
+ }
61
+ newPositions.set(thread.id, top);
62
+ }
63
+ setPositions(newPositions);
64
+ }, [editor, orderedThreads, pluginState?.selectedThreadId, elements]);
65
+ useEffect(() => {
66
+ if (!pluginState)
67
+ return;
68
+ setOrderedThreads(Array.from(pluginState.threadPositions, ([threadId, position]) => ({ threadId, position })).reduce((acc, { threadId, position }) => {
69
+ const thread = threads.find((thread2) => thread2.id === threadId);
70
+ if (!thread)
71
+ return acc;
72
+ acc.push({ thread, position });
73
+ return acc;
74
+ }, []));
75
+ handlePositionThreads();
76
+ }, [pluginState, threads]);
77
+ useLayoutEffect(() => {
78
+ handlePositionThreads();
79
+ }, [handlePositionThreads]);
80
+ useEffect(() => {
81
+ const observer = new ResizeObserver(handlePositionThreads);
82
+ for (const element of elements.values()) {
83
+ observer.observe(element);
84
+ }
85
+ return () => observer.disconnect();
86
+ }, [elements, handlePositionThreads]);
87
+ const onItemAdd = useCallback((id, el) => {
88
+ setElements((prev) => new Map(prev).set(id, el));
89
+ }, []);
90
+ const onItemRemove = useCallback((id) => {
91
+ setElements((prev) => {
92
+ const items = new Map(prev);
93
+ items.delete(id);
94
+ return items;
95
+ });
96
+ }, []);
97
+ const onThreadSelect = useCallback((id) => {
98
+ if (!editor)
99
+ return;
100
+ editor.commands.selectThread(id);
101
+ }, [editor]);
102
+ if (!editor)
103
+ return null;
104
+ return /* @__PURE__ */ React.createElement("div", {
105
+ ...props,
106
+ className: classNames(className, "lb-root lb-tiptap-anchored-threads"),
107
+ ref: containerRef,
108
+ style: {
109
+ position: "relative",
110
+ ...style
111
+ }
112
+ }, orderedThreads.map(({ thread, position }) => {
113
+ const coords = editor.view.coordsAtPos(Math.min(position.from, editor.state.doc.content.size - 1));
114
+ const rect = getRectFromCoords(coords);
115
+ const offset = editor.options.element.getBoundingClientRect().top;
116
+ let top = rect.top - offset;
117
+ if (positions.has(thread.id)) {
118
+ top = positions.get(thread.id);
119
+ }
120
+ const isActive = thread.id === pluginState?.selectedThreadId;
121
+ return /* @__PURE__ */ React.createElement(ThreadWrapper, {
122
+ key: thread.id,
123
+ onThreadClick: onThreadSelect,
124
+ onItemAdd,
125
+ onItemRemove,
126
+ Thread: Thread$1,
127
+ thread,
128
+ isActive,
129
+ style: {
130
+ position: "absolute",
131
+ transform: `translate3d(${isActive ? ACTIVE_THREAD_OFFSET : 0}, ${top}px, 0)`,
132
+ insetInlineStart: 0,
133
+ inlineSize: "100%",
134
+ paddingBlockEnd: GAP
135
+ }
136
+ });
137
+ }));
138
+ }
139
+ function ThreadWrapper({
140
+ onThreadClick,
141
+ onItemAdd,
142
+ onItemRemove,
143
+ thread,
144
+ Thread,
145
+ className,
146
+ isActive,
147
+ ...props
148
+ }) {
149
+ const handleRef = useCallback(
150
+ (el) => {
151
+ onItemAdd(thread.id, el);
152
+ return () => onItemRemove(thread.id);
153
+ },
154
+ [thread.id, onItemAdd, onItemRemove]
155
+ );
156
+ function handleThreadClick() {
157
+ onThreadClick(thread.id);
158
+ }
159
+ return /* @__PURE__ */ React.createElement("div", {
160
+ ref: handleRef,
161
+ className: classNames(
162
+ "lb-tiptap-anchored-threads-thread-container",
163
+ className
164
+ ),
165
+ ...props
166
+ }, /* @__PURE__ */ React.createElement(Thread, {
167
+ thread,
168
+ "data-state": isActive ? "active" : "inactive",
169
+ onClick: handleThreadClick,
170
+ className: "lb-tiptap-anchored-threads-thread",
171
+ showComposer: isActive ? true : false
172
+ }));
173
+ }
174
+
175
+ export { AnchoredThreads };
176
+ //# sourceMappingURL=AnchoredThreads.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnchoredThreads.mjs","sources":["../../src/comments/AnchoredThreads.tsx"],"sourcesContent":["import type { BaseMetadata, DM, ThreadData } from \"@liveblocks/core\";\nimport {\n Thread as DefaultThread,\n type ThreadProps,\n} from \"@liveblocks/react-ui\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { ComponentPropsWithoutRef, ComponentType } from \"react\";\nimport React, {\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport { classNames } from \"../classnames\";\nimport type { ThreadPluginState } from \"../types\";\nimport { THREADS_PLUGIN_KEY } from \"../types\";\nimport { getRectFromCoords } from \"../utils\";\n\nconst DEFAULT_GAP = 20;\nconst DEFAULT_ACTIVE_THREAD_OFFSET = -12;\n\n// TODO: move that back to a variable\nconst GAP = `var(--lb-tiptap-anchored-threads-gap, ${DEFAULT_GAP}px)`;\nconst ACTIVE_THREAD_OFFSET = `var(--lb-tiptap-anchored-threads-active-thread-offset, ${DEFAULT_ACTIVE_THREAD_OFFSET}px)`;\n\ntype AnchoredThreadsComponents = {\n Thread: ComponentType<ThreadProps>;\n};\n\nexport interface AnchoredThreadsProps<M extends BaseMetadata = DM>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /**\n * The threads to display.\n */\n threads: ThreadData<M>[];\n\n /**\n * Override the component's components.\n */\n components?: Partial<AnchoredThreadsComponents>;\n /**\n * The tiptap editor\n */\n editor: Editor | null;\n}\n\nexport function AnchoredThreads({\n threads,\n components,\n className,\n style,\n editor,\n ...props\n}: AnchoredThreadsProps) {\n const Thread = components?.Thread ?? DefaultThread;\n const containerRef = useRef<HTMLDivElement>(null);\n const [orderedThreads, setOrderedThreads] = useState<{ position: { from: number, to: number }, thread: ThreadData }[]>([]);\n const [elements, setElements] = useState<Map<string, HTMLElement>>(new Map());\n const [positions, setPositions] = useState<Map<string, number>>(new Map()); // A map of thread ids to their 'top' position in the document\n\n const pluginState = editor ? THREADS_PLUGIN_KEY.getState(editor.state) as ThreadPluginState : null;\n\n // TODO: lexical supoprts multiple threads being active, should probably do that here as well\n const handlePositionThreads = useCallback(() => {\n const container = containerRef.current;\n if (container === null || !editor) return;\n\n const activeIndex = orderedThreads.findIndex(({ thread }) =>\n thread.id === pluginState?.selectedThreadId\n );\n const ascending = activeIndex !== -1 ? orderedThreads.slice(activeIndex) : orderedThreads;\n const descending = activeIndex !== -1 ? orderedThreads.slice(0, activeIndex) : [];\n\n const newPositions = new Map<string, number>();\n\n // Iterate over each thread and calculate its new position by taking into account the position of the previously positioned threads\n for (const { thread, position } of ascending) {\n const coords = editor.view.coordsAtPos(Math.min(position.from, editor.view.state.doc.content.size - 1));\n const rect = getRectFromCoords(coords);\n let top = rect.top - container.getBoundingClientRect().top;\n\n for (const [id, position] of newPositions) {\n // Retrieve the element associated with the thread\n const el = elements.get(id);\n if (el === undefined) continue;\n\n if (\n top >= position &&\n top <= position + el.getBoundingClientRect().height\n ) {\n top = position + el.getBoundingClientRect().height;\n }\n }\n\n newPositions.set(thread.id, top);\n }\n\n for (const { thread, position } of descending.reverse()) {\n const coords = editor.view.coordsAtPos(position.from);\n const rect = getRectFromCoords(coords);\n // Retrieve the element associated with the current thread\n const el = elements.get(thread.id);\n if (el === undefined) continue;\n\n let top = rect.top - container.getBoundingClientRect().top;\n for (const [, position] of newPositions) {\n if (top >= position - el.getBoundingClientRect().height) {\n top = position - el.getBoundingClientRect().height;\n }\n }\n\n newPositions.set(thread.id, top);\n }\n\n setPositions(newPositions);\n }, [editor, orderedThreads, pluginState?.selectedThreadId, elements]);\n\n useEffect(() => {\n if (!pluginState) return;\n setOrderedThreads(Array.from(pluginState.threadPositions, ([threadId, position]) => ({ threadId, position })).reduce((acc, { threadId, position }) => {\n const thread = threads.find((thread) => thread.id === threadId);\n if (!thread) return acc;\n acc.push({ thread, position });\n return acc;\n }, [] as { thread: ThreadData, position: { from: number, to: number } }[]));\n handlePositionThreads();\n // disable exhaustive deps because we don't want an infinite loop\n // eslint-disable-next-line react-hooks/exhaustive-deps \n }, [pluginState, threads]);\n\n\n useLayoutEffect(() => {\n handlePositionThreads();\n }, [handlePositionThreads]);\n\n useEffect(() => {\n const observer = new ResizeObserver(handlePositionThreads);\n for (const element of elements.values()) {\n observer.observe(element);\n }\n\n return () => observer.disconnect();\n }, [elements, handlePositionThreads]);\n\n const onItemAdd = useCallback((id: string, el: HTMLElement) => {\n setElements((prev) => new Map(prev).set(id, el));\n }, []);\n\n const onItemRemove = useCallback((id: string) => {\n setElements((prev) => {\n const items = new Map(prev);\n items.delete(id);\n return items;\n });\n }, []);\n\n const onThreadSelect = useCallback((id: string) => {\n if (!editor) return;\n editor.commands.selectThread(id);\n }, [editor]);\n\n\n if (!editor) return null;\n\n return (\n <div\n {...props}\n className={classNames(className, \"lb-root lb-tiptap-anchored-threads\")}\n ref={containerRef}\n style={{\n position: \"relative\",\n ...style,\n }}\n >\n {orderedThreads.map(({ thread, position }) => {\n const coords = editor.view.coordsAtPos(Math.min(position.from, editor.state.doc.content.size - 1));\n const rect = getRectFromCoords(coords);\n const offset = editor.options.element.getBoundingClientRect().top;\n\n let top = rect.top - offset;\n\n if (positions.has(thread.id)) {\n top = positions.get(thread.id)!;\n }\n\n const isActive = thread.id === pluginState?.selectedThreadId;\n\n return (\n <ThreadWrapper\n key={thread.id}\n onThreadClick={onThreadSelect}\n onItemAdd={onItemAdd}\n onItemRemove={onItemRemove}\n Thread={Thread}\n thread={thread}\n isActive={isActive}\n style={{\n position: \"absolute\",\n transform: `translate3d(${isActive ? ACTIVE_THREAD_OFFSET : 0}, ${top}px, 0)`,\n insetInlineStart: 0,\n inlineSize: \"100%\",\n paddingBlockEnd: GAP,\n }}\n />\n );\n })}\n </div>\n );\n}\n\ninterface ThreadWrapperProps extends ThreadProps {\n Thread: ComponentType<ThreadProps>;\n onThreadClick: (id: string) => void;\n onItemAdd: (id: string, el: HTMLElement) => void;\n onItemRemove: (id: string) => void;\n isActive: boolean;\n}\n\nfunction ThreadWrapper({\n onThreadClick,\n onItemAdd,\n onItemRemove,\n thread,\n Thread,\n className,\n isActive,\n ...props\n}: ThreadWrapperProps) {\n\n const handleRef = useCallback(\n (el: HTMLDivElement) => {\n onItemAdd(thread.id, el);\n return () => onItemRemove(thread.id);\n },\n [thread.id, onItemAdd, onItemRemove]\n );\n\n\n function handleThreadClick() {\n onThreadClick(thread.id);\n }\n\n return (\n <div\n ref={handleRef}\n className={classNames(\n \"lb-tiptap-anchored-threads-thread-container\",\n className\n )}\n {...props}\n >\n <Thread\n thread={thread}\n data-state={isActive ? \"active\" : \"inactive\"}\n onClick={handleThreadClick}\n className=\"lb-tiptap-anchored-threads-thread\"\n showComposer={isActive ? true : false}\n />\n </div>\n );\n}\n"],"names":["Thread","DefaultThread","position","thread"],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ 'use strict';
2
+
3
+ var core = require('@tiptap/core');
4
+ var state = require('@tiptap/pm/state');
5
+ var view = require('@tiptap/pm/view');
6
+ var yProsemirror = require('y-prosemirror');
7
+ var types = require('../types.js');
8
+
9
+ const Comment = core.Mark.create({
10
+ name: types.LIVEBLOCKS_COMMENT_MARK_TYPE,
11
+ excludes: "",
12
+ inclusive: false,
13
+ keepOnSplit: true,
14
+ addAttributes() {
15
+ return {
16
+ threadId: {
17
+ parseHTML: (element) => element.getAttribute("data-lb-thread-id"),
18
+ renderHTML: (attributes) => {
19
+ return {
20
+ "data-lb-thread-id": attributes.threadId
21
+ };
22
+ },
23
+ default: ""
24
+ }
25
+ };
26
+ },
27
+ renderHTML({ HTMLAttributes }) {
28
+ return [
29
+ "span",
30
+ core.mergeAttributes(HTMLAttributes, {
31
+ class: "lb-root lb-tiptap-thread-mark"
32
+ })
33
+ ];
34
+ },
35
+ addProseMirrorPlugins() {
36
+ const updateState = (doc, selectedThreadId) => {
37
+ const threadPositions = /* @__PURE__ */ new Map();
38
+ const decorations = [];
39
+ doc.descendants((node, pos) => {
40
+ node.marks.forEach((mark) => {
41
+ if (mark.type === this.type) {
42
+ const thisThreadId = mark.attrs.threadId;
43
+ if (!thisThreadId) {
44
+ return;
45
+ }
46
+ const from = pos;
47
+ const to = from + node.nodeSize;
48
+ const currentPosition = threadPositions.get(thisThreadId) ?? {
49
+ from: Infinity,
50
+ to: 0
51
+ };
52
+ threadPositions.set(thisThreadId, {
53
+ from: Math.min(from, currentPosition.from),
54
+ to: Math.max(to, currentPosition.to)
55
+ });
56
+ if (selectedThreadId === thisThreadId) {
57
+ decorations.push(
58
+ view.Decoration.inline(from, to, {
59
+ class: "lb-root lb-tiptap-thread-mark-selected"
60
+ })
61
+ );
62
+ }
63
+ }
64
+ });
65
+ });
66
+ return {
67
+ decorations: view.DecorationSet.create(doc, decorations),
68
+ selectedThreadId,
69
+ threadPositions,
70
+ selectedThreadPos: selectedThreadId !== null ? threadPositions.get(selectedThreadId)?.to ?? null : null
71
+ };
72
+ };
73
+ return [
74
+ new state.Plugin({
75
+ key: types.THREADS_PLUGIN_KEY,
76
+ state: {
77
+ init() {
78
+ return {
79
+ threadPositions: /* @__PURE__ */ new Map(),
80
+ selectedThreadId: null,
81
+ selectedThreadPos: null,
82
+ decorations: view.DecorationSet.empty
83
+ };
84
+ },
85
+ apply(tr, state) {
86
+ const action = tr.getMeta(types.THREADS_PLUGIN_KEY);
87
+ if (!tr.docChanged && !action) {
88
+ return state;
89
+ }
90
+ if (!action) {
91
+ return updateState(tr.doc, state.selectedThreadId);
92
+ }
93
+ if (action.name === types.ThreadPluginActions.SET_SELECTED_THREAD_ID && state.selectedThreadId !== action.data) {
94
+ return updateState(tr.doc, action.data);
95
+ }
96
+ return state;
97
+ }
98
+ },
99
+ props: {
100
+ decorations: (state) => {
101
+ return types.THREADS_PLUGIN_KEY.getState(state)?.decorations ?? view.DecorationSet.empty;
102
+ },
103
+ handleClick: (view, pos, event) => {
104
+ if (event.button !== 0) {
105
+ return;
106
+ }
107
+ const selectThread = (threadId2) => {
108
+ view.dispatch(
109
+ view.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
110
+ name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
111
+ data: threadId2
112
+ })
113
+ );
114
+ };
115
+ const node = view.state.doc.nodeAt(pos);
116
+ if (!node) {
117
+ selectThread(null);
118
+ return;
119
+ }
120
+ const threadId = node.marks.find((mark) => mark.type === this.type)?.attrs.threadId;
121
+ selectThread(threadId ?? null);
122
+ }
123
+ }
124
+ })
125
+ ];
126
+ }
127
+ });
128
+ const CommentsExtension = core.Extension.create({
129
+ name: "liveblocksComments",
130
+ addExtensions() {
131
+ return [Comment];
132
+ },
133
+ addStorage() {
134
+ return {
135
+ pendingCommentSelection: null
136
+ };
137
+ },
138
+ addCommands() {
139
+ return {
140
+ addPendingComment: () => () => {
141
+ if (this.editor.state.selection.empty) {
142
+ return false;
143
+ }
144
+ this.editor.view.dispatch(
145
+ this.editor.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
146
+ name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
147
+ data: null
148
+ })
149
+ );
150
+ this.storage.pendingCommentSelection = new state.TextSelection(
151
+ this.editor.state.selection.$anchor,
152
+ this.editor.state.selection.$head
153
+ );
154
+ return true;
155
+ },
156
+ selectThread: (id) => () => {
157
+ this.editor.view.dispatch(
158
+ this.editor.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
159
+ name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
160
+ data: id
161
+ })
162
+ );
163
+ return true;
164
+ },
165
+ addComment: (id) => ({ commands }) => {
166
+ if (!this.storage.pendingCommentSelection) {
167
+ return false;
168
+ }
169
+ this.editor.state.selection = this.storage.pendingCommentSelection;
170
+ commands.setMark(types.LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });
171
+ this.storage.pendingCommentSelection = null;
172
+ return true;
173
+ }
174
+ };
175
+ },
176
+ onSelectionUpdate({ transaction }) {
177
+ if (!this.storage.pendingCommentSelection || transaction.getMeta(yProsemirror.ySyncPluginKey)) {
178
+ return;
179
+ }
180
+ this.storage.pendingCommentSelection = null;
181
+ },
182
+ addProseMirrorPlugins() {
183
+ return [
184
+ new state.Plugin({
185
+ key: types.ACTIVE_SELECTION_PLUGIN,
186
+ props: {
187
+ decorations: ({ doc }) => {
188
+ const active = this.storage.pendingCommentSelection !== null;
189
+ if (!active) {
190
+ return view.DecorationSet.create(doc, []);
191
+ }
192
+ const { from, to } = this.storage.pendingCommentSelection;
193
+ const decorations = [
194
+ view.Decoration.inline(from, to, {
195
+ class: "lb-root lb-tiptap-active-selection"
196
+ })
197
+ ];
198
+ return view.DecorationSet.create(doc, decorations);
199
+ }
200
+ }
201
+ })
202
+ ];
203
+ }
204
+ });
205
+
206
+ exports.CommentsExtension = CommentsExtension;
207
+ //# sourceMappingURL=CommentsExtension.js.map
@@ -0,0 +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 type { Transaction } from \"@tiptap/pm/state\";\nimport { Plugin, TextSelection } 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 ACTIVE_SELECTION_PLUGIN,\n LIVEBLOCKS_COMMENT_MARK_TYPE,\n ThreadPluginActions,\n THREADS_PLUGIN_KEY,\n} from \"../types\";\n\ntype ThreadPluginAction = {\n name: ThreadPluginActions;\n data: string | null;\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 addAttributes() {\n // Return an object with attribute configuration\n return {\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 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 }\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 return [\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 threadId = node.marks.find((mark) => mark.type === this.type)\n ?.attrs.threadId as string | undefined;\n selectThread(threadId ?? null);\n },\n },\n }),\n ];\n },\n});\n\nexport const CommentsExtension = Extension.create<\n never,\n CommentsExtensionStorage\n>({\n name: \"liveblocksComments\",\n addExtensions() {\n return [Comment];\n },\n\n addStorage() {\n return {\n pendingCommentSelection: null,\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.pendingCommentSelection = new TextSelection(\n this.editor.state.selection.$anchor,\n this.editor.state.selection.$head\n );\n return true;\n },\n selectThread: (id: string | null) => () => {\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 (!this.storage.pendingCommentSelection) {\n return false;\n }\n this.editor.state.selection = this.storage.pendingCommentSelection;\n commands.setMark(LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });\n this.storage.pendingCommentSelection = null;\n\n return true;\n },\n };\n },\n\n //@ts-expect-error - this is incorrectly typed upstream in Mark.ts of TipTap. This event does include transaction\n // correct: https://github.com/ueberdosis/tiptap/blob/2ff327ced84df6865b4ef98947b667aa79992292/packages/core/src/types.ts#L60\n // incorrect: https://github.com/ueberdosis/tiptap/blob/2ff327ced84df6865b4ef98947b667aa79992292/packages/core/src/Mark.ts#L330\n onSelectionUpdate(\n this: { storage: Storage }, // 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 (\n !this.storage.pendingCommentSelection ||\n transaction.getMeta(ySyncPluginKey)\n ) {\n return;\n }\n this.storage.pendingCommentSelection = null;\n },\n // TODO: this.storage.pendingCommentSelection needs to be a Yjs Relative Position that gets translated back to absolute position.\n // Commit: eba949d32d6010a3d8b3f7967d73d4deb015b02a has code that can help with this.\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: ACTIVE_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc }) => {\n const active = this.storage.pendingCommentSelection !== null;\n if (!active) {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = this.storage\n .pendingCommentSelection as TextSelection;\n const decorations: Decoration[] = [\n Decoration.inline(from, to, {\n class: \"lb-root lb-tiptap-active-selection\",\n }),\n ];\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n ];\n },\n});\n"],"names":["Mark","LIVEBLOCKS_COMMENT_MARK_TYPE","mergeAttributes","Decoration","DecorationSet","Plugin","THREADS_PLUGIN_KEY","ThreadPluginActions","threadId","Extension","TextSelection","ySyncPluginKey","ACTIVE_SELECTION_PLUGIN"],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ import { Mark, mergeAttributes, Extension } from '@tiptap/core';
2
+ import { Plugin, TextSelection } from '@tiptap/pm/state';
3
+ import { Decoration, DecorationSet } from '@tiptap/pm/view';
4
+ import { ySyncPluginKey } from 'y-prosemirror';
5
+ import { LIVEBLOCKS_COMMENT_MARK_TYPE, THREADS_PLUGIN_KEY, ThreadPluginActions, ACTIVE_SELECTION_PLUGIN } from '../types.mjs';
6
+
7
+ const Comment = Mark.create({
8
+ name: LIVEBLOCKS_COMMENT_MARK_TYPE,
9
+ excludes: "",
10
+ inclusive: false,
11
+ keepOnSplit: true,
12
+ addAttributes() {
13
+ return {
14
+ threadId: {
15
+ parseHTML: (element) => element.getAttribute("data-lb-thread-id"),
16
+ renderHTML: (attributes) => {
17
+ return {
18
+ "data-lb-thread-id": attributes.threadId
19
+ };
20
+ },
21
+ default: ""
22
+ }
23
+ };
24
+ },
25
+ renderHTML({ HTMLAttributes }) {
26
+ return [
27
+ "span",
28
+ mergeAttributes(HTMLAttributes, {
29
+ class: "lb-root lb-tiptap-thread-mark"
30
+ })
31
+ ];
32
+ },
33
+ addProseMirrorPlugins() {
34
+ const updateState = (doc, selectedThreadId) => {
35
+ const threadPositions = /* @__PURE__ */ new Map();
36
+ const decorations = [];
37
+ doc.descendants((node, pos) => {
38
+ node.marks.forEach((mark) => {
39
+ if (mark.type === this.type) {
40
+ const thisThreadId = mark.attrs.threadId;
41
+ if (!thisThreadId) {
42
+ return;
43
+ }
44
+ const from = pos;
45
+ const to = from + node.nodeSize;
46
+ const currentPosition = threadPositions.get(thisThreadId) ?? {
47
+ from: Infinity,
48
+ to: 0
49
+ };
50
+ threadPositions.set(thisThreadId, {
51
+ from: Math.min(from, currentPosition.from),
52
+ to: Math.max(to, currentPosition.to)
53
+ });
54
+ if (selectedThreadId === thisThreadId) {
55
+ decorations.push(
56
+ Decoration.inline(from, to, {
57
+ class: "lb-root lb-tiptap-thread-mark-selected"
58
+ })
59
+ );
60
+ }
61
+ }
62
+ });
63
+ });
64
+ return {
65
+ decorations: DecorationSet.create(doc, decorations),
66
+ selectedThreadId,
67
+ threadPositions,
68
+ selectedThreadPos: selectedThreadId !== null ? threadPositions.get(selectedThreadId)?.to ?? null : null
69
+ };
70
+ };
71
+ return [
72
+ new Plugin({
73
+ key: THREADS_PLUGIN_KEY,
74
+ state: {
75
+ init() {
76
+ return {
77
+ threadPositions: /* @__PURE__ */ new Map(),
78
+ selectedThreadId: null,
79
+ selectedThreadPos: null,
80
+ decorations: DecorationSet.empty
81
+ };
82
+ },
83
+ apply(tr, state) {
84
+ const action = tr.getMeta(THREADS_PLUGIN_KEY);
85
+ if (!tr.docChanged && !action) {
86
+ return state;
87
+ }
88
+ if (!action) {
89
+ return updateState(tr.doc, state.selectedThreadId);
90
+ }
91
+ if (action.name === ThreadPluginActions.SET_SELECTED_THREAD_ID && state.selectedThreadId !== action.data) {
92
+ return updateState(tr.doc, action.data);
93
+ }
94
+ return state;
95
+ }
96
+ },
97
+ props: {
98
+ decorations: (state) => {
99
+ return THREADS_PLUGIN_KEY.getState(state)?.decorations ?? DecorationSet.empty;
100
+ },
101
+ handleClick: (view, pos, event) => {
102
+ if (event.button !== 0) {
103
+ return;
104
+ }
105
+ const selectThread = (threadId2) => {
106
+ view.dispatch(
107
+ view.state.tr.setMeta(THREADS_PLUGIN_KEY, {
108
+ name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
109
+ data: threadId2
110
+ })
111
+ );
112
+ };
113
+ const node = view.state.doc.nodeAt(pos);
114
+ if (!node) {
115
+ selectThread(null);
116
+ return;
117
+ }
118
+ const threadId = node.marks.find((mark) => mark.type === this.type)?.attrs.threadId;
119
+ selectThread(threadId ?? null);
120
+ }
121
+ }
122
+ })
123
+ ];
124
+ }
125
+ });
126
+ const CommentsExtension = Extension.create({
127
+ name: "liveblocksComments",
128
+ addExtensions() {
129
+ return [Comment];
130
+ },
131
+ addStorage() {
132
+ return {
133
+ pendingCommentSelection: null
134
+ };
135
+ },
136
+ addCommands() {
137
+ return {
138
+ addPendingComment: () => () => {
139
+ if (this.editor.state.selection.empty) {
140
+ return false;
141
+ }
142
+ this.editor.view.dispatch(
143
+ this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {
144
+ name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
145
+ data: null
146
+ })
147
+ );
148
+ this.storage.pendingCommentSelection = new TextSelection(
149
+ this.editor.state.selection.$anchor,
150
+ this.editor.state.selection.$head
151
+ );
152
+ return true;
153
+ },
154
+ selectThread: (id) => () => {
155
+ this.editor.view.dispatch(
156
+ this.editor.state.tr.setMeta(THREADS_PLUGIN_KEY, {
157
+ name: ThreadPluginActions.SET_SELECTED_THREAD_ID,
158
+ data: id
159
+ })
160
+ );
161
+ return true;
162
+ },
163
+ addComment: (id) => ({ commands }) => {
164
+ if (!this.storage.pendingCommentSelection) {
165
+ return false;
166
+ }
167
+ this.editor.state.selection = this.storage.pendingCommentSelection;
168
+ commands.setMark(LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });
169
+ this.storage.pendingCommentSelection = null;
170
+ return true;
171
+ }
172
+ };
173
+ },
174
+ onSelectionUpdate({ transaction }) {
175
+ if (!this.storage.pendingCommentSelection || transaction.getMeta(ySyncPluginKey)) {
176
+ return;
177
+ }
178
+ this.storage.pendingCommentSelection = null;
179
+ },
180
+ addProseMirrorPlugins() {
181
+ return [
182
+ new Plugin({
183
+ key: ACTIVE_SELECTION_PLUGIN,
184
+ props: {
185
+ decorations: ({ doc }) => {
186
+ const active = this.storage.pendingCommentSelection !== null;
187
+ if (!active) {
188
+ return DecorationSet.create(doc, []);
189
+ }
190
+ const { from, to } = this.storage.pendingCommentSelection;
191
+ const decorations = [
192
+ Decoration.inline(from, to, {
193
+ class: "lb-root lb-tiptap-active-selection"
194
+ })
195
+ ];
196
+ return DecorationSet.create(doc, decorations);
197
+ }
198
+ }
199
+ })
200
+ ];
201
+ }
202
+ });
203
+
204
+ export { CommentsExtension };
205
+ //# sourceMappingURL=CommentsExtension.mjs.map