@dxos/plugin-thread 0.7.4 → 0.7.5-main.9d26e3a

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
@@ -5,10 +5,12 @@
5
5
  import React from 'react';
6
6
 
7
7
  import {
8
+ createIntent,
9
+ createResolver,
10
+ createSurface,
8
11
  type IntentPluginProvides,
9
12
  isLayoutParts,
10
13
  LayoutAction,
11
- type LocationProvides,
12
14
  NavigationAction,
13
15
  parseIntentPlugin,
14
16
  parseMetadataResolverPlugin,
@@ -18,33 +20,28 @@ import {
18
20
  resolvePlugin,
19
21
  } from '@dxos/app-framework';
20
22
  import { type UnsubscribeCallback } from '@dxos/async';
23
+ import { Ref } from '@dxos/echo-schema';
21
24
  import { LocalStorageStore } from '@dxos/local-storage';
22
25
  import { log } from '@dxos/log';
23
- import { parseClientPlugin } from '@dxos/plugin-client';
24
- import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxos/plugin-graph';
25
- import { ObservabilityAction } from '@dxos/plugin-observability/meta';
26
- import { memoizeQuery, SpaceAction } from '@dxos/plugin-space';
26
+ import { parseClientPlugin } from '@dxos/plugin-client/types';
27
+ import { createExtension, toSignal } from '@dxos/plugin-graph';
28
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
29
+ import { memoizeQuery } from '@dxos/plugin-space';
27
30
  import { ChannelType, MessageType, ThreadType } from '@dxos/plugin-space/types';
28
31
  import {
29
32
  create,
30
33
  fullyQualifiedId,
31
34
  getSpace,
32
35
  getTypename,
33
- loadObjectReferences,
36
+ makeRef,
34
37
  parseId,
35
38
  type ReactiveEchoObject,
39
+ RefArray,
36
40
  SpaceState,
37
41
  } from '@dxos/react-client/echo';
38
42
  import { translations as threadTranslations } from '@dxos/react-ui-thread';
39
43
 
40
- import {
41
- ChatContainer,
42
- ChatHeading,
43
- ThreadArticle,
44
- ThreadComplementary,
45
- ThreadMain,
46
- ThreadSettings,
47
- } from './components';
44
+ import { ThreadArticle, ThreadComplementary, ThreadSettings } from './components';
48
45
  import { threads } from './extensions';
49
46
  import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
50
47
  import translations from './translations';
@@ -73,7 +70,6 @@ export const ThreadPlugin = (): PluginDefinition<
73
70
  return viewStore[subjectId];
74
71
  };
75
72
 
76
- let navigationPlugin: Plugin<LocationProvides> | undefined;
77
73
  let intentPlugin: Plugin<IntentPluginProvides> | undefined;
78
74
 
79
75
  const unsubscribeCallbacks = [] as UnsubscribeCallback[];
@@ -91,8 +87,7 @@ export const ThreadPlugin = (): PluginDefinition<
91
87
  },
92
88
  };
93
89
  },
94
- ready: async (plugins) => {
95
- navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
90
+ ready: async ({ plugins }) => {
96
91
  intentPlugin = resolvePlugin(plugins, parseIntentPlugin)!;
97
92
  },
98
93
  unload: async () => {
@@ -103,15 +98,15 @@ export const ThreadPlugin = (): PluginDefinition<
103
98
  metadata: {
104
99
  records: {
105
100
  [ChannelType.typename]: {
106
- createObject: ThreadAction.CREATE,
101
+ createObject: (props: { name?: string }) => createIntent(ThreadAction.Create, props),
107
102
  placeholder: ['channel name placeholder', { ns: THREAD_PLUGIN }],
108
103
  icon: 'ph--chat--regular',
109
104
  // TODO(wittjosiah): Move out of metadata.
110
- loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
105
+ loadReferences: async (channel: ChannelType) => await RefArray.loadAll(channel.threads ?? []),
111
106
  },
112
107
  [ThreadType.typename]: {
113
108
  // TODO(wittjosiah): Move out of metadata.
114
- loadReferences: (thread: ThreadType) => loadObjectReferences(thread, (thread) => thread.messages),
109
+ loadReferences: async (thread: ThreadType) => await RefArray.loadAll(thread.messages ?? []),
115
110
  },
116
111
  [MessageType.typename]: {
117
112
  // TODO(wittjosiah): Move out of metadata.
@@ -144,7 +139,7 @@ export const ThreadPlugin = (): PluginDefinition<
144
139
  graph: {
145
140
  builder: (plugins) => {
146
141
  const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
147
- const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
142
+ const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatchPromise;
148
143
  const metadataResolver = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
149
144
  if (!client || !dispatch || !metadataResolver) {
150
145
  return [];
@@ -221,16 +216,15 @@ export const ThreadPlugin = (): PluginDefinition<
221
216
  const [spaceId, objectId] = dataId.split(':');
222
217
 
223
218
  const viewState = getViewState(dataId);
224
- const toggle = () => {
219
+ const toggle = async () => {
225
220
  const newToggleState = !viewState.showResolvedThreads;
226
221
  viewState.showResolvedThreads = newToggleState;
227
- void dispatch({
228
- action: ObservabilityAction.SEND_EVENT,
229
- data: {
222
+ await dispatch(
223
+ createIntent(ObservabilityAction.SendEvent, {
230
224
  name: 'threads.toggle-show-resolved',
231
225
  properties: { spaceId, threadId: objectId, showResolved: newToggleState },
232
- },
233
- });
226
+ }),
227
+ );
234
228
  };
235
229
 
236
230
  return [
@@ -248,405 +242,265 @@ export const ThreadPlugin = (): PluginDefinition<
248
242
  ];
249
243
  },
250
244
  }),
251
- createExtension({
252
- id: ThreadAction.CREATE,
253
- filter: (node): node is ActionGroup =>
254
- !!settings.values.standalone && isActionGroup(node) && node.id.startsWith(SpaceAction.ADD_OBJECT),
255
- actions: ({ node }) => {
256
- const id = node.id.split('/').at(-1);
257
- const [spaceId, objectId] = id?.split(':') ?? [];
258
- const space = client.spaces.get().find((space) => space.id === spaceId);
259
- const object = objectId && space?.db.getObjectById(objectId);
260
- const target = objectId ? object : space;
261
- if (!target) {
262
- return;
263
- }
264
-
265
- return [
266
- {
267
- id: `${THREAD_PLUGIN}/create/${node.id}`,
268
- data: async () => {
269
- await dispatch([
270
- { plugin: THREAD_PLUGIN, action: ThreadAction.CREATE },
271
- { action: SpaceAction.ADD_OBJECT, data: { target } },
272
- { action: NavigationAction.OPEN },
273
- ]);
274
- },
275
- properties: {
276
- label: ['create channel label', { ns: THREAD_PLUGIN }],
277
- icon: 'ph--chat--regular',
278
- testId: 'threadPlugin.createObject',
279
- },
280
- },
281
- ];
282
- },
283
- }),
284
245
  ];
285
246
  },
286
247
  },
287
248
  surface: {
288
- component: ({ data, role }) => {
289
- switch (role) {
290
- case 'main': {
291
- return data.active instanceof ChannelType && data.active.threads[0] ? (
292
- <ThreadMain thread={data.active.threads[0]} />
293
- ) : null;
294
- }
295
-
296
- case 'settings': {
297
- return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
298
- }
299
-
300
- case 'article': {
301
- const location = navigationPlugin?.provides.location;
302
-
303
- if (data.object instanceof ChannelType && data.object.threads[0]) {
304
- const channel = data.object;
249
+ definitions: ({ plugins }) => {
250
+ const location = resolvePlugin(plugins, parseNavigationPlugin)?.provides.location;
251
+ return [
252
+ createSurface({
253
+ id: `${THREAD_PLUGIN}/channel`,
254
+ role: 'article',
255
+ filter: (data): data is { subject: ChannelType } =>
256
+ data.subject instanceof ChannelType && !!data.subject.threads[0],
257
+ component: ({ data }) => {
258
+ const channel = data.subject;
259
+ const thread = channel.threads[0].target!;
305
260
  // TODO(zan): Maybe we should have utility for positional main object ids.
306
261
  if (isLayoutParts(location?.active) && location.active.main) {
307
262
  const layoutEntries = location.active.main;
308
263
  const currentPosition = layoutEntries.findIndex((entry) => channel.id === entry.id);
309
264
  if (currentPosition > 0) {
310
265
  const objectToTheLeft = layoutEntries[currentPosition - 1];
311
- const context = getSpace(data.object)?.db.getObjectById(objectToTheLeft.id);
312
- return <ThreadArticle thread={data.object.threads[0]} context={context} />;
266
+ const context = getSpace(channel)?.db.getObjectById(objectToTheLeft.id);
267
+ return <ThreadArticle thread={thread} context={context} />;
313
268
  }
314
269
  }
315
270
 
316
- return <ThreadArticle thread={data.object.threads[0]} />;
317
- }
318
-
319
- if (data.subject instanceof ThreadType) {
320
- return (
321
- <>
322
- <ChatHeading attendableId={data.subject.id} />
323
- <ChatContainer thread={data.subject} />
324
- </>
325
- );
326
- }
327
-
328
- break;
329
- }
330
-
331
- case 'complementary--comments': {
332
- if (
333
- data.subject &&
271
+ return <ThreadArticle thread={thread} />;
272
+ },
273
+ }),
274
+ createSurface({
275
+ id: `${THREAD_PLUGIN}/thread`,
276
+ role: 'complementary--comments',
277
+ filter: (data): data is { subject: { threads: Ref<ThreadType>[] } } =>
278
+ !!data.subject &&
334
279
  typeof data.subject === 'object' &&
335
280
  'threads' in data.subject &&
336
281
  Array.isArray(data.subject.threads) &&
337
- !(data.subject instanceof ChannelType)
338
- ) {
282
+ !(data.subject instanceof ChannelType),
283
+ component: ({ data }) => {
339
284
  const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
340
285
  return (
341
286
  <ThreadComplementary
342
- role={role}
343
287
  subject={data.subject}
344
288
  drafts={state.drafts[fullyQualifiedId(data.subject)]}
345
289
  current={state.current}
346
290
  showResolvedThreads={showResolvedThreads}
347
291
  />
348
292
  );
349
- }
350
-
351
- break;
352
- }
353
- }
354
-
355
- return null;
293
+ },
294
+ }),
295
+ createSurface({
296
+ id: `${THREAD_PLUGIN}/settings`,
297
+ role: 'settings',
298
+ filter: (data): data is any => data.plugin === THREAD_PLUGIN,
299
+ component: () => <ThreadSettings settings={settings.values} />,
300
+ }),
301
+ ];
356
302
  },
357
303
  },
358
304
  intent: {
359
- resolver: async (intent) => {
360
- switch (intent.action) {
361
- case ThreadAction.CREATE: {
362
- if (intent.data && intent.data.cursor !== undefined) {
363
- const { cursor, name, subject } = intent.data;
364
-
365
- // Seed the threads array if it does not exist.
366
- if (subject?.threads === undefined) {
367
- try {
368
- // Static schema will throw an error if subject does not support threads array property.
369
- subject.threads = [];
370
- } catch (err) {
371
- log.error('Subject does not support threads array', subject?.typename);
372
- return;
373
- }
374
- }
375
-
376
- const subjectId = fullyQualifiedId(subject);
377
- const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
378
- const draft = state.drafts[subjectId];
379
- if (draft) {
380
- draft.push(thread);
381
- } else {
382
- state.drafts[subjectId] = [thread];
305
+ resolvers: () => [
306
+ createResolver(ThreadAction.Create, ({ name, cursor, subject }) => {
307
+ if (cursor && subject) {
308
+ // Seed the threads array if it does not exist.
309
+ if (subject?.threads === undefined) {
310
+ try {
311
+ // Static schema will throw an error if subject does not support threads array property.
312
+ subject.threads = [];
313
+ } catch (err) {
314
+ log.error('Subject does not support threads array', subject?.typename);
315
+ return;
383
316
  }
317
+ }
384
318
 
385
- return {
386
- data: thread,
387
- intents: [
388
- [
389
- {
390
- action: ThreadAction.SELECT,
391
- data: { current: fullyQualifiedId(thread) },
392
- },
393
- ],
394
- [
395
- {
396
- action: NavigationAction.OPEN,
397
- data: {
398
- activeParts: { complementary: 'comments' },
399
- },
400
- },
401
- ],
402
- [
403
- {
404
- action: LayoutAction.SET_LAYOUT,
405
- data: {
406
- element: 'complementary',
407
- state: true,
408
- },
409
- },
410
- ],
411
- ],
412
- };
319
+ const subjectId = fullyQualifiedId(subject);
320
+ const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
321
+ const draft = state.drafts[subjectId];
322
+ if (draft) {
323
+ draft.push(thread);
413
324
  } else {
414
- // NOTE: This is the standalone thread creation case.
415
- return {
416
- data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
417
- };
325
+ state.drafts[subjectId] = [thread];
418
326
  }
419
- }
420
-
421
- case ThreadAction.SELECT: {
422
- state.current = intent.data?.current;
423
327
 
424
328
  return {
425
- data: true,
426
- intents: !intent.data?.skipOpen
427
- ? [
428
- [
429
- {
430
- action: NavigationAction.OPEN,
431
- data: {
432
- activeParts: { complementary: 'comments' },
433
- },
434
- },
435
- ],
436
- ]
437
- : [],
329
+ data: { object: thread },
330
+ intents: [
331
+ createIntent(ThreadAction.Select, { current: fullyQualifiedId(thread) }),
332
+ createIntent(NavigationAction.Open, { activeParts: { complementary: 'comments' } }),
333
+ createIntent(LayoutAction.SetLayout, { element: 'complementary', state: true }),
334
+ ],
335
+ };
336
+ } else {
337
+ // NOTE: This is the standalone thread creation case.
338
+ return {
339
+ data: { object: create(ChannelType, { threads: [makeRef(create(ThreadType, { messages: [] }))] }) },
438
340
  };
439
341
  }
342
+ }),
343
+ createResolver(ThreadAction.Select, ({ current, skipOpen }) => {
344
+ state.current = current;
345
+
346
+ return {
347
+ intents: !skipOpen
348
+ ? [createIntent(NavigationAction.Open, { activeParts: { complementary: 'comments' } })]
349
+ : undefined,
350
+ };
351
+ }),
352
+ createResolver(ThreadAction.ToggleResolved, ({ thread }) => {
353
+ if (thread.status === 'active' || thread.status === undefined) {
354
+ thread.status = 'resolved';
355
+ } else if (thread.status === 'resolved') {
356
+ thread.status = 'active';
357
+ }
440
358
 
441
- case ThreadAction.TOGGLE_RESOLVED: {
442
- const { thread } = intent.data ?? {};
443
- if (!(thread instanceof ThreadType)) {
359
+ const space = getSpace(thread);
360
+ const spaceId = space?.id;
361
+
362
+ return {
363
+ intents: [
364
+ createIntent(ObservabilityAction.SendEvent, {
365
+ name: 'threads.toggle-resolved',
366
+ properties: { threadId: thread.id, spaceId },
367
+ }),
368
+ ],
369
+ };
370
+ }),
371
+ createResolver(ThreadAction.Delete, ({ subject, thread }, undo) => {
372
+ const subjectId = fullyQualifiedId(subject);
373
+ const draft = state.drafts[subjectId];
374
+ if (draft) {
375
+ // Check if we're deleting a draft; if so, remove it.
376
+ const index = draft.findIndex((t) => t.id === thread.id);
377
+ if (index !== -1) {
378
+ draft.splice(index, 1);
444
379
  return;
445
380
  }
381
+ }
382
+
383
+ const space = getSpace(thread);
384
+ if (!space || !subject.threads) {
385
+ return;
386
+ }
446
387
 
447
- if (thread.status === 'active' || thread.status === undefined) {
448
- thread.status = 'resolved';
449
- } else if (thread.status === 'resolved') {
450
- thread.status = 'active';
388
+ if (!undo) {
389
+ const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
390
+ const cursor = subject.threads[index]?.anchor;
391
+ if (index !== -1) {
392
+ subject.threads?.splice(index, 1);
451
393
  }
452
394
 
453
- const space = getSpace(thread);
454
- const spaceId = space?.id;
395
+ space.db.remove(thread);
455
396
 
456
397
  return {
457
- data: true,
398
+ undoable: {
399
+ message: ['thread deleted label', { ns: THREAD_PLUGIN }],
400
+ data: { cursor },
401
+ },
458
402
  intents: [
459
- [
460
- {
461
- action: ObservabilityAction.SEND_EVENT,
462
- data: { name: 'threads.toggle-resolved', properties: { threadId: thread.id, spaceId } },
463
- },
464
- ],
403
+ createIntent(ObservabilityAction.SendEvent, {
404
+ name: 'threads.delete',
405
+ properties: { threadId: thread.id, spaceId: space.id },
406
+ }),
465
407
  ],
466
408
  };
467
- }
468
-
469
- case ThreadAction.DELETE: {
470
- const { subject, thread } = intent.data ?? {};
471
- if (
472
- !(thread instanceof ThreadType) ||
473
- !(subject && typeof subject === 'object' && 'threads' in subject)
474
- ) {
475
- return;
476
- }
409
+ } else {
410
+ // TODO(wittjosiah): SDK should do this automatically.
411
+ const savedThread = space.db.add(thread);
412
+ subject.threads.push(savedThread);
477
413
 
478
- const subjectId = fullyQualifiedId(subject);
479
- const draft = state.drafts[subjectId];
480
- if (draft) {
481
- // Check if we're deleting a draft; if so, remove it.
482
- const index = draft.findIndex((t) => t.id === thread.id);
483
- if (index !== -1) {
484
- draft.splice(index, 1);
485
- return { data: true };
486
- }
487
- }
414
+ return {
415
+ intents: [
416
+ createIntent(ObservabilityAction.SendEvent, {
417
+ name: 'threads.undo-delete',
418
+ properties: { threadId: thread.id, spaceId: space.id },
419
+ }),
420
+ ],
421
+ };
422
+ }
423
+ }),
424
+ createResolver(ThreadAction.OnMessageAdd, ({ thread, subject }) => {
425
+ const subjectId = fullyQualifiedId(subject);
426
+ const space = getSpace(subject);
427
+ const intents = [];
428
+ const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
429
+
430
+ if (state.drafts[subjectId]?.find((t) => t === thread)) {
431
+ // Move draft to document.
432
+ thread.status = 'active';
433
+ subject.threads ? subject.threads.push(makeRef(thread)) : (subject.threads = [makeRef(thread)]);
434
+ state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
435
+ intents.push(
436
+ createIntent(ObservabilityAction.SendEvent, {
437
+ name: 'threads.thread-created',
438
+ properties: analyticsProperties,
439
+ }),
440
+ );
441
+ }
488
442
 
489
- const space = getSpace(thread);
490
- if (!space || !subject.threads) {
443
+ intents.push(
444
+ createIntent(ObservabilityAction.SendEvent, {
445
+ name: 'threads.message-added',
446
+ properties: { ...analyticsProperties, threadLength: thread.messages.length },
447
+ }),
448
+ );
449
+
450
+ return { intents };
451
+ }),
452
+ createResolver(ThreadAction.DeleteMessage, ({ subject, thread, messageId, message, messageIndex }, undo) => {
453
+ const space = getSpace(subject);
454
+
455
+ if (!undo) {
456
+ const messageIndex = thread.messages.findIndex(Ref.hasObjectId(messageId));
457
+ const message = thread.messages[messageIndex]?.target;
458
+ if (!message) {
491
459
  return;
492
460
  }
493
461
 
494
- if (!intent.undo) {
495
- const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
496
- const cursor = subject.threads[index]?.anchor;
497
- if (index !== -1) {
498
- subject.threads?.splice(index, 1);
499
- }
500
-
501
- space.db.remove(thread);
502
-
503
- return {
504
- undoable: {
505
- message: translations[0]['en-US'][THREAD_PLUGIN]['thread deleted label'],
506
- data: { cursor },
507
- },
508
- intents: [
509
- [
510
- {
511
- action: ObservabilityAction.SEND_EVENT,
512
- data: { name: 'threads.delete', properties: { threadId: thread.id, spaceId: space.id } },
513
- },
514
- ],
515
- ],
516
- };
517
- } else if (intent.undo) {
518
- // TODO(wittjosiah): SDK should do this automatically.
519
- const savedThread = space.db.add(thread);
520
- subject.threads.push(savedThread);
521
-
462
+ if (messageIndex === 0 && thread.messages.length === 1) {
463
+ // If the message is the only message in the thread, delete the thread.
522
464
  return {
523
- data: true,
524
- intents: [
525
- [
526
- {
527
- action: ObservabilityAction.SEND_EVENT,
528
- data: { name: 'threads.undo-delete', properties: { threadId: thread.id, spaceId: space.id } },
529
- },
530
- ],
531
- ],
465
+ intents: [createIntent(ThreadAction.Delete, { subject, thread })],
532
466
  };
467
+ } else {
468
+ thread.messages.splice(messageIndex, 1);
533
469
  }
534
- break;
535
- }
536
-
537
- case ThreadAction.ON_MESSAGE_ADD: {
538
- const { thread, subject } = intent.data ?? {};
539
- if (
540
- !(thread instanceof ThreadType) ||
541
- !(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
542
- ) {
543
- return;
544
- }
545
-
546
- const subjectId = fullyQualifiedId(subject);
547
- const space = getSpace(subject);
548
- const intents = [];
549
- const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
550
-
551
- if (state.drafts[subjectId]?.find((t) => t === thread)) {
552
- // Move draft to document.
553
- thread.status = 'active';
554
- subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
555
- state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
556
- intents.push({
557
- action: ObservabilityAction.SEND_EVENT,
558
- data: { name: 'threads.thread-created', properties: analyticsProperties },
559
- });
560
- }
561
-
562
- intents.push({
563
- action: ObservabilityAction.SEND_EVENT,
564
- data: {
565
- name: 'threads.message-added',
566
- properties: { ...analyticsProperties, threadLength: thread.messages.length },
567
- },
568
- });
569
470
 
570
471
  return {
571
- data: thread,
572
- intents: [intents],
472
+ undoable: {
473
+ message: ['message deleted label', { ns: THREAD_PLUGIN }],
474
+ data: { message, messageIndex },
475
+ },
476
+ intents: [
477
+ createIntent(ObservabilityAction.SendEvent, {
478
+ name: 'threads.message.delete',
479
+ properties: { threadId: thread.id, spaceId: space?.id },
480
+ }),
481
+ ],
573
482
  };
574
- }
575
-
576
- case ThreadAction.DELETE_MESSAGE: {
577
- const { subject, thread, messageId } = intent.data ?? {};
578
- const space = getSpace(subject);
579
-
580
- if (
581
- !(thread instanceof ThreadType) ||
582
- !(subject && typeof subject === 'object' && 'threads' in subject)
583
- ) {
483
+ } else {
484
+ if (!messageIndex || !message) {
584
485
  return;
585
486
  }
586
487
 
587
- if (!intent.undo) {
588
- const message = thread.messages.find((m) => m?.id === messageId);
589
- const messageIndex = thread.messages.findIndex((m) => m?.id === messageId);
590
- if (messageIndex === -1 || !message) {
591
- return undefined;
592
- }
593
-
594
- if (messageIndex === 0 && thread.messages.length === 1) {
595
- // If the message is the only message in the thread, delete the thread.
596
- return {
597
- intents: [[{ action: ThreadAction.DELETE, data: { subject, thread } }]],
598
- };
599
- } else {
600
- thread.messages.splice(messageIndex, 1);
601
- }
602
-
603
- return {
604
- undoable: {
605
- message: translations[0]['en-US'][THREAD_PLUGIN]['message deleted label'],
606
- data: { message, messageIndex },
607
- },
608
- intents: [
609
- [
610
- {
611
- action: ObservabilityAction.SEND_EVENT,
612
- data: {
613
- name: 'threads.message.delete',
614
- properties: { threadId: thread.id, spaceId: space?.id },
615
- },
616
- },
617
- ],
618
- ],
619
- };
620
- } else if (intent.undo) {
621
- const message = intent.data?.message;
622
- const messageIndex = intent.data?.messageIndex;
623
- if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
624
- return;
625
- }
626
-
627
- thread.messages.splice(messageIndex, 0, message);
628
- return {
629
- data: true,
630
- intents: [
631
- [
632
- {
633
- action: ObservabilityAction.SEND_EVENT,
634
- data: {
635
- name: 'threads.message.undo-delete',
636
- properties: { threadId: thread.id, spaceId: space?.id },
637
- },
638
- },
639
- ],
640
- ],
641
- };
642
- }
488
+ thread.messages.splice(messageIndex, 0, makeRef(message));
489
+ return {
490
+ intents: [
491
+ createIntent(ObservabilityAction.SendEvent, {
492
+ name: 'threads.message.undo-delete',
493
+ properties: { threadId: thread.id, spaceId: space?.id },
494
+ }),
495
+ ],
496
+ };
643
497
  }
644
- }
645
- },
498
+ }),
499
+ ],
646
500
  },
647
501
  markdown: {
648
502
  extensions: ({ document: doc }) => {
649
- return threads(state, doc, intentPlugin?.provides.intent.dispatch);
503
+ return threads(state, doc, intentPlugin?.provides.intent.dispatchPromise);
650
504
  },
651
505
  },
652
506
  },