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

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 (186) hide show
  1. package/dist/lib/browser/ThreadContainer-6OP5AG2W.mjs +19 -0
  2. package/dist/lib/browser/ThreadContainer-6OP5AG2W.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ENERBDYI.mjs → chunk-636QDHE3.mjs} +3 -3
  4. package/dist/lib/browser/chunk-636QDHE3.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-BNEWNQBO.mjs +71 -0
  6. package/dist/lib/browser/chunk-BNEWNQBO.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-MDOPJ2AL.mjs +15 -0
  8. package/dist/lib/browser/chunk-MDOPJ2AL.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-NAO3LZMZ.mjs +16 -0
  10. package/dist/lib/browser/chunk-NAO3LZMZ.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-OD2IZKRP.mjs → chunk-UPKU23DA.mjs} +29 -105
  12. package/dist/lib/browser/chunk-UPKU23DA.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-6KTMQPL4.mjs → chunk-YK3ICB7G.mjs} +5 -3
  14. package/dist/lib/browser/chunk-YK3ICB7G.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +147 -942
  16. package/dist/lib/browser/index.mjs.map +4 -4
  17. package/dist/lib/browser/intent-resolver-FAFQ54NP.mjs +287 -0
  18. package/dist/lib/browser/intent-resolver-FAFQ54NP.mjs.map +7 -0
  19. package/dist/lib/browser/markdown-RPYOUBVR.mjs +112 -0
  20. package/dist/lib/browser/markdown-RPYOUBVR.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/react-surface-KXXLCZMW.mjs +407 -0
  23. package/dist/lib/browser/react-surface-KXXLCZMW.mjs.map +7 -0
  24. package/dist/lib/browser/settings-WQTE5AEZ.mjs +22 -0
  25. package/dist/lib/browser/settings-WQTE5AEZ.mjs.map +7 -0
  26. package/dist/lib/browser/state-GNFUURIC.mjs +33 -0
  27. package/dist/lib/browser/state-GNFUURIC.mjs.map +7 -0
  28. package/dist/lib/browser/types.mjs +6 -4
  29. package/dist/lib/node/{ThreadArticle-ZCNMFBAO.cjs → ThreadContainer-LTKKLPXX.cjs} +14 -13
  30. package/dist/lib/node/ThreadContainer-LTKKLPXX.cjs.map +7 -0
  31. package/dist/lib/node/chunk-AWSTZ2KN.cjs +93 -0
  32. package/dist/lib/node/chunk-AWSTZ2KN.cjs.map +7 -0
  33. package/dist/lib/node/{ThreadMain-5AYGCXXP.cjs → chunk-BT24JUE3.cjs} +21 -23
  34. package/dist/lib/node/chunk-BT24JUE3.cjs.map +7 -0
  35. package/dist/lib/node/{chunk-VW2P26MZ.cjs → chunk-CMEMMYDL.cjs} +11 -8
  36. package/dist/lib/node/chunk-CMEMMYDL.cjs.map +7 -0
  37. package/dist/lib/node/{chunk-IC2NICNC.cjs → chunk-F735VMTN.cjs} +7 -7
  38. package/dist/lib/node/chunk-F735VMTN.cjs.map +7 -0
  39. package/dist/lib/node/{meta.cjs → chunk-FPWNZ6TB.cjs} +13 -10
  40. package/dist/lib/node/chunk-FPWNZ6TB.cjs.map +7 -0
  41. package/dist/lib/node/{chunk-2CB5AVBL.cjs → chunk-RQR5Z23G.cjs} +74 -151
  42. package/dist/lib/node/chunk-RQR5Z23G.cjs.map +7 -0
  43. package/dist/lib/node/index.cjs +146 -935
  44. package/dist/lib/node/index.cjs.map +4 -4
  45. package/dist/lib/node/intent-resolver-DVBEXJ33.cjs +299 -0
  46. package/dist/lib/node/intent-resolver-DVBEXJ33.cjs.map +7 -0
  47. package/dist/lib/node/markdown-ELRGISXE.cjs +122 -0
  48. package/dist/lib/node/markdown-ELRGISXE.cjs.map +7 -0
  49. package/dist/lib/node/meta.json +1 -1
  50. package/dist/lib/node/react-surface-H5PDWLPO.cjs +409 -0
  51. package/dist/lib/node/react-surface-H5PDWLPO.cjs.map +7 -0
  52. package/dist/lib/node/settings-7VQYARM5.cjs +36 -0
  53. package/dist/lib/node/settings-7VQYARM5.cjs.map +7 -0
  54. package/dist/lib/node/state-TQWEZ6B3.cjs +49 -0
  55. package/dist/lib/node/state-TQWEZ6B3.cjs.map +7 -0
  56. package/dist/lib/node/types.cjs +6 -4
  57. package/dist/lib/node/types.cjs.map +2 -2
  58. package/dist/lib/node-esm/ThreadContainer-SV74JFRH.mjs +20 -0
  59. package/dist/lib/node-esm/ThreadContainer-SV74JFRH.mjs.map +7 -0
  60. package/dist/lib/node-esm/{chunk-RMYATBXM.mjs → chunk-33NDMQQQ.mjs} +5 -3
  61. package/dist/lib/node-esm/chunk-33NDMQQQ.mjs.map +7 -0
  62. package/dist/lib/node-esm/{chunk-AQ74G3GY.mjs → chunk-CISLDOH7.mjs} +29 -105
  63. package/dist/lib/node-esm/chunk-CISLDOH7.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-7QRGG25F.mjs → chunk-FXUATCLW.mjs} +3 -3
  65. package/dist/lib/node-esm/chunk-FXUATCLW.mjs.map +7 -0
  66. package/dist/lib/node-esm/chunk-HLWJTUWZ.mjs +73 -0
  67. package/dist/lib/node-esm/chunk-HLWJTUWZ.mjs.map +7 -0
  68. package/dist/lib/node-esm/chunk-UNLSWYA4.mjs +17 -0
  69. package/dist/lib/node-esm/chunk-UNLSWYA4.mjs.map +7 -0
  70. package/dist/lib/node-esm/chunk-ZJITX3TO.mjs +17 -0
  71. package/dist/lib/node-esm/chunk-ZJITX3TO.mjs.map +7 -0
  72. package/dist/lib/node-esm/index.mjs +147 -942
  73. package/dist/lib/node-esm/index.mjs.map +4 -4
  74. package/dist/lib/node-esm/intent-resolver-DHVLBNCC.mjs +288 -0
  75. package/dist/lib/node-esm/intent-resolver-DHVLBNCC.mjs.map +7 -0
  76. package/dist/lib/node-esm/markdown-23OB7EZG.mjs +113 -0
  77. package/dist/lib/node-esm/markdown-23OB7EZG.mjs.map +7 -0
  78. package/dist/lib/node-esm/meta.json +1 -1
  79. package/dist/lib/node-esm/react-surface-SNZ44ZXI.mjs +408 -0
  80. package/dist/lib/node-esm/react-surface-SNZ44ZXI.mjs.map +7 -0
  81. package/dist/lib/node-esm/settings-SGMM7UDT.mjs +23 -0
  82. package/dist/lib/node-esm/settings-SGMM7UDT.mjs.map +7 -0
  83. package/dist/lib/node-esm/state-7MLR27YG.mjs +34 -0
  84. package/dist/lib/node-esm/state-7MLR27YG.mjs.map +7 -0
  85. package/dist/lib/node-esm/types.mjs +6 -4
  86. package/dist/types/src/ThreadPlugin.d.ts +1 -3
  87. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  88. package/dist/types/src/capabilities/capabilities.d.ts +15 -0
  89. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  90. package/dist/types/src/capabilities/index.d.ts +10 -0
  91. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  92. package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
  93. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  94. package/dist/types/src/capabilities/markdown.d.ts +4 -0
  95. package/dist/types/src/capabilities/markdown.d.ts.map +1 -0
  96. package/dist/types/src/capabilities/react-surface.d.ts +4 -0
  97. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  98. package/dist/types/src/capabilities/settings.d.ts +4 -0
  99. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  100. package/dist/types/src/capabilities/state.d.ts +7 -0
  101. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  102. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  103. package/dist/types/src/components/ChatContainer.d.ts +2 -3
  104. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  105. package/dist/types/src/components/CommentContainer.d.ts +1 -2
  106. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  107. package/dist/types/src/components/CommentsContainer.d.ts +2 -8
  108. package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
  109. package/dist/types/src/components/MessageContainer.d.ts +1 -2
  110. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  111. package/dist/types/src/components/ThreadComplementary.d.ts +2 -7
  112. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  113. package/dist/types/src/components/ThreadContainer.d.ts +7 -0
  114. package/dist/types/src/components/ThreadContainer.d.ts.map +1 -0
  115. package/dist/types/src/components/ThreadSettings.d.ts +1 -2
  116. package/dist/types/src/components/ThreadSettings.d.ts.map +1 -1
  117. package/dist/types/src/components/command-extension.d.ts.map +1 -1
  118. package/dist/types/src/components/index.d.ts +3 -15
  119. package/dist/types/src/components/index.d.ts.map +1 -1
  120. package/dist/types/src/components/testing.d.ts +12 -12
  121. package/dist/types/src/components/testing.d.ts.map +1 -1
  122. package/dist/types/src/components/types.d.ts +5 -5
  123. package/dist/types/src/components/types.d.ts.map +1 -1
  124. package/dist/types/src/index.d.ts +1 -3
  125. package/dist/types/src/index.d.ts.map +1 -1
  126. package/dist/types/src/meta.d.ts +1 -2
  127. package/dist/types/src/meta.d.ts.map +1 -1
  128. package/dist/types/src/translations.d.ts +3 -3
  129. package/dist/types/src/types.d.ts +12 -18
  130. package/dist/types/src/types.d.ts.map +1 -1
  131. package/dist/types/tsconfig.tsbuildinfo +1 -1
  132. package/package.json +30 -38
  133. package/src/ThreadPlugin.tsx +105 -475
  134. package/src/capabilities/capabilities.ts +19 -0
  135. package/src/capabilities/index.ts +13 -0
  136. package/src/capabilities/intent-resolver.ts +239 -0
  137. package/src/capabilities/markdown.ts +18 -0
  138. package/src/capabilities/react-surface.tsx +61 -0
  139. package/src/capabilities/settings.ts +14 -0
  140. package/src/capabilities/state.ts +25 -0
  141. package/src/components/Chat.stories.tsx +22 -24
  142. package/src/components/ChatContainer.tsx +2 -2
  143. package/src/components/CommentContainer.tsx +18 -32
  144. package/src/components/Comments.stories.tsx +1 -1
  145. package/src/components/CommentsContainer.tsx +1 -17
  146. package/src/components/MessageContainer.tsx +6 -19
  147. package/src/components/ThreadComplementary.tsx +87 -50
  148. package/src/components/ThreadContainer.tsx +22 -0
  149. package/src/components/ThreadSettings.tsx +5 -11
  150. package/src/components/command-extension.ts +2 -5
  151. package/src/components/index.ts +1 -2
  152. package/src/components/types.ts +5 -5
  153. package/src/index.ts +1 -5
  154. package/src/meta.ts +1 -1
  155. package/src/translations.ts +1 -1
  156. package/src/types.ts +6 -29
  157. package/dist/lib/browser/ThreadArticle-EWLTNU6Z.mjs +0 -18
  158. package/dist/lib/browser/ThreadArticle-EWLTNU6Z.mjs.map +0 -7
  159. package/dist/lib/browser/ThreadMain-ABEDFWHC.mjs +0 -26
  160. package/dist/lib/browser/ThreadMain-ABEDFWHC.mjs.map +0 -7
  161. package/dist/lib/browser/chunk-6KTMQPL4.mjs.map +0 -7
  162. package/dist/lib/browser/chunk-ENERBDYI.mjs.map +0 -7
  163. package/dist/lib/browser/chunk-OD2IZKRP.mjs.map +0 -7
  164. package/dist/lib/browser/meta.mjs +0 -11
  165. package/dist/lib/browser/meta.mjs.map +0 -7
  166. package/dist/lib/node/ThreadArticle-ZCNMFBAO.cjs.map +0 -7
  167. package/dist/lib/node/ThreadMain-5AYGCXXP.cjs.map +0 -7
  168. package/dist/lib/node/chunk-2CB5AVBL.cjs.map +0 -7
  169. package/dist/lib/node/chunk-IC2NICNC.cjs.map +0 -7
  170. package/dist/lib/node/chunk-VW2P26MZ.cjs.map +0 -7
  171. package/dist/lib/node/meta.cjs.map +0 -7
  172. package/dist/lib/node-esm/ThreadArticle-F37HYTOU.mjs +0 -19
  173. package/dist/lib/node-esm/ThreadArticle-F37HYTOU.mjs.map +0 -7
  174. package/dist/lib/node-esm/ThreadMain-OL6ZHNZY.mjs +0 -27
  175. package/dist/lib/node-esm/ThreadMain-OL6ZHNZY.mjs.map +0 -7
  176. package/dist/lib/node-esm/chunk-7QRGG25F.mjs.map +0 -7
  177. package/dist/lib/node-esm/chunk-AQ74G3GY.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-RMYATBXM.mjs.map +0 -7
  179. package/dist/lib/node-esm/meta.mjs +0 -12
  180. package/dist/lib/node-esm/meta.mjs.map +0 -7
  181. package/dist/types/src/components/ThreadArticle.d.ts +0 -5
  182. package/dist/types/src/components/ThreadArticle.d.ts.map +0 -1
  183. package/dist/types/src/components/ThreadMain.d.ts +0 -7
  184. package/dist/types/src/components/ThreadMain.d.ts.map +0 -1
  185. package/src/components/ThreadArticle.tsx +0 -18
  186. package/src/components/ThreadMain.tsx +0 -27
@@ -2,117 +2,73 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
6
-
7
- import {
8
- createIntent,
9
- createResolver,
10
- createSurface,
11
- type IntentPluginProvides,
12
- isLayoutParts,
13
- LayoutAction,
14
- NavigationAction,
15
- parseIntentPlugin,
16
- parseMetadataResolverPlugin,
17
- parseNavigationPlugin,
18
- type Plugin,
19
- type PluginDefinition,
20
- resolvePlugin,
21
- } from '@dxos/app-framework';
22
- import { type UnsubscribeCallback } from '@dxos/async';
23
- import { Ref } from '@dxos/echo-schema';
24
- import { LocalStorageStore } from '@dxos/local-storage';
25
- import { log } from '@dxos/log';
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';
30
- import { ChannelType, MessageType, ThreadType } from '@dxos/plugin-space/types';
31
- import {
32
- create,
33
- fullyQualifiedId,
34
- getSpace,
35
- getTypename,
36
- makeRef,
37
- parseId,
38
- type ReactiveEchoObject,
39
- RefArray,
40
- SpaceState,
41
- } from '@dxos/react-client/echo';
5
+ import { Capabilities, contributes, createIntent, defineModule, definePlugin, Events } from '@dxos/app-framework';
6
+ import { ClientCapabilities, ClientEvents } from '@dxos/plugin-client';
7
+ import { DeckCapabilities, DeckEvents } from '@dxos/plugin-deck';
8
+ import { MarkdownEvents } from '@dxos/plugin-markdown';
9
+ import { SpaceCapabilities, ThreadEvents } from '@dxos/plugin-space';
10
+ import { ChannelType, defineObjectForm, MessageType, ThreadType } from '@dxos/plugin-space/types';
11
+ import { type ReactiveEchoObject, RefArray } from '@dxos/react-client/echo';
42
12
  import { translations as threadTranslations } from '@dxos/react-ui-thread';
43
13
 
44
- import { ThreadArticle, ThreadComplementary, ThreadSettings } from './components';
45
- import { threads } from './extensions';
46
- import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
14
+ import { IntentResolver, Markdown, ReactSurface, ThreadState } from './capabilities';
15
+ import { meta, THREAD_ITEM, THREAD_PLUGIN } from './meta';
47
16
  import translations from './translations';
48
- import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps, type ThreadState } from './types';
49
-
50
- type SubjectId = string;
51
-
52
- const initialViewState = { showResolvedThreads: false };
53
-
54
- type ViewStore = Record<SubjectId, typeof initialViewState>;
17
+ import { ThreadAction } from './types';
55
18
 
56
19
  // TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
57
20
  // NOTE(burdon): Review/discuss CursorConverter semantics.
58
- export const ThreadPlugin = (): PluginDefinition<
59
- Omit<ThreadPluginProvides, 'echo'>,
60
- Pick<ThreadPluginProvides, 'echo'>
61
- > => {
62
- const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
63
- const state = create<ThreadState>({ drafts: {} });
64
-
65
- const viewStore = create<ViewStore>({});
66
- const getViewState = (subjectId: string) => {
67
- if (!viewStore[subjectId]) {
68
- viewStore[subjectId] = { ...initialViewState };
69
- }
70
- return viewStore[subjectId];
71
- };
72
-
73
- let intentPlugin: Plugin<IntentPluginProvides> | undefined;
74
-
75
- const unsubscribeCallbacks = [] as UnsubscribeCallback[];
76
-
77
- return {
78
- meta,
79
- initialize: async () => {
80
- settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
81
-
82
- return {
83
- echo: {
84
- system: [ThreadType, MessageType],
85
- // TODO(wittjosiah): Requires reload.
86
- ...(settings.values.standalone ? { schema: [ChannelType] } : {}),
87
- },
88
- };
89
- },
90
- ready: async ({ plugins }) => {
91
- intentPlugin = resolvePlugin(plugins, parseIntentPlugin)!;
92
- },
93
- unload: async () => {
94
- unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
95
- },
96
- provides: {
97
- settings: settings.values,
98
- metadata: {
99
- records: {
100
- [ChannelType.typename]: {
101
- createObject: (props: { name?: string }) => createIntent(ThreadAction.Create, props),
21
+ export const ThreadPlugin = () =>
22
+ definePlugin(meta, [
23
+ // TODO(wittjosiah): Currently not used but leaving because there will likely be settings for threads again.
24
+ // defineModule({
25
+ // id: `${meta.id}/module/settings`,
26
+ // activatesOn: Events.SetupSettings,
27
+ // activate: ThreadSettings,
28
+ // }),
29
+ defineModule({
30
+ id: `${meta.id}/module/state`,
31
+ // TODO(wittjosiah): Does not integrate with settings store.
32
+ // Should this be a different event?
33
+ // Should settings store be renamed to be more generic?
34
+ activatesOn: Events.SetupSettings,
35
+ activate: ThreadState,
36
+ }),
37
+ defineModule({
38
+ id: `${meta.id}/module/translations`,
39
+ activatesOn: Events.SetupTranslations,
40
+ activate: () => contributes(Capabilities.Translations, [...translations, ...threadTranslations]),
41
+ }),
42
+ defineModule({
43
+ id: `${meta.id}/module/metadata`,
44
+ activatesOn: Events.SetupMetadata,
45
+ activate: () => [
46
+ contributes(Capabilities.Metadata, {
47
+ id: ChannelType.typename,
48
+ metadata: {
102
49
  placeholder: ['channel name placeholder', { ns: THREAD_PLUGIN }],
103
50
  icon: 'ph--chat--regular',
104
51
  // TODO(wittjosiah): Move out of metadata.
105
52
  loadReferences: async (channel: ChannelType) => await RefArray.loadAll(channel.threads ?? []),
106
53
  },
107
- [ThreadType.typename]: {
54
+ }),
55
+ contributes(Capabilities.Metadata, {
56
+ id: ThreadType.typename,
57
+ metadata: {
108
58
  // TODO(wittjosiah): Move out of metadata.
109
59
  loadReferences: async (thread: ThreadType) => await RefArray.loadAll(thread.messages ?? []),
110
60
  },
111
- [MessageType.typename]: {
61
+ }),
62
+ contributes(Capabilities.Metadata, {
63
+ id: MessageType.typename,
64
+ metadata: {
112
65
  // TODO(wittjosiah): Move out of metadata.
113
66
  loadReferences: (message: MessageType) => [], // loadObjectReferences(message, (message) => [...message.parts, message.context]),
114
67
  },
115
- [THREAD_ITEM]: {
68
+ }),
69
+ contributes(Capabilities.Metadata, {
70
+ id: THREAD_ITEM,
71
+ metadata: {
116
72
  parse: (item: ReactiveEchoObject<any>, type: string) => {
117
73
  switch (type) {
118
74
  case 'node':
@@ -124,385 +80,59 @@ export const ThreadPlugin = (): PluginDefinition<
124
80
  }
125
81
  },
126
82
  },
127
- },
128
- },
129
- translations: [...translations, ...threadTranslations],
130
- complementary: {
131
- panels: [
132
- {
133
- id: 'comments',
134
- label: ['open comments panel label', { ns: THREAD_PLUGIN }],
135
- icon: 'ph--chat-text--regular',
136
- },
137
- ],
138
- },
139
- graph: {
140
- builder: (plugins) => {
141
- const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
142
- const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatchPromise;
143
- const metadataResolver = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
144
- if (!client || !dispatch || !metadataResolver) {
145
- return [];
146
- }
147
-
148
- const type = 'orphan-comments-for-subject';
149
- const icon = 'ph--chat-text--regular';
150
-
151
- return [
152
- createExtension({
153
- id: `${THREAD_PLUGIN}/comments-for-subject`,
154
- resolver: ({ id }) => {
155
- // TODO(Zan): Find util (or make one).
156
- if (!id.endsWith('~comments')) {
157
- return;
158
- }
159
-
160
- const [subjectId] = id.split('~');
161
- const { spaceId, objectId } = parseId(subjectId);
162
- const spaces = toSignal(
163
- (onChange) => client.spaces.subscribe(() => onChange()).unsubscribe,
164
- () => client.spaces.get(),
165
- );
166
- const space = spaces?.find(
167
- (space) => space.id === spaceId && space.state.get() === SpaceState.SPACE_READY,
168
- );
169
- if (!objectId) {
170
- // TODO(wittjosiah): Support comments for arbitrary subjects.
171
- // This is to ensure that the comments panel is not stuck on an old object.
172
- return {
173
- id,
174
- type,
175
- data: null,
176
- properties: {
177
- icon,
178
- label: ['unnamed object threads label', { ns: THREAD_PLUGIN }],
179
- showResolvedThreads: false,
180
- object: null,
181
- space,
182
- },
183
- };
184
- }
185
-
186
- const [object] = memoizeQuery(space, { id: objectId });
187
- if (!object || !subjectId) {
188
- return;
189
- }
190
-
191
- const meta = metadataResolver(getTypename(object) ?? '');
192
- const label = meta.label?.(object) ||
193
- object.name ||
194
- meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
195
-
196
- const viewState = getViewState(subjectId);
197
-
198
- return {
199
- id,
200
- type,
201
- data: null,
202
- properties: {
203
- icon,
204
- label,
205
- showResolvedThreads: viewState.showResolvedThreads,
206
- object,
207
- },
208
- };
209
- },
210
- actions: ({ node }) => {
211
- const dataId = node.id.split('~').at(0);
212
- if (!node.id.endsWith('~comments') || !dataId) {
213
- return;
214
- }
215
-
216
- const [spaceId, objectId] = dataId.split(':');
217
-
218
- const viewState = getViewState(dataId);
219
- const toggle = async () => {
220
- const newToggleState = !viewState.showResolvedThreads;
221
- viewState.showResolvedThreads = newToggleState;
222
- await dispatch(
223
- createIntent(ObservabilityAction.SendEvent, {
224
- name: 'threads.toggle-show-resolved',
225
- properties: { spaceId, threadId: objectId, showResolved: newToggleState },
226
- }),
227
- );
228
- };
229
-
230
- return [
231
- {
232
- id: `${THREAD_PLUGIN}/toggle-show-resolved/${node.id}`,
233
- data: toggle,
234
- properties: {
235
- label: ['toggle show resolved', { ns: THREAD_PLUGIN }],
236
- menuItemType: 'toggle',
237
- isChecked: viewState.showResolvedThreads,
238
- testId: 'threadPlugin.toggleShowResolved',
239
- icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
240
- },
241
- },
242
- ];
243
- },
244
- }),
245
- ];
246
- },
247
- },
248
- surface: {
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!;
260
- // TODO(zan): Maybe we should have utility for positional main object ids.
261
- if (isLayoutParts(location?.active) && location.active.main) {
262
- const layoutEntries = location.active.main;
263
- const currentPosition = layoutEntries.findIndex((entry) => channel.id === entry.id);
264
- if (currentPosition > 0) {
265
- const objectToTheLeft = layoutEntries[currentPosition - 1];
266
- const context = getSpace(channel)?.db.getObjectById(objectToTheLeft.id);
267
- return <ThreadArticle thread={thread} context={context} />;
268
- }
269
- }
270
-
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 &&
279
- typeof data.subject === 'object' &&
280
- 'threads' in data.subject &&
281
- Array.isArray(data.subject.threads) &&
282
- !(data.subject instanceof ChannelType),
283
- component: ({ data }) => {
284
- const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
285
- return (
286
- <ThreadComplementary
287
- subject={data.subject}
288
- drafts={state.drafts[fullyQualifiedId(data.subject)]}
289
- current={state.current}
290
- showResolvedThreads={showResolvedThreads}
291
- />
292
- );
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
- ];
302
- },
303
- },
304
- intent: {
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;
316
- }
317
- }
318
-
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);
324
- } else {
325
- state.drafts[subjectId] = [thread];
326
- }
327
-
328
- return {
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: [] }))] }) },
340
- };
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
- }
358
-
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);
379
- return;
380
- }
381
- }
382
-
383
- const space = getSpace(thread);
384
- if (!space || !subject.threads) {
385
- return;
386
- }
387
-
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);
393
- }
394
-
395
- space.db.remove(thread);
396
-
397
- return {
398
- undoable: {
399
- message: ['thread deleted label', { ns: THREAD_PLUGIN }],
400
- data: { cursor },
401
- },
402
- intents: [
403
- createIntent(ObservabilityAction.SendEvent, {
404
- name: 'threads.delete',
405
- properties: { threadId: thread.id, spaceId: space.id },
406
- }),
407
- ],
408
- };
409
- } else {
410
- // TODO(wittjosiah): SDK should do this automatically.
411
- const savedThread = space.db.add(thread);
412
- subject.threads.push(savedThread);
413
-
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
- }
442
-
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) {
459
- return;
460
- }
461
-
462
- if (messageIndex === 0 && thread.messages.length === 1) {
463
- // If the message is the only message in the thread, delete the thread.
464
- return {
465
- intents: [createIntent(ThreadAction.Delete, { subject, thread })],
466
- };
467
- } else {
468
- thread.messages.splice(messageIndex, 1);
469
- }
470
-
471
- return {
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
- ],
482
- };
483
- } else {
484
- if (!messageIndex || !message) {
485
- return;
486
- }
487
-
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
- };
497
- }
83
+ }),
84
+ ],
85
+ }),
86
+ defineModule({
87
+ id: `${meta.id}/module/object-form`,
88
+ activatesOn: ClientEvents.SetupSchema,
89
+ activate: () =>
90
+ contributes(
91
+ SpaceCapabilities.ObjectForm,
92
+ defineObjectForm({
93
+ objectSchema: ChannelType,
94
+ getIntent: () => createIntent(ThreadAction.Create),
498
95
  }),
499
- ],
500
- },
501
- markdown: {
502
- extensions: ({ document: doc }) => {
503
- return threads(state, doc, intentPlugin?.provides.intent.dispatchPromise);
504
- },
505
- },
506
- },
507
- };
508
- };
96
+ ),
97
+ }),
98
+ defineModule({
99
+ id: `${meta.id}/module/schema`,
100
+ activatesOn: ClientEvents.SetupSchema,
101
+ activate: () => contributes(ClientCapabilities.Schema, [ThreadType, MessageType]),
102
+ }),
103
+ defineModule({
104
+ id: `${meta.id}/module/complementary-panel`,
105
+ activatesOn: DeckEvents.SetupComplementaryPanels,
106
+ activate: () =>
107
+ contributes(DeckCapabilities.ComplementaryPanel, {
108
+ id: 'comments',
109
+ label: ['comments panel label', { ns: THREAD_PLUGIN }],
110
+ icon: 'ph--chat-text--regular',
111
+ // TODO(wittjosiah): Support comments on any object.
112
+ // filter: (node) => isEchoObject(node.data) && !!getSpace(node.data),
113
+ filter: (node) =>
114
+ !!node.data &&
115
+ typeof node.data === 'object' &&
116
+ 'threads' in node.data &&
117
+ Array.isArray(node.data.threads) &&
118
+ !(node.data instanceof ChannelType),
119
+ }),
120
+ }),
121
+ defineModule({
122
+ id: `${meta.id}/module/markdown`,
123
+ activatesOn: MarkdownEvents.SetupExtensions,
124
+ activate: Markdown,
125
+ }),
126
+ defineModule({
127
+ id: `${meta.id}/module/react-surface`,
128
+ activatesOn: Events.SetupReactSurface,
129
+ // TODO(wittjosiah): Should occur before the comments thread is loaded when surfaces activation is more granular.
130
+ activatesBefore: [ThreadEvents.SetupThread],
131
+ activate: ReactSurface,
132
+ }),
133
+ defineModule({
134
+ id: `${meta.id}/module/intent-resolver`,
135
+ activatesOn: Events.SetupIntentResolver,
136
+ activate: IntentResolver,
137
+ }),
138
+ ]);
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { defineCapability } from '@dxos/app-framework';
6
+ import { type DeepReadonly } from '@dxos/util';
7
+
8
+ import { THREAD_PLUGIN } from '../meta';
9
+ import { type ViewState, type ThreadState } from '../types';
10
+
11
+ export namespace ThreadCapabilities {
12
+ type GetViewState = (subjectId: string) => ViewState;
13
+ export const State = defineCapability<{ state: DeepReadonly<ThreadState>; getViewState: GetViewState }>(
14
+ `${THREAD_PLUGIN}/capability/state`,
15
+ );
16
+ export const MutableState = defineCapability<{ state: ThreadState; getViewState: GetViewState }>(
17
+ `${THREAD_PLUGIN}/capability/state`,
18
+ );
19
+ }
@@ -0,0 +1,13 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { lazy } from '@dxos/app-framework';
6
+
7
+ export const IntentResolver = lazy(() => import('./intent-resolver'));
8
+ export const Markdown = lazy(() => import('./markdown'));
9
+ export const ReactSurface = lazy(() => import('./react-surface'));
10
+ export const ThreadSettings = lazy(() => import('./settings'));
11
+ export const ThreadState = lazy(() => import('./state'));
12
+
13
+ export * from './capabilities';