@dxos/plugin-thread 0.6.12 → 0.6.13-main.548ca8d
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 +411 -393
- 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 +442 -428
- 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 +1121 -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/index.d.ts +3 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- 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 +120 -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/components/index.ts +3 -2
- 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,85 +2,56 @@
|
|
|
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
|
-
NavigationAction,
|
|
13
9
|
type LocationProvides,
|
|
10
|
+
NavigationAction,
|
|
14
11
|
type Plugin,
|
|
15
12
|
type PluginDefinition,
|
|
13
|
+
isLayoutParts,
|
|
16
14
|
parseIntentPlugin,
|
|
17
15
|
parseNavigationPlugin,
|
|
18
|
-
resolvePlugin,
|
|
19
|
-
parseGraphPlugin,
|
|
20
|
-
SLUG_PATH_SEPARATOR,
|
|
21
|
-
SLUG_COLLECTION_INDICATOR,
|
|
22
|
-
isLayoutParts,
|
|
23
16
|
parseMetadataResolverPlugin,
|
|
24
|
-
|
|
25
|
-
useResolvePlugins,
|
|
17
|
+
resolvePlugin,
|
|
26
18
|
} from '@dxos/app-framework';
|
|
27
19
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
28
|
-
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
|
-
import { create } from '@dxos/echo-schema';
|
|
20
|
+
import { create, type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
30
21
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
31
|
-
import {
|
|
22
|
+
import { log } from '@dxos/log';
|
|
32
23
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
33
|
-
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
-
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
24
|
+
import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxos/plugin-graph';
|
|
35
25
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
36
26
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
37
27
|
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';
|
|
28
|
+
import { getSpace, fullyQualifiedId, loadObjectReferences, parseFullyQualifiedId } from '@dxos/react-client/echo';
|
|
49
29
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
|
-
import { nonNullable } from '@dxos/util';
|
|
51
30
|
|
|
52
31
|
import {
|
|
53
|
-
ThreadMain,
|
|
54
|
-
ThreadSettings,
|
|
55
|
-
CommentsContainer,
|
|
56
|
-
CommentsHeading,
|
|
57
32
|
ChatContainer,
|
|
58
33
|
ChatHeading,
|
|
59
34
|
ThreadArticle,
|
|
35
|
+
ThreadComplementary,
|
|
36
|
+
ThreadMain,
|
|
37
|
+
ThreadSettings,
|
|
60
38
|
} from './components';
|
|
39
|
+
import { threads } from './extensions';
|
|
61
40
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
62
41
|
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
|
-
};
|
|
42
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps, type ThreadState } from './types';
|
|
71
43
|
|
|
72
44
|
type SubjectId = string;
|
|
45
|
+
|
|
73
46
|
const initialViewState = { showResolvedThreads: false };
|
|
74
|
-
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
78
|
-
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
48
|
+
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
79
49
|
|
|
80
50
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
51
|
+
// NOTE(burdon): Review/discuss CursorConverter semantics.
|
|
81
52
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
82
53
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
83
|
-
const state = create<ThreadState>({
|
|
54
|
+
const state = create<ThreadState>({ drafts: {} });
|
|
84
55
|
|
|
85
56
|
const viewStore = create<ViewStore>({});
|
|
86
57
|
const getViewState = (subjectId: string) => {
|
|
@@ -90,10 +61,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
90
61
|
return viewStore[subjectId];
|
|
91
62
|
};
|
|
92
63
|
|
|
93
|
-
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
94
64
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
95
65
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
-
let dispatch: IntentDispatcher | undefined;
|
|
97
66
|
|
|
98
67
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
99
68
|
|
|
@@ -102,45 +71,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
102
71
|
ready: async (plugins) => {
|
|
103
72
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
104
73
|
|
|
105
|
-
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
106
74
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
107
75
|
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
76
|
},
|
|
145
77
|
unload: async () => {
|
|
146
78
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -151,8 +83,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
151
83
|
records: {
|
|
152
84
|
[ChannelType.typename]: {
|
|
153
85
|
placeholder: ['channel title placeholder', { ns: THREAD_PLUGIN }],
|
|
154
|
-
icon:
|
|
155
|
-
iconSymbol: 'ph--chat--regular',
|
|
86
|
+
icon: 'ph--chat--regular',
|
|
156
87
|
// TODO(wittjosiah): Move out of metadata.
|
|
157
88
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
158
89
|
},
|
|
@@ -194,35 +125,46 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
194
125
|
createExtension({
|
|
195
126
|
id: `${THREAD_PLUGIN}/comments-for-subject`,
|
|
196
127
|
resolver: ({ id }) => {
|
|
128
|
+
// TODO(Zan): Find util (or make one).
|
|
197
129
|
if (!id.endsWith('~comments')) {
|
|
198
130
|
return;
|
|
199
131
|
}
|
|
200
132
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] = docId?.split(':') ?? [];
|
|
133
|
+
const [subjectId] = id.split('~');
|
|
134
|
+
const [spaceId, objectId] = parseFullyQualifiedId(subjectId);
|
|
204
135
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
const
|
|
206
|
-
|
|
136
|
+
const object = toSignal(
|
|
137
|
+
(onChange) => {
|
|
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) {
|
|
207
149
|
return;
|
|
208
150
|
}
|
|
209
151
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
152
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
153
|
+
const label = meta.label?.(object) ||
|
|
154
|
+
object.name ||
|
|
155
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
156
|
|
|
216
|
-
const viewState = getViewState(
|
|
157
|
+
const viewState = getViewState(subjectId);
|
|
217
158
|
|
|
218
159
|
return {
|
|
219
160
|
id,
|
|
220
161
|
type: 'orphan-comments-for-subject',
|
|
221
|
-
data:
|
|
162
|
+
data: null,
|
|
222
163
|
properties: {
|
|
223
|
-
icon:
|
|
164
|
+
icon: 'ph--chat-text--regular',
|
|
224
165
|
label,
|
|
225
166
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
167
|
+
object,
|
|
226
168
|
},
|
|
227
169
|
};
|
|
228
170
|
},
|
|
@@ -256,6 +198,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
198
|
menuItemType: 'toggle',
|
|
257
199
|
isChecked: viewState.showResolvedThreads,
|
|
258
200
|
testId: 'threadPlugin.toggleShowResolved',
|
|
201
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
202
|
},
|
|
260
203
|
},
|
|
261
204
|
];
|
|
@@ -287,8 +230,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
230
|
},
|
|
288
231
|
properties: {
|
|
289
232
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
233
|
+
icon: 'ph--chat--regular',
|
|
292
234
|
testId: 'threadPlugin.createObject',
|
|
293
235
|
},
|
|
294
236
|
},
|
|
@@ -311,11 +253,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
311
253
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
312
254
|
}
|
|
313
255
|
|
|
314
|
-
case 'article':
|
|
315
|
-
case 'complementary': {
|
|
256
|
+
case 'article': {
|
|
316
257
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
258
|
|
|
320
259
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
260
|
const channel = data.object;
|
|
@@ -342,93 +281,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
342
281
|
);
|
|
343
282
|
}
|
|
344
283
|
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case 'complementary--comments': {
|
|
345
288
|
if (
|
|
346
289
|
data.subject &&
|
|
347
290
|
typeof data.subject === 'object' &&
|
|
348
291
|
'threads' in data.subject &&
|
|
349
292
|
Array.isArray(data.subject.threads)
|
|
350
293
|
) {
|
|
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);
|
|
294
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
295
|
|
|
371
296
|
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>
|
|
297
|
+
<ThreadComplementary
|
|
298
|
+
role={role}
|
|
299
|
+
subject={data.subject}
|
|
300
|
+
drafts={state.drafts[fullyQualifiedId(data.subject)]}
|
|
301
|
+
current={state.current}
|
|
302
|
+
showResolvedThreads={showResolvedThreads}
|
|
303
|
+
/>
|
|
430
304
|
);
|
|
431
305
|
}
|
|
306
|
+
|
|
307
|
+
break;
|
|
432
308
|
}
|
|
433
309
|
}
|
|
434
310
|
|
|
@@ -442,37 +318,40 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
318
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
319
|
const { cursor, name, subject } = intent.data;
|
|
444
320
|
|
|
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
|
+
|
|
445
332
|
const subjectId = fullyQualifiedId(subject);
|
|
446
333
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
449
|
-
|
|
334
|
+
const draft = state.drafts[subjectId];
|
|
335
|
+
if (draft) {
|
|
336
|
+
draft.push(thread);
|
|
450
337
|
} else {
|
|
451
|
-
state.
|
|
338
|
+
state.drafts[subjectId] = [thread];
|
|
452
339
|
}
|
|
453
340
|
|
|
454
341
|
return {
|
|
455
342
|
data: thread,
|
|
456
343
|
intents: [
|
|
457
344
|
[
|
|
458
|
-
{
|
|
459
|
-
action: NavigationAction.OPEN,
|
|
460
|
-
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
345
|
{
|
|
467
346
|
action: ThreadAction.SELECT,
|
|
468
|
-
data: { current: thread
|
|
347
|
+
data: { current: fullyQualifiedId(thread) },
|
|
469
348
|
},
|
|
349
|
+
],
|
|
350
|
+
[
|
|
470
351
|
{
|
|
471
|
-
action:
|
|
352
|
+
action: NavigationAction.OPEN,
|
|
472
353
|
data: {
|
|
473
|
-
|
|
474
|
-
subject: subjectId,
|
|
475
|
-
state: true,
|
|
354
|
+
activeParts: { complementary: 'comments' },
|
|
476
355
|
},
|
|
477
356
|
},
|
|
478
357
|
],
|
|
@@ -480,14 +359,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
480
359
|
};
|
|
481
360
|
} else {
|
|
482
361
|
// NOTE: This is the standalone thread creation case.
|
|
483
|
-
return {
|
|
362
|
+
return {
|
|
363
|
+
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
364
|
+
};
|
|
484
365
|
}
|
|
485
366
|
}
|
|
486
367
|
|
|
487
368
|
case ThreadAction.SELECT: {
|
|
488
|
-
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
489
369
|
state.current = intent.data?.current;
|
|
490
|
-
|
|
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
|
+
};
|
|
491
386
|
}
|
|
492
387
|
|
|
493
388
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -506,6 +401,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
506
401
|
const spaceId = space?.id;
|
|
507
402
|
|
|
508
403
|
return {
|
|
404
|
+
data: true,
|
|
509
405
|
intents: [
|
|
510
406
|
[
|
|
511
407
|
{
|
|
@@ -519,19 +415,21 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
415
|
|
|
520
416
|
case ThreadAction.DELETE: {
|
|
521
417
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
418
|
+
if (
|
|
419
|
+
!(thread instanceof ThreadType) ||
|
|
420
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
421
|
+
) {
|
|
523
422
|
return;
|
|
524
423
|
}
|
|
525
424
|
|
|
526
425
|
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);
|
|
426
|
+
const draft = state.drafts[subjectId];
|
|
427
|
+
if (draft) {
|
|
428
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
429
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
532
430
|
if (index !== -1) {
|
|
533
|
-
|
|
534
|
-
return;
|
|
431
|
+
draft.splice(index, 1);
|
|
432
|
+
return { data: true };
|
|
535
433
|
}
|
|
536
434
|
}
|
|
537
435
|
|
|
@@ -541,7 +439,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
439
|
}
|
|
542
440
|
|
|
543
441
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
442
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
443
|
const cursor = subject.threads[index]?.anchor;
|
|
546
444
|
if (index !== -1) {
|
|
547
445
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +485,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
485
|
const { thread, subject } = intent.data ?? {};
|
|
588
486
|
if (
|
|
589
487
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
488
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
489
|
) {
|
|
592
490
|
return;
|
|
593
491
|
}
|
|
@@ -597,12 +495,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
597
495
|
const intents = [];
|
|
598
496
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
599
497
|
|
|
600
|
-
if (state.
|
|
601
|
-
// Move
|
|
498
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
499
|
+
// Move draft to document.
|
|
602
500
|
thread.status = 'active';
|
|
603
501
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
604
|
-
state.
|
|
605
|
-
|
|
502
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
606
503
|
intents.push({
|
|
607
504
|
action: ObservabilityAction.SEND_EVENT,
|
|
608
505
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -627,7 +524,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
524
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
525
|
const space = getSpace(subject);
|
|
629
526
|
|
|
630
|
-
if (
|
|
527
|
+
if (
|
|
528
|
+
!(thread instanceof ThreadType) ||
|
|
529
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
530
|
+
) {
|
|
631
531
|
return;
|
|
632
532
|
}
|
|
633
533
|
|
|
@@ -658,7 +558,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
558
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
559
|
data: {
|
|
660
560
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
561
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
562
|
},
|
|
663
563
|
},
|
|
664
564
|
],
|
|
@@ -667,13 +567,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
667
567
|
} else if (intent.undo) {
|
|
668
568
|
const message = intent.data?.message;
|
|
669
569
|
const messageIndex = intent.data?.messageIndex;
|
|
670
|
-
|
|
671
570
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
672
571
|
return;
|
|
673
572
|
}
|
|
674
573
|
|
|
675
574
|
thread.messages.splice(messageIndex, 0, message);
|
|
676
|
-
|
|
677
575
|
return {
|
|
678
576
|
data: true,
|
|
679
577
|
intents: [
|
|
@@ -682,7 +580,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
580
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
581
|
data: {
|
|
684
582
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
583
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
584
|
},
|
|
687
585
|
},
|
|
688
586
|
],
|
|
@@ -694,96 +592,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
694
592
|
},
|
|
695
593
|
},
|
|
696
594
|
markdown: {
|
|
697
|
-
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
698
595
|
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
|
-
];
|
|
596
|
+
return threads(state, doc, intentPlugin?.provides.intent.dispatch);
|
|
787
597
|
},
|
|
788
598
|
},
|
|
789
599
|
},
|