@dxos/plugin-thread 0.6.13 → 0.6.14-main.69511f5
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-2Y7UWZLA.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-VK5UEBQ7.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-LHT42MTJ.mjs} +16 -16
- package/dist/lib/browser/chunk-LHT42MTJ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-WKCIKGGW.mjs} +2 -2
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-WKCIKGGW.mjs.map} +1 -1
- package/dist/lib/browser/index.mjs +431 -383
- 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-CIYBYISR.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-V6KBUQLH.cjs} +7 -7
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-A2IORKQK.cjs} +6 -6
- package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-A2IORKQK.cjs.map} +1 -1
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-HWN7Q5XK.cjs} +65 -65
- package/dist/lib/node/chunk-HWN7Q5XK.cjs.map +7 -0
- 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 +480 -436
- 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-PXO4ZJFU.mjs +19 -0
- package/dist/lib/node-esm/ThreadArticle-PXO4ZJFU.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-U7R24CN3.mjs +27 -0
- package/dist/lib/node-esm/ThreadMain-U7R24CN3.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-KWNXG6FG.mjs +21 -0
- package/dist/lib/node-esm/chunk-KWNXG6FG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SXXOBRAX.mjs +411 -0
- package/dist/lib/node-esm/chunk-SXXOBRAX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1151 -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 +7 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +41 -46
- package/src/ThreadPlugin.tsx +146 -307
- 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 +13 -23
- package/src/components/ThreadComplementary.tsx +113 -0
- package/src/components/ThreadSettings.tsx +3 -3
- 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 +9 -4
- 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-2Y7UWZLA.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-VK5UEBQ7.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-CIYBYISR.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-V6KBUQLH.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, parseId } 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
|
},
|
|
@@ -185,44 +117,74 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
185
117
|
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
186
118
|
const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
|
|
187
119
|
const metadataResolver = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
|
|
188
|
-
|
|
189
120
|
if (!client || !dispatch || !metadataResolver) {
|
|
190
121
|
return [];
|
|
191
122
|
}
|
|
192
123
|
|
|
124
|
+
const type = 'orphan-comments-for-subject';
|
|
125
|
+
const icon = 'ph--chat-text--regular';
|
|
126
|
+
|
|
193
127
|
return [
|
|
194
128
|
createExtension({
|
|
195
129
|
id: `${THREAD_PLUGIN}/comments-for-subject`,
|
|
196
130
|
resolver: ({ id }) => {
|
|
131
|
+
// TODO(Zan): Find util (or make one).
|
|
197
132
|
if (!id.endsWith('~comments')) {
|
|
198
133
|
return;
|
|
199
134
|
}
|
|
200
135
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] = docId?.split(':') ?? [];
|
|
136
|
+
const [subjectId] = id.split('~');
|
|
137
|
+
const { spaceId, objectId } = parseId(subjectId);
|
|
204
138
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
|
|
206
|
-
|
|
139
|
+
if (!objectId) {
|
|
140
|
+
// TODO(wittjosiah): Support comments for arbitrary subjects.
|
|
141
|
+
// This is to ensure that the comments panel is not stuck on an old object.
|
|
142
|
+
return {
|
|
143
|
+
id,
|
|
144
|
+
type,
|
|
145
|
+
data: null,
|
|
146
|
+
properties: {
|
|
147
|
+
icon,
|
|
148
|
+
label: ['unnamed object threads label', { ns: THREAD_PLUGIN }],
|
|
149
|
+
showResolvedThreads: false,
|
|
150
|
+
object: null,
|
|
151
|
+
space,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const object = toSignal(
|
|
157
|
+
(onChange) => {
|
|
158
|
+
const timeout = setTimeout(async () => {
|
|
159
|
+
await space?.db.loadObjectById(objectId);
|
|
160
|
+
onChange();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return () => clearTimeout(timeout);
|
|
164
|
+
},
|
|
165
|
+
() => space?.db.getObjectById(objectId),
|
|
166
|
+
subjectId,
|
|
167
|
+
);
|
|
168
|
+
if (!object || !subjectId) {
|
|
207
169
|
return;
|
|
208
170
|
}
|
|
209
171
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
172
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
173
|
+
const label = meta.label?.(object) ||
|
|
174
|
+
object.name ||
|
|
175
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
176
|
|
|
216
|
-
const viewState = getViewState(
|
|
177
|
+
const viewState = getViewState(subjectId);
|
|
217
178
|
|
|
218
179
|
return {
|
|
219
180
|
id,
|
|
220
|
-
type
|
|
221
|
-
data:
|
|
181
|
+
type,
|
|
182
|
+
data: null,
|
|
222
183
|
properties: {
|
|
223
|
-
icon
|
|
184
|
+
icon,
|
|
224
185
|
label,
|
|
225
186
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
187
|
+
object,
|
|
226
188
|
},
|
|
227
189
|
};
|
|
228
190
|
},
|
|
@@ -256,6 +218,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
218
|
menuItemType: 'toggle',
|
|
257
219
|
isChecked: viewState.showResolvedThreads,
|
|
258
220
|
testId: 'threadPlugin.toggleShowResolved',
|
|
221
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
222
|
},
|
|
260
223
|
},
|
|
261
224
|
];
|
|
@@ -287,8 +250,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
250
|
},
|
|
288
251
|
properties: {
|
|
289
252
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
253
|
+
icon: 'ph--chat--regular',
|
|
292
254
|
testId: 'threadPlugin.createObject',
|
|
293
255
|
},
|
|
294
256
|
},
|
|
@@ -311,11 +273,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
311
273
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
312
274
|
}
|
|
313
275
|
|
|
314
|
-
case 'article':
|
|
315
|
-
case 'complementary': {
|
|
276
|
+
case 'article': {
|
|
316
277
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
278
|
|
|
320
279
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
280
|
const channel = data.object;
|
|
@@ -342,93 +301,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
342
301
|
);
|
|
343
302
|
}
|
|
344
303
|
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case 'complementary--comments': {
|
|
345
308
|
if (
|
|
346
309
|
data.subject &&
|
|
347
310
|
typeof data.subject === 'object' &&
|
|
348
311
|
'threads' in data.subject &&
|
|
349
312
|
Array.isArray(data.subject.threads)
|
|
350
313
|
) {
|
|
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);
|
|
314
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
315
|
|
|
371
316
|
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>
|
|
317
|
+
<ThreadComplementary
|
|
318
|
+
role={role}
|
|
319
|
+
subject={data.subject}
|
|
320
|
+
drafts={state.drafts[fullyQualifiedId(data.subject)]}
|
|
321
|
+
current={state.current}
|
|
322
|
+
showResolvedThreads={showResolvedThreads}
|
|
323
|
+
/>
|
|
430
324
|
);
|
|
431
325
|
}
|
|
326
|
+
|
|
327
|
+
break;
|
|
432
328
|
}
|
|
433
329
|
}
|
|
434
330
|
|
|
@@ -442,36 +338,48 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
338
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
339
|
const { cursor, name, subject } = intent.data;
|
|
444
340
|
|
|
341
|
+
// Seed the threads array if it does not exist.
|
|
342
|
+
if (subject?.threads === undefined) {
|
|
343
|
+
try {
|
|
344
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
345
|
+
subject.threads = [];
|
|
346
|
+
} catch (err) {
|
|
347
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
445
352
|
const subjectId = fullyQualifiedId(subject);
|
|
446
353
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
449
|
-
|
|
354
|
+
const draft = state.drafts[subjectId];
|
|
355
|
+
if (draft) {
|
|
356
|
+
draft.push(thread);
|
|
450
357
|
} else {
|
|
451
|
-
state.
|
|
358
|
+
state.drafts[subjectId] = [thread];
|
|
452
359
|
}
|
|
453
360
|
|
|
454
361
|
return {
|
|
455
362
|
data: thread,
|
|
456
363
|
intents: [
|
|
364
|
+
[
|
|
365
|
+
{
|
|
366
|
+
action: ThreadAction.SELECT,
|
|
367
|
+
data: { current: fullyQualifiedId(thread) },
|
|
368
|
+
},
|
|
369
|
+
],
|
|
457
370
|
[
|
|
458
371
|
{
|
|
459
372
|
action: NavigationAction.OPEN,
|
|
460
373
|
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
374
|
+
activeParts: { complementary: 'comments' },
|
|
464
375
|
},
|
|
465
376
|
},
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
data: { current: thread.id, focus: true },
|
|
469
|
-
},
|
|
377
|
+
],
|
|
378
|
+
[
|
|
470
379
|
{
|
|
471
380
|
action: LayoutAction.SET_LAYOUT,
|
|
472
381
|
data: {
|
|
473
382
|
element: 'complementary',
|
|
474
|
-
subject: subjectId,
|
|
475
383
|
state: true,
|
|
476
384
|
},
|
|
477
385
|
},
|
|
@@ -480,14 +388,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
480
388
|
};
|
|
481
389
|
} else {
|
|
482
390
|
// NOTE: This is the standalone thread creation case.
|
|
483
|
-
return {
|
|
391
|
+
return {
|
|
392
|
+
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
393
|
+
};
|
|
484
394
|
}
|
|
485
395
|
}
|
|
486
396
|
|
|
487
397
|
case ThreadAction.SELECT: {
|
|
488
|
-
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
489
398
|
state.current = intent.data?.current;
|
|
490
|
-
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
data: true,
|
|
402
|
+
intents: !intent.data?.skipOpen
|
|
403
|
+
? [
|
|
404
|
+
[
|
|
405
|
+
{
|
|
406
|
+
action: NavigationAction.OPEN,
|
|
407
|
+
data: {
|
|
408
|
+
activeParts: { complementary: 'comments' },
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
]
|
|
413
|
+
: [],
|
|
414
|
+
};
|
|
491
415
|
}
|
|
492
416
|
|
|
493
417
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -506,6 +430,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
506
430
|
const spaceId = space?.id;
|
|
507
431
|
|
|
508
432
|
return {
|
|
433
|
+
data: true,
|
|
509
434
|
intents: [
|
|
510
435
|
[
|
|
511
436
|
{
|
|
@@ -519,19 +444,21 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
444
|
|
|
520
445
|
case ThreadAction.DELETE: {
|
|
521
446
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
447
|
+
if (
|
|
448
|
+
!(thread instanceof ThreadType) ||
|
|
449
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
450
|
+
) {
|
|
523
451
|
return;
|
|
524
452
|
}
|
|
525
453
|
|
|
526
454
|
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);
|
|
455
|
+
const draft = state.drafts[subjectId];
|
|
456
|
+
if (draft) {
|
|
457
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
458
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
532
459
|
if (index !== -1) {
|
|
533
|
-
|
|
534
|
-
return;
|
|
460
|
+
draft.splice(index, 1);
|
|
461
|
+
return { data: true };
|
|
535
462
|
}
|
|
536
463
|
}
|
|
537
464
|
|
|
@@ -541,7 +468,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
468
|
}
|
|
542
469
|
|
|
543
470
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
471
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
472
|
const cursor = subject.threads[index]?.anchor;
|
|
546
473
|
if (index !== -1) {
|
|
547
474
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +514,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
514
|
const { thread, subject } = intent.data ?? {};
|
|
588
515
|
if (
|
|
589
516
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
517
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
518
|
) {
|
|
592
519
|
return;
|
|
593
520
|
}
|
|
@@ -597,12 +524,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
597
524
|
const intents = [];
|
|
598
525
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
599
526
|
|
|
600
|
-
if (state.
|
|
601
|
-
// Move
|
|
527
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
528
|
+
// Move draft to document.
|
|
602
529
|
thread.status = 'active';
|
|
603
530
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
604
|
-
state.
|
|
605
|
-
|
|
531
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
606
532
|
intents.push({
|
|
607
533
|
action: ObservabilityAction.SEND_EVENT,
|
|
608
534
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -627,7 +553,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
553
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
554
|
const space = getSpace(subject);
|
|
629
555
|
|
|
630
|
-
if (
|
|
556
|
+
if (
|
|
557
|
+
!(thread instanceof ThreadType) ||
|
|
558
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
559
|
+
) {
|
|
631
560
|
return;
|
|
632
561
|
}
|
|
633
562
|
|
|
@@ -658,7 +587,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
587
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
588
|
data: {
|
|
660
589
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
590
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
591
|
},
|
|
663
592
|
},
|
|
664
593
|
],
|
|
@@ -667,13 +596,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
667
596
|
} else if (intent.undo) {
|
|
668
597
|
const message = intent.data?.message;
|
|
669
598
|
const messageIndex = intent.data?.messageIndex;
|
|
670
|
-
|
|
671
599
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
672
600
|
return;
|
|
673
601
|
}
|
|
674
602
|
|
|
675
603
|
thread.messages.splice(messageIndex, 0, message);
|
|
676
|
-
|
|
677
604
|
return {
|
|
678
605
|
data: true,
|
|
679
606
|
intents: [
|
|
@@ -682,7 +609,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
609
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
610
|
data: {
|
|
684
611
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
612
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
613
|
},
|
|
687
614
|
},
|
|
688
615
|
],
|
|
@@ -694,96 +621,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
694
621
|
},
|
|
695
622
|
},
|
|
696
623
|
markdown: {
|
|
697
|
-
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
698
624
|
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
|
-
];
|
|
625
|
+
return threads(state, doc, intentPlugin?.provides.intent.dispatch);
|
|
787
626
|
},
|
|
788
627
|
},
|
|
789
628
|
},
|