@dxos/plugin-thread 0.6.11-staging.e6894a4 → 0.6.12-main.5cc132e

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 (68) hide show
  1. package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs → ThreadArticle-6L6BGE4Z.mjs} +2 -2
  2. package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-DU6LBSN3.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-AVC4CGJM.mjs} +6 -6
  4. package/dist/lib/browser/chunk-AVC4CGJM.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-U5UVX4ZP.mjs} +1 -1
  6. package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-U5UVX4ZP.mjs.map} +1 -1
  7. package/dist/lib/browser/index.mjs +217 -156
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/types.mjs +1 -1
  11. package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs → ThreadArticle-7BWXVIJ4.cjs} +6 -6
  12. package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-XSRB6NKJ.cjs} +6 -6
  13. package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-4B2ARV7L.cjs} +4 -4
  14. package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-4B2ARV7L.cjs.map} +1 -1
  15. package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-T7TXCNHL.cjs} +8 -8
  16. package/dist/lib/node/chunk-T7TXCNHL.cjs.map +7 -0
  17. package/dist/lib/node/index.cjs +258 -199
  18. package/dist/lib/node/index.cjs.map +4 -4
  19. package/dist/lib/node/meta.json +1 -1
  20. package/dist/lib/node/types.cjs +2 -2
  21. package/dist/lib/node/types.cjs.map +1 -1
  22. package/dist/lib/node-esm/ThreadArticle-6L6BGE4Z.mjs +18 -0
  23. package/dist/lib/node-esm/ThreadArticle-6L6BGE4Z.mjs.map +7 -0
  24. package/dist/lib/node-esm/ThreadMain-DU6LBSN3.mjs +26 -0
  25. package/dist/lib/node-esm/ThreadMain-DU6LBSN3.mjs.map +7 -0
  26. package/dist/lib/node-esm/chunk-5IBJO53M.mjs +21 -0
  27. package/dist/lib/node-esm/chunk-5IBJO53M.mjs.map +7 -0
  28. package/dist/lib/node-esm/chunk-AVC4CGJM.mjs +410 -0
  29. package/dist/lib/node-esm/chunk-AVC4CGJM.mjs.map +7 -0
  30. package/dist/lib/node-esm/chunk-U5UVX4ZP.mjs +20 -0
  31. package/dist/lib/node-esm/chunk-U5UVX4ZP.mjs.map +7 -0
  32. package/dist/lib/node-esm/index.mjs +1166 -0
  33. package/dist/lib/node-esm/index.mjs.map +7 -0
  34. package/dist/lib/node-esm/meta.json +1 -0
  35. package/dist/lib/node-esm/meta.mjs +11 -0
  36. package/dist/lib/node-esm/meta.mjs.map +7 -0
  37. package/dist/lib/node-esm/types.mjs +8 -0
  38. package/dist/lib/node-esm/types.mjs.map +7 -0
  39. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  40. package/dist/types/src/components/Chat.stories.d.ts +0 -1
  41. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  43. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
  45. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  46. package/dist/types/src/components/ThreadComplementary.d.ts +11 -0
  47. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -0
  48. package/dist/types/src/types.d.ts +1 -1
  49. package/dist/types/src/types.d.ts.map +1 -1
  50. package/package.json +35 -32
  51. package/src/ThreadPlugin.tsx +49 -123
  52. package/src/components/Chat.stories.tsx +3 -3
  53. package/src/components/ChatContainer.tsx +4 -4
  54. package/src/components/CommentContainer.tsx +10 -7
  55. package/src/components/Comments.stories.tsx +10 -15
  56. package/src/components/CommentsContainer.tsx +18 -14
  57. package/src/components/MessageContainer.tsx +2 -1
  58. package/src/components/ThreadComplementary.tsx +120 -0
  59. package/src/types.ts +1 -1
  60. package/dist/lib/browser/chunk-P7ZE5N5M.mjs.map +0 -7
  61. package/dist/lib/node/chunk-5PZNPPQF.cjs.map +0 -7
  62. package/dist/types/src/types.test.d.ts +0 -2
  63. package/dist/types/src/types.test.d.ts.map +0 -1
  64. package/src/types.test.ts +0 -13
  65. /package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs.map → ThreadArticle-6L6BGE4Z.mjs.map} +0 -0
  66. /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-DU6LBSN3.mjs.map} +0 -0
  67. /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-7BWXVIJ4.cjs.map} +0 -0
  68. /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-XSRB6NKJ.cjs.map} +0 -0
@@ -0,0 +1,1166 @@
1
+ import {
2
+ ThreadAction
3
+ } from "./chunk-U5UVX4ZP.mjs";
4
+ import {
5
+ ChatContainer,
6
+ ChatHeading,
7
+ MessageContainer,
8
+ command,
9
+ getMessageMetadata,
10
+ useAnalyticsCallback,
11
+ useOnEditAnalytics,
12
+ useStatus
13
+ } from "./chunk-AVC4CGJM.mjs";
14
+ import {
15
+ THREAD_ITEM,
16
+ THREAD_PLUGIN,
17
+ meta_default
18
+ } from "./chunk-5IBJO53M.mjs";
19
+
20
+ // packages/plugins/plugin-thread/src/ThreadPlugin.tsx
21
+ import { Chat } from "@phosphor-icons/react";
22
+ import { computed, effect } from "@preact/signals-core";
23
+ import React6 from "react";
24
+ import { LayoutAction as LayoutAction2, NavigationAction, parseIntentPlugin, parseNavigationPlugin, resolvePlugin, parseGraphPlugin, SLUG_PATH_SEPARATOR, SLUG_COLLECTION_INDICATOR, isLayoutParts, parseMetadataResolverPlugin } from "@dxos/app-framework";
25
+ import { getTypename } from "@dxos/echo-schema";
26
+ import { create as create2 } from "@dxos/echo-schema";
27
+ import { LocalStorageStore } from "@dxos/local-storage";
28
+ import { log } from "@dxos/log";
29
+ import { parseAttentionPlugin } from "@dxos/plugin-attention";
30
+ import { parseClientPlugin } from "@dxos/plugin-client";
31
+ import { createExtension, isActionGroup } from "@dxos/plugin-graph";
32
+ import { ObservabilityAction } from "@dxos/plugin-observability/meta";
33
+ import { SpaceAction } from "@dxos/plugin-space";
34
+ import { ThreadType, MessageType as MessageType2, ChannelType } from "@dxos/plugin-space/types";
35
+ import { getSpace as getSpace2, getTextInRange, Filter, createDocAccessor, fullyQualifiedId as fullyQualifiedId4, loadObjectReferences } from "@dxos/react-client/echo";
36
+ import { comments, createExternalCommentSync, listener as listener2 } from "@dxos/react-ui-editor";
37
+ import { translations as threadTranslations } from "@dxos/react-ui-thread";
38
+ import { nonNullable as nonNullable3 } from "@dxos/util";
39
+
40
+ // packages/plugins/plugin-thread/src/components/index.ts
41
+ import React4 from "react";
42
+
43
+ // packages/plugins/plugin-thread/src/components/ThreadSettings.tsx
44
+ import React from "react";
45
+ import { SettingsValue } from "@dxos/plugin-settings";
46
+ import { Input, useTranslation } from "@dxos/react-ui";
47
+ var ThreadSettings = ({ settings }) => {
48
+ const { t } = useTranslation(THREAD_PLUGIN);
49
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SettingsValue, {
50
+ label: t("settings standalone label")
51
+ }, /* @__PURE__ */ React.createElement(Input.Switch, {
52
+ checked: settings.standalone,
53
+ onCheckedChange: (checked) => settings.standalone = !!checked
54
+ })));
55
+ };
56
+
57
+ // packages/plugins/plugin-thread/src/components/CommentContainer.tsx
58
+ import { CheckCircle, X } from "@phosphor-icons/react";
59
+ import React2, { useCallback, useEffect, useMemo, useRef, useState } from "react";
60
+ import { create } from "@dxos/echo-schema";
61
+ import { MessageType } from "@dxos/plugin-space/types";
62
+ import { fullyQualifiedId, getSpace, useMembers } from "@dxos/react-client/echo";
63
+ import { useIdentity } from "@dxos/react-client/halo";
64
+ import { Button, Tag, Tooltip, useThemeContext, useTranslation as useTranslation2 } from "@dxos/react-ui";
65
+ import { createBasicExtensions, createThemeExtensions, listener } from "@dxos/react-ui-editor";
66
+ import { getSize, hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from "@dxos/react-ui-theme";
67
+ import { MessageTextbox, Thread, ThreadFooter, ThreadHeading } from "@dxos/react-ui-thread";
68
+ import { nonNullable } from "@dxos/util";
69
+ var sizeClass = getSize(4);
70
+ var ToggleResolvedButton = ({ isResolved, onResolve }) => {
71
+ const { t } = useTranslation2(THREAD_PLUGIN);
72
+ const label = t(isResolved ? "mark as unresolved label" : "mark as resolved label");
73
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
74
+ asChild: true
75
+ }, /* @__PURE__ */ React2.createElement(Button, {
76
+ variant: "ghost",
77
+ "data-testid": "thread.toggle-resolved",
78
+ onClick: onResolve,
79
+ classNames: [
80
+ "min-bs-0 p-1",
81
+ !isResolved && hoverableControlItem
82
+ ]
83
+ }, /* @__PURE__ */ React2.createElement("span", {
84
+ className: "sr-only"
85
+ }, label), isResolved ? /* @__PURE__ */ React2.createElement(CheckCircle, {
86
+ className: sizeClass,
87
+ weight: "fill"
88
+ }) : /* @__PURE__ */ React2.createElement(CheckCircle, {
89
+ className: sizeClass
90
+ }))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, {
91
+ classNames: "z-[21]"
92
+ }, label, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
93
+ };
94
+ var DeleteThreadButton = ({ onDelete }) => {
95
+ const { t } = useTranslation2(THREAD_PLUGIN);
96
+ const label = t("delete thread label");
97
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
98
+ asChild: true
99
+ }, /* @__PURE__ */ React2.createElement(Button, {
100
+ variant: "ghost",
101
+ "data-testid": "thread.delete",
102
+ onClick: onDelete,
103
+ classNames: [
104
+ "min-bs-0 p-1",
105
+ hoverableControlItem
106
+ ]
107
+ }, /* @__PURE__ */ React2.createElement("span", {
108
+ className: "sr-only"
109
+ }, label), /* @__PURE__ */ React2.createElement(X, {
110
+ className: sizeClass
111
+ }))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, {
112
+ classNames: "z-[21]"
113
+ }, label, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
114
+ };
115
+ var CommentContainer = ({ thread, detached, context, current, autoFocusTextbox, onAttend, onThreadDelete, onMessageDelete, onResolve, onComment }) => {
116
+ const identity = useIdentity();
117
+ const space = getSpace(thread);
118
+ const members = useMembers(space?.key);
119
+ const activity = useStatus(space, fullyQualifiedId(thread));
120
+ const { t } = useTranslation2(THREAD_PLUGIN);
121
+ const threadScrollRef = useRef(null);
122
+ const [autoFocus, setAutoFocus] = useState(!!autoFocusTextbox);
123
+ const { themeMode } = useThemeContext();
124
+ const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
125
+ const [_count, _setCount] = useState(0);
126
+ const rerenderEditor = () => _setCount((count) => count + 1);
127
+ const messageRef = useRef("");
128
+ const extensions = useMemo(() => [
129
+ createBasicExtensions({
130
+ placeholder: t("message placeholder")
131
+ }),
132
+ createThemeExtensions({
133
+ themeMode
134
+ }),
135
+ listener({
136
+ onChange: (text) => messageRef.current = text
137
+ }),
138
+ command
139
+ ], [
140
+ _count
141
+ ]);
142
+ useEffect(() => {
143
+ setAutoFocus(!!autoFocusTextbox);
144
+ }, [
145
+ autoFocusTextbox
146
+ ]);
147
+ const scrollToEnd = (behavior) => setTimeout(() => threadScrollRef.current?.scrollIntoView({
148
+ behavior,
149
+ block: "end"
150
+ }), 10);
151
+ const handleCreate = useCallback(() => {
152
+ if (!messageRef.current) {
153
+ return false;
154
+ }
155
+ thread.messages.push(create(MessageType, {
156
+ sender: {
157
+ identityKey: identity.identityKey.toHex()
158
+ },
159
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
160
+ text: messageRef.current,
161
+ context
162
+ }));
163
+ onComment?.(thread);
164
+ messageRef.current = "";
165
+ setAutoFocus(true);
166
+ scrollToEnd("instant");
167
+ rerenderEditor();
168
+ return true;
169
+ }, [
170
+ thread,
171
+ identity
172
+ ]);
173
+ return /* @__PURE__ */ React2.createElement(Thread, {
174
+ onClickCapture: onAttend,
175
+ onFocusCapture: onAttend,
176
+ current,
177
+ id: fullyQualifiedId(thread)
178
+ }, /* @__PURE__ */ React2.createElement("div", {
179
+ role: "none",
180
+ className: mx("col-span-2 grid grid-cols-[var(--rail-size)_1fr_min-content]", hoverableControls, hoverableFocusedWithinControls)
181
+ }, detached ? /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
182
+ asChild: true
183
+ }, /* @__PURE__ */ React2.createElement(ThreadHeading, {
184
+ detached: true
185
+ }, thread.name ?? t("thread title placeholder"))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, {
186
+ classNames: "z-[21]",
187
+ side: "top"
188
+ }, t("detached thread label"), /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null)))) : /* @__PURE__ */ React2.createElement(ThreadHeading, null, thread.name ?? t("thread title placeholder")), /* @__PURE__ */ React2.createElement("div", {
189
+ className: "flex flex-row items-center pli-1"
190
+ }, thread.status === "staged" && /* @__PURE__ */ React2.createElement(Tag, {
191
+ palette: "neutral"
192
+ }, t("draft button")), onResolve && !(thread?.status === "staged") && /* @__PURE__ */ React2.createElement(ToggleResolvedButton, {
193
+ isResolved: thread?.status === "resolved",
194
+ onResolve
195
+ }), onThreadDelete && /* @__PURE__ */ React2.createElement(DeleteThreadButton, {
196
+ onDelete: onThreadDelete
197
+ }))), thread.messages.filter(nonNullable).map((message) => /* @__PURE__ */ React2.createElement(MessageContainer, {
198
+ key: message.id,
199
+ message,
200
+ members,
201
+ onDelete: (id) => onMessageDelete?.(id)
202
+ })), /* @__PURE__ */ React2.createElement(MessageTextbox, {
203
+ extensions,
204
+ autoFocus,
205
+ onSend: handleCreate,
206
+ ...textboxMetadata
207
+ }), /* @__PURE__ */ React2.createElement(ThreadFooter, {
208
+ activity
209
+ }, t("activity message")), /* @__PURE__ */ React2.createElement("div", {
210
+ role: "none",
211
+ className: "bs-px -mbs-px",
212
+ ref: threadScrollRef
213
+ }));
214
+ };
215
+
216
+ // packages/plugins/plugin-thread/src/components/CommentsContainer.tsx
217
+ import { ChatText, Quotes } from "@phosphor-icons/react";
218
+ import React3, { useEffect as useEffect2 } from "react";
219
+ import { fullyQualifiedId as fullyQualifiedId2 } from "@dxos/react-client/echo";
220
+ import { useTranslation as useTranslation3, Trans } from "@dxos/react-ui";
221
+ import { PlankHeading, plankHeadingIconProps } from "@dxos/react-ui-deck";
222
+ import { descriptionText, mx as mx2 } from "@dxos/react-ui-theme";
223
+ var CommentsHeading = ({ attendableId }) => {
224
+ const { t } = useTranslation3(THREAD_PLUGIN);
225
+ return /* @__PURE__ */ React3.createElement("div", {
226
+ role: "none",
227
+ className: "flex items-center"
228
+ }, /* @__PURE__ */ React3.createElement(PlankHeading.Button, {
229
+ attendableId
230
+ }, /* @__PURE__ */ React3.createElement(Quotes, plankHeadingIconProps)), /* @__PURE__ */ React3.createElement(PlankHeading.Label, {
231
+ attendableId
232
+ }, t("comments heading")));
233
+ };
234
+ var CommentsContainer = ({ threads, detached = [], currentId, autoFocusCurrentTextbox, showResolvedThreads, onThreadAttend, onThreadDelete, onMessageDelete, onThreadToggleResolved, ...props }) => {
235
+ const { t } = useTranslation3(THREAD_PLUGIN);
236
+ const filteredThreads = showResolvedThreads ? threads : threads.filter((thread) => !(thread?.status === "resolved"));
237
+ useEffect2(() => {
238
+ if (currentId) {
239
+ document.getElementById(currentId)?.scrollIntoView({
240
+ behavior: "smooth",
241
+ block: "end"
242
+ });
243
+ }
244
+ }, [
245
+ currentId
246
+ ]);
247
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, filteredThreads.length > 0 ? filteredThreads.map((thread) => {
248
+ const threadId = fullyQualifiedId2(thread);
249
+ return /* @__PURE__ */ React3.createElement(CommentContainer, {
250
+ key: threadId,
251
+ thread,
252
+ current: currentId === threadId,
253
+ detached: detached.includes(threadId),
254
+ autoFocusTextbox: autoFocusCurrentTextbox && currentId === threadId,
255
+ ...onThreadAttend && {
256
+ onAttend: () => onThreadAttend(thread)
257
+ },
258
+ ...onThreadDelete && {
259
+ onThreadDelete: () => onThreadDelete(thread)
260
+ },
261
+ ...onMessageDelete && {
262
+ onMessageDelete: (messageId) => onMessageDelete(thread, messageId)
263
+ },
264
+ ...onThreadToggleResolved && {
265
+ onResolve: () => onThreadToggleResolved(thread)
266
+ },
267
+ ...props
268
+ });
269
+ }) : /* @__PURE__ */ React3.createElement("div", {
270
+ role: "alert",
271
+ className: mx2(descriptionText, "place-self-center border border-dashed border-neutral-400/50 rounded-lg text-center p-4 m-4")
272
+ }, /* @__PURE__ */ React3.createElement("h2", {
273
+ className: "mbe-2 font-medium text-baseText"
274
+ }, t("no comments title")), /* @__PURE__ */ React3.createElement("p", null, /* @__PURE__ */ React3.createElement(Trans, {
275
+ t,
276
+ i18nKey: "no comments message",
277
+ components: {
278
+ commentIcon: /* @__PURE__ */ React3.createElement(ChatText, {
279
+ className: "inline-block"
280
+ })
281
+ }
282
+ }))));
283
+ };
284
+
285
+ // packages/plugins/plugin-thread/src/components/index.ts
286
+ var ThreadMain = React4.lazy(() => import("./ThreadMain-DU6LBSN3.mjs"));
287
+ var ThreadArticle = React4.lazy(() => import("./ThreadArticle-6L6BGE4Z.mjs"));
288
+
289
+ // packages/plugins/plugin-thread/src/components/ThreadComplementary.tsx
290
+ import React5, { useEffect as useEffect3, useMemo as useMemo2 } from "react";
291
+ import { LayoutAction, useIntentDispatcher, useResolvePlugins } from "@dxos/app-framework";
292
+ import { fullyQualifiedId as fullyQualifiedId3 } from "@dxos/react-client/echo";
293
+ import { ScrollArea } from "@dxos/react-ui";
294
+ import { createAttendableAttributes, useAttentionContext } from "@dxos/react-ui-attention";
295
+ import { nonNullable as nonNullable2 } from "@dxos/util";
296
+ var providesThreadsConfig = (plugin) => "thread" in plugin.provides ? plugin : void 0;
297
+ var ThreadComplementary = ({ role, subject, stagedThreads, current, focus, showResolvedThreads }) => {
298
+ const dispatch = useIntentDispatcher();
299
+ const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
300
+ const threadProvides = useMemo2(() => threadsIntegrators.map((p) => p.provides.thread), [
301
+ threadsIntegrators
302
+ ]);
303
+ const createSort = useMemo2(() => threadProvides.find((p) => p.predicate(subject))?.createSort, [
304
+ threadProvides,
305
+ subject
306
+ ]);
307
+ const sort = useMemo2(() => createSort?.(subject), [
308
+ createSort,
309
+ subject
310
+ ]);
311
+ const threads = useMemo2(() => {
312
+ return subject.threads.concat(stagedThreads ?? []).filter(nonNullable2);
313
+ }, [
314
+ JSON.stringify(subject.threads),
315
+ JSON.stringify(stagedThreads)
316
+ ]);
317
+ const detachedIds = useMemo2(() => {
318
+ return threads.filter(({ anchor }) => !anchor).map((thread) => fullyQualifiedId3(thread));
319
+ }, [
320
+ threads
321
+ ]);
322
+ useEffect3(() => {
323
+ if (!sort) {
324
+ return;
325
+ }
326
+ threads.sort((a, b) => sort(a?.anchor, b?.anchor));
327
+ }, [
328
+ sort,
329
+ threads
330
+ ]);
331
+ const { attended } = useAttentionContext("Thread Complementary");
332
+ const qualifiedSubjectId = fullyQualifiedId3(subject);
333
+ const attendableAttrs = createAttendableAttributes(qualifiedSubjectId);
334
+ return /* @__PURE__ */ React5.createElement("div", {
335
+ role: "none",
336
+ className: "contents group/attention",
337
+ ...attendableAttrs
338
+ }, role === "complementary" && /* @__PURE__ */ React5.createElement(CommentsHeading, {
339
+ attendableId: qualifiedSubjectId
340
+ }), /* @__PURE__ */ React5.createElement(ScrollArea.Root, {
341
+ classNames: "row-span-2"
342
+ }, /* @__PURE__ */ React5.createElement(ScrollArea.Viewport, null, /* @__PURE__ */ React5.createElement(CommentsContainer, {
343
+ threads,
344
+ detached: detachedIds,
345
+ currentId: attended.has(qualifiedSubjectId) ? current : void 0,
346
+ autoFocusCurrentTextbox: focus,
347
+ showResolvedThreads,
348
+ onThreadAttend: (thread) => {
349
+ const threadId = fullyQualifiedId3(thread);
350
+ if (current !== threadId) {
351
+ current = threadId;
352
+ void dispatch?.([
353
+ {
354
+ action: LayoutAction.SCROLL_INTO_VIEW,
355
+ data: {
356
+ id: fullyQualifiedId3(subject),
357
+ thread: threadId,
358
+ cursor: thread.anchor
359
+ }
360
+ }
361
+ ]);
362
+ }
363
+ },
364
+ onThreadDelete: (thread) => {
365
+ return dispatch({
366
+ plugin: THREAD_PLUGIN,
367
+ action: ThreadAction.DELETE,
368
+ data: {
369
+ subject,
370
+ thread
371
+ }
372
+ });
373
+ },
374
+ onMessageDelete: (thread, messageId) => dispatch({
375
+ plugin: THREAD_PLUGIN,
376
+ action: ThreadAction.DELETE_MESSAGE,
377
+ data: {
378
+ subject,
379
+ thread,
380
+ messageId
381
+ }
382
+ }),
383
+ onThreadToggleResolved: (thread) => dispatch({
384
+ plugin: THREAD_PLUGIN,
385
+ action: ThreadAction.TOGGLE_RESOLVED,
386
+ data: {
387
+ thread
388
+ }
389
+ }),
390
+ onComment: (thread) => {
391
+ void dispatch({
392
+ action: ThreadAction.ON_MESSAGE_ADD,
393
+ data: {
394
+ thread,
395
+ subject
396
+ }
397
+ });
398
+ }
399
+ }), /* @__PURE__ */ React5.createElement("div", {
400
+ role: "none",
401
+ className: "bs-10"
402
+ }), /* @__PURE__ */ React5.createElement(ScrollArea.Scrollbar, null, /* @__PURE__ */ React5.createElement(ScrollArea.Thumb, null)))));
403
+ };
404
+
405
+ // packages/plugins/plugin-thread/src/translations.ts
406
+ var translations_default = [
407
+ {
408
+ "en-US": {
409
+ [THREAD_PLUGIN]: {
410
+ "plugin name": "Threads",
411
+ "channel name placeholder": "New channel",
412
+ "channel title label": "Title",
413
+ "delete channel label": "Delete channel",
414
+ "create channel label": "Create channel",
415
+ "message placeholder": "Reply\u2026",
416
+ "settings standalone label": "Enable channel creation (experimental)",
417
+ "activity message": "Processing\u2026",
418
+ "anonymous label": "Anonymous",
419
+ "delete message block label": "Delete message block",
420
+ "detached thread label": "Referenced content was removed",
421
+ "delete thread label": "Delete thread",
422
+ "thread deleted label": "You deleted a thread.",
423
+ "message deleted label": "You deleted a message.",
424
+ "comments heading": "Comments",
425
+ "chat heading": "Chat",
426
+ "draft button": "DRAFT",
427
+ "no comments title": "No comments (yet)",
428
+ "no comments message": "To start a comment thread, select a range of text in a document and click the <commentIcon></commentIcon> Create comment button in the toolbar.",
429
+ "toggle show resolved": "Show resolved",
430
+ "unnamed object threads label": "Threads",
431
+ "mark as resolved label": "Mark as resolved",
432
+ "mark as unresolved label": "Mark as unresolved",
433
+ "save message label": "Save",
434
+ "edit message label": "Edit",
435
+ "delete message label": "Delete message"
436
+ }
437
+ }
438
+ }
439
+ ];
440
+
441
+ // packages/plugins/plugin-thread/src/ThreadPlugin.tsx
442
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/plugins/plugin-thread/src/ThreadPlugin.tsx";
443
+ var initialViewState = {
444
+ showResolvedThreads: false
445
+ };
446
+ var ThreadPlugin = () => {
447
+ const settings = new LocalStorageStore(THREAD_PLUGIN);
448
+ const state = create2({
449
+ staging: {}
450
+ });
451
+ const viewStore = create2({});
452
+ const getViewState = (subjectId) => {
453
+ if (!viewStore[subjectId]) {
454
+ viewStore[subjectId] = {
455
+ ...initialViewState
456
+ };
457
+ }
458
+ return viewStore[subjectId];
459
+ };
460
+ let attentionPlugin;
461
+ let navigationPlugin;
462
+ let intentPlugin;
463
+ const unsubscribeCallbacks = [];
464
+ return {
465
+ meta: meta_default,
466
+ ready: async (plugins) => {
467
+ settings.prop({
468
+ key: "standalone",
469
+ type: LocalStorageStore.bool({
470
+ allowUndefined: true
471
+ })
472
+ });
473
+ attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
474
+ navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
475
+ intentPlugin = resolvePlugin(plugins, parseIntentPlugin);
476
+ const graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
477
+ const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
478
+ if (!client) {
479
+ return;
480
+ }
481
+ const channelsQuery = client.spaces.query(Filter.schema(ChannelType));
482
+ const queryUnsubscribe = channelsQuery.subscribe();
483
+ const unsubscribe = effect(() => {
484
+ const attention = attentionPlugin?.provides.attention;
485
+ if (!attention?.attended) {
486
+ return;
487
+ }
488
+ const firstAttendedNodeWithComments = Array.from(attention.attended).map((id) => graphPlugin?.provides.graph.findNode(id)).find((node) => {
489
+ const data = node?.data;
490
+ return Array.isArray(data?.threads) && data.threads.length > 0;
491
+ });
492
+ if (firstAttendedNodeWithComments) {
493
+ void intentPlugin?.provides.intent.dispatch({
494
+ action: NavigationAction.OPEN,
495
+ data: {
496
+ activeParts: {
497
+ complementary: `${firstAttendedNodeWithComments.id}${SLUG_PATH_SEPARATOR}comments`
498
+ }
499
+ }
500
+ });
501
+ }
502
+ });
503
+ unsubscribeCallbacks.push(queryUnsubscribe);
504
+ unsubscribeCallbacks.push(unsubscribe);
505
+ },
506
+ unload: async () => {
507
+ unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
508
+ },
509
+ provides: {
510
+ settings: settings.values,
511
+ metadata: {
512
+ records: {
513
+ [ChannelType.typename]: {
514
+ placeholder: [
515
+ "channel title placeholder",
516
+ {
517
+ ns: THREAD_PLUGIN
518
+ }
519
+ ],
520
+ icon: (props) => /* @__PURE__ */ React6.createElement(Chat, props),
521
+ iconSymbol: "ph--chat--regular",
522
+ // TODO(wittjosiah): Move out of metadata.
523
+ loadReferences: (channel) => loadObjectReferences(channel, (channel2) => channel2.threads)
524
+ },
525
+ [ThreadType.typename]: {
526
+ // TODO(wittjosiah): Move out of metadata.
527
+ loadReferences: (thread) => loadObjectReferences(thread, (thread2) => thread2.messages)
528
+ },
529
+ [MessageType2.typename]: {
530
+ // TODO(wittjosiah): Move out of metadata.
531
+ loadReferences: (message) => []
532
+ },
533
+ [THREAD_ITEM]: {
534
+ parse: (item, type) => {
535
+ switch (type) {
536
+ case "node":
537
+ return {
538
+ id: item.id,
539
+ label: item.title,
540
+ data: item
541
+ };
542
+ case "object":
543
+ return item;
544
+ case "view-object":
545
+ return {
546
+ id: `${item.id}-view`,
547
+ object: item
548
+ };
549
+ }
550
+ }
551
+ }
552
+ }
553
+ },
554
+ translations: [
555
+ ...translations_default,
556
+ ...threadTranslations
557
+ ],
558
+ echo: {
559
+ schema: [
560
+ ChannelType,
561
+ ThreadType,
562
+ MessageType2
563
+ ]
564
+ },
565
+ graph: {
566
+ builder: (plugins) => {
567
+ const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
568
+ const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
569
+ const metadataResolver = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
570
+ if (!client || !dispatch || !metadataResolver) {
571
+ return [];
572
+ }
573
+ return [
574
+ createExtension({
575
+ id: `${THREAD_PLUGIN}/comments-for-subject`,
576
+ resolver: ({ id }) => {
577
+ if (!id.endsWith("~comments")) {
578
+ return;
579
+ }
580
+ const docId = id.split("~").at(0);
581
+ const [spaceId, objectId] = docId?.split(":") ?? [];
582
+ const space = client.spaces.get().find((space2) => space2.id === spaceId);
583
+ const doc = space?.db.getObjectById(objectId);
584
+ if (!doc || !docId) {
585
+ return;
586
+ }
587
+ const docMeta = metadataResolver(getTypename(doc) ?? "");
588
+ const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder || [
589
+ "unnamed object threads label",
590
+ {
591
+ ns: THREAD_PLUGIN
592
+ }
593
+ ];
594
+ const viewState = getViewState(docId);
595
+ return {
596
+ id,
597
+ type: "orphan-comments-for-subject",
598
+ data: doc,
599
+ properties: {
600
+ icon: meta_default.iconComponent,
601
+ label,
602
+ showResolvedThreads: viewState.showResolvedThreads
603
+ }
604
+ };
605
+ },
606
+ actions: ({ node }) => {
607
+ const dataId = node.id.split("~").at(0);
608
+ if (!node.id.endsWith("~comments") || !dataId) {
609
+ return;
610
+ }
611
+ const [spaceId, objectId] = dataId.split(":");
612
+ const viewState = getViewState(dataId);
613
+ const toggle = () => {
614
+ const newToggleState = !viewState.showResolvedThreads;
615
+ viewState.showResolvedThreads = newToggleState;
616
+ void dispatch({
617
+ action: ObservabilityAction.SEND_EVENT,
618
+ data: {
619
+ name: "threads.toggle-show-resolved",
620
+ properties: {
621
+ spaceId,
622
+ threadId: objectId,
623
+ showResolved: newToggleState
624
+ }
625
+ }
626
+ });
627
+ };
628
+ return [
629
+ {
630
+ id: `${THREAD_PLUGIN}/toggle-show-resolved/${node.id}`,
631
+ data: toggle,
632
+ properties: {
633
+ label: [
634
+ "toggle show resolved",
635
+ {
636
+ ns: THREAD_PLUGIN
637
+ }
638
+ ],
639
+ menuItemType: "toggle",
640
+ isChecked: viewState.showResolvedThreads,
641
+ testId: "threadPlugin.toggleShowResolved",
642
+ iconSymbol: viewState.showResolvedThreads ? "ph--eye-slash--regular" : "ph--eye--regular"
643
+ }
644
+ }
645
+ ];
646
+ }
647
+ }),
648
+ createExtension({
649
+ id: ThreadAction.CREATE,
650
+ filter: (node) => !!settings.values.standalone && isActionGroup(node) && node.id.startsWith(SpaceAction.ADD_OBJECT),
651
+ actions: ({ node }) => {
652
+ const id = node.id.split("/").at(-1);
653
+ const [spaceId, objectId] = id?.split(":") ?? [];
654
+ const space = client.spaces.get().find((space2) => space2.id === spaceId);
655
+ const object = objectId && space?.db.getObjectById(objectId);
656
+ const target = objectId ? object : space;
657
+ if (!target) {
658
+ return;
659
+ }
660
+ return [
661
+ {
662
+ id: `${THREAD_PLUGIN}/create/${node.id}`,
663
+ data: async () => {
664
+ await dispatch([
665
+ {
666
+ plugin: THREAD_PLUGIN,
667
+ action: ThreadAction.CREATE
668
+ },
669
+ {
670
+ action: SpaceAction.ADD_OBJECT,
671
+ data: {
672
+ target
673
+ }
674
+ },
675
+ {
676
+ action: NavigationAction.OPEN
677
+ }
678
+ ]);
679
+ },
680
+ properties: {
681
+ label: [
682
+ "create channel label",
683
+ {
684
+ ns: THREAD_PLUGIN
685
+ }
686
+ ],
687
+ icon: (props) => /* @__PURE__ */ React6.createElement(Chat, props),
688
+ iconSymbol: "ph--chat--regular",
689
+ testId: "threadPlugin.createObject"
690
+ }
691
+ }
692
+ ];
693
+ }
694
+ })
695
+ ];
696
+ }
697
+ },
698
+ surface: {
699
+ component: ({ data, role }) => {
700
+ switch (role) {
701
+ case "main": {
702
+ return data.active instanceof ChannelType && data.active.threads[0] ? /* @__PURE__ */ React6.createElement(ThreadMain, {
703
+ thread: data.active.threads[0]
704
+ }) : null;
705
+ }
706
+ case "settings": {
707
+ return data.plugin === meta_default.id ? /* @__PURE__ */ React6.createElement(ThreadSettings, {
708
+ settings: settings.values
709
+ }) : null;
710
+ }
711
+ case "article":
712
+ case "complementary": {
713
+ const location = navigationPlugin?.provides.location;
714
+ if (data.object instanceof ChannelType && data.object.threads[0]) {
715
+ const channel = data.object;
716
+ if (isLayoutParts(location?.active) && location.active.main) {
717
+ const layoutEntries = location.active.main;
718
+ const currentPosition = layoutEntries.findIndex((entry) => channel.id === entry.id);
719
+ if (currentPosition > 0) {
720
+ const objectToTheLeft = layoutEntries[currentPosition - 1];
721
+ const context = getSpace2(data.object)?.db.getObjectById(objectToTheLeft.id);
722
+ return /* @__PURE__ */ React6.createElement(ThreadArticle, {
723
+ thread: data.object.threads[0],
724
+ context
725
+ });
726
+ }
727
+ }
728
+ return /* @__PURE__ */ React6.createElement(ThreadArticle, {
729
+ thread: data.object.threads[0]
730
+ });
731
+ }
732
+ if (data.subject instanceof ThreadType) {
733
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(ChatHeading, {
734
+ attendableId: data.subject.id
735
+ }), /* @__PURE__ */ React6.createElement(ChatContainer, {
736
+ thread: data.subject
737
+ }));
738
+ }
739
+ if (data.subject && typeof data.subject === "object" && "threads" in data.subject && Array.isArray(data.subject.threads)) {
740
+ const { showResolvedThreads } = getViewState(fullyQualifiedId4(data.subject));
741
+ return /* @__PURE__ */ React6.createElement(ThreadComplementary, {
742
+ role,
743
+ subject: data.subject,
744
+ stagedThreads: state.staging[fullyQualifiedId4(data.subject)],
745
+ current: state.current,
746
+ focus: true,
747
+ showResolvedThreads
748
+ });
749
+ }
750
+ }
751
+ }
752
+ return null;
753
+ }
754
+ },
755
+ intent: {
756
+ resolver: async (intent) => {
757
+ switch (intent.action) {
758
+ case ThreadAction.CREATE: {
759
+ if (intent.data && intent.data.cursor !== void 0) {
760
+ const { cursor, name, subject } = intent.data;
761
+ if (subject?.threads === void 0) {
762
+ try {
763
+ subject.threads = [];
764
+ } catch (e) {
765
+ log.error("Subject does not support threads array", subject?.typename, {
766
+ F: __dxlog_file,
767
+ L: 369,
768
+ S: void 0,
769
+ C: (f, a) => f(...a)
770
+ });
771
+ return;
772
+ }
773
+ }
774
+ const subjectId = fullyQualifiedId4(subject);
775
+ const thread = create2(ThreadType, {
776
+ name,
777
+ anchor: cursor,
778
+ messages: [],
779
+ status: "staged"
780
+ });
781
+ const stagingArea = state.staging[subjectId];
782
+ if (stagingArea) {
783
+ stagingArea.push(thread);
784
+ } else {
785
+ state.staging[subjectId] = [
786
+ thread
787
+ ];
788
+ }
789
+ return {
790
+ data: thread,
791
+ intents: [
792
+ [
793
+ {
794
+ action: NavigationAction.OPEN,
795
+ data: {
796
+ activeParts: {
797
+ complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`
798
+ }
799
+ }
800
+ },
801
+ {
802
+ action: ThreadAction.SELECT,
803
+ data: {
804
+ current: fullyQualifiedId4(thread),
805
+ focus: true
806
+ }
807
+ },
808
+ {
809
+ action: LayoutAction2.SET_LAYOUT,
810
+ data: {
811
+ element: "complementary",
812
+ subject: subjectId,
813
+ state: true
814
+ }
815
+ }
816
+ ]
817
+ ]
818
+ };
819
+ } else {
820
+ return {
821
+ data: create2(ChannelType, {
822
+ threads: [
823
+ create2(ThreadType, {
824
+ messages: []
825
+ })
826
+ ]
827
+ })
828
+ };
829
+ }
830
+ }
831
+ case ThreadAction.SELECT: {
832
+ state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
833
+ state.current = intent.data?.current;
834
+ return {
835
+ data: true
836
+ };
837
+ }
838
+ case ThreadAction.TOGGLE_RESOLVED: {
839
+ const { thread } = intent.data ?? {};
840
+ if (!(thread instanceof ThreadType)) {
841
+ return;
842
+ }
843
+ if (thread.status === "active" || thread.status === void 0) {
844
+ thread.status = "resolved";
845
+ } else if (thread.status === "resolved") {
846
+ thread.status = "active";
847
+ }
848
+ const space = getSpace2(thread);
849
+ const spaceId = space?.id;
850
+ return {
851
+ intents: [
852
+ [
853
+ {
854
+ action: ObservabilityAction.SEND_EVENT,
855
+ data: {
856
+ name: "threads.toggle-resolved",
857
+ properties: {
858
+ threadId: thread.id,
859
+ spaceId
860
+ }
861
+ }
862
+ }
863
+ ]
864
+ ]
865
+ };
866
+ }
867
+ case ThreadAction.DELETE: {
868
+ const { subject, thread } = intent.data ?? {};
869
+ if (!(thread instanceof ThreadType) || !(subject && typeof subject === "object" && "threads" in subject)) {
870
+ return;
871
+ }
872
+ const subjectId = fullyQualifiedId4(subject);
873
+ const stagingArea = state.staging[subjectId];
874
+ if (stagingArea) {
875
+ const index = stagingArea.findIndex((t) => t.id === thread.id);
876
+ if (index !== -1) {
877
+ stagingArea.splice(index, 1);
878
+ return {
879
+ data: true
880
+ };
881
+ }
882
+ }
883
+ const space = getSpace2(thread);
884
+ if (!space || !subject.threads) {
885
+ return;
886
+ }
887
+ if (!intent.undo) {
888
+ const index = subject.threads.findIndex((t) => t?.id === thread.id);
889
+ const cursor = subject.threads[index]?.anchor;
890
+ if (index !== -1) {
891
+ subject.threads?.splice(index, 1);
892
+ }
893
+ space.db.remove(thread);
894
+ return {
895
+ undoable: {
896
+ message: translations_default[0]["en-US"][THREAD_PLUGIN]["thread deleted label"],
897
+ data: {
898
+ cursor
899
+ }
900
+ },
901
+ intents: [
902
+ [
903
+ {
904
+ action: ObservabilityAction.SEND_EVENT,
905
+ data: {
906
+ name: "threads.delete",
907
+ properties: {
908
+ threadId: thread.id,
909
+ spaceId: space.id
910
+ }
911
+ }
912
+ }
913
+ ]
914
+ ]
915
+ };
916
+ } else if (intent.undo) {
917
+ const savedThread = space.db.add(thread);
918
+ subject.threads.push(savedThread);
919
+ return {
920
+ data: true,
921
+ intents: [
922
+ [
923
+ {
924
+ action: ObservabilityAction.SEND_EVENT,
925
+ data: {
926
+ name: "threads.undo-delete",
927
+ properties: {
928
+ threadId: thread.id,
929
+ spaceId: space.id
930
+ }
931
+ }
932
+ }
933
+ ]
934
+ ]
935
+ };
936
+ }
937
+ break;
938
+ }
939
+ case ThreadAction.ON_MESSAGE_ADD: {
940
+ const { thread, subject } = intent.data ?? {};
941
+ if (!(thread instanceof ThreadType) || !(subject && typeof subject === "object" && "threads" in subject && Array.isArray(subject.threads))) {
942
+ return;
943
+ }
944
+ const subjectId = fullyQualifiedId4(subject);
945
+ const space = getSpace2(subject);
946
+ const intents = [];
947
+ const analyticsProperties = {
948
+ threadId: thread.id,
949
+ spaceId: space?.id
950
+ };
951
+ if (state.staging[subjectId]?.find((t) => t === thread)) {
952
+ thread.status = "active";
953
+ subject.threads ? subject.threads.push(thread) : subject.threads = [
954
+ thread
955
+ ];
956
+ state.staging[subjectId] = state.staging[subjectId]?.filter((t) => t.id !== thread.id);
957
+ intents.push({
958
+ action: ObservabilityAction.SEND_EVENT,
959
+ data: {
960
+ name: "threads.thread-created",
961
+ properties: analyticsProperties
962
+ }
963
+ });
964
+ }
965
+ intents.push({
966
+ action: ObservabilityAction.SEND_EVENT,
967
+ data: {
968
+ name: "threads.message-added",
969
+ properties: {
970
+ ...analyticsProperties,
971
+ threadLength: thread.messages.length
972
+ }
973
+ }
974
+ });
975
+ return {
976
+ data: thread,
977
+ intents: [
978
+ intents
979
+ ]
980
+ };
981
+ }
982
+ case ThreadAction.DELETE_MESSAGE: {
983
+ const { subject, thread, messageId } = intent.data ?? {};
984
+ const space = getSpace2(subject);
985
+ if (!(thread instanceof ThreadType) || !(subject && typeof subject === "object" && "threads" in subject)) {
986
+ return;
987
+ }
988
+ if (!intent.undo) {
989
+ const message = thread.messages.find((m) => m?.id === messageId);
990
+ const messageIndex = thread.messages.findIndex((m) => m?.id === messageId);
991
+ if (messageIndex === -1 || !message) {
992
+ return void 0;
993
+ }
994
+ if (messageIndex === 0 && thread.messages.length === 1) {
995
+ return {
996
+ intents: [
997
+ [
998
+ {
999
+ action: ThreadAction.DELETE,
1000
+ data: {
1001
+ subject,
1002
+ thread
1003
+ }
1004
+ }
1005
+ ]
1006
+ ]
1007
+ };
1008
+ } else {
1009
+ thread.messages.splice(messageIndex, 1);
1010
+ }
1011
+ return {
1012
+ undoable: {
1013
+ message: translations_default[0]["en-US"][THREAD_PLUGIN]["message deleted label"],
1014
+ data: {
1015
+ message,
1016
+ messageIndex
1017
+ }
1018
+ },
1019
+ intents: [
1020
+ [
1021
+ {
1022
+ action: ObservabilityAction.SEND_EVENT,
1023
+ data: {
1024
+ name: "threads.message.delete",
1025
+ properties: {
1026
+ threadId: thread.id,
1027
+ spaceId: space?.id
1028
+ }
1029
+ }
1030
+ }
1031
+ ]
1032
+ ]
1033
+ };
1034
+ } else if (intent.undo) {
1035
+ const message = intent.data?.message;
1036
+ const messageIndex = intent.data?.messageIndex;
1037
+ if (!(message instanceof MessageType2) || !(typeof messageIndex === "number")) {
1038
+ return;
1039
+ }
1040
+ thread.messages.splice(messageIndex, 0, message);
1041
+ return {
1042
+ data: true,
1043
+ intents: [
1044
+ [
1045
+ {
1046
+ action: ObservabilityAction.SEND_EVENT,
1047
+ data: {
1048
+ name: "threads.message.undo-delete",
1049
+ properties: {
1050
+ threadId: thread.id,
1051
+ spaceId: space?.id
1052
+ }
1053
+ }
1054
+ }
1055
+ ]
1056
+ ]
1057
+ };
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ },
1063
+ markdown: {
1064
+ // TODO(burdon): Factor out extension factory into separate file (for simplicity).
1065
+ extensions: ({ document: doc }) => {
1066
+ const space = doc && getSpace2(doc);
1067
+ if (!doc || !space) {
1068
+ return [
1069
+ comments()
1070
+ ];
1071
+ }
1072
+ const threads = computed(() => [
1073
+ ...doc.threads.filter(nonNullable3),
1074
+ ...state.staging[fullyQualifiedId4(doc)] ?? []
1075
+ ].filter((thread) => !(thread?.status === "resolved")));
1076
+ return [
1077
+ listener2({
1078
+ onChange: () => {
1079
+ doc.threads.forEach((thread) => {
1080
+ if (thread?.anchor) {
1081
+ const [start, end] = thread.anchor.split(":");
1082
+ const name = doc.content && getTextInRange(createDocAccessor(doc.content, [
1083
+ "content"
1084
+ ]), start, end);
1085
+ if (name && name !== thread.name) {
1086
+ thread.name = name;
1087
+ }
1088
+ }
1089
+ });
1090
+ }
1091
+ }),
1092
+ createExternalCommentSync(doc.id, (sink) => effect(() => sink()), () => threads.value.filter((thread) => thread?.anchor).map((thread) => ({
1093
+ id: fullyQualifiedId4(thread),
1094
+ cursor: thread.anchor
1095
+ }))),
1096
+ comments({
1097
+ id: doc.id,
1098
+ onCreate: ({ cursor }) => {
1099
+ const [start, end] = cursor.split(":");
1100
+ const name = doc.content && getTextInRange(createDocAccessor(doc.content, [
1101
+ "content"
1102
+ ]), start, end);
1103
+ void intentPlugin?.provides.intent.dispatch({
1104
+ action: ThreadAction.CREATE,
1105
+ data: {
1106
+ cursor,
1107
+ name,
1108
+ subject: doc
1109
+ }
1110
+ });
1111
+ },
1112
+ onDelete: ({ id }) => {
1113
+ const stagingArea = state.staging[fullyQualifiedId4(doc)];
1114
+ if (stagingArea) {
1115
+ const index = stagingArea.findIndex((thread2) => fullyQualifiedId4(thread2) === id);
1116
+ if (index !== -1) {
1117
+ stagingArea.splice(index, 1);
1118
+ }
1119
+ }
1120
+ const thread = doc.threads.find((thread2) => thread2?.id === id);
1121
+ if (thread) {
1122
+ thread.anchor = void 0;
1123
+ }
1124
+ },
1125
+ onUpdate: ({ id, cursor }) => {
1126
+ const thread = state.staging[fullyQualifiedId4(doc)]?.find((thread2) => fullyQualifiedId4(thread2) === id) ?? doc.threads.find((thread2) => thread2?.id === id);
1127
+ if (thread instanceof ThreadType && thread.anchor) {
1128
+ const [start, end] = thread.anchor.split(":");
1129
+ thread.name = doc.content && getTextInRange(createDocAccessor(doc.content, [
1130
+ "content"
1131
+ ]), start, end);
1132
+ thread.anchor = cursor;
1133
+ }
1134
+ },
1135
+ onSelect: ({ selection: { current, closest } }) => {
1136
+ const dispatch = intentPlugin?.provides.intent.dispatch;
1137
+ if (dispatch) {
1138
+ void dispatch([
1139
+ {
1140
+ action: ThreadAction.SELECT,
1141
+ data: {
1142
+ current: current ?? closest
1143
+ }
1144
+ }
1145
+ ]);
1146
+ }
1147
+ }
1148
+ })
1149
+ ];
1150
+ }
1151
+ }
1152
+ }
1153
+ };
1154
+ };
1155
+
1156
+ // packages/plugins/plugin-thread/src/index.ts
1157
+ var src_default = ThreadPlugin;
1158
+ export {
1159
+ ThreadAction,
1160
+ ThreadPlugin,
1161
+ src_default as default,
1162
+ useAnalyticsCallback,
1163
+ useOnEditAnalytics,
1164
+ useStatus
1165
+ };
1166
+ //# sourceMappingURL=index.mjs.map