@dxos/plugin-thread 0.6.13-main.ed424a1 → 0.6.13-staging.1e988a3

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