@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.
- package/dist/LiveblocksExtension.js +144 -0
- package/dist/LiveblocksExtension.js.map +1 -0
- package/dist/LiveblocksExtension.mjs +142 -0
- package/dist/LiveblocksExtension.mjs.map +1 -0
- package/dist/classnames.js +8 -0
- package/dist/classnames.js.map +1 -0
- package/dist/classnames.mjs +6 -0
- package/dist/classnames.mjs.map +1 -0
- package/dist/comments/AnchoredThreads.js +178 -0
- package/dist/comments/AnchoredThreads.js.map +1 -0
- package/dist/comments/AnchoredThreads.mjs +176 -0
- package/dist/comments/AnchoredThreads.mjs.map +1 -0
- package/dist/comments/CommentsExtension.js +207 -0
- package/dist/comments/CommentsExtension.js.map +1 -0
- package/dist/comments/CommentsExtension.mjs +205 -0
- package/dist/comments/CommentsExtension.mjs.map +1 -0
- package/dist/comments/FloatingComposer.js +103 -0
- package/dist/comments/FloatingComposer.js.map +1 -0
- package/dist/comments/FloatingComposer.mjs +100 -0
- package/dist/comments/FloatingComposer.mjs.map +1 -0
- package/dist/comments/FloatingThreads.js +154 -0
- package/dist/comments/FloatingThreads.js.map +1 -0
- package/dist/comments/FloatingThreads.mjs +151 -0
- package/dist/comments/FloatingThreads.mjs.map +1 -0
- package/dist/index.d.mts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mentions/Avatar.js +53 -0
- package/dist/mentions/Avatar.js.map +1 -0
- package/dist/mentions/Avatar.mjs +51 -0
- package/dist/mentions/Avatar.mjs.map +1 -0
- package/dist/mentions/Mention.js +24 -0
- package/dist/mentions/Mention.js.map +1 -0
- package/dist/mentions/Mention.mjs +22 -0
- package/dist/mentions/Mention.mjs.map +1 -0
- package/dist/mentions/MentionExtension.js +221 -0
- package/dist/mentions/MentionExtension.js.map +1 -0
- package/dist/mentions/MentionExtension.mjs +219 -0
- package/dist/mentions/MentionExtension.mjs.map +1 -0
- package/dist/mentions/MentionsList.js +123 -0
- package/dist/mentions/MentionsList.js.map +1 -0
- package/dist/mentions/MentionsList.mjs +119 -0
- package/dist/mentions/MentionsList.mjs.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +24 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.js +47 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +43 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/version-history/HistoryVersionPreview.js +79 -0
- package/dist/version-history/HistoryVersionPreview.js.map +1 -0
- package/dist/version-history/HistoryVersionPreview.mjs +77 -0
- package/dist/version-history/HistoryVersionPreview.mjs.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/dist/version.mjs +6 -0
- package/dist/version.mjs.map +1 -0
- package/package.json +77 -1
- package/src/styles/constants.css +9 -0
- package/src/styles/index.css +247 -0
- package/src/styles/utils.css +6 -0
- package/styles.css +1 -0
- package/styles.css.d.ts +1 -0
- 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":";;;;;;;;AAyBA,MAAM,OAAA,GAAUA,UAAK,MAAO,CAAA;AAAA,EAC1B,IAAM,EAAAC,kCAAA;AAAA,EACN,QAAU,EAAA,EAAA;AAAA,EACV,SAAW,EAAA,KAAA;AAAA,EACX,WAAa,EAAA,IAAA;AAAA,EACb,aAAgB,GAAA;AAEd,IAAO,OAAA;AAAA,MACL,QAAU,EAAA;AAAA,QACR,SAAW,EAAA,CAAC,OAAY,KAAA,OAAA,CAAQ,aAAa,mBAAmB,CAAA;AAAA,QAChE,UAAA,EAAY,CAAC,UAAe,KAAA;AAC1B,UAAO,OAAA;AAAA,YACL,qBAAsB,UAAoC,CAAA,QAAA;AAAA,WAC5D,CAAA;AAAA,SACF;AAAA,QACA,OAAS,EAAA,EAAA;AAAA,OACX;AAAA,KACF,CAAA;AAAA,GACF;AAAA,EAEA,UAAA,CAAW,EAAE,cAAA,EAA2D,EAAA;AACtE,IAAO,OAAA;AAAA,MACL,MAAA;AAAA,MACAC,qBAAgB,cAAgB,EAAA;AAAA,QAC9B,KAAO,EAAA,+BAAA;AAAA,OACR,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AAAA,EAKA,qBAAwB,GAAA;AACtB,IAAM,MAAA,WAAA,GAAc,CAAC,GAAA,EAAW,gBAAoC,KAAA;AAClE,MAAM,MAAA,eAAA,uBAAsB,GAA0C,EAAA,CAAA;AACtE,MAAA,MAAM,cAA4B,EAAC,CAAA;AAEnC,MAAI,GAAA,CAAA,WAAA,CAAY,CAAC,IAAA,EAAM,GAAQ,KAAA;AAC7B,QAAK,IAAA,CAAA,KAAA,CAAM,OAAQ,CAAA,CAAC,IAAS,KAAA;AAC3B,UAAI,IAAA,IAAA,CAAK,IAAS,KAAA,IAAA,CAAK,IAAM,EAAA;AAC3B,YAAM,MAAA,YAAA,GACJ,KAAK,KACL,CAAA,QAAA,CAAA;AACF,YAAA,IAAI,CAAC,YAAc,EAAA;AACjB,cAAA,OAAA;AAAA,aACF;AACA,YAAA,MAAM,IAAO,GAAA,GAAA,CAAA;AACb,YAAM,MAAA,EAAA,GAAK,OAAO,IAAK,CAAA,QAAA,CAAA;AAIvB,YAAA,MAAM,eAAkB,GAAA,eAAA,CAAgB,GAAI,CAAA,YAAY,CAAK,IAAA;AAAA,cAC3D,IAAM,EAAA,QAAA;AAAA,cACN,EAAI,EAAA,CAAA;AAAA,aACN,CAAA;AACA,YAAA,eAAA,CAAgB,IAAI,YAAc,EAAA;AAAA,cAChC,IAAM,EAAA,IAAA,CAAK,GAAI,CAAA,IAAA,EAAM,gBAAgB,IAAI,CAAA;AAAA,cACzC,EAAI,EAAA,IAAA,CAAK,GAAI,CAAA,EAAA,EAAI,gBAAgB,EAAE,CAAA;AAAA,aACpC,CAAA,CAAA;AAED,YAAA,IAAI,qBAAqB,YAAc,EAAA;AACrC,cAAY,WAAA,CAAA,IAAA;AAAA,gBACVC,eAAA,CAAW,MAAO,CAAA,IAAA,EAAM,EAAI,EAAA;AAAA,kBAC1B,KAAO,EAAA,wCAAA;AAAA,iBACR,CAAA;AAAA,eACH,CAAA;AAAA,aACF;AAAA,WACF;AAAA,SACD,CAAA,CAAA;AAAA,OACF,CAAA,CAAA;AACD,MAAO,OAAA;AAAA,QACL,WAAa,EAAAC,kBAAA,CAAc,MAAO,CAAA,GAAA,EAAK,WAAW,CAAA;AAAA,QAClD,gBAAA;AAAA,QACA,eAAA;AAAA,QACA,iBAAA,EACE,qBAAqB,IACjB,GAAA,eAAA,CAAgB,IAAI,gBAAgB,CAAA,EAAG,MAAM,IAC7C,GAAA,IAAA;AAAA,OACR,CAAA;AAAA,KACF,CAAA;AAEA,IAAO,OAAA;AAAA,MACL,IAAIC,YAAO,CAAA;AAAA,QACT,GAAK,EAAAC,wBAAA;AAAA,QACL,KAAO,EAAA;AAAA,UACL,IAAO,GAAA;AACL,YAAO,OAAA;AAAA,cACL,eAAA,sBAAqB,GAA0C,EAAA;AAAA,cAC/D,gBAAkB,EAAA,IAAA;AAAA,cAClB,iBAAmB,EAAA,IAAA;AAAA,cACnB,aAAaF,kBAAc,CAAA,KAAA;AAAA,aAC7B,CAAA;AAAA,WACF;AAAA,UACA,KAAA,CAAM,IAAI,KAAO,EAAA;AACf,YAAM,MAAA,MAAA,GAAS,EAAG,CAAA,OAAA,CAAQE,wBAAkB,CAAA,CAAA;AAC5C,YAAA,IAAI,CAAC,EAAA,CAAG,UAAc,IAAA,CAAC,MAAQ,EAAA;AAC7B,cAAO,OAAA,KAAA,CAAA;AAAA,aACT;AAEA,YAAA,IAAI,CAAC,MAAQ,EAAA;AAEX,cAAA,OAAO,WAAY,CAAA,EAAA,CAAG,GAAK,EAAA,KAAA,CAAM,gBAAgB,CAAA,CAAA;AAAA,aACnD;AAEA,YAAA,IACE,OAAO,IAAS,KAAAC,yBAAA,CAAoB,0BACpC,KAAM,CAAA,gBAAA,KAAqB,OAAO,IAClC,EAAA;AACA,cAAA,OAAO,WAAY,CAAA,EAAA,CAAG,GAAK,EAAA,MAAA,CAAO,IAAI,CAAA,CAAA;AAAA,aACxC;AAEA,YAAO,OAAA,KAAA,CAAA;AAAA,WACT;AAAA,SACF;AAAA,QACA,KAAO,EAAA;AAAA,UACL,WAAA,EAAa,CAAC,KAAU,KAAA;AACtB,YAAA,OACED,wBAAmB,CAAA,QAAA,CAAS,KAAK,CAAA,EAAG,eACpCF,kBAAc,CAAA,KAAA,CAAA;AAAA,WAElB;AAAA,UACA,WAAa,EAAA,CAAC,IAAM,EAAA,GAAA,EAAK,KAAU,KAAA;AACjC,YAAI,IAAA,KAAA,CAAM,WAAW,CAAG,EAAA;AACtB,cAAA,OAAA;AAAA,aACF;AAEA,YAAM,MAAA,YAAA,GAAe,CAACI,SAA4B,KAAA;AAChD,cAAK,IAAA,CAAA,QAAA;AAAA,gBACH,IAAK,CAAA,KAAA,CAAM,EAAG,CAAA,OAAA,CAAQF,wBAAoB,EAAA;AAAA,kBACxC,MAAMC,yBAAoB,CAAA,sBAAA;AAAA,kBAC1B,IAAMC,EAAAA,SAAAA;AAAA,iBACP,CAAA;AAAA,eACH,CAAA;AAAA,aACF,CAAA;AAEA,YAAA,MAAM,IAAO,GAAA,IAAA,CAAK,KAAM,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAA;AACtC,YAAA,IAAI,CAAC,IAAM,EAAA;AACT,cAAA,YAAA,CAAa,IAAI,CAAA,CAAA;AACjB,cAAA,OAAA;AAAA,aACF;AACA,YAAM,MAAA,QAAA,GAAW,IAAK,CAAA,KAAA,CAAM,IAAK,CAAA,CAAC,IAAS,KAAA,IAAA,CAAK,IAAS,KAAA,IAAA,CAAK,IAAI,CAAA,EAC9D,KAAM,CAAA,QAAA,CAAA;AACV,YAAA,YAAA,CAAa,YAAY,IAAI,CAAA,CAAA;AAAA,WAC/B;AAAA,SACF;AAAA,OACD,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AACF,CAAC,CAAA,CAAA;AAEY,MAAA,iBAAA,GAAoBC,eAAU,MAGzC,CAAA;AAAA,EACA,IAAM,EAAA,oBAAA;AAAA,EACN,aAAgB,GAAA;AACd,IAAA,OAAO,CAAC,OAAO,CAAA,CAAA;AAAA,GACjB;AAAA,EAEA,UAAa,GAAA;AACX,IAAO,OAAA;AAAA,MACL,uBAAyB,EAAA,IAAA;AAAA,KAC3B,CAAA;AAAA,GACF;AAAA,EAEA,WAAc,GAAA;AACZ,IAAO,OAAA;AAAA,MACL,iBAAA,EAAmB,MAAM,MAAM;AAC7B,QAAA,IAAI,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA,SAAA,CAAU,KAAO,EAAA;AACrC,UAAO,OAAA,KAAA,CAAA;AAAA,SACT;AAEA,QAAA,IAAA,CAAK,OAAO,IAAK,CAAA,QAAA;AAAA,UACf,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA,EAAA,CAAG,QAAQH,wBAAoB,EAAA;AAAA,YAC/C,MAAMC,yBAAoB,CAAA,sBAAA;AAAA,YAC1B,IAAM,EAAA,IAAA;AAAA,WACP,CAAA;AAAA,SACH,CAAA;AACA,QAAK,IAAA,CAAA,OAAA,CAAQ,0BAA0B,IAAIG,mBAAA;AAAA,UACzC,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,SAAU,CAAA,OAAA;AAAA,UAC5B,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,SAAU,CAAA,KAAA;AAAA,SAC9B,CAAA;AACA,QAAO,OAAA,IAAA,CAAA;AAAA,OACT;AAAA,MACA,YAAA,EAAc,CAAC,EAAA,KAAsB,MAAM;AACzC,QAAA,IAAA,CAAK,OAAO,IAAK,CAAA,QAAA;AAAA,UACf,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA,EAAA,CAAG,QAAQJ,wBAAoB,EAAA;AAAA,YAC/C,MAAMC,yBAAoB,CAAA,sBAAA;AAAA,YAC1B,IAAM,EAAA,EAAA;AAAA,WACP,CAAA;AAAA,SACH,CAAA;AACA,QAAO,OAAA,IAAA,CAAA;AAAA,OACT;AAAA,MACA,YACE,CAAC,EAAA,KACD,CAAC,EAAE,UAAe,KAAA;AAChB,QAAI,IAAA,CAAC,IAAK,CAAA,OAAA,CAAQ,uBAAyB,EAAA;AACzC,UAAO,OAAA,KAAA,CAAA;AAAA,SACT;AACA,QAAA,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,SAAY,GAAA,IAAA,CAAK,OAAQ,CAAA,uBAAA,CAAA;AAC3C,QAAA,QAAA,CAAS,OAAQ,CAAAN,kCAAA,EAA8B,EAAE,QAAA,EAAU,IAAI,CAAA,CAAA;AAC/D,QAAA,IAAA,CAAK,QAAQ,uBAA0B,GAAA,IAAA,CAAA;AAEvC,QAAO,OAAA,IAAA,CAAA;AAAA,OACT;AAAA,KACJ,CAAA;AAAA,GACF;AAAA,EAKA,iBAAA,CAEE,EAAE,WAAA,EACF,EAAA;AAEA,IAAA,IACE,CAAC,IAAK,CAAA,OAAA,CAAQ,2BACd,WAAY,CAAA,OAAA,CAAQU,2BAAc,CAClC,EAAA;AACA,MAAA,OAAA;AAAA,KACF;AACA,IAAA,IAAA,CAAK,QAAQ,uBAA0B,GAAA,IAAA,CAAA;AAAA,GACzC;AAAA,EAGA,qBAAwB,GAAA;AACtB,IAAO,OAAA;AAAA,MACL,IAAIN,YAAO,CAAA;AAAA,QACT,GAAK,EAAAO,6BAAA;AAAA,QACL,KAAO,EAAA;AAAA,UACL,WAAa,EAAA,CAAC,EAAE,GAAA,EAAU,KAAA;AACxB,YAAM,MAAA,MAAA,GAAS,IAAK,CAAA,OAAA,CAAQ,uBAA4B,KAAA,IAAA,CAAA;AACxD,YAAA,IAAI,CAAC,MAAQ,EAAA;AACX,cAAA,OAAOR,kBAAc,CAAA,MAAA,CAAO,GAAK,EAAA,EAAE,CAAA,CAAA;AAAA,aACrC;AACA,YAAA,MAAM,EAAE,IAAA,EAAM,EAAG,EAAA,GAAI,KAAK,OACvB,CAAA,uBAAA,CAAA;AACH,YAAA,MAAM,WAA4B,GAAA;AAAA,cAChCD,eAAA,CAAW,MAAO,CAAA,IAAA,EAAM,EAAI,EAAA;AAAA,gBAC1B,KAAO,EAAA,oCAAA;AAAA,eACR,CAAA;AAAA,aACH,CAAA;AACA,YAAO,OAAAC,kBAAA,CAAc,MAAO,CAAA,GAAA,EAAK,WAAW,CAAA,CAAA;AAAA,WAC9C;AAAA,SACF;AAAA,OACD,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AACF,CAAC;;;;"}
|
|
@@ -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
|