@dxos/plugin-thread 0.6.11 → 0.6.12-main.2d19bf1
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-O64ZECMF.mjs → ThreadArticle-GBPIH47W.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-PGF7W5EV.mjs} +3 -3
- package/dist/lib/browser/chunk-5G3KL6TC.mjs +16 -0
- package/dist/lib/browser/chunk-5G3KL6TC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-JFQZ5W2I.mjs} +2 -2
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-JFQZ5W2I.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-O4YRHIZC.mjs} +13 -11
- package/dist/lib/browser/chunk-O4YRHIZC.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +360 -341
- 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-DO56JXBU.cjs → ThreadArticle-GH5UBWFU.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-CO6I3DNX.cjs} +7 -7
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-FDYRG7HE.cjs} +65 -63
- package/dist/lib/node/chunk-FDYRG7HE.cjs.map +7 -0
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-FOZUQZF4.cjs} +6 -6
- package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-FOZUQZF4.cjs.map} +1 -1
- package/dist/lib/node/{chunk-BWBONZGU.cjs → chunk-JIJ7Z6NO.cjs} +7 -22
- package/dist/lib/node/chunk-JIJ7Z6NO.cjs.map +7 -0
- package/dist/lib/node/index.cjs +433 -418
- 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/lib/node-esm/ThreadArticle-ZTYD52TR.mjs +19 -0
- package/dist/lib/node-esm/ThreadArticle-ZTYD52TR.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-LEHGOF7R.mjs +27 -0
- package/dist/lib/node-esm/ThreadMain-LEHGOF7R.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs +18 -0
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-XOVJ65DG.mjs +413 -0
- package/dist/lib/node-esm/chunk-XOVJ65DG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs +21 -0
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1122 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/meta.mjs +12 -0
- package/dist/lib/node-esm/meta.mjs.map +7 -0
- package/dist/lib/node-esm/types.mjs +9 -0
- package/dist/lib/node-esm/types.mjs.map +7 -0
- 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 +1 -5
- 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/ThreadComplementary.d.ts +10 -0
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -0
- package/dist/types/src/components/testing.d.ts +2 -0
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/threads.d.ts +9 -0
- package/dist/types/src/extensions/threads.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +3 -9
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +6 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +40 -37
- package/src/ThreadPlugin.tsx +115 -310
- package/src/components/ChatContainer.tsx +7 -8
- package/src/components/CommentContainer.tsx +18 -19
- package/src/components/Comments.stories.tsx +2 -2
- package/src/components/CommentsContainer.tsx +18 -31
- package/src/components/MessageContainer.tsx +2 -1
- package/src/components/ThreadComplementary.tsx +113 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/threads.ts +117 -0
- package/src/meta.ts +15 -0
- package/src/types.ts +8 -3
- package/dist/lib/browser/chunk-5IBJO53M.mjs +0 -21
- package/dist/lib/browser/chunk-5IBJO53M.mjs.map +0 -7
- package/dist/lib/browser/chunk-P7ZE5N5M.mjs.map +0 -7
- package/dist/lib/node/chunk-5PZNPPQF.cjs.map +0 -7
- package/dist/lib/node/chunk-BWBONZGU.cjs.map +0 -7
- package/dist/types/src/types.test.d.ts +0 -2
- package/dist/types/src/types.test.d.ts.map +0 -1
- package/src/meta.tsx +0 -20
- package/src/types.test.ts +0 -13
- /package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs.map → ThreadArticle-GBPIH47W.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-PGF7W5EV.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-GH5UBWFU.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-CO6I3DNX.cjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -2,13 +2,10 @@
|
|
|
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';
|
|
7
5
|
import React from 'react';
|
|
8
6
|
|
|
9
7
|
import {
|
|
10
8
|
type IntentPluginProvides,
|
|
11
|
-
LayoutAction,
|
|
12
9
|
NavigationAction,
|
|
13
10
|
type LocationProvides,
|
|
14
11
|
type Plugin,
|
|
@@ -16,71 +13,40 @@ import {
|
|
|
16
13
|
parseIntentPlugin,
|
|
17
14
|
parseNavigationPlugin,
|
|
18
15
|
resolvePlugin,
|
|
19
|
-
parseGraphPlugin,
|
|
20
|
-
SLUG_PATH_SEPARATOR,
|
|
21
|
-
SLUG_COLLECTION_INDICATOR,
|
|
22
16
|
isLayoutParts,
|
|
23
17
|
parseMetadataResolverPlugin,
|
|
24
|
-
type IntentDispatcher,
|
|
25
|
-
useResolvePlugins,
|
|
26
18
|
} from '@dxos/app-framework';
|
|
27
19
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
28
20
|
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
21
|
import { create } from '@dxos/echo-schema';
|
|
30
22
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
31
|
-
import {
|
|
23
|
+
import { log } from '@dxos/log';
|
|
32
24
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
33
|
-
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
-
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
25
|
+
import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxos/plugin-graph';
|
|
35
26
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
36
27
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
37
28
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
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
|
+
import { getSpace, fullyQualifiedId, loadObjectReferences } from '@dxos/react-client/echo';
|
|
49
30
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
|
-
import { nonNullable } from '@dxos/util';
|
|
51
31
|
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
CommentsContainer,
|
|
56
|
-
CommentsHeading,
|
|
57
|
-
ChatContainer,
|
|
58
|
-
ChatHeading,
|
|
59
|
-
ThreadArticle,
|
|
60
|
-
} from './components';
|
|
32
|
+
import { ThreadMain, ThreadSettings, ChatContainer, ChatHeading, ThreadArticle } from './components';
|
|
33
|
+
import { ThreadComplementary } from './components/ThreadComplementary';
|
|
34
|
+
import { threads } from './extensions';
|
|
61
35
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
62
36
|
import translations from './translations';
|
|
63
|
-
import { ThreadAction, type
|
|
64
|
-
|
|
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
|
-
};
|
|
37
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps, type ThreadState } from './types';
|
|
71
38
|
|
|
72
39
|
type SubjectId = string;
|
|
40
|
+
|
|
73
41
|
const initialViewState = { showResolvedThreads: false };
|
|
74
|
-
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
42
|
|
|
76
|
-
|
|
77
|
-
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
78
|
-
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
43
|
+
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
79
44
|
|
|
80
45
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
46
|
+
// NOTE(burdon): Review/discuss CursorConverter semantics.
|
|
81
47
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
82
48
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
83
|
-
const state = create<ThreadState>({
|
|
49
|
+
const state = create<ThreadState>({ drafts: {} });
|
|
84
50
|
|
|
85
51
|
const viewStore = create<ViewStore>({});
|
|
86
52
|
const getViewState = (subjectId: string) => {
|
|
@@ -90,10 +56,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
90
56
|
return viewStore[subjectId];
|
|
91
57
|
};
|
|
92
58
|
|
|
93
|
-
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
94
59
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
95
60
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
-
let dispatch: IntentDispatcher | undefined;
|
|
97
61
|
|
|
98
62
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
99
63
|
|
|
@@ -102,45 +66,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
102
66
|
ready: async (plugins) => {
|
|
103
67
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
104
68
|
|
|
105
|
-
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
106
69
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
107
70
|
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);
|
|
144
71
|
},
|
|
145
72
|
unload: async () => {
|
|
146
73
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -151,8 +78,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
151
78
|
records: {
|
|
152
79
|
[ChannelType.typename]: {
|
|
153
80
|
placeholder: ['channel title placeholder', { ns: THREAD_PLUGIN }],
|
|
154
|
-
icon:
|
|
155
|
-
iconSymbol: 'ph--chat--regular',
|
|
81
|
+
icon: 'ph--chat--regular',
|
|
156
82
|
// TODO(wittjosiah): Move out of metadata.
|
|
157
83
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
158
84
|
},
|
|
@@ -198,31 +124,42 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
198
124
|
return;
|
|
199
125
|
}
|
|
200
126
|
|
|
201
|
-
// TODO(Zan): Find util (or make one)
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] =
|
|
127
|
+
// TODO(Zan): Find util (or make one).
|
|
128
|
+
const subjectId = id.split('~').at(0);
|
|
129
|
+
const [spaceId, objectId] = subjectId?.split(':') ?? [];
|
|
204
130
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
const
|
|
206
|
-
|
|
131
|
+
const object = toSignal(
|
|
132
|
+
(onChange) => {
|
|
133
|
+
const timeout = setTimeout(async () => {
|
|
134
|
+
await space?.db.loadObjectById(objectId);
|
|
135
|
+
onChange();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return () => clearTimeout(timeout);
|
|
139
|
+
},
|
|
140
|
+
() => space?.db.getObjectById(objectId),
|
|
141
|
+
subjectId,
|
|
142
|
+
);
|
|
143
|
+
if (!object || !subjectId) {
|
|
207
144
|
return;
|
|
208
145
|
}
|
|
209
146
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
147
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
148
|
+
const label = meta.label?.(object) ||
|
|
149
|
+
object.name ||
|
|
150
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
151
|
|
|
216
|
-
const viewState = getViewState(
|
|
152
|
+
const viewState = getViewState(subjectId);
|
|
217
153
|
|
|
218
154
|
return {
|
|
219
155
|
id,
|
|
220
156
|
type: 'orphan-comments-for-subject',
|
|
221
|
-
data:
|
|
157
|
+
data: null,
|
|
222
158
|
properties: {
|
|
223
|
-
icon:
|
|
159
|
+
icon: 'ph--chat-text--regular',
|
|
224
160
|
label,
|
|
225
161
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
162
|
+
object,
|
|
226
163
|
},
|
|
227
164
|
};
|
|
228
165
|
},
|
|
@@ -256,6 +193,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
193
|
menuItemType: 'toggle',
|
|
257
194
|
isChecked: viewState.showResolvedThreads,
|
|
258
195
|
testId: 'threadPlugin.toggleShowResolved',
|
|
196
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
197
|
},
|
|
260
198
|
},
|
|
261
199
|
];
|
|
@@ -287,8 +225,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
225
|
},
|
|
288
226
|
properties: {
|
|
289
227
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
228
|
+
icon: 'ph--chat--regular',
|
|
292
229
|
testId: 'threadPlugin.createObject',
|
|
293
230
|
},
|
|
294
231
|
},
|
|
@@ -311,11 +248,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
311
248
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
312
249
|
}
|
|
313
250
|
|
|
314
|
-
case 'article':
|
|
315
|
-
case 'complementary': {
|
|
251
|
+
case 'article': {
|
|
316
252
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
253
|
|
|
320
254
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
255
|
const channel = data.object;
|
|
@@ -342,93 +276,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
342
276
|
);
|
|
343
277
|
}
|
|
344
278
|
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'complementary--comments': {
|
|
345
283
|
if (
|
|
346
284
|
data.subject &&
|
|
347
285
|
typeof data.subject === 'object' &&
|
|
348
286
|
'threads' in data.subject &&
|
|
349
287
|
Array.isArray(data.subject.threads)
|
|
350
288
|
) {
|
|
351
|
-
const
|
|
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);
|
|
289
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
290
|
|
|
371
291
|
return (
|
|
372
|
-
<
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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>
|
|
292
|
+
<ThreadComplementary
|
|
293
|
+
role={role}
|
|
294
|
+
subject={data.subject}
|
|
295
|
+
drafts={state.drafts[fullyQualifiedId(data.subject)]}
|
|
296
|
+
current={state.current}
|
|
297
|
+
showResolvedThreads={showResolvedThreads}
|
|
298
|
+
/>
|
|
430
299
|
);
|
|
431
300
|
}
|
|
301
|
+
|
|
302
|
+
break;
|
|
432
303
|
}
|
|
433
304
|
}
|
|
434
305
|
|
|
@@ -442,37 +313,40 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
313
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
314
|
const { cursor, name, subject } = intent.data;
|
|
444
315
|
|
|
316
|
+
// Seed the threads array if it does not exist.
|
|
317
|
+
if (subject?.threads === undefined) {
|
|
318
|
+
try {
|
|
319
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
320
|
+
subject.threads = [];
|
|
321
|
+
} catch (err) {
|
|
322
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
445
327
|
const subjectId = fullyQualifiedId(subject);
|
|
446
328
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
449
|
-
|
|
329
|
+
const draft = state.drafts[subjectId];
|
|
330
|
+
if (draft) {
|
|
331
|
+
draft.push(thread);
|
|
450
332
|
} else {
|
|
451
|
-
state.
|
|
333
|
+
state.drafts[subjectId] = [thread];
|
|
452
334
|
}
|
|
453
335
|
|
|
454
336
|
return {
|
|
455
337
|
data: thread,
|
|
456
338
|
intents: [
|
|
457
339
|
[
|
|
458
|
-
{
|
|
459
|
-
action: NavigationAction.OPEN,
|
|
460
|
-
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
340
|
{
|
|
467
341
|
action: ThreadAction.SELECT,
|
|
468
|
-
data: { current: thread
|
|
342
|
+
data: { current: fullyQualifiedId(thread) },
|
|
469
343
|
},
|
|
344
|
+
],
|
|
345
|
+
[
|
|
470
346
|
{
|
|
471
|
-
action:
|
|
347
|
+
action: NavigationAction.OPEN,
|
|
472
348
|
data: {
|
|
473
|
-
|
|
474
|
-
subject: subjectId,
|
|
475
|
-
state: true,
|
|
349
|
+
activeParts: { complementary: 'comments' },
|
|
476
350
|
},
|
|
477
351
|
},
|
|
478
352
|
],
|
|
@@ -480,14 +354,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
480
354
|
};
|
|
481
355
|
} else {
|
|
482
356
|
// NOTE: This is the standalone thread creation case.
|
|
483
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
359
|
+
};
|
|
484
360
|
}
|
|
485
361
|
}
|
|
486
362
|
|
|
487
363
|
case ThreadAction.SELECT: {
|
|
488
|
-
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
489
364
|
state.current = intent.data?.current;
|
|
490
|
-
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
data: true,
|
|
368
|
+
intents: !intent.data?.skipOpen
|
|
369
|
+
? [
|
|
370
|
+
[
|
|
371
|
+
{
|
|
372
|
+
action: NavigationAction.OPEN,
|
|
373
|
+
data: {
|
|
374
|
+
activeParts: { complementary: 'comments' },
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
]
|
|
379
|
+
: [],
|
|
380
|
+
};
|
|
491
381
|
}
|
|
492
382
|
|
|
493
383
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -506,6 +396,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
506
396
|
const spaceId = space?.id;
|
|
507
397
|
|
|
508
398
|
return {
|
|
399
|
+
data: true,
|
|
509
400
|
intents: [
|
|
510
401
|
[
|
|
511
402
|
{
|
|
@@ -519,19 +410,21 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
410
|
|
|
520
411
|
case ThreadAction.DELETE: {
|
|
521
412
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
413
|
+
if (
|
|
414
|
+
!(thread instanceof ThreadType) ||
|
|
415
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
416
|
+
) {
|
|
523
417
|
return;
|
|
524
418
|
}
|
|
525
419
|
|
|
526
420
|
const subjectId = fullyQualifiedId(subject);
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
// Check if we're deleting a
|
|
530
|
-
|
|
531
|
-
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
421
|
+
const draft = state.drafts[subjectId];
|
|
422
|
+
if (draft) {
|
|
423
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
424
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
532
425
|
if (index !== -1) {
|
|
533
|
-
|
|
534
|
-
return;
|
|
426
|
+
draft.splice(index, 1);
|
|
427
|
+
return { data: true };
|
|
535
428
|
}
|
|
536
429
|
}
|
|
537
430
|
|
|
@@ -541,7 +434,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
434
|
}
|
|
542
435
|
|
|
543
436
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
437
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
438
|
const cursor = subject.threads[index]?.anchor;
|
|
546
439
|
if (index !== -1) {
|
|
547
440
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +480,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
480
|
const { thread, subject } = intent.data ?? {};
|
|
588
481
|
if (
|
|
589
482
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
483
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
484
|
) {
|
|
592
485
|
return;
|
|
593
486
|
}
|
|
@@ -597,12 +490,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
597
490
|
const intents = [];
|
|
598
491
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
599
492
|
|
|
600
|
-
if (state.
|
|
601
|
-
// Move
|
|
493
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
494
|
+
// Move draft to document.
|
|
602
495
|
thread.status = 'active';
|
|
603
496
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
604
|
-
state.
|
|
605
|
-
|
|
497
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
606
498
|
intents.push({
|
|
607
499
|
action: ObservabilityAction.SEND_EVENT,
|
|
608
500
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -627,7 +519,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
519
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
520
|
const space = getSpace(subject);
|
|
629
521
|
|
|
630
|
-
if (
|
|
522
|
+
if (
|
|
523
|
+
!(thread instanceof ThreadType) ||
|
|
524
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
525
|
+
) {
|
|
631
526
|
return;
|
|
632
527
|
}
|
|
633
528
|
|
|
@@ -658,7 +553,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
553
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
554
|
data: {
|
|
660
555
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
556
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
557
|
},
|
|
663
558
|
},
|
|
664
559
|
],
|
|
@@ -667,13 +562,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
667
562
|
} else if (intent.undo) {
|
|
668
563
|
const message = intent.data?.message;
|
|
669
564
|
const messageIndex = intent.data?.messageIndex;
|
|
670
|
-
|
|
671
565
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
672
566
|
return;
|
|
673
567
|
}
|
|
674
568
|
|
|
675
569
|
thread.messages.splice(messageIndex, 0, message);
|
|
676
|
-
|
|
677
570
|
return {
|
|
678
571
|
data: true,
|
|
679
572
|
intents: [
|
|
@@ -682,7 +575,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
575
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
576
|
data: {
|
|
684
577
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
578
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
579
|
},
|
|
687
580
|
},
|
|
688
581
|
],
|
|
@@ -694,96 +587,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
694
587
|
},
|
|
695
588
|
},
|
|
696
589
|
markdown: {
|
|
697
|
-
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
698
590
|
extensions: ({ document: doc }) => {
|
|
699
|
-
|
|
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
|
-
];
|
|
591
|
+
return threads(state, doc, intentPlugin?.provides.intent.dispatch);
|
|
787
592
|
},
|
|
788
593
|
},
|
|
789
594
|
},
|