@dxos/plugin-thread 0.6.10 → 0.6.11-staging.e6894a4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["types.cjs"],
4
- "sourcesContent": ["import {\n ThreadAction\n} from \"./chunk-WPPLQNMJ.cjs\";\nimport \"./chunk-BWBONZGU.cjs\";\nexport {\n ThreadAction\n};\n//# sourceMappingURL=types.cjs.map\n"],
4
+ "sourcesContent": ["import {\n ThreadAction\n} from \"./chunk-D5GOVXHS.cjs\";\nimport \"./chunk-BWBONZGU.cjs\";\nexport {\n ThreadAction\n};\n//# sourceMappingURL=types.cjs.map\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAEO;AACP,4BAAO;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadPlugin.d.ts","sourceRoot":"","sources":["../../../src/ThreadPlugin.tsx"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,gBAAgB,EAUtB,MAAM,qBAAqB,CAAC;AAsC7B,OAAO,EAAgB,KAAK,oBAAoB,EAA4B,MAAM,SAAS,CAAC;AAY5F,eAAO,MAAM,YAAY,QAAO,gBAAgB,CAAC,oBAAoB,CAiqBpE,CAAC"}
1
+ {"version":3,"file":"ThreadPlugin.d.ts","sourceRoot":"","sources":["../../../src/ThreadPlugin.tsx"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,gBAAgB,EAWtB,MAAM,qBAAqB,CAAC;AAqC7B,OAAO,EAAqC,KAAK,oBAAoB,EAA4B,MAAM,SAAS,CAAC;AAcjH,eAAO,MAAM,YAAY,QAAO,gBAAgB,CAAC,oBAAoB,CAmtBpE,CAAC"}
@@ -6,9 +6,16 @@ export declare enum ThreadAction {
6
6
  CREATE = "dxos.org/plugin/thread/action/create",
7
7
  SELECT = "dxos.org/plugin/thread/action/select",
8
8
  DELETE = "dxos.org/plugin/thread/action/delete",
9
+ ON_MESSAGE_ADD = "dxos.org/plugin/thread/action/on-message-add",
9
10
  DELETE_MESSAGE = "dxos.org/plugin/thread/action/delete-message",
10
11
  TOGGLE_RESOLVED = "dxos.org/plugin/thread/action/toggle-resolved"
11
12
  }
13
+ export type ThreadProvides<T> = {
14
+ thread: {
15
+ predicate: (obj: any) => obj is T;
16
+ createSort: (obj: T) => (anchorA: string, anchorB: string) => number;
17
+ };
18
+ };
12
19
  export type ThreadPluginProvides = SurfaceProvides & IntentResolverProvides & GraphBuilderProvides & MetadataRecordsProvides & SettingsProvides & TranslationsProvides & SchemaProvides & MarkdownExtensionProvides;
13
20
  export type ThreadSettingsProps = {
14
21
  standalone?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAK3D,oBAAY,YAAY;IACtB,MAAM,yCAA4B;IAClC,MAAM,yCAA4B;IAClC,MAAM,yCAA4B;IAClC,cAAc,iDAAoC;IAClD,eAAe,kDAAqC;CACrD;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAChD,sBAAsB,GACtB,oBAAoB,GACpB,uBAAuB,GACvB,gBAAgB,GAChB,oBAAoB,GACpB,cAAc,GACd,yBAAyB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAAG;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAK3D,oBAAY,YAAY;IACtB,MAAM,yCAA4B;IAClC,MAAM,yCAA4B;IAClC,MAAM,yCAA4B;IAClC,cAAc,iDAAoC;IAClD,cAAc,iDAAoC;IAClD,eAAe,kDAAqC;CACrD;AAID,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,MAAM,EAAE;QACN,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;QAClC,UAAU,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;KACtE,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAChD,sBAAsB,GACtB,oBAAoB,GACpB,uBAAuB,GACvB,gBAAgB,GAChB,oBAAoB,GACpB,cAAc,GACd,yBAAyB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAAG;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAC;CAClB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-thread",
3
- "version": "0.6.10",
3
+ "version": "0.6.11-staging.e6894a4",
4
4
  "description": "DXOS Threads Surface plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -51,28 +51,28 @@
51
51
  "@preact/signals-core": "^1.6.0",
52
52
  "date-fns": "^3.3.1",
53
53
  "lodash.get": "^4.4.2",
54
- "@dxos/app-framework": "0.6.10",
55
- "@dxos/async": "0.6.10",
56
- "@dxos/display-name": "0.6.10",
57
- "@dxos/echo-schema": "0.6.10",
58
- "@dxos/invariant": "0.6.10",
59
- "@dxos/local-storage": "0.6.10",
60
- "@dxos/log": "0.6.10",
61
- "@dxos/plugin-attention": "0.6.10",
62
- "@dxos/plugin-client": "0.6.10",
63
- "@dxos/plugin-graph": "0.6.10",
64
- "@dxos/plugin-markdown": "0.6.10",
65
- "@dxos/plugin-settings": "0.6.10",
66
- "@dxos/plugin-observability": "0.6.10",
67
- "@dxos/plugin-space": "0.6.10",
68
- "@dxos/react-client": "0.6.10",
69
- "@dxos/react-ui-deck": "0.6.10",
70
- "@dxos/react-ui-attention": "0.6.10",
71
- "@dxos/react-ui-editor": "0.6.10",
72
- "@dxos/react-ui-mosaic": "0.6.10",
73
- "@dxos/react-ui-theme": "0.6.10",
74
- "@dxos/react-ui-thread": "0.6.10",
75
- "@dxos/util": "0.6.10"
54
+ "@dxos/async": "0.6.11-staging.e6894a4",
55
+ "@dxos/display-name": "0.6.11-staging.e6894a4",
56
+ "@dxos/echo-schema": "0.6.11-staging.e6894a4",
57
+ "@dxos/invariant": "0.6.11-staging.e6894a4",
58
+ "@dxos/local-storage": "0.6.11-staging.e6894a4",
59
+ "@dxos/log": "0.6.11-staging.e6894a4",
60
+ "@dxos/plugin-attention": "0.6.11-staging.e6894a4",
61
+ "@dxos/plugin-client": "0.6.11-staging.e6894a4",
62
+ "@dxos/plugin-graph": "0.6.11-staging.e6894a4",
63
+ "@dxos/app-framework": "0.6.11-staging.e6894a4",
64
+ "@dxos/plugin-markdown": "0.6.11-staging.e6894a4",
65
+ "@dxos/plugin-observability": "0.6.11-staging.e6894a4",
66
+ "@dxos/plugin-settings": "0.6.11-staging.e6894a4",
67
+ "@dxos/plugin-space": "0.6.11-staging.e6894a4",
68
+ "@dxos/react-client": "0.6.11-staging.e6894a4",
69
+ "@dxos/react-ui-attention": "0.6.11-staging.e6894a4",
70
+ "@dxos/react-ui-deck": "0.6.11-staging.e6894a4",
71
+ "@dxos/react-ui-editor": "0.6.11-staging.e6894a4",
72
+ "@dxos/react-ui-theme": "0.6.11-staging.e6894a4",
73
+ "@dxos/react-ui-mosaic": "0.6.11-staging.e6894a4",
74
+ "@dxos/react-ui-thread": "0.6.11-staging.e6894a4",
75
+ "@dxos/util": "0.6.11-staging.e6894a4"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@phosphor-icons/react": "^2.1.5",
@@ -82,16 +82,16 @@
82
82
  "react": "~18.2.0",
83
83
  "react-dom": "~18.2.0",
84
84
  "vite": "^5.3.4",
85
- "@dxos/random": "0.6.10",
86
- "@dxos/react-ui": "0.6.10",
87
- "@dxos/storybook-utils": "0.6.10"
85
+ "@dxos/react-ui": "0.6.11-staging.e6894a4",
86
+ "@dxos/random": "0.6.11-staging.e6894a4",
87
+ "@dxos/storybook-utils": "0.6.11-staging.e6894a4"
88
88
  },
89
89
  "optionalDependencies": {
90
90
  "@phosphor-icons/react": "^2.1.5",
91
91
  "react": "^18.0.0",
92
92
  "react-dom": "^18.0.0",
93
- "@dxos/react-ui": "0.6.10",
94
- "@dxos/react-ui-theme": "0.6.10"
93
+ "@dxos/react-ui": "0.6.11-staging.e6894a4",
94
+ "@dxos/react-ui-theme": "0.6.11-staging.e6894a4"
95
95
  },
96
96
  "publishConfig": {
97
97
  "access": "public"
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Chat, type IconProps } from '@phosphor-icons/react';
6
6
  import { computed, effect } from '@preact/signals-core';
7
- import React from 'react';
7
+ import React, { useCallback } from 'react';
8
8
 
9
9
  import {
10
10
  type IntentPluginProvides,
@@ -22,6 +22,7 @@ import {
22
22
  isLayoutParts,
23
23
  parseMetadataResolverPlugin,
24
24
  type IntentDispatcher,
25
+ useResolvePlugins,
25
26
  } from '@dxos/app-framework';
26
27
  import { type UnsubscribeCallback } from '@dxos/async';
27
28
  import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
@@ -40,7 +41,6 @@ import {
40
41
  Filter,
41
42
  createDocAccessor,
42
43
  fullyQualifiedId,
43
- getRangeFromCursor,
44
44
  loadObjectReferences,
45
45
  } from '@dxos/react-client/echo';
46
46
  import { ScrollArea } from '@dxos/react-ui';
@@ -60,9 +60,10 @@ import {
60
60
  } from './components';
61
61
  import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
62
62
  import translations from './translations';
63
- import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
63
+ import { ThreadAction, type ThreadProvides, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
64
64
 
65
65
  type ThreadState = {
66
+ /** An in-memory staging area for threads that are being drafted. */
66
67
  staging: Record<string, ThreadType[]>;
67
68
  current?: string | undefined;
68
69
  focus?: boolean;
@@ -72,6 +73,7 @@ type SubjectId = string;
72
73
  const initialViewState = { showResolvedThreads: false };
73
74
  type ViewStore = Record<SubjectId, typeof initialViewState>;
74
75
 
76
+ // TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
75
77
  export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
76
78
  const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
77
79
  const state = create<ThreadState>({ staging: {} });
@@ -311,6 +313,16 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
311
313
  case 'complementary': {
312
314
  const location = navigationPlugin?.provides.location;
313
315
 
316
+ const providesThreadsConfig = useCallback(
317
+ (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
318
+ // TODO(Zan): More robust runtime check.
319
+ 'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined,
320
+ [],
321
+ );
322
+
323
+ const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
324
+ const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
325
+
314
326
  if (data.object instanceof ChannelType && data.object.threads[0]) {
315
327
  const channel = data.object;
316
328
  // TODO(zan): Maybe we should have utility for positional main object ids.
@@ -329,45 +341,41 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
329
341
  return <ThreadArticle thread={data.object.threads[0]} />;
330
342
  }
331
343
 
332
- if (data.subject instanceof DocumentType) {
333
- const doc = data.subject;
334
- const accessor = doc.content ? createDocAccessor(doc.content, ['content']) : undefined;
344
+ if (data.subject instanceof ThreadType) {
345
+ return (
346
+ <>
347
+ <ChatHeading attendableId={data.subject.id} />
348
+ <ChatContainer thread={data.subject} />
349
+ </>
350
+ );
351
+ }
335
352
 
336
- if (!accessor) {
337
- return null;
353
+ if (
354
+ data.subject &&
355
+ typeof data.subject === 'object' &&
356
+ 'threads' in data.subject &&
357
+ Array.isArray(data.subject.threads)
358
+ ) {
359
+ const threads = data.subject.threads
360
+ .concat(state.staging[fullyQualifiedId(data.subject)])
361
+ .filter(nonNullable);
362
+
363
+ const createSort = threadProvides.find((p) => p.predicate(data.subject))?.createSort;
364
+ if (createSort) {
365
+ const sort = createSort(data.subject);
366
+ threads.sort((a, b) => sort(a.anchor, b.anchor));
338
367
  }
339
368
 
340
- const getStartPosition = (cursor: string | undefined) => {
341
- const range = cursor ? getRangeFromCursor(accessor, cursor) : undefined;
342
- return range?.start ?? Number.MAX_SAFE_INTEGER;
343
- };
344
-
345
- const threads = doc.threads
346
- .concat(state.staging[data.subject.id])
347
- .filter(nonNullable)
348
- .sort((a, b) => getStartPosition(a.anchor) - getStartPosition(b.anchor));
349
-
350
369
  const detached = data.subject.threads
351
370
  .filter(nonNullable)
352
371
  .filter(({ anchor }) => !anchor)
353
372
  .map((thread) => thread.id);
354
373
 
355
- const qualifiedSubjectId = fullyQualifiedId(doc);
374
+ const qualifiedSubjectId = fullyQualifiedId(data.subject);
356
375
  const attention = attentionPlugin?.provides.attention?.attended ?? new Set([qualifiedSubjectId]);
357
376
  const attendableAttrs = createAttendableAttributes(qualifiedSubjectId);
358
- const space = getSpace(doc);
359
377
  const { showResolvedThreads } = getViewState(qualifiedSubjectId);
360
378
 
361
- const dispatchAnalytic = (name: string, meta: any) => {
362
- void dispatch?.({
363
- action: ObservabilityAction.SEND_EVENT,
364
- data: {
365
- name,
366
- properties: { ...meta, space: space?.id },
367
- },
368
- });
369
- };
370
-
371
379
  return (
372
380
  <div role='none' className='contents group/attention' {...attendableAttrs}>
373
381
  {role === 'complementary' && <CommentsHeading attendableId={qualifiedSubjectId} />}
@@ -396,14 +404,14 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
396
404
  dispatch?.({
397
405
  plugin: THREAD_PLUGIN,
398
406
  action: ThreadAction.DELETE,
399
- data: { document: data.subject, thread },
407
+ data: { subject: data.subject, thread },
400
408
  })
401
409
  }
402
410
  onMessageDelete={(thread, messageId) =>
403
411
  dispatch?.({
404
412
  plugin: THREAD_PLUGIN,
405
413
  action: ThreadAction.DELETE_MESSAGE,
406
- data: { document: data.subject, thread, messageId },
414
+ data: { subject: data.subject, thread, messageId },
407
415
  })
408
416
  }
409
417
  onThreadToggleResolved={(thread) =>
@@ -414,19 +422,9 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
414
422
  })
415
423
  }
416
424
  onComment={(thread) => {
417
- // TODO(Zan): This might be a bit too much logic for a component. Move to intents?
418
- const doc = data.subject as DocumentType;
419
- if (state.staging[doc.id]?.find((t) => t === thread)) {
420
- // Move thread from staging to document.
421
- thread.status = 'active';
422
- doc.threads ? doc.threads.push(thread) : (doc.threads = [thread]);
423
- state.staging[doc.id] = state.staging[doc.id]?.filter((t) => t.id !== thread.id);
424
- dispatchAnalytic('threads.thread-created', { threadId: thread.id });
425
- }
426
-
427
- dispatchAnalytic('threads.message-added', {
428
- threadId: thread.id,
429
- threadLength: thread.messages.length,
425
+ void intentPlugin?.provides.intent.dispatch({
426
+ action: ThreadAction.ON_MESSAGE_ADD,
427
+ data: { thread, subject: data.subject },
430
428
  });
431
429
  }}
432
430
  />
@@ -438,13 +436,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
438
436
  </ScrollArea.Root>
439
437
  </div>
440
438
  );
441
- } else if (data.subject instanceof ThreadType) {
442
- return (
443
- <>
444
- <ChatHeading attendableId={data.subject.id} />
445
- <ChatContainer thread={data.subject} />
446
- </>
447
- );
448
439
  }
449
440
  }
450
441
  }
@@ -456,7 +447,50 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
456
447
  resolver: async (intent) => {
457
448
  switch (intent.action) {
458
449
  case ThreadAction.CREATE: {
459
- return { data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }) };
450
+ if (intent.data && intent.data.cursor !== undefined) {
451
+ const { cursor, name, subject } = intent.data;
452
+
453
+ const subjectId = fullyQualifiedId(subject);
454
+ const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
455
+
456
+ const stagingArea = state.staging[subjectId];
457
+ if (stagingArea) {
458
+ stagingArea.push(thread);
459
+ } else {
460
+ state.staging[subjectId] = [thread];
461
+ }
462
+
463
+ return {
464
+ data: thread,
465
+ intents: [
466
+ [
467
+ {
468
+ action: NavigationAction.OPEN,
469
+ data: {
470
+ activeParts: {
471
+ complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
472
+ },
473
+ },
474
+ },
475
+ {
476
+ action: ThreadAction.SELECT,
477
+ data: { current: thread.id, focus: true },
478
+ },
479
+ {
480
+ action: LayoutAction.SET_LAYOUT,
481
+ data: {
482
+ element: 'complementary',
483
+ subject: subjectId,
484
+ state: true,
485
+ },
486
+ },
487
+ ],
488
+ ],
489
+ };
490
+ } else {
491
+ // NOTE: This is the standalone thread creation case.
492
+ return { data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }) };
493
+ }
460
494
  }
461
495
 
462
496
  case ThreadAction.SELECT: {
@@ -493,32 +527,33 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
493
527
  }
494
528
 
495
529
  case ThreadAction.DELETE: {
496
- const { document: doc, thread } = intent.data ?? {};
497
- if (!(doc instanceof DocumentType) || !(thread instanceof ThreadType)) {
530
+ const { subject, thread } = intent.data ?? {};
531
+ if (!(subject instanceof DocumentType) || !(thread instanceof ThreadType)) {
498
532
  return;
499
533
  }
500
534
 
501
- const stagingArea = state.staging[doc.id];
535
+ const subjectId = fullyQualifiedId(subject);
536
+ const stagingArea = state.staging[subjectId];
502
537
  if (stagingArea) {
503
538
  // Check if we're deleting a thread that's in the staging area.
504
539
  // If so, remove it from the staging area without ceremony.
505
- const index = state.staging[doc.id]?.findIndex((t) => t.id === thread.id);
540
+ const index = stagingArea.findIndex((t) => t.id === thread.id);
506
541
  if (index !== -1) {
507
- state.staging[doc.id]?.splice(index, 1);
542
+ stagingArea.splice(index, 1);
508
543
  return;
509
544
  }
510
545
  }
511
546
 
512
547
  const space = getSpace(thread);
513
- if (!space || !doc.threads) {
548
+ if (!space || !subject.threads) {
514
549
  return;
515
550
  }
516
551
 
517
552
  if (!intent.undo) {
518
- const index = doc.threads.findIndex((t) => t?.id === thread.id);
519
- const cursor = doc.threads[index]?.anchor;
553
+ const index = subject.threads.findIndex((t) => t?.id === thread.id);
554
+ const cursor = subject.threads[index]?.anchor;
520
555
  if (index !== -1) {
521
- doc.threads?.splice(index, 1);
556
+ subject.threads?.splice(index, 1);
522
557
  }
523
558
 
524
559
  space.db.remove(thread);
@@ -540,7 +575,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
540
575
  } else if (intent.undo) {
541
576
  // TODO(wittjosiah): SDK should do this automatically.
542
577
  const savedThread = space.db.add(thread);
543
- doc.threads.push(savedThread);
578
+ subject.threads.push(savedThread);
544
579
 
545
580
  return {
546
581
  data: true,
@@ -556,11 +591,52 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
556
591
  }
557
592
  break;
558
593
  }
594
+
595
+ case ThreadAction.ON_MESSAGE_ADD: {
596
+ const { thread, subject } = intent.data ?? {};
597
+ if (
598
+ !(thread instanceof ThreadType) ||
599
+ !(subject && typeof subject === 'object' && 'threads' in subject)
600
+ ) {
601
+ return;
602
+ }
603
+
604
+ const subjectId = fullyQualifiedId(subject);
605
+ const space = getSpace(subject);
606
+ const intents = [];
607
+ const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
608
+
609
+ if (state.staging[subjectId]?.find((t) => t === thread)) {
610
+ // Move thread from staging to document.
611
+ thread.status = 'active';
612
+ subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
613
+ state.staging[subjectId] = state.staging[subjectId]?.filter((t) => t.id !== thread.id);
614
+
615
+ intents.push({
616
+ action: ObservabilityAction.SEND_EVENT,
617
+ data: { name: 'threads.thread-created', properties: analyticsProperties },
618
+ });
619
+ }
620
+
621
+ intents.push({
622
+ action: ObservabilityAction.SEND_EVENT,
623
+ data: {
624
+ name: 'threads.message-added',
625
+ properties: { ...analyticsProperties, threadLength: thread.messages.length },
626
+ },
627
+ });
628
+
629
+ return {
630
+ data: thread,
631
+ intents: [intents],
632
+ };
633
+ }
634
+
559
635
  case ThreadAction.DELETE_MESSAGE: {
560
- const { document: doc, thread, messageId } = intent.data ?? {};
561
- const space = getSpace(doc);
636
+ const { subject, thread, messageId } = intent.data ?? {};
637
+ const space = getSpace(subject);
562
638
 
563
- if (!(doc instanceof DocumentType) || !(thread instanceof ThreadType) || !space) {
639
+ if (!(subject instanceof DocumentType) || !(thread instanceof ThreadType) || !space) {
564
640
  return;
565
641
  }
566
642
 
@@ -574,7 +650,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
574
650
  if (messageIndex === 0 && thread.messages.length === 1) {
575
651
  // If the message is the only message in the thread, delete the thread.
576
652
  return {
577
- intents: [[{ action: ThreadAction.DELETE, data: { document: doc, thread } }]],
653
+ intents: [[{ action: ThreadAction.DELETE, data: { subject, thread } }]],
578
654
  };
579
655
  } else {
580
656
  thread.messages.splice(messageIndex, 1);
@@ -638,7 +714,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
638
714
 
639
715
  // TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
640
716
  const threads = computed(() =>
641
- [...doc.threads.filter(nonNullable), ...(state.staging[doc.id] ?? [])].filter(
717
+ [...doc.threads.filter(nonNullable), ...(state.staging[fullyQualifiedId(doc)] ?? [])].filter(
642
718
  (thread) => !(thread?.status === 'resolved'),
643
719
  ),
644
720
  );
@@ -673,43 +749,19 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
673
749
  onCreate: ({ cursor }) => {
674
750
  const [start, end] = cursor.split(':');
675
751
  const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
676
- const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
677
752
 
678
- const stagingArea = state.staging[doc.id];
679
- if (stagingArea) {
680
- stagingArea.push(thread);
681
- } else {
682
- state.staging[doc.id] = [thread];
683
- }
684
-
685
- void intentPlugin?.provides.intent.dispatch([
686
- {
687
- action: NavigationAction.OPEN,
688
- data: {
689
- activeParts: {
690
- complementary: `${fullyQualifiedId(doc)}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
691
- },
692
- },
693
- },
694
- {
695
- action: ThreadAction.SELECT,
696
- data: { current: thread.id, focus: true },
697
- },
698
- {
699
- action: LayoutAction.SET_LAYOUT,
700
- data: {
701
- element: 'complementary',
702
- subject: doc,
703
- state: true,
704
- },
753
+ void intentPlugin?.provides.intent.dispatch({
754
+ action: ThreadAction.CREATE,
755
+ data: {
756
+ cursor,
757
+ name,
758
+ subject: doc,
705
759
  },
706
- ]);
707
-
708
- return thread.id;
760
+ });
709
761
  },
710
762
  onDelete: ({ id }) => {
711
763
  // If the thread is in the staging area, remove it.
712
- const stagingArea = state.staging[doc.id];
764
+ const stagingArea = state.staging[fullyQualifiedId(doc)];
713
765
  if (stagingArea) {
714
766
  const index = stagingArea.findIndex((thread) => thread.id === id);
715
767
  if (index !== -1) {
@@ -724,7 +776,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
724
776
  },
725
777
  onUpdate: ({ id, cursor }) => {
726
778
  const thread =
727
- state.staging[doc.id]?.find((thread) => thread.id === id) ??
779
+ state.staging[fullyQualifiedId(doc)]?.find((thread) => thread.id === id) ??
728
780
  doc.threads.find((thread) => thread?.id === id);
729
781
 
730
782
  if (thread instanceof ThreadType && thread.anchor) {
package/src/types.ts CHANGED
@@ -21,10 +21,20 @@ export enum ThreadAction {
21
21
  CREATE = `${THREAD_ACTION}/create`,
22
22
  SELECT = `${THREAD_ACTION}/select`,
23
23
  DELETE = `${THREAD_ACTION}/delete`,
24
+ ON_MESSAGE_ADD = `${THREAD_ACTION}/on-message-add`,
24
25
  DELETE_MESSAGE = `${THREAD_ACTION}/delete-message`,
25
26
  TOGGLE_RESOLVED = `${THREAD_ACTION}/toggle-resolved`,
26
27
  }
27
28
 
29
+ // TODO(Zan): Move this to the plugin-space plugin or another common location
30
+ // when we implement threads in sheets.
31
+ export type ThreadProvides<T> = {
32
+ thread: {
33
+ predicate: (obj: any) => obj is T;
34
+ createSort: (obj: T) => (anchorA: string, anchorB: string) => number;
35
+ };
36
+ };
37
+
28
38
  export type ThreadPluginProvides = SurfaceProvides &
29
39
  IntentResolverProvides &
30
40
  GraphBuilderProvides &
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport type {\n GraphBuilderProvides,\n IntentResolverProvides,\n MetadataRecordsProvides,\n SettingsProvides,\n SurfaceProvides,\n TranslationsProvides,\n} from '@dxos/app-framework';\nimport { type SchemaProvides } from '@dxos/plugin-client';\nimport { type MarkdownExtensionProvides } from '@dxos/plugin-markdown';\nimport { type ThreadType } from '@dxos/plugin-space/types';\n\nimport { THREAD_PLUGIN } from './meta';\n\nconst THREAD_ACTION = `${THREAD_PLUGIN}/action`;\nexport enum ThreadAction {\n CREATE = `${THREAD_ACTION}/create`,\n SELECT = `${THREAD_ACTION}/select`,\n DELETE = `${THREAD_ACTION}/delete`,\n DELETE_MESSAGE = `${THREAD_ACTION}/delete-message`,\n TOGGLE_RESOLVED = `${THREAD_ACTION}/toggle-resolved`,\n}\n\nexport type ThreadPluginProvides = SurfaceProvides &\n IntentResolverProvides &\n GraphBuilderProvides &\n MetadataRecordsProvides &\n SettingsProvides &\n TranslationsProvides &\n SchemaProvides &\n MarkdownExtensionProvides;\n\nexport type ThreadSettingsProps = { standalone?: boolean };\n\nexport interface ThreadModel {\n root: ThreadType;\n}\n"],
5
- "mappings": ";;;;;AAkBA,IAAMA,gBAAgB,GAAGC,aAAAA;;UACbC,eAAAA;0CACD,GAAGF,aAAAA,SAAsB,IAAA;0CACzB,GAAGA,aAAAA,SAAsB,IAAA;0CACzB,GAAGA,aAAAA,SAAsB,IAAA;kDACjB,GAAGA,aAAAA,iBAA8B,IAAA;mDAChC,GAAGA,aAAAA,kBAA+B,IAAA;GAL1CE,iBAAAA,eAAAA,CAAAA,EAAAA;",
6
- "names": ["THREAD_ACTION", "THREAD_PLUGIN", "ThreadAction"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport type {\n GraphBuilderProvides,\n IntentResolverProvides,\n MetadataRecordsProvides,\n SettingsProvides,\n SurfaceProvides,\n TranslationsProvides,\n} from '@dxos/app-framework';\nimport { type SchemaProvides } from '@dxos/plugin-client';\nimport { type MarkdownExtensionProvides } from '@dxos/plugin-markdown';\nimport { type ThreadType } from '@dxos/plugin-space/types';\n\nimport { THREAD_PLUGIN } from './meta';\n\nconst THREAD_ACTION = `${THREAD_PLUGIN}/action`;\nexport enum ThreadAction {\n CREATE = `${THREAD_ACTION}/create`,\n SELECT = `${THREAD_ACTION}/select`,\n DELETE = `${THREAD_ACTION}/delete`,\n DELETE_MESSAGE = `${THREAD_ACTION}/delete-message`,\n TOGGLE_RESOLVED = `${THREAD_ACTION}/toggle-resolved`,\n}\n\nexport type ThreadPluginProvides = SurfaceProvides &\n IntentResolverProvides &\n GraphBuilderProvides &\n MetadataRecordsProvides &\n SettingsProvides &\n TranslationsProvides &\n SchemaProvides &\n MarkdownExtensionProvides;\n\nexport type ThreadSettingsProps = { standalone?: boolean };\n\nexport interface ThreadModel {\n root: ThreadType;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAkBA,IAAMA,gBAAgB,GAAGC,mCAAAA;;UACbC,eAAAA;0CACD,GAAGF,aAAAA,SAAsB,IAAA;0CACzB,GAAGA,aAAAA,SAAsB,IAAA;0CACzB,GAAGA,aAAAA,SAAsB,IAAA;kDACjB,GAAGA,aAAAA,iBAA8B,IAAA;mDAChC,GAAGA,aAAAA,kBAA+B,IAAA;GAL1CE,iBAAAA,eAAAA,CAAAA,EAAAA;",
6
- "names": ["THREAD_ACTION", "THREAD_PLUGIN", "ThreadAction"]
7
- }