@dxos/plugin-thread 0.6.13-main.548ca8d → 0.6.13-staging.1e988a3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{ThreadArticle-GBPIH47W.mjs → ThreadArticle-O64ZECMF.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-PGF7W5EV.mjs → ThreadMain-ZDCRC6RG.mjs} +3 -3
- package/dist/lib/browser/chunk-5IBJO53M.mjs +21 -0
- package/dist/lib/browser/chunk-5IBJO53M.mjs.map +7 -0
- package/dist/lib/browser/{chunk-JFQZ5W2I.mjs → chunk-GKPZHUT3.mjs} +2 -2
- package/dist/lib/browser/{chunk-JFQZ5W2I.mjs.map → chunk-GKPZHUT3.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-O4YRHIZC.mjs → chunk-P7ZE5N5M.mjs} +11 -13
- package/dist/lib/browser/chunk-P7ZE5N5M.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +393 -411
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types.mjs +2 -2
- package/dist/lib/node/{ThreadArticle-GH5UBWFU.cjs → ThreadArticle-DO56JXBU.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-CO6I3DNX.cjs → ThreadMain-XJX75GHG.cjs} +7 -7
- package/dist/lib/node/{chunk-FDYRG7HE.cjs → chunk-5PZNPPQF.cjs} +63 -65
- package/dist/lib/node/chunk-5PZNPPQF.cjs.map +7 -0
- package/dist/lib/node/{chunk-JIJ7Z6NO.cjs → chunk-BWBONZGU.cjs} +22 -7
- package/dist/lib/node/chunk-BWBONZGU.cjs.map +7 -0
- package/dist/lib/node/{chunk-FOZUQZF4.cjs → chunk-D5GOVXHS.cjs} +6 -6
- package/dist/lib/node/{chunk-FOZUQZF4.cjs.map → chunk-D5GOVXHS.cjs.map} +1 -1
- package/dist/lib/node/index.cjs +428 -442
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +4 -4
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +3 -3
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
- package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
- package/dist/types/src/components/CommentContainer.d.ts +1 -1
- package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
- package/dist/types/src/components/CommentsContainer.d.ts +5 -1
- package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
- package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -3
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +9 -3
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -6
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/types.test.d.ts +2 -0
- package/dist/types/src/types.test.d.ts.map +1 -0
- package/package.json +37 -40
- package/src/ThreadPlugin.tsx +310 -120
- package/src/components/ChatContainer.tsx +8 -7
- package/src/components/CommentContainer.tsx +19 -18
- package/src/components/Comments.stories.tsx +2 -2
- package/src/components/CommentsContainer.tsx +31 -18
- package/src/components/MessageContainer.tsx +1 -2
- package/src/components/index.ts +2 -3
- package/src/meta.tsx +20 -0
- package/src/types.test.ts +13 -0
- package/src/types.ts +3 -8
- package/dist/lib/browser/chunk-5G3KL6TC.mjs +0 -16
- package/dist/lib/browser/chunk-5G3KL6TC.mjs.map +0 -7
- package/dist/lib/browser/chunk-O4YRHIZC.mjs.map +0 -7
- package/dist/lib/node/chunk-FDYRG7HE.cjs.map +0 -7
- package/dist/lib/node/chunk-JIJ7Z6NO.cjs.map +0 -7
- package/dist/lib/node-esm/ThreadArticle-ZTYD52TR.mjs +0 -19
- package/dist/lib/node-esm/ThreadArticle-ZTYD52TR.mjs.map +0 -7
- package/dist/lib/node-esm/ThreadMain-LEHGOF7R.mjs +0 -27
- package/dist/lib/node-esm/ThreadMain-LEHGOF7R.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs +0 -18
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XOVJ65DG.mjs +0 -413
- package/dist/lib/node-esm/chunk-XOVJ65DG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs +0 -21
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1121
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/meta.mjs +0 -12
- package/dist/lib/node-esm/meta.mjs.map +0 -7
- package/dist/lib/node-esm/types.mjs +0 -9
- package/dist/lib/node-esm/types.mjs.map +0 -7
- package/dist/types/src/components/ThreadComplementary.d.ts +0 -10
- package/dist/types/src/components/ThreadComplementary.d.ts.map +0 -1
- package/dist/types/src/extensions/index.d.ts +0 -2
- package/dist/types/src/extensions/index.d.ts.map +0 -1
- package/dist/types/src/extensions/threads.d.ts +0 -9
- package/dist/types/src/extensions/threads.d.ts.map +0 -1
- package/src/components/ThreadComplementary.tsx +0 -113
- package/src/extensions/index.ts +0 -5
- package/src/extensions/threads.ts +0 -117
- package/src/meta.ts +0 -15
- /package/dist/lib/browser/{ThreadArticle-GBPIH47W.mjs.map → ThreadArticle-O64ZECMF.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-PGF7W5EV.mjs.map → ThreadMain-ZDCRC6RG.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-GH5UBWFU.cjs.map → ThreadArticle-DO56JXBU.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-CO6I3DNX.cjs.map → ThreadMain-XJX75GHG.cjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -2,56 +2,85 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Chat, type IconProps } from '@phosphor-icons/react';
|
|
6
|
+
import { computed, effect } from '@preact/signals-core';
|
|
5
7
|
import React from 'react';
|
|
6
8
|
|
|
7
9
|
import {
|
|
8
10
|
type IntentPluginProvides,
|
|
9
|
-
|
|
11
|
+
LayoutAction,
|
|
10
12
|
NavigationAction,
|
|
13
|
+
type LocationProvides,
|
|
11
14
|
type Plugin,
|
|
12
15
|
type PluginDefinition,
|
|
13
|
-
isLayoutParts,
|
|
14
16
|
parseIntentPlugin,
|
|
15
17
|
parseNavigationPlugin,
|
|
16
|
-
parseMetadataResolverPlugin,
|
|
17
18
|
resolvePlugin,
|
|
19
|
+
parseGraphPlugin,
|
|
20
|
+
SLUG_PATH_SEPARATOR,
|
|
21
|
+
SLUG_COLLECTION_INDICATOR,
|
|
22
|
+
isLayoutParts,
|
|
23
|
+
parseMetadataResolverPlugin,
|
|
24
|
+
type IntentDispatcher,
|
|
25
|
+
useResolvePlugins,
|
|
18
26
|
} from '@dxos/app-framework';
|
|
19
27
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
20
|
-
import {
|
|
28
|
+
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
|
+
import { create } from '@dxos/echo-schema';
|
|
21
30
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
22
|
-
import {
|
|
31
|
+
import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin-attention';
|
|
23
32
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
24
|
-
import { type ActionGroup, createExtension, isActionGroup
|
|
33
|
+
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
+
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
25
35
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
26
36
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
27
37
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
28
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
getSpace,
|
|
40
|
+
getTextInRange,
|
|
41
|
+
Filter,
|
|
42
|
+
createDocAccessor,
|
|
43
|
+
fullyQualifiedId,
|
|
44
|
+
loadObjectReferences,
|
|
45
|
+
} from '@dxos/react-client/echo';
|
|
46
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
47
|
+
import { createAttendableAttributes } from '@dxos/react-ui-attention';
|
|
48
|
+
import { comments, createExternalCommentSync, listener } from '@dxos/react-ui-editor';
|
|
29
49
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
|
+
import { nonNullable } from '@dxos/util';
|
|
30
51
|
|
|
31
52
|
import {
|
|
53
|
+
ThreadMain,
|
|
54
|
+
ThreadSettings,
|
|
55
|
+
CommentsContainer,
|
|
56
|
+
CommentsHeading,
|
|
32
57
|
ChatContainer,
|
|
33
58
|
ChatHeading,
|
|
34
59
|
ThreadArticle,
|
|
35
|
-
ThreadComplementary,
|
|
36
|
-
ThreadMain,
|
|
37
|
-
ThreadSettings,
|
|
38
60
|
} from './components';
|
|
39
|
-
import { threads } from './extensions';
|
|
40
61
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
41
62
|
import translations from './translations';
|
|
42
|
-
import { ThreadAction, type
|
|
63
|
+
import { ThreadAction, type ThreadProvides, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
|
|
43
64
|
|
|
44
|
-
type
|
|
65
|
+
type ThreadState = {
|
|
66
|
+
/** An in-memory staging area for threads that are being drafted. */
|
|
67
|
+
staging: Record<string, ThreadType[]>;
|
|
68
|
+
current?: string | undefined;
|
|
69
|
+
focus?: boolean;
|
|
70
|
+
};
|
|
45
71
|
|
|
72
|
+
type SubjectId = string;
|
|
46
73
|
const initialViewState = { showResolvedThreads: false };
|
|
47
|
-
|
|
48
74
|
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
49
75
|
|
|
76
|
+
// TODO(Zan): More robust runtime check.
|
|
77
|
+
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
78
|
+
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
79
|
+
|
|
50
80
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
51
|
-
// NOTE(burdon): Review/discuss CursorConverter semantics.
|
|
52
81
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
53
82
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
54
|
-
const state = create<ThreadState>({
|
|
83
|
+
const state = create<ThreadState>({ staging: {} });
|
|
55
84
|
|
|
56
85
|
const viewStore = create<ViewStore>({});
|
|
57
86
|
const getViewState = (subjectId: string) => {
|
|
@@ -61,8 +90,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
61
90
|
return viewStore[subjectId];
|
|
62
91
|
};
|
|
63
92
|
|
|
93
|
+
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
64
94
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
65
95
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
+
let dispatch: IntentDispatcher | undefined;
|
|
66
97
|
|
|
67
98
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
68
99
|
|
|
@@ -71,8 +102,45 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
71
102
|
ready: async (plugins) => {
|
|
72
103
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
73
104
|
|
|
105
|
+
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
74
106
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
75
107
|
intentPlugin = resolvePlugin(plugins, parseIntentPlugin)!;
|
|
108
|
+
dispatch = intentPlugin?.provides.intent.dispatch;
|
|
109
|
+
|
|
110
|
+
const graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
|
|
111
|
+
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
112
|
+
if (!client) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// TODO(wittjosiah): This is a hack to make standalone threads work in the c11y sidebar.
|
|
117
|
+
// This should have a better solution when deck is introduced.
|
|
118
|
+
const channelsQuery = client.spaces.query(Filter.schema(ChannelType));
|
|
119
|
+
const queryUnsubscribe = channelsQuery.subscribe();
|
|
120
|
+
const unsubscribe = effect(() => {
|
|
121
|
+
const attention = attentionPlugin?.provides.attention;
|
|
122
|
+
if (!attention?.attended) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const firstAttendedNodeWithComments = Array.from(attention.attended)
|
|
127
|
+
.map((id) => graphPlugin?.provides.graph.findNode(id))
|
|
128
|
+
.find((node) => node?.data instanceof DocumentType && (node.data.threads?.length ?? 0) > 0);
|
|
129
|
+
|
|
130
|
+
if (firstAttendedNodeWithComments) {
|
|
131
|
+
void intentPlugin?.provides.intent.dispatch({
|
|
132
|
+
action: NavigationAction.OPEN,
|
|
133
|
+
data: {
|
|
134
|
+
activeParts: {
|
|
135
|
+
complementary: `${firstAttendedNodeWithComments.id}${SLUG_PATH_SEPARATOR}comments`,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
unsubscribeCallbacks.push(queryUnsubscribe);
|
|
143
|
+
unsubscribeCallbacks.push(unsubscribe);
|
|
76
144
|
},
|
|
77
145
|
unload: async () => {
|
|
78
146
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -83,7 +151,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
83
151
|
records: {
|
|
84
152
|
[ChannelType.typename]: {
|
|
85
153
|
placeholder: ['channel title placeholder', { ns: THREAD_PLUGIN }],
|
|
86
|
-
icon:
|
|
154
|
+
icon: (props: IconProps) => <Chat {...props} />,
|
|
155
|
+
iconSymbol: 'ph--chat--regular',
|
|
87
156
|
// TODO(wittjosiah): Move out of metadata.
|
|
88
157
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
89
158
|
},
|
|
@@ -125,46 +194,35 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
125
194
|
createExtension({
|
|
126
195
|
id: `${THREAD_PLUGIN}/comments-for-subject`,
|
|
127
196
|
resolver: ({ id }) => {
|
|
128
|
-
// TODO(Zan): Find util (or make one).
|
|
129
197
|
if (!id.endsWith('~comments')) {
|
|
130
198
|
return;
|
|
131
199
|
}
|
|
132
200
|
|
|
133
|
-
|
|
134
|
-
const
|
|
201
|
+
// TODO(Zan): Find util (or make one)
|
|
202
|
+
const docId = id.split('~').at(0);
|
|
203
|
+
const [spaceId, objectId] = docId?.split(':') ?? [];
|
|
135
204
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
const timeout = setTimeout(async () => {
|
|
139
|
-
await space?.db.loadObjectById(objectId);
|
|
140
|
-
onChange();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
return () => clearTimeout(timeout);
|
|
144
|
-
},
|
|
145
|
-
() => space?.db.getObjectById(objectId),
|
|
146
|
-
subjectId,
|
|
147
|
-
);
|
|
148
|
-
if (!object || !subjectId) {
|
|
205
|
+
const doc = space?.db.getObjectById(objectId);
|
|
206
|
+
if (!doc || !docId) {
|
|
149
207
|
return;
|
|
150
208
|
}
|
|
151
209
|
|
|
152
|
-
const
|
|
153
|
-
const label =
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
const docMeta = metadataResolver(getTypename(doc) ?? '');
|
|
211
|
+
const label = docMeta.label?.(doc) ||
|
|
212
|
+
doc.name ||
|
|
213
|
+
docMeta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
214
|
+
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
156
215
|
|
|
157
|
-
const viewState = getViewState(
|
|
216
|
+
const viewState = getViewState(docId);
|
|
158
217
|
|
|
159
218
|
return {
|
|
160
219
|
id,
|
|
161
220
|
type: 'orphan-comments-for-subject',
|
|
162
|
-
data:
|
|
221
|
+
data: doc,
|
|
163
222
|
properties: {
|
|
164
|
-
icon:
|
|
223
|
+
icon: meta.iconComponent,
|
|
165
224
|
label,
|
|
166
225
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
167
|
-
object,
|
|
168
226
|
},
|
|
169
227
|
};
|
|
170
228
|
},
|
|
@@ -198,7 +256,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
198
256
|
menuItemType: 'toggle',
|
|
199
257
|
isChecked: viewState.showResolvedThreads,
|
|
200
258
|
testId: 'threadPlugin.toggleShowResolved',
|
|
201
|
-
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
202
259
|
},
|
|
203
260
|
},
|
|
204
261
|
];
|
|
@@ -230,7 +287,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
230
287
|
},
|
|
231
288
|
properties: {
|
|
232
289
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
233
|
-
icon:
|
|
290
|
+
icon: (props: IconProps) => <Chat {...props} />,
|
|
291
|
+
iconSymbol: 'ph--chat--regular',
|
|
234
292
|
testId: 'threadPlugin.createObject',
|
|
235
293
|
},
|
|
236
294
|
},
|
|
@@ -253,8 +311,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
253
311
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
254
312
|
}
|
|
255
313
|
|
|
256
|
-
case 'article':
|
|
314
|
+
case 'article':
|
|
315
|
+
case 'complementary': {
|
|
257
316
|
const location = navigationPlugin?.provides.location;
|
|
317
|
+
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
+
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
258
319
|
|
|
259
320
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
260
321
|
const channel = data.object;
|
|
@@ -281,30 +342,93 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
281
342
|
);
|
|
282
343
|
}
|
|
283
344
|
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
case 'complementary--comments': {
|
|
288
345
|
if (
|
|
289
346
|
data.subject &&
|
|
290
347
|
typeof data.subject === 'object' &&
|
|
291
348
|
'threads' in data.subject &&
|
|
292
349
|
Array.isArray(data.subject.threads)
|
|
293
350
|
) {
|
|
294
|
-
const
|
|
351
|
+
const threads = data.subject.threads
|
|
352
|
+
.concat(state.staging[fullyQualifiedId(data.subject)])
|
|
353
|
+
.filter(nonNullable);
|
|
354
|
+
|
|
355
|
+
const createSort = threadProvides.find((p) => p.predicate(data.subject))?.createSort;
|
|
356
|
+
if (createSort) {
|
|
357
|
+
const sort = createSort(data.subject);
|
|
358
|
+
threads.sort((a, b) => sort(a.anchor, b.anchor));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const detached = data.subject.threads
|
|
362
|
+
.filter(nonNullable)
|
|
363
|
+
.filter(({ anchor }) => !anchor)
|
|
364
|
+
.map((thread) => thread.id);
|
|
365
|
+
|
|
366
|
+
const qualifiedSubjectId = fullyQualifiedId(data.subject);
|
|
367
|
+
const attention = attentionPlugin?.provides.attention?.attended ?? new Set([qualifiedSubjectId]);
|
|
368
|
+
const attendableAttrs = createAttendableAttributes(qualifiedSubjectId);
|
|
369
|
+
const { showResolvedThreads } = getViewState(qualifiedSubjectId);
|
|
295
370
|
|
|
296
371
|
return (
|
|
297
|
-
<
|
|
298
|
-
role={
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
372
|
+
<div role='none' className='contents group/attention' {...attendableAttrs}>
|
|
373
|
+
{role === 'complementary' && <CommentsHeading attendableId={qualifiedSubjectId} />}
|
|
374
|
+
<ScrollArea.Root classNames='row-span-2'>
|
|
375
|
+
<ScrollArea.Viewport>
|
|
376
|
+
<CommentsContainer
|
|
377
|
+
threads={threads}
|
|
378
|
+
detached={detached}
|
|
379
|
+
currentId={attention.has(qualifiedSubjectId) ? state.current : undefined}
|
|
380
|
+
autoFocusCurrentTextbox={state.focus}
|
|
381
|
+
showResolvedThreads={showResolvedThreads}
|
|
382
|
+
onThreadAttend={(thread) => {
|
|
383
|
+
if (state.current !== thread.id) {
|
|
384
|
+
state.current = thread.id;
|
|
385
|
+
void dispatch?.({
|
|
386
|
+
action: LayoutAction.SCROLL_INTO_VIEW,
|
|
387
|
+
data: {
|
|
388
|
+
id: fullyQualifiedId(data.subject!),
|
|
389
|
+
thread: fullyQualifiedId(thread),
|
|
390
|
+
cursor: thread.anchor,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}}
|
|
395
|
+
onThreadDelete={(thread) =>
|
|
396
|
+
dispatch?.({
|
|
397
|
+
plugin: THREAD_PLUGIN,
|
|
398
|
+
action: ThreadAction.DELETE,
|
|
399
|
+
data: { subject: data.subject, thread },
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
onMessageDelete={(thread, messageId) =>
|
|
403
|
+
dispatch?.({
|
|
404
|
+
plugin: THREAD_PLUGIN,
|
|
405
|
+
action: ThreadAction.DELETE_MESSAGE,
|
|
406
|
+
data: { subject: data.subject, thread, messageId },
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
onThreadToggleResolved={(thread) =>
|
|
410
|
+
dispatch?.({
|
|
411
|
+
plugin: THREAD_PLUGIN,
|
|
412
|
+
action: ThreadAction.TOGGLE_RESOLVED,
|
|
413
|
+
data: { thread },
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
onComment={(thread) => {
|
|
417
|
+
void intentPlugin?.provides.intent.dispatch({
|
|
418
|
+
action: ThreadAction.ON_MESSAGE_ADD,
|
|
419
|
+
data: { thread, subject: data.subject },
|
|
420
|
+
});
|
|
421
|
+
}}
|
|
422
|
+
/>
|
|
423
|
+
<div role='none' className='bs-10' />
|
|
424
|
+
<ScrollArea.Scrollbar>
|
|
425
|
+
<ScrollArea.Thumb />
|
|
426
|
+
</ScrollArea.Scrollbar>
|
|
427
|
+
</ScrollArea.Viewport>
|
|
428
|
+
</ScrollArea.Root>
|
|
429
|
+
</div>
|
|
304
430
|
);
|
|
305
431
|
}
|
|
306
|
-
|
|
307
|
-
break;
|
|
308
432
|
}
|
|
309
433
|
}
|
|
310
434
|
|
|
@@ -318,40 +442,37 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
318
442
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
319
443
|
const { cursor, name, subject } = intent.data;
|
|
320
444
|
|
|
321
|
-
// Seed the threads array if it does not exist.
|
|
322
|
-
if (subject?.threads === undefined) {
|
|
323
|
-
try {
|
|
324
|
-
// Static schema will throw an error if subject does not support threads array property.
|
|
325
|
-
subject.threads = [];
|
|
326
|
-
} catch (err) {
|
|
327
|
-
log.error('Subject does not support threads array', subject?.typename);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
445
|
const subjectId = fullyQualifiedId(subject);
|
|
333
446
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
336
|
-
|
|
447
|
+
const stagingArea = state.staging[subjectId];
|
|
448
|
+
if (stagingArea) {
|
|
449
|
+
stagingArea.push(thread);
|
|
337
450
|
} else {
|
|
338
|
-
state.
|
|
451
|
+
state.staging[subjectId] = [thread];
|
|
339
452
|
}
|
|
340
453
|
|
|
341
454
|
return {
|
|
342
455
|
data: thread,
|
|
343
456
|
intents: [
|
|
344
457
|
[
|
|
458
|
+
{
|
|
459
|
+
action: NavigationAction.OPEN,
|
|
460
|
+
data: {
|
|
461
|
+
activeParts: {
|
|
462
|
+
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
345
466
|
{
|
|
346
467
|
action: ThreadAction.SELECT,
|
|
347
|
-
data: { current:
|
|
468
|
+
data: { current: thread.id, focus: true },
|
|
348
469
|
},
|
|
349
|
-
],
|
|
350
|
-
[
|
|
351
470
|
{
|
|
352
|
-
action:
|
|
471
|
+
action: LayoutAction.SET_LAYOUT,
|
|
353
472
|
data: {
|
|
354
|
-
|
|
473
|
+
element: 'complementary',
|
|
474
|
+
subject: subjectId,
|
|
475
|
+
state: true,
|
|
355
476
|
},
|
|
356
477
|
},
|
|
357
478
|
],
|
|
@@ -359,30 +480,14 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
359
480
|
};
|
|
360
481
|
} else {
|
|
361
482
|
// NOTE: This is the standalone thread creation case.
|
|
362
|
-
return {
|
|
363
|
-
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
364
|
-
};
|
|
483
|
+
return { data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }) };
|
|
365
484
|
}
|
|
366
485
|
}
|
|
367
486
|
|
|
368
487
|
case ThreadAction.SELECT: {
|
|
488
|
+
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
369
489
|
state.current = intent.data?.current;
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
data: true,
|
|
373
|
-
intents: !intent.data?.skipOpen
|
|
374
|
-
? [
|
|
375
|
-
[
|
|
376
|
-
{
|
|
377
|
-
action: NavigationAction.OPEN,
|
|
378
|
-
data: {
|
|
379
|
-
activeParts: { complementary: 'comments' },
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
]
|
|
384
|
-
: [],
|
|
385
|
-
};
|
|
490
|
+
return { data: true };
|
|
386
491
|
}
|
|
387
492
|
|
|
388
493
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -401,7 +506,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
401
506
|
const spaceId = space?.id;
|
|
402
507
|
|
|
403
508
|
return {
|
|
404
|
-
data: true,
|
|
405
509
|
intents: [
|
|
406
510
|
[
|
|
407
511
|
{
|
|
@@ -415,21 +519,19 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
415
519
|
|
|
416
520
|
case ThreadAction.DELETE: {
|
|
417
521
|
const { subject, thread } = intent.data ?? {};
|
|
418
|
-
if (
|
|
419
|
-
!(thread instanceof ThreadType) ||
|
|
420
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
421
|
-
) {
|
|
522
|
+
if (!(subject instanceof DocumentType) || !(thread instanceof ThreadType)) {
|
|
422
523
|
return;
|
|
423
524
|
}
|
|
424
525
|
|
|
425
526
|
const subjectId = fullyQualifiedId(subject);
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
// Check if we're deleting a
|
|
429
|
-
|
|
527
|
+
const stagingArea = state.staging[subjectId];
|
|
528
|
+
if (stagingArea) {
|
|
529
|
+
// Check if we're deleting a thread that's in the staging area.
|
|
530
|
+
// If so, remove it from the staging area without ceremony.
|
|
531
|
+
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
430
532
|
if (index !== -1) {
|
|
431
|
-
|
|
432
|
-
return
|
|
533
|
+
stagingArea.splice(index, 1);
|
|
534
|
+
return;
|
|
433
535
|
}
|
|
434
536
|
}
|
|
435
537
|
|
|
@@ -439,7 +541,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
439
541
|
}
|
|
440
542
|
|
|
441
543
|
if (!intent.undo) {
|
|
442
|
-
const index = subject.threads.findIndex((t
|
|
544
|
+
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
443
545
|
const cursor = subject.threads[index]?.anchor;
|
|
444
546
|
if (index !== -1) {
|
|
445
547
|
subject.threads?.splice(index, 1);
|
|
@@ -485,7 +587,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
485
587
|
const { thread, subject } = intent.data ?? {};
|
|
486
588
|
if (
|
|
487
589
|
!(thread instanceof ThreadType) ||
|
|
488
|
-
!(subject && typeof subject === 'object' && 'threads' in subject
|
|
590
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
489
591
|
) {
|
|
490
592
|
return;
|
|
491
593
|
}
|
|
@@ -495,11 +597,12 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
495
597
|
const intents = [];
|
|
496
598
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
497
599
|
|
|
498
|
-
if (state.
|
|
499
|
-
// Move
|
|
600
|
+
if (state.staging[subjectId]?.find((t) => t === thread)) {
|
|
601
|
+
// Move thread from staging to document.
|
|
500
602
|
thread.status = 'active';
|
|
501
603
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
502
|
-
state.
|
|
604
|
+
state.staging[subjectId] = state.staging[subjectId]?.filter((t) => t.id !== thread.id);
|
|
605
|
+
|
|
503
606
|
intents.push({
|
|
504
607
|
action: ObservabilityAction.SEND_EVENT,
|
|
505
608
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -524,10 +627,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
524
627
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
525
628
|
const space = getSpace(subject);
|
|
526
629
|
|
|
527
|
-
if (
|
|
528
|
-
!(thread instanceof ThreadType) ||
|
|
529
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
530
|
-
) {
|
|
630
|
+
if (!(subject instanceof DocumentType) || !(thread instanceof ThreadType) || !space) {
|
|
531
631
|
return;
|
|
532
632
|
}
|
|
533
633
|
|
|
@@ -558,7 +658,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
558
658
|
action: ObservabilityAction.SEND_EVENT,
|
|
559
659
|
data: {
|
|
560
660
|
name: 'threads.message.delete',
|
|
561
|
-
properties: { threadId: thread.id, spaceId: space
|
|
661
|
+
properties: { threadId: thread.id, spaceId: space.id },
|
|
562
662
|
},
|
|
563
663
|
},
|
|
564
664
|
],
|
|
@@ -567,11 +667,13 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
567
667
|
} else if (intent.undo) {
|
|
568
668
|
const message = intent.data?.message;
|
|
569
669
|
const messageIndex = intent.data?.messageIndex;
|
|
670
|
+
|
|
570
671
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
571
672
|
return;
|
|
572
673
|
}
|
|
573
674
|
|
|
574
675
|
thread.messages.splice(messageIndex, 0, message);
|
|
676
|
+
|
|
575
677
|
return {
|
|
576
678
|
data: true,
|
|
577
679
|
intents: [
|
|
@@ -580,7 +682,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
580
682
|
action: ObservabilityAction.SEND_EVENT,
|
|
581
683
|
data: {
|
|
582
684
|
name: 'threads.message.undo-delete',
|
|
583
|
-
properties: { threadId: thread.id, spaceId: space
|
|
685
|
+
properties: { threadId: thread.id, spaceId: space.id },
|
|
584
686
|
},
|
|
585
687
|
},
|
|
586
688
|
],
|
|
@@ -592,8 +694,96 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
592
694
|
},
|
|
593
695
|
},
|
|
594
696
|
markdown: {
|
|
697
|
+
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
595
698
|
extensions: ({ document: doc }) => {
|
|
596
|
-
|
|
699
|
+
const space = doc && getSpace(doc);
|
|
700
|
+
if (!doc || !space) {
|
|
701
|
+
// Include no-op comments extension here to ensure that the facets are always present when they are expected.
|
|
702
|
+
// TODO(wittjosiah): The Editor should only look for these facets when comments are available.
|
|
703
|
+
return [comments()];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
|
|
707
|
+
const threads = computed(() =>
|
|
708
|
+
[...doc.threads.filter(nonNullable), ...(state.staging[fullyQualifiedId(doc)] ?? [])].filter(
|
|
709
|
+
(thread) => !(thread?.status === 'resolved'),
|
|
710
|
+
),
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
return [
|
|
714
|
+
listener({
|
|
715
|
+
onChange: () => {
|
|
716
|
+
doc.threads.forEach((thread) => {
|
|
717
|
+
if (thread?.anchor) {
|
|
718
|
+
const [start, end] = thread.anchor.split(':');
|
|
719
|
+
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
720
|
+
// TODO(burdon): This seems unsafe; review.
|
|
721
|
+
// Only update if the name has changed, otherwise this will cause an infinite loop.
|
|
722
|
+
// Skip if the name is empty - this means comment text was deleted, but thread name should remain.
|
|
723
|
+
if (name && name !== thread.name) {
|
|
724
|
+
thread.name = name;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
},
|
|
729
|
+
}),
|
|
730
|
+
createExternalCommentSync(
|
|
731
|
+
doc.id,
|
|
732
|
+
(sink) => effect(() => sink()),
|
|
733
|
+
() =>
|
|
734
|
+
threads.value
|
|
735
|
+
.filter((thread) => thread?.anchor)
|
|
736
|
+
.map((thread) => ({ id: thread.id, cursor: thread.anchor! })),
|
|
737
|
+
),
|
|
738
|
+
comments({
|
|
739
|
+
id: doc.id,
|
|
740
|
+
onCreate: ({ cursor }) => {
|
|
741
|
+
const [start, end] = cursor.split(':');
|
|
742
|
+
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
743
|
+
|
|
744
|
+
void intentPlugin?.provides.intent.dispatch({
|
|
745
|
+
action: ThreadAction.CREATE,
|
|
746
|
+
data: {
|
|
747
|
+
cursor,
|
|
748
|
+
name,
|
|
749
|
+
subject: doc,
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
},
|
|
753
|
+
onDelete: ({ id }) => {
|
|
754
|
+
// If the thread is in the staging area, remove it.
|
|
755
|
+
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
756
|
+
if (stagingArea) {
|
|
757
|
+
const index = stagingArea.findIndex((thread) => thread.id === id);
|
|
758
|
+
if (index !== -1) {
|
|
759
|
+
stagingArea.splice(index, 1);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const thread = doc.threads.find((thread) => thread?.id === id);
|
|
764
|
+
if (thread) {
|
|
765
|
+
thread.anchor = undefined;
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
onUpdate: ({ id, cursor }) => {
|
|
769
|
+
const thread =
|
|
770
|
+
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread.id === id) ??
|
|
771
|
+
doc.threads.find((thread) => thread?.id === id);
|
|
772
|
+
|
|
773
|
+
if (thread instanceof ThreadType && thread.anchor) {
|
|
774
|
+
const [start, end] = thread.anchor.split(':');
|
|
775
|
+
thread.name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
776
|
+
thread.anchor = cursor;
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
onSelect: ({ selection: { current, closest } }) => {
|
|
780
|
+
const dispatch = intentPlugin?.provides.intent.dispatch;
|
|
781
|
+
if (dispatch) {
|
|
782
|
+
void dispatch([{ action: ThreadAction.SELECT, data: { current: current ?? closest } }]);
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
}),
|
|
786
|
+
];
|
|
597
787
|
},
|
|
598
788
|
},
|
|
599
789
|
},
|