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