@liveblocks/react-tiptap 0.0.1 → 2.8.3-tiptap2
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 +206 -0
- package/dist/LiveblocksExtension.js.map +1 -0
- package/dist/LiveblocksExtension.mjs +204 -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 +224 -0
- package/dist/comments/CommentsExtension.js.map +1 -0
- package/dist/comments/CommentsExtension.mjs +222 -0
- package/dist/comments/CommentsExtension.mjs.map +1 -0
- package/dist/comments/FloatingComposer.js +100 -0
- package/dist/comments/FloatingComposer.js.map +1 -0
- package/dist/comments/FloatingComposer.mjs +97 -0
- package/dist/comments/FloatingComposer.mjs.map +1 -0
- package/dist/comments/FloatingThreads.js +159 -0
- package/dist/comments/FloatingThreads.js.map +1 -0
- package/dist/comments/FloatingThreads.mjs +156 -0
- package/dist/comments/FloatingThreads.mjs.map +1 -0
- package/dist/index.d.mts +78 -0
- package/dist/index.d.ts +78 -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 +222 -0
- package/dist/mentions/MentionExtension.js.map +1 -0
- package/dist/mentions/MentionExtension.mjs +220 -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 +250 -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,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactUi = require('@liveblocks/react-ui');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var classnames = require('../classnames.js');
|
|
6
|
+
var types = require('../types.js');
|
|
7
|
+
var utils = require('../utils.js');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_GAP = 20;
|
|
10
|
+
const DEFAULT_ACTIVE_THREAD_OFFSET = -12;
|
|
11
|
+
const GAP = `var(--lb-tiptap-anchored-threads-gap, ${DEFAULT_GAP}px)`;
|
|
12
|
+
const ACTIVE_THREAD_OFFSET = `var(--lb-tiptap-anchored-threads-active-thread-offset, ${DEFAULT_ACTIVE_THREAD_OFFSET}px)`;
|
|
13
|
+
function AnchoredThreads({
|
|
14
|
+
threads,
|
|
15
|
+
components,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
editor,
|
|
19
|
+
...props
|
|
20
|
+
}) {
|
|
21
|
+
const Thread = components?.Thread ?? reactUi.Thread;
|
|
22
|
+
const containerRef = React.useRef(null);
|
|
23
|
+
const [orderedThreads, setOrderedThreads] = React.useState([]);
|
|
24
|
+
const [elements, setElements] = React.useState(/* @__PURE__ */ new Map());
|
|
25
|
+
const [positions, setPositions] = React.useState(/* @__PURE__ */ new Map());
|
|
26
|
+
const pluginState = editor ? types.THREADS_PLUGIN_KEY.getState(editor.state) : null;
|
|
27
|
+
const handlePositionThreads = React.useCallback(() => {
|
|
28
|
+
const container = containerRef.current;
|
|
29
|
+
if (container === null || !editor)
|
|
30
|
+
return;
|
|
31
|
+
const activeIndex = orderedThreads.findIndex(
|
|
32
|
+
({ thread }) => thread.id === pluginState?.selectedThreadId
|
|
33
|
+
);
|
|
34
|
+
const ascending = activeIndex !== -1 ? orderedThreads.slice(activeIndex) : orderedThreads;
|
|
35
|
+
const descending = activeIndex !== -1 ? orderedThreads.slice(0, activeIndex) : [];
|
|
36
|
+
const newPositions = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const { thread, position } of ascending) {
|
|
38
|
+
const coords = editor.view.coordsAtPos(Math.min(position.from, editor.view.state.doc.content.size - 1));
|
|
39
|
+
const rect = utils.getRectFromCoords(coords);
|
|
40
|
+
let top = rect.top - container.getBoundingClientRect().top;
|
|
41
|
+
for (const [id, position2] of newPositions) {
|
|
42
|
+
const el = elements.get(id);
|
|
43
|
+
if (el === void 0)
|
|
44
|
+
continue;
|
|
45
|
+
if (top >= position2 && top <= position2 + el.getBoundingClientRect().height) {
|
|
46
|
+
top = position2 + el.getBoundingClientRect().height;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
newPositions.set(thread.id, top);
|
|
50
|
+
}
|
|
51
|
+
for (const { thread, position } of descending.reverse()) {
|
|
52
|
+
const coords = editor.view.coordsAtPos(position.from);
|
|
53
|
+
const rect = utils.getRectFromCoords(coords);
|
|
54
|
+
const el = elements.get(thread.id);
|
|
55
|
+
if (el === void 0)
|
|
56
|
+
continue;
|
|
57
|
+
let top = rect.top - container.getBoundingClientRect().top;
|
|
58
|
+
for (const [, position2] of newPositions) {
|
|
59
|
+
if (top >= position2 - el.getBoundingClientRect().height) {
|
|
60
|
+
top = position2 - el.getBoundingClientRect().height;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
newPositions.set(thread.id, top);
|
|
64
|
+
}
|
|
65
|
+
setPositions(newPositions);
|
|
66
|
+
}, [editor, orderedThreads, pluginState?.selectedThreadId, elements]);
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
if (!pluginState)
|
|
69
|
+
return;
|
|
70
|
+
setOrderedThreads(Array.from(pluginState.threadPositions, ([threadId, position]) => ({ threadId, position })).reduce((acc, { threadId, position }) => {
|
|
71
|
+
const thread = threads.find((thread2) => thread2.id === threadId && !thread2.resolved);
|
|
72
|
+
if (!thread)
|
|
73
|
+
return acc;
|
|
74
|
+
acc.push({ thread, position });
|
|
75
|
+
return acc;
|
|
76
|
+
}, []));
|
|
77
|
+
handlePositionThreads();
|
|
78
|
+
}, [pluginState, threads]);
|
|
79
|
+
React.useLayoutEffect(() => {
|
|
80
|
+
handlePositionThreads();
|
|
81
|
+
}, [handlePositionThreads]);
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const observer = new ResizeObserver(handlePositionThreads);
|
|
84
|
+
for (const element of elements.values()) {
|
|
85
|
+
observer.observe(element);
|
|
86
|
+
}
|
|
87
|
+
return () => observer.disconnect();
|
|
88
|
+
}, [elements, handlePositionThreads]);
|
|
89
|
+
const onItemAdd = React.useCallback((id, el) => {
|
|
90
|
+
setElements((prev) => new Map(prev).set(id, el));
|
|
91
|
+
}, []);
|
|
92
|
+
const onItemRemove = React.useCallback((id) => {
|
|
93
|
+
setElements((prev) => {
|
|
94
|
+
const items = new Map(prev);
|
|
95
|
+
items.delete(id);
|
|
96
|
+
return items;
|
|
97
|
+
});
|
|
98
|
+
}, []);
|
|
99
|
+
const onThreadSelect = React.useCallback((id) => {
|
|
100
|
+
if (!editor)
|
|
101
|
+
return;
|
|
102
|
+
editor.commands.selectThread(id);
|
|
103
|
+
}, [editor]);
|
|
104
|
+
if (!editor)
|
|
105
|
+
return null;
|
|
106
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
107
|
+
...props,
|
|
108
|
+
className: classnames.classNames(className, "lb-root lb-tiptap-anchored-threads"),
|
|
109
|
+
ref: containerRef,
|
|
110
|
+
style: {
|
|
111
|
+
position: "relative",
|
|
112
|
+
...style
|
|
113
|
+
}
|
|
114
|
+
}, orderedThreads.map(({ thread, position }) => {
|
|
115
|
+
const coords = editor.view.coordsAtPos(Math.min(position.from, editor.state.doc.content.size - 1));
|
|
116
|
+
const rect = utils.getRectFromCoords(coords);
|
|
117
|
+
const offset = editor.options.element.getBoundingClientRect().top;
|
|
118
|
+
let top = rect.top - offset;
|
|
119
|
+
if (positions.has(thread.id)) {
|
|
120
|
+
top = positions.get(thread.id);
|
|
121
|
+
}
|
|
122
|
+
const isActive = thread.id === pluginState?.selectedThreadId;
|
|
123
|
+
return /* @__PURE__ */ React.createElement(ThreadWrapper, {
|
|
124
|
+
key: thread.id,
|
|
125
|
+
onThreadClick: onThreadSelect,
|
|
126
|
+
onItemAdd,
|
|
127
|
+
onItemRemove,
|
|
128
|
+
Thread,
|
|
129
|
+
thread,
|
|
130
|
+
isActive,
|
|
131
|
+
style: {
|
|
132
|
+
position: "absolute",
|
|
133
|
+
transform: `translate3d(${isActive ? ACTIVE_THREAD_OFFSET : 0}, ${top}px, 0)`,
|
|
134
|
+
insetInlineStart: 0,
|
|
135
|
+
inlineSize: "100%",
|
|
136
|
+
paddingBlockEnd: GAP
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
function ThreadWrapper({
|
|
142
|
+
onThreadClick,
|
|
143
|
+
onItemAdd,
|
|
144
|
+
onItemRemove,
|
|
145
|
+
thread,
|
|
146
|
+
Thread,
|
|
147
|
+
className,
|
|
148
|
+
isActive,
|
|
149
|
+
...props
|
|
150
|
+
}) {
|
|
151
|
+
const handleRef = React.useCallback(
|
|
152
|
+
(el) => {
|
|
153
|
+
onItemAdd(thread.id, el);
|
|
154
|
+
return () => onItemRemove(thread.id);
|
|
155
|
+
},
|
|
156
|
+
[thread.id, onItemAdd, onItemRemove]
|
|
157
|
+
);
|
|
158
|
+
function handleThreadClick() {
|
|
159
|
+
onThreadClick(thread.id);
|
|
160
|
+
}
|
|
161
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
162
|
+
ref: handleRef,
|
|
163
|
+
className: classnames.classNames(
|
|
164
|
+
"lb-tiptap-anchored-threads-thread-container",
|
|
165
|
+
className
|
|
166
|
+
),
|
|
167
|
+
...props
|
|
168
|
+
}, /* @__PURE__ */ React.createElement(Thread, {
|
|
169
|
+
thread,
|
|
170
|
+
"data-state": isActive ? "active" : "inactive",
|
|
171
|
+
onClick: handleThreadClick,
|
|
172
|
+
className: "lb-tiptap-anchored-threads-thread",
|
|
173
|
+
showComposer: isActive ? true : false
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
exports.AnchoredThreads = AnchoredThreads;
|
|
178
|
+
//# sourceMappingURL=AnchoredThreads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnchoredThreads.js","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 && !thread.resolved);\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":["DefaultThread","useRef","useState","THREADS_PLUGIN_KEY","useCallback","getRectFromCoords","position","useEffect","thread","useLayoutEffect","classNames"],"mappings":"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"}
|
|
@@ -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 && !thread2.resolved);
|
|
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 && !thread.resolved);\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,224 @@
|
|
|
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
|
+
orphan: {
|
|
17
|
+
parseHTML: (element) => !!element.getAttribute("data-orphan"),
|
|
18
|
+
renderHTML: (attributes) => {
|
|
19
|
+
return attributes.orphan ? {
|
|
20
|
+
"data-orphan": "true"
|
|
21
|
+
} : {};
|
|
22
|
+
},
|
|
23
|
+
default: false
|
|
24
|
+
},
|
|
25
|
+
threadId: {
|
|
26
|
+
parseHTML: (element) => element.getAttribute("data-lb-thread-id"),
|
|
27
|
+
renderHTML: (attributes) => {
|
|
28
|
+
return {
|
|
29
|
+
"data-lb-thread-id": attributes.threadId
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
default: ""
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
renderHTML({ HTMLAttributes }) {
|
|
37
|
+
return [
|
|
38
|
+
"span",
|
|
39
|
+
core.mergeAttributes(HTMLAttributes, {
|
|
40
|
+
class: "lb-root lb-tiptap-thread-mark"
|
|
41
|
+
})
|
|
42
|
+
];
|
|
43
|
+
},
|
|
44
|
+
addProseMirrorPlugins() {
|
|
45
|
+
const updateState = (doc, selectedThreadId) => {
|
|
46
|
+
const threadPositions = /* @__PURE__ */ new Map();
|
|
47
|
+
const decorations = [];
|
|
48
|
+
doc.descendants((node, pos) => {
|
|
49
|
+
node.marks.forEach((mark) => {
|
|
50
|
+
if (mark.type === this.type) {
|
|
51
|
+
const thisThreadId = mark.attrs.threadId;
|
|
52
|
+
if (!thisThreadId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const from = pos;
|
|
56
|
+
const to = from + node.nodeSize;
|
|
57
|
+
const currentPosition = threadPositions.get(thisThreadId) ?? {
|
|
58
|
+
from: Infinity,
|
|
59
|
+
to: 0
|
|
60
|
+
};
|
|
61
|
+
threadPositions.set(thisThreadId, {
|
|
62
|
+
from: Math.min(from, currentPosition.from),
|
|
63
|
+
to: Math.max(to, currentPosition.to)
|
|
64
|
+
});
|
|
65
|
+
if (selectedThreadId === thisThreadId) {
|
|
66
|
+
decorations.push(
|
|
67
|
+
view.Decoration.inline(from, to, {
|
|
68
|
+
class: "lb-root lb-tiptap-thread-mark-selected"
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
decorations: view.DecorationSet.create(doc, decorations),
|
|
77
|
+
selectedThreadId,
|
|
78
|
+
threadPositions,
|
|
79
|
+
selectedThreadPos: selectedThreadId !== null ? threadPositions.get(selectedThreadId)?.to ?? null : null
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
return [
|
|
83
|
+
new state.Plugin({
|
|
84
|
+
key: types.THREADS_PLUGIN_KEY,
|
|
85
|
+
state: {
|
|
86
|
+
init() {
|
|
87
|
+
return {
|
|
88
|
+
threadPositions: /* @__PURE__ */ new Map(),
|
|
89
|
+
selectedThreadId: null,
|
|
90
|
+
selectedThreadPos: null,
|
|
91
|
+
decorations: view.DecorationSet.empty
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
apply(tr, state) {
|
|
95
|
+
const action = tr.getMeta(types.THREADS_PLUGIN_KEY);
|
|
96
|
+
if (!tr.docChanged && !action) {
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
if (!action) {
|
|
100
|
+
return updateState(tr.doc, state.selectedThreadId);
|
|
101
|
+
}
|
|
102
|
+
if (action.name === types.ThreadPluginActions.SET_SELECTED_THREAD_ID && state.selectedThreadId !== action.data) {
|
|
103
|
+
return updateState(tr.doc, action.data);
|
|
104
|
+
}
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
props: {
|
|
109
|
+
decorations: (state) => {
|
|
110
|
+
return types.THREADS_PLUGIN_KEY.getState(state)?.decorations ?? view.DecorationSet.empty;
|
|
111
|
+
},
|
|
112
|
+
handleClick: (view, pos, event) => {
|
|
113
|
+
if (event.button !== 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const selectThread = (threadId2) => {
|
|
117
|
+
view.dispatch(
|
|
118
|
+
view.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
|
|
119
|
+
name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
|
|
120
|
+
data: threadId2
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
const node = view.state.doc.nodeAt(pos);
|
|
125
|
+
if (!node) {
|
|
126
|
+
selectThread(null);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const commentMark = node.marks.find(
|
|
130
|
+
(mark) => mark.type === this.type
|
|
131
|
+
);
|
|
132
|
+
if (commentMark?.attrs.orphan) {
|
|
133
|
+
selectThread(null);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const threadId = commentMark?.attrs.threadId;
|
|
137
|
+
selectThread(threadId ?? null);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const CommentsExtension = core.Extension.create({
|
|
145
|
+
name: "liveblocksComments",
|
|
146
|
+
priority: 95,
|
|
147
|
+
addExtensions() {
|
|
148
|
+
return [Comment];
|
|
149
|
+
},
|
|
150
|
+
addStorage() {
|
|
151
|
+
return {
|
|
152
|
+
pendingCommentSelection: null
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
addCommands() {
|
|
156
|
+
return {
|
|
157
|
+
addPendingComment: () => () => {
|
|
158
|
+
if (this.editor.state.selection.empty) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
this.editor.view.dispatch(
|
|
162
|
+
this.editor.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
|
|
163
|
+
name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
|
|
164
|
+
data: null
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
this.storage.pendingCommentSelection = new state.TextSelection(
|
|
168
|
+
this.editor.state.selection.$anchor,
|
|
169
|
+
this.editor.state.selection.$head
|
|
170
|
+
);
|
|
171
|
+
return true;
|
|
172
|
+
},
|
|
173
|
+
selectThread: (id) => () => {
|
|
174
|
+
this.editor.view.dispatch(
|
|
175
|
+
this.editor.state.tr.setMeta(types.THREADS_PLUGIN_KEY, {
|
|
176
|
+
name: types.ThreadPluginActions.SET_SELECTED_THREAD_ID,
|
|
177
|
+
data: id
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
return true;
|
|
181
|
+
},
|
|
182
|
+
addComment: (id) => ({ commands }) => {
|
|
183
|
+
if (!this.storage.pendingCommentSelection) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
this.editor.state.selection = this.storage.pendingCommentSelection;
|
|
187
|
+
commands.setMark(types.LIVEBLOCKS_COMMENT_MARK_TYPE, { threadId: id });
|
|
188
|
+
this.storage.pendingCommentSelection = null;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
onSelectionUpdate({ transaction }) {
|
|
194
|
+
if (!this.storage.pendingCommentSelection || transaction.getMeta(yProsemirror.ySyncPluginKey)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.storage.pendingCommentSelection = null;
|
|
198
|
+
},
|
|
199
|
+
addProseMirrorPlugins() {
|
|
200
|
+
return [
|
|
201
|
+
new state.Plugin({
|
|
202
|
+
key: types.ACTIVE_SELECTION_PLUGIN,
|
|
203
|
+
props: {
|
|
204
|
+
decorations: ({ doc }) => {
|
|
205
|
+
const active = this.storage.pendingCommentSelection !== null;
|
|
206
|
+
if (!active) {
|
|
207
|
+
return view.DecorationSet.create(doc, []);
|
|
208
|
+
}
|
|
209
|
+
const { from, to } = this.storage.pendingCommentSelection;
|
|
210
|
+
const decorations = [
|
|
211
|
+
view.Decoration.inline(from, to, {
|
|
212
|
+
class: "lb-root lb-tiptap-active-selection"
|
|
213
|
+
})
|
|
214
|
+
];
|
|
215
|
+
return view.DecorationSet.create(doc, decorations);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
exports.CommentsExtension = CommentsExtension;
|
|
224
|
+
//# sourceMappingURL=CommentsExtension.js.map
|