@dxos/plugin-thread 0.7.4 → 0.7.5-main.499c70c

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 (72) hide show
  1. package/dist/lib/browser/{ThreadArticle-6IXTL7CH.mjs → ThreadArticle-EWLTNU6Z.mjs} +2 -2
  2. package/dist/lib/browser/{ThreadMain-4IAS67KE.mjs → ThreadMain-ABEDFWHC.mjs} +2 -2
  3. package/dist/lib/browser/chunk-6KTMQPL4.mjs +77 -0
  4. package/dist/lib/browser/chunk-6KTMQPL4.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-52HWDLBR.mjs → chunk-OD2IZKRP.mjs} +23 -45
  6. package/dist/lib/browser/chunk-OD2IZKRP.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +323 -480
  8. package/dist/lib/browser/index.mjs.map +3 -3
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/types.mjs +1 -1
  11. package/dist/lib/node/{ThreadArticle-I7ADXDW3.cjs → ThreadArticle-ZCNMFBAO.cjs} +6 -6
  12. package/dist/lib/node/{ThreadMain-MRNJC3TI.cjs → ThreadMain-5AYGCXXP.cjs} +6 -6
  13. package/dist/lib/node/{chunk-YCDM2SZM.cjs → chunk-2CB5AVBL.cjs} +27 -50
  14. package/dist/lib/node/chunk-2CB5AVBL.cjs.map +7 -0
  15. package/dist/lib/node/chunk-VW2P26MZ.cjs +96 -0
  16. package/dist/lib/node/chunk-VW2P26MZ.cjs.map +7 -0
  17. package/dist/lib/node/index.cjs +336 -491
  18. package/dist/lib/node/index.cjs.map +3 -3
  19. package/dist/lib/node/meta.json +1 -1
  20. package/dist/lib/node/types.cjs +2 -2
  21. package/dist/lib/node/types.cjs.map +1 -1
  22. package/dist/lib/node-esm/{ThreadArticle-CJHYMF2W.mjs → ThreadArticle-F37HYTOU.mjs} +2 -2
  23. package/dist/lib/node-esm/{ThreadMain-VISGW4IF.mjs → ThreadMain-OL6ZHNZY.mjs} +2 -2
  24. package/dist/lib/node-esm/{chunk-JG7MXH6O.mjs → chunk-AQ74G3GY.mjs} +23 -45
  25. package/dist/lib/node-esm/chunk-AQ74G3GY.mjs.map +7 -0
  26. package/dist/lib/node-esm/chunk-RMYATBXM.mjs +78 -0
  27. package/dist/lib/node-esm/chunk-RMYATBXM.mjs.map +7 -0
  28. package/dist/lib/node-esm/index.mjs +323 -480
  29. package/dist/lib/node-esm/index.mjs.map +3 -3
  30. package/dist/lib/node-esm/meta.json +1 -1
  31. package/dist/lib/node-esm/types.mjs +1 -1
  32. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  33. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  34. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  35. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  36. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  37. package/dist/types/src/components/ThreadComplementary.d.ts +1 -2
  38. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  39. package/dist/types/src/components/testing.d.ts +50 -66
  40. package/dist/types/src/components/testing.d.ts.map +1 -1
  41. package/dist/types/src/extensions/threads.d.ts +2 -2
  42. package/dist/types/src/extensions/threads.d.ts.map +1 -1
  43. package/dist/types/src/hooks/analytics.d.ts.map +1 -1
  44. package/dist/types/src/types.d.ts +76 -8
  45. package/dist/types/src/types.d.ts.map +1 -1
  46. package/dist/types/tsconfig.tsbuildinfo +1 -0
  47. package/package.json +30 -30
  48. package/src/ThreadPlugin.tsx +215 -361
  49. package/src/components/Chat.stories.tsx +10 -8
  50. package/src/components/ChatContainer.tsx +13 -11
  51. package/src/components/CommentContainer.tsx +14 -11
  52. package/src/components/MessageContainer.tsx +7 -5
  53. package/src/components/ThreadComplementary.tsx +18 -32
  54. package/src/components/testing.ts +26 -22
  55. package/src/extensions/threads.ts +16 -25
  56. package/src/hooks/analytics.ts +6 -10
  57. package/src/types.ts +60 -9
  58. package/dist/lib/browser/chunk-52HWDLBR.mjs.map +0 -7
  59. package/dist/lib/browser/chunk-XUHNZID5.mjs +0 -20
  60. package/dist/lib/browser/chunk-XUHNZID5.mjs.map +0 -7
  61. package/dist/lib/node/chunk-XFO6D2LG.cjs +0 -39
  62. package/dist/lib/node/chunk-XFO6D2LG.cjs.map +0 -7
  63. package/dist/lib/node/chunk-YCDM2SZM.cjs.map +0 -7
  64. package/dist/lib/node-esm/chunk-I5MU54RT.mjs +0 -21
  65. package/dist/lib/node-esm/chunk-I5MU54RT.mjs.map +0 -7
  66. package/dist/lib/node-esm/chunk-JG7MXH6O.mjs.map +0 -7
  67. /package/dist/lib/browser/{ThreadArticle-6IXTL7CH.mjs.map → ThreadArticle-EWLTNU6Z.mjs.map} +0 -0
  68. /package/dist/lib/browser/{ThreadMain-4IAS67KE.mjs.map → ThreadMain-ABEDFWHC.mjs.map} +0 -0
  69. /package/dist/lib/node/{ThreadArticle-I7ADXDW3.cjs.map → ThreadArticle-ZCNMFBAO.cjs.map} +0 -0
  70. /package/dist/lib/node/{ThreadMain-MRNJC3TI.cjs.map → ThreadMain-5AYGCXXP.cjs.map} +0 -0
  71. /package/dist/lib/node-esm/{ThreadArticle-CJHYMF2W.mjs.map → ThreadArticle-F37HYTOU.mjs.map} +0 -0
  72. /package/dist/lib/node-esm/{ThreadMain-VISGW4IF.mjs.map → ThreadMain-OL6ZHNZY.mjs.map} +0 -0
@@ -7,8 +7,8 @@ import '@dxos-theme';
7
7
  import { type Meta } from '@storybook/react';
8
8
  import React, { useEffect, useState } from 'react';
9
9
 
10
- import { SurfaceProvider } from '@dxos/app-framework';
11
- import { ThreadType } from '@dxos/plugin-space/types';
10
+ import { createSurface, SurfaceProvider } from '@dxos/app-framework';
11
+ import { MessageType, ThreadType } from '@dxos/plugin-space/types';
12
12
  import { faker } from '@dxos/random';
13
13
  import { useClient } from '@dxos/react-client';
14
14
  import { type Space } from '@dxos/react-client/echo';
@@ -48,10 +48,12 @@ const Story = () => {
48
48
  return (
49
49
  <SurfaceProvider
50
50
  value={{
51
- components: {
52
- ObjectMessage: ({ role }) => {
53
- return <span>{JSON.stringify({ role })}</span>;
54
- },
51
+ surfaces: {
52
+ ObjectMessage: createSurface({
53
+ id: 'test',
54
+ role: 'card',
55
+ component: ({ role }) => <span>{JSON.stringify({ role })}</span>,
56
+ }),
55
57
  },
56
58
  }}
57
59
  >
@@ -71,8 +73,8 @@ const meta: Meta = {
71
73
  title: 'plugins/plugin-thread/Chat',
72
74
  component: Thread,
73
75
  // TODO(burdon): Use decorator.
74
- render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType]} />,
75
- decorators: [withTheme, withLayout({ fullscreen: true })],
76
+ render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType, MessageType]} />,
77
+ decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
76
78
  parameters: { translations },
77
79
  };
78
80
 
@@ -4,16 +4,16 @@
4
4
 
5
5
  import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
6
6
 
7
- import { create } from '@dxos/live-object';
7
+ import { Ref } from '@dxos/echo-schema';
8
+ import { create, makeRef, RefArray } from '@dxos/live-object';
8
9
  import { MessageType } from '@dxos/plugin-space/types';
9
- import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
10
+ import { fullyQualifiedId, getSpace, useMembers, type Expando } from '@dxos/react-client/echo';
10
11
  import { useIdentity } from '@dxos/react-client/halo';
11
12
  import { Icon, ScrollArea, useThemeContext, useTranslation } from '@dxos/react-ui';
12
13
  import { createBasicExtensions, createThemeExtensions, listener } from '@dxos/react-ui-editor';
13
14
  import { StackItem } from '@dxos/react-ui-stack';
14
15
  import { mx } from '@dxos/react-ui-theme';
15
16
  import { MessageTextbox, type MessageTextboxProps, Thread, ThreadFooter, threadLayout } from '@dxos/react-ui-thread';
16
- import { nonNullable } from '@dxos/util';
17
17
 
18
18
  import { MessageContainer } from './MessageContainer';
19
19
  import { command } from './command-extension';
@@ -77,12 +77,14 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
77
77
  }
78
78
 
79
79
  thread.messages.push(
80
- create(MessageType, {
81
- sender: { identityKey: identity.identityKey.toHex() },
82
- timestamp: new Date().toISOString(),
83
- text: messageRef.current,
84
- context,
85
- }),
80
+ makeRef(
81
+ create(MessageType, {
82
+ sender: { identityKey: identity.identityKey.toHex() },
83
+ timestamp: new Date().toISOString(),
84
+ text: messageRef.current,
85
+ context: makeRef(context as Expando),
86
+ }),
87
+ ),
86
88
  );
87
89
 
88
90
  messageRef.current = '';
@@ -93,7 +95,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
93
95
  };
94
96
 
95
97
  const handleDelete = (id: string) => {
96
- const messageIndex = thread.messages.filter(nonNullable).findIndex((message) => message.id === id);
98
+ const messageIndex = thread.messages.findIndex(Ref.hasObjectId(id));
97
99
  if (messageIndex !== -1) {
98
100
  thread.messages.splice(messageIndex, 1);
99
101
  }
@@ -108,7 +110,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
108
110
  <ScrollArea.Root classNames='col-span-2'>
109
111
  <ScrollArea.Viewport classNames='overflow-anchored after:overflow-anchor after:block after:bs-px after:-mbs-px [&>div]:min-bs-full [&>div]:!grid [&>div]:grid-rows-[1fr_0]'>
110
112
  <div role='none' className={mx(threadLayout, 'place-self-end')}>
111
- {thread.messages.filter(nonNullable).map((message) => (
113
+ {RefArray.allResolvedTargets(thread.messages ?? []).map((message) => (
112
114
  <MessageContainer key={message.id} message={message} members={members} onDelete={handleDelete} />
113
115
  ))}
114
116
  </div>
@@ -5,7 +5,7 @@
5
5
  import { CheckCircle, X } from '@phosphor-icons/react';
6
6
  import React, { useCallback, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { create } from '@dxos/live-object';
8
+ import { create, makeRef, RefArray } from '@dxos/live-object';
9
9
  import { MessageType } from '@dxos/plugin-space/types';
10
10
  import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
11
11
  import { useIdentity } from '@dxos/react-client/halo';
@@ -56,7 +56,7 @@ const ToggleResolvedButton = ({
56
56
  </Button>
57
57
  </Tooltip.Trigger>
58
58
  <Tooltip.Portal>
59
- <Tooltip.Content classNames={'z-[21]'}>
59
+ <Tooltip.Content>
60
60
  {label}
61
61
  <Tooltip.Arrow />
62
62
  </Tooltip.Content>
@@ -82,7 +82,7 @@ const DeleteThreadButton = ({ onDelete }: { onDelete: () => void }) => {
82
82
  </Button>
83
83
  </Tooltip.Trigger>
84
84
  <Tooltip.Portal>
85
- <Tooltip.Content classNames='z-[21]'>
85
+ <Tooltip.Content classNames='z-30'>
86
86
  {label}
87
87
  <Tooltip.Arrow />
88
88
  </Tooltip.Content>
@@ -135,12 +135,14 @@ export const CommentContainer = ({
135
135
  }
136
136
 
137
137
  thread.messages.push(
138
- create(MessageType, {
139
- sender: { identityKey: identity.identityKey.toHex() },
140
- timestamp: new Date().toISOString(),
141
- text: messageRef.current,
142
- context,
143
- }),
138
+ makeRef(
139
+ create(MessageType, {
140
+ sender: { identityKey: identity.identityKey.toHex() },
141
+ timestamp: new Date().toISOString(),
142
+ text: messageRef.current,
143
+ context: context ? makeRef(context) : undefined,
144
+ }),
145
+ ),
144
146
  );
145
147
  onComment?.(thread);
146
148
 
@@ -168,7 +170,7 @@ export const CommentContainer = ({
168
170
  <ThreadHeading detached>{thread.name}</ThreadHeading>
169
171
  </Tooltip.Trigger>
170
172
  <Tooltip.Portal>
171
- <Tooltip.Content classNames='z-[21]' side='top'>
173
+ <Tooltip.Content classNames='z-30' side='top'>
172
174
  {t('detached thread label')}
173
175
  <Tooltip.Arrow />
174
176
  </Tooltip.Content>
@@ -185,7 +187,8 @@ export const CommentContainer = ({
185
187
  {onThreadDelete && <DeleteThreadButton onDelete={onThreadDelete} />}
186
188
  </div>
187
189
  </div>
188
- {thread.messages.filter(nonNullable).map((message) => (
190
+ {/** TODO(dmaretskyi): How's `thread.messages` undefined? */}
191
+ {RefArray.allResolvedTargets(thread.messages?.filter(nonNullable) ?? []).map((message) => (
189
192
  <MessageContainer
190
193
  key={message.id}
191
194
  message={message}
@@ -7,6 +7,7 @@ import { Check, PencilSimple, X } from '@phosphor-icons/react';
7
7
  import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
8
8
 
9
9
  import { Surface } from '@dxos/app-framework';
10
+ import { RefArray } from '@dxos/live-object';
10
11
  import { type MessageType } from '@dxos/plugin-space/types';
11
12
  import { PublicKey } from '@dxos/react-client';
12
13
  import { type ReactiveEchoObject, type Expando, type SpaceMember } from '@dxos/react-client/echo';
@@ -22,7 +23,6 @@ import {
22
23
  mx,
23
24
  } from '@dxos/react-ui-theme';
24
25
  import { MessageHeading, MessageRoot } from '@dxos/react-ui-thread';
25
- import { nonNullable } from '@dxos/util';
26
26
 
27
27
  import { command } from './command-extension';
28
28
  import { useOnEditAnalytics } from '../hooks';
@@ -70,7 +70,7 @@ export const MessageContainer = ({
70
70
  </Button>
71
71
  </Tooltip.Trigger>
72
72
  <Tooltip.Portal>
73
- <Tooltip.Content classNames='z-[21]'>
73
+ <Tooltip.Content>
74
74
  {editLabel}
75
75
  <Tooltip.Arrow />
76
76
  </Tooltip.Content>
@@ -91,7 +91,7 @@ export const MessageContainer = ({
91
91
  </Button>
92
92
  </Tooltip.Trigger>
93
93
  <Tooltip.Portal>
94
- <Tooltip.Content classNames='z-[21]'>
94
+ <Tooltip.Content>
95
95
  {deleteLabel}
96
96
  <Tooltip.Arrow />
97
97
  </Tooltip.Content>
@@ -101,7 +101,9 @@ export const MessageContainer = ({
101
101
  </ButtonGroup>
102
102
  </MessageHeading>
103
103
  <TextboxBlock message={message} isAuthor={userIsAuthor} editing={editing} />
104
- {message.parts?.filter(nonNullable).map((part, index) => <MessagePart key={index} part={part} />)}
104
+ {RefArray.allResolvedTargets(message.parts ?? []).map((part, index) => (
105
+ <MessagePart key={index} part={part} />
106
+ ))}
105
107
  </MessageRoot>
106
108
  );
107
109
  };
@@ -180,7 +182,7 @@ const MessageBlockObjectTile: MosaicTileComponent<ReactiveEchoObject<any>> = for
180
182
  <Surface
181
183
  role='card'
182
184
  limit={1}
183
- data={{ content: item }}
185
+ data={{ subject: item }}
184
186
  draggableProps={draggableProps}
185
187
  fallback={title}
186
188
  {...props}
@@ -4,33 +4,30 @@
4
4
 
5
5
  import React, { useEffect, useMemo } from 'react';
6
6
 
7
- import { LayoutAction, type Plugin, useIntentDispatcher, useResolvePlugins } from '@dxos/app-framework';
7
+ import { createIntent, LayoutAction, type Plugin, useIntentDispatcher, useResolvePlugins } from '@dxos/app-framework';
8
8
  import { type ThreadType } from '@dxos/plugin-space';
9
- import { fullyQualifiedId } from '@dxos/react-client/echo';
9
+ import { fullyQualifiedId, RefArray } from '@dxos/react-client/echo';
10
10
  import { useAttended } from '@dxos/react-ui-attention';
11
11
  import { nonNullable } from '@dxos/util';
12
12
 
13
13
  import { CommentsContainer } from '../components';
14
- import { THREAD_PLUGIN } from '../meta';
15
14
  import { ThreadAction, type ThreadProvides } from '../types';
16
15
 
17
16
  const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
18
17
  'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
19
18
 
20
19
  export const ThreadComplementary = ({
21
- role,
22
20
  subject,
23
21
  drafts,
24
22
  current,
25
23
  showResolvedThreads,
26
24
  }: {
27
- role: string;
28
25
  subject: any;
29
26
  drafts: ThreadType[] | undefined;
30
27
  current?: string;
31
28
  showResolvedThreads?: boolean;
32
29
  }) => {
33
- const dispatch = useIntentDispatcher();
30
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
34
31
 
35
32
  const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
36
33
  const threadProvides = useMemo(() => threadsIntegrators.map((p) => p.provides.thread), [threadsIntegrators]);
@@ -40,9 +37,11 @@ export const ThreadComplementary = ({
40
37
  );
41
38
  const sort = useMemo(() => createSort?.(subject), [createSort, subject]);
42
39
 
40
+ const threadObjects = RefArray.allResolvedTargets(subject.threads ?? []);
41
+
43
42
  const threads = useMemo(() => {
44
- return subject.threads.concat(drafts ?? []).filter(nonNullable) as ThreadType[];
45
- }, [JSON.stringify(subject.threads), JSON.stringify(drafts)]);
43
+ return threadObjects.concat(drafts ?? []).filter(nonNullable) as ThreadType[];
44
+ }, [JSON.stringify(threadObjects), JSON.stringify(drafts)]);
46
45
 
47
46
  const detachedIds = useMemo(() => {
48
47
  return threads.filter(({ anchor }) => !anchor).map((thread) => fullyQualifiedId(thread));
@@ -72,34 +71,21 @@ export const ThreadComplementary = ({
72
71
  // TODO(wittjosiah): Should this be a thread-specific intent?
73
72
  // The layout doesn't know about threads and this working depends on other plugins conditionally handling it.
74
73
  // This may be overloading this intent or highjacking its intended purpose.
75
- void dispatch?.([
76
- {
77
- action: LayoutAction.SCROLL_INTO_VIEW,
78
- data: {
79
- id: fullyQualifiedId(subject),
80
- thread: threadId,
81
- cursor: thread.anchor,
82
- },
83
- },
84
- ]);
74
+ void dispatch?.(
75
+ createIntent(LayoutAction.ScrollIntoView, {
76
+ id: fullyQualifiedId(subject),
77
+ cursor: thread.anchor,
78
+ ref: threadId,
79
+ }),
80
+ );
85
81
  }
86
82
  }}
87
- onThreadDelete={(thread) => {
88
- return dispatch({ plugin: THREAD_PLUGIN, action: ThreadAction.DELETE, data: { subject, thread } });
89
- }}
83
+ onThreadDelete={(thread) => dispatch(createIntent(ThreadAction.Delete, { thread, subject }))}
90
84
  onMessageDelete={(thread, messageId) =>
91
- dispatch({
92
- plugin: THREAD_PLUGIN,
93
- action: ThreadAction.DELETE_MESSAGE,
94
- data: { subject, thread, messageId },
95
- })
85
+ dispatch(createIntent(ThreadAction.DeleteMessage, { thread, subject, messageId }))
96
86
  }
97
- onThreadToggleResolved={(thread) =>
98
- dispatch({ plugin: THREAD_PLUGIN, action: ThreadAction.TOGGLE_RESOLVED, data: { thread } })
99
- }
100
- onComment={(thread) => {
101
- void dispatch({ action: ThreadAction.ON_MESSAGE_ADD, data: { thread, subject } });
102
- }}
87
+ onThreadToggleResolved={(thread) => dispatch(createIntent(ThreadAction.ToggleResolved, { thread }))}
88
+ onComment={(thread) => dispatch(createIntent(ThreadAction.OnMessageAdd, { thread, subject }))}
103
89
  />
104
90
  );
105
91
  };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Expando } from '@dxos/echo-schema';
6
6
  import { PublicKey } from '@dxos/keys';
7
- import { create } from '@dxos/live-object';
7
+ import { create, makeRef } from '@dxos/live-object';
8
8
  import { MessageType, ThreadType } from '@dxos/plugin-space/types';
9
9
  import { faker } from '@dxos/random';
10
10
  import { type Identity } from '@dxos/react-client/halo';
@@ -12,20 +12,22 @@ import { type Identity } from '@dxos/react-client/halo';
12
12
  export const createChatThread = (identity: Identity) => {
13
13
  return create(ThreadType, {
14
14
  messages: Array.from({ length: 8 }).map(() =>
15
- create(MessageType, {
16
- sender: {
17
- identityKey: faker.datatype.boolean() ? identity.identityKey.toHex() : PublicKey.random().toHex(),
18
- },
19
- timestamp: faker.date.recent().toISOString(),
20
- text: faker.lorem.sentences(3),
21
- parts: faker.helpers.multiple(
22
- () =>
23
- faker.datatype.boolean({ probability: 0.8 })
24
- ? undefined
25
- : create(Expando, { name: faker.lorem.sentence() }),
26
- { count: { min: 1, max: 3 } },
27
- ),
28
- }),
15
+ makeRef(
16
+ create(MessageType, {
17
+ sender: {
18
+ identityKey: faker.datatype.boolean() ? identity.identityKey.toHex() : PublicKey.random().toHex(),
19
+ },
20
+ timestamp: faker.date.recent().toISOString(),
21
+ text: faker.lorem.sentences(3),
22
+ parts: faker.helpers.multiple(
23
+ () =>
24
+ faker.datatype.boolean({ probability: 0.8 })
25
+ ? undefined
26
+ : (makeRef(create(Expando, { name: faker.lorem.sentence() })) as any), // TODO(dmaretskyi): Fix types in schema.
27
+ { count: { min: 1, max: 3 } },
28
+ ),
29
+ }),
30
+ ),
29
31
  ),
30
32
  });
31
33
  };
@@ -34,13 +36,15 @@ export const createCommentThread = (identity: Identity) => {
34
36
  return create(ThreadType, {
35
37
  messages: faker.helpers.multiple(
36
38
  () =>
37
- create(MessageType, {
38
- sender: {
39
- identityKey: faker.datatype.boolean() ? identity.identityKey.toHex() : PublicKey.random().toHex(),
40
- },
41
- timestamp: new Date().toISOString(),
42
- text: faker.lorem.sentences(3),
43
- }),
39
+ makeRef(
40
+ create(MessageType, {
41
+ sender: {
42
+ identityKey: faker.datatype.boolean() ? identity.identityKey.toHex() : PublicKey.random().toHex(),
43
+ },
44
+ timestamp: new Date().toISOString(),
45
+ text: faker.lorem.sentences(3),
46
+ }),
47
+ ),
44
48
  { count: { min: 2, max: 3 } },
45
49
  ),
46
50
  });
@@ -6,12 +6,13 @@ import { type Extension } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { computed, effect } from '@preact/signals-core';
8
8
 
9
- import { type IntentDispatcher } from '@dxos/app-framework';
9
+ import { createIntent, type PromiseIntentDispatcher } from '@dxos/app-framework';
10
+ import { Ref } from '@dxos/echo-schema';
11
+ import { RefArray } from '@dxos/live-object';
10
12
  import { type DocumentType } from '@dxos/plugin-markdown/types';
11
13
  import { ThreadType } from '@dxos/plugin-space/types';
12
14
  import { getSpace, getTextInRange, createDocAccessor, fullyQualifiedId } from '@dxos/react-client/echo';
13
15
  import { comments, createExternalCommentSync } from '@dxos/react-ui-editor';
14
- import { nonNullable } from '@dxos/util';
15
16
 
16
17
  import { ThreadAction, type ThreadState } from '../types';
17
18
 
@@ -19,14 +20,14 @@ import { ThreadAction, type ThreadState } from '../types';
19
20
  const getName = (doc: DocumentType, anchor: string): string | undefined => {
20
21
  if (doc.content) {
21
22
  const [start, end] = anchor.split(':');
22
- return getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
23
+ return getTextInRange(createDocAccessor(doc.content.target!, ['content']), start, end);
23
24
  }
24
25
  };
25
26
 
26
27
  /**
27
28
  * Construct plugins.
28
29
  */
29
- export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: IntentDispatcher): Extension => {
30
+ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: PromiseIntentDispatcher): Extension => {
30
31
  const space = doc && getSpace(doc);
31
32
  if (!doc || !space || !dispatch) {
32
33
  // Include no-op comments extension here to ensure that the facets are always present when they are expected.
@@ -36,7 +37,7 @@ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: Inten
36
37
 
37
38
  // TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
38
39
  const threads = computed(() =>
39
- [...doc.threads.filter(nonNullable), ...(state.drafts[fullyQualifiedId(doc)] ?? [])].filter(
40
+ [...RefArray.allResolvedTargets(doc.threads), ...(state.drafts[fullyQualifiedId(doc)] ?? [])].filter(
40
41
  (thread) => !(thread?.status === 'resolved'),
41
42
  ),
42
43
  );
@@ -44,8 +45,8 @@ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: Inten
44
45
  return [
45
46
  EditorView.updateListener.of((update) => {
46
47
  if (update.docChanged) {
47
- doc.threads.forEach((thread) => {
48
- if (thread?.anchor) {
48
+ RefArray.allResolvedTargets(doc.threads).forEach((thread) => {
49
+ if (thread.anchor) {
49
50
  // Only update if the name has changed, otherwise this will cause an infinite loop.
50
51
  // Skip if the name is empty; this means comment text was deleted, but thread name should remain.
51
52
  const name = getName(doc, thread.anchor);
@@ -70,14 +71,7 @@ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: Inten
70
71
  id: fullyQualifiedId(doc),
71
72
  onCreate: ({ cursor }) => {
72
73
  const name = getName(doc, cursor);
73
- void dispatch({
74
- action: ThreadAction.CREATE,
75
- data: {
76
- cursor,
77
- name,
78
- subject: doc,
79
- },
80
- });
74
+ void dispatch(createIntent(ThreadAction.Create, { cursor, name, subject: doc }));
81
75
  },
82
76
  onDelete: ({ id }) => {
83
77
  const draft = state.drafts[fullyQualifiedId(doc)];
@@ -88,7 +82,7 @@ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: Inten
88
82
  }
89
83
  }
90
84
 
91
- const thread = doc.threads.find((thread) => thread?.id === id);
85
+ const thread = doc.threads.find(Ref.hasObjectId(id))?.target;
92
86
  if (thread) {
93
87
  thread.anchor = undefined;
94
88
  }
@@ -96,21 +90,18 @@ export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: Inten
96
90
  onUpdate: ({ id, cursor }) => {
97
91
  const thread =
98
92
  state.drafts[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
99
- doc.threads.find((thread) => thread?.id === id);
93
+ doc.threads.find(Ref.hasObjectId(id))?.target;
100
94
 
101
95
  if (thread instanceof ThreadType && thread.anchor) {
102
96
  thread.name = getName(doc, thread.anchor);
103
97
  thread.anchor = cursor;
104
98
  }
105
99
  },
106
- onSelect: ({ selection: { current, closest } }) => {
107
- void dispatch({
108
- action: ThreadAction.SELECT,
109
- data: {
110
- current: current ?? closest,
111
- skipOpen: true,
112
- },
113
- });
100
+ onSelect: ({ selection }) => {
101
+ const current = selection.current ?? selection.closest;
102
+ if (current) {
103
+ void dispatch(createIntent(ThreadAction.Select, { current, skipOpen: true }));
104
+ }
114
105
  },
115
106
  }),
116
107
  ];
@@ -4,24 +4,20 @@
4
4
 
5
5
  import { useCallback } from 'react';
6
6
 
7
- import { useIntentDispatcher } from '@dxos/app-framework';
7
+ import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
8
8
  import { type ReactiveObject } from '@dxos/live-object';
9
- import { ObservabilityAction } from '@dxos/plugin-observability/meta';
9
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
10
10
  import { getSpace } from '@dxos/react-client/echo';
11
11
  import { useOnTransition } from '@dxos/react-ui';
12
12
 
13
13
  export const useAnalyticsCallback = (spaceId: string | undefined, name: string, meta?: any) => {
14
- const dispatch = useIntentDispatcher();
14
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
15
15
 
16
16
  return useCallback(
17
17
  (dynamicMeta?: any) => {
18
- void dispatch({
19
- action: ObservabilityAction.SEND_EVENT,
20
- data: {
21
- name,
22
- properties: { ...meta, ...dynamicMeta, spaceId },
23
- },
24
- });
18
+ void dispatch(
19
+ createIntent(ObservabilityAction.SendEvent, { name, properties: { ...meta, ...dynamicMeta, spaceId } }),
20
+ );
25
21
  },
26
22
  [dispatch, name, meta, spaceId],
27
23
  );
package/src/types.ts CHANGED
@@ -10,21 +10,72 @@ import type {
10
10
  SurfaceProvides,
11
11
  TranslationsProvides,
12
12
  } from '@dxos/app-framework';
13
+ import { S } from '@dxos/echo-schema';
13
14
  import { type PanelProvides } from '@dxos/plugin-deck/types';
14
15
  import { type MarkdownExtensionProvides } from '@dxos/plugin-markdown';
15
16
  import { type SchemaProvides } from '@dxos/plugin-space';
16
- import { type ThreadType } from '@dxos/plugin-space/types';
17
+ import { ChannelType, MessageType, ThreadType } from '@dxos/plugin-space/types';
18
+ import { EchoObjectSchema } from '@dxos/react-client/echo';
17
19
 
18
20
  import { THREAD_PLUGIN } from './meta';
19
21
 
20
- const THREAD_ACTION = `${THREAD_PLUGIN}/action`;
21
- export enum ThreadAction {
22
- CREATE = `${THREAD_ACTION}/create`,
23
- SELECT = `${THREAD_ACTION}/select`,
24
- DELETE = `${THREAD_ACTION}/delete`,
25
- ON_MESSAGE_ADD = `${THREAD_ACTION}/on-message-add`,
26
- DELETE_MESSAGE = `${THREAD_ACTION}/delete-message`,
27
- TOGGLE_RESOLVED = `${THREAD_ACTION}/toggle-resolved`,
22
+ export namespace ThreadAction {
23
+ const THREAD_ACTION = `${THREAD_PLUGIN}/action`;
24
+
25
+ export class Create extends S.TaggedClass<Create>()(`${THREAD_ACTION}/create`, {
26
+ input: S.Struct({
27
+ name: S.optional(S.String),
28
+ cursor: S.optional(S.String),
29
+ subject: S.optional(EchoObjectSchema),
30
+ }),
31
+ output: S.Struct({
32
+ object: S.Union(ChannelType, ThreadType),
33
+ }),
34
+ }) {}
35
+
36
+ export class Select extends S.TaggedClass<Select>()(`${THREAD_ACTION}/select`, {
37
+ input: S.Struct({
38
+ current: S.String,
39
+ skipOpen: S.optional(S.Boolean),
40
+ }),
41
+ output: S.Void,
42
+ }) {}
43
+
44
+ export class ToggleResolved extends S.TaggedClass<ToggleResolved>()(`${THREAD_ACTION}/toggle-resolved`, {
45
+ input: S.Struct({
46
+ thread: ThreadType,
47
+ }),
48
+ output: S.Void,
49
+ }) {}
50
+
51
+ export class Delete extends S.TaggedClass<Delete>()(`${THREAD_ACTION}/delete`, {
52
+ input: S.Struct({
53
+ thread: ThreadType,
54
+ subject: EchoObjectSchema,
55
+ cursor: S.optional(S.String),
56
+ }),
57
+ output: S.Void,
58
+ }) {}
59
+
60
+ // TODO(wittjosiah): Rename?
61
+ export class OnMessageAdd extends S.TaggedClass<OnMessageAdd>()(`${THREAD_ACTION}/on-message-add`, {
62
+ input: S.Struct({
63
+ thread: ThreadType,
64
+ subject: EchoObjectSchema,
65
+ }),
66
+ output: S.Void,
67
+ }) {}
68
+
69
+ export class DeleteMessage extends S.TaggedClass<DeleteMessage>()(`${THREAD_ACTION}/delete-message`, {
70
+ input: S.Struct({
71
+ thread: ThreadType,
72
+ subject: EchoObjectSchema,
73
+ messageId: S.String,
74
+ message: S.optional(MessageType),
75
+ messageIndex: S.optional(S.Number),
76
+ }),
77
+ output: S.Void,
78
+ }) {}
28
79
  }
29
80
 
30
81
  // TODO(Zan): Move this to the plugin-space plugin or another common location when we implement threads in sheets.