@dxos/plugin-thread 0.6.13 → 0.6.14-main.1366248

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