@dxos/plugin-thread 0.6.11 → 0.6.12-main.15a606f
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-4VU752F4.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-ML3P4XSD.mjs} +3 -3
- package/dist/lib/browser/{chunk-5IBJO53M.mjs → chunk-7NUYW5IW.mjs} +2 -5
- package/dist/lib/browser/chunk-7NUYW5IW.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-GJRBBPXI.mjs} +2 -2
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-GJRBBPXI.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-XF5QKI75.mjs} +13 -11
- package/dist/lib/browser/chunk-XF5QKI75.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +237 -207
- 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-ERUHPHVT.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-6DN7FDNB.cjs} +7 -7
- package/dist/lib/node/{chunk-BWBONZGU.cjs → chunk-6EVXCSDS.cjs} +5 -18
- package/dist/lib/node/chunk-6EVXCSDS.cjs.map +7 -0
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-FXD3DYMO.cjs} +6 -6
- package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-FXD3DYMO.cjs.map} +1 -1
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-PZVUW4W2.cjs} +65 -63
- package/dist/lib/node/chunk-PZVUW4W2.cjs.map +7 -0
- package/dist/lib/node/index.cjs +324 -296
- 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-NKI5F4W5.mjs +19 -0
- package/dist/lib/node-esm/ThreadArticle-NKI5F4W5.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-2A3VQLCW.mjs +27 -0
- package/dist/lib/node-esm/ThreadMain-2A3VQLCW.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-A44C4MTQ.mjs +413 -0
- package/dist/lib/node-esm/chunk-A44C4MTQ.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-Y475KZTX.mjs +20 -0
- package/dist/lib/node-esm/chunk-Y475KZTX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-YO46JGAE.mjs +21 -0
- package/dist/lib/node-esm/chunk-YO46JGAE.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1133 -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.map +1 -1
- package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
- package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
- package/dist/types/src/components/ThreadComplementary.d.ts +11 -0
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -0
- package/dist/types/src/components/testing.d.ts +2 -0
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +37 -34
- package/src/ThreadPlugin.tsx +67 -178
- package/src/components/ChatContainer.tsx +7 -8
- package/src/components/CommentContainer.tsx +10 -7
- package/src/components/Comments.stories.tsx +2 -2
- package/src/components/CommentsContainer.tsx +22 -18
- package/src/components/MessageContainer.tsx +2 -1
- package/src/components/ThreadComplementary.tsx +120 -0
- package/src/meta.tsx +1 -5
- package/src/types.ts +1 -1
- 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/types.test.ts +0 -13
- /package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs.map → ThreadArticle-4VU752F4.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-ML3P4XSD.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-ERUHPHVT.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-6DN7FDNB.cjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Chat, type IconProps } from '@phosphor-icons/react';
|
|
6
5
|
import { computed, effect } from '@preact/signals-core';
|
|
7
6
|
import React from 'react';
|
|
8
7
|
|
|
@@ -16,51 +15,35 @@ import {
|
|
|
16
15
|
parseIntentPlugin,
|
|
17
16
|
parseNavigationPlugin,
|
|
18
17
|
resolvePlugin,
|
|
19
|
-
parseGraphPlugin,
|
|
20
|
-
SLUG_PATH_SEPARATOR,
|
|
21
|
-
SLUG_COLLECTION_INDICATOR,
|
|
22
18
|
isLayoutParts,
|
|
23
19
|
parseMetadataResolverPlugin,
|
|
24
|
-
type IntentDispatcher,
|
|
25
|
-
useResolvePlugins,
|
|
26
20
|
} from '@dxos/app-framework';
|
|
27
21
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
28
22
|
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
23
|
import { create } from '@dxos/echo-schema';
|
|
30
24
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
31
|
-
import {
|
|
25
|
+
import { log } from '@dxos/log';
|
|
32
26
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
33
|
-
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
-
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
27
|
+
import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxos/plugin-graph';
|
|
35
28
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
36
29
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
37
30
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
38
31
|
import {
|
|
39
32
|
getSpace,
|
|
40
33
|
getTextInRange,
|
|
41
|
-
Filter,
|
|
42
34
|
createDocAccessor,
|
|
43
35
|
fullyQualifiedId,
|
|
44
36
|
loadObjectReferences,
|
|
45
37
|
} from '@dxos/react-client/echo';
|
|
46
|
-
import { ScrollArea } from '@dxos/react-ui';
|
|
47
|
-
import { createAttendableAttributes } from '@dxos/react-ui-attention';
|
|
48
38
|
import { comments, createExternalCommentSync, listener } from '@dxos/react-ui-editor';
|
|
49
39
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
40
|
import { nonNullable } from '@dxos/util';
|
|
51
41
|
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
ThreadSettings,
|
|
55
|
-
CommentsContainer,
|
|
56
|
-
CommentsHeading,
|
|
57
|
-
ChatContainer,
|
|
58
|
-
ChatHeading,
|
|
59
|
-
ThreadArticle,
|
|
60
|
-
} from './components';
|
|
42
|
+
import { ThreadMain, ThreadSettings, ChatContainer, ChatHeading, ThreadArticle } from './components';
|
|
43
|
+
import { ThreadComplementary } from './components/ThreadComplementary';
|
|
61
44
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
62
45
|
import translations from './translations';
|
|
63
|
-
import { ThreadAction, type
|
|
46
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
|
|
64
47
|
|
|
65
48
|
type ThreadState = {
|
|
66
49
|
/** An in-memory staging area for threads that are being drafted. */
|
|
@@ -74,8 +57,6 @@ const initialViewState = { showResolvedThreads: false };
|
|
|
74
57
|
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
58
|
|
|
76
59
|
// TODO(Zan): More robust runtime check.
|
|
77
|
-
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
78
|
-
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
79
60
|
|
|
80
61
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
81
62
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
@@ -90,10 +71,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
90
71
|
return viewStore[subjectId];
|
|
91
72
|
};
|
|
92
73
|
|
|
93
|
-
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
94
74
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
95
75
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
-
let dispatch: IntentDispatcher | undefined;
|
|
97
76
|
|
|
98
77
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
99
78
|
|
|
@@ -102,45 +81,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
102
81
|
ready: async (plugins) => {
|
|
103
82
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
104
83
|
|
|
105
|
-
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
106
84
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
107
85
|
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
86
|
},
|
|
145
87
|
unload: async () => {
|
|
146
88
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -151,8 +93,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
151
93
|
records: {
|
|
152
94
|
[ChannelType.typename]: {
|
|
153
95
|
placeholder: ['channel title placeholder', { ns: THREAD_PLUGIN }],
|
|
154
|
-
icon:
|
|
155
|
-
iconSymbol: 'ph--chat--regular',
|
|
96
|
+
icon: 'ph--chat--regular',
|
|
156
97
|
// TODO(wittjosiah): Move out of metadata.
|
|
157
98
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
158
99
|
},
|
|
@@ -198,29 +139,38 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
198
139
|
return;
|
|
199
140
|
}
|
|
200
141
|
|
|
201
|
-
// TODO(Zan): Find util (or make one)
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] =
|
|
142
|
+
// TODO(Zan): Find util (or make one).
|
|
143
|
+
const subjectId = id.split('~').at(0);
|
|
144
|
+
const [spaceId, objectId] = subjectId?.split(':') ?? [];
|
|
204
145
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
const
|
|
206
|
-
|
|
146
|
+
const object = toSignal(
|
|
147
|
+
(onChange) => {
|
|
148
|
+
const timeout = setTimeout(async () => {
|
|
149
|
+
await space?.db.loadObjectById(objectId);
|
|
150
|
+
onChange();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return () => clearTimeout(timeout);
|
|
154
|
+
},
|
|
155
|
+
() => space?.db.getObjectById(objectId),
|
|
156
|
+
);
|
|
157
|
+
if (!object || !subjectId) {
|
|
207
158
|
return;
|
|
208
159
|
}
|
|
209
160
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
161
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
162
|
+
const label = meta.label?.(object) ||
|
|
163
|
+
object.name ||
|
|
164
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
165
|
|
|
216
|
-
const viewState = getViewState(
|
|
166
|
+
const viewState = getViewState(subjectId);
|
|
217
167
|
|
|
218
168
|
return {
|
|
219
169
|
id,
|
|
220
170
|
type: 'orphan-comments-for-subject',
|
|
221
|
-
data:
|
|
171
|
+
data: object,
|
|
222
172
|
properties: {
|
|
223
|
-
icon: meta.
|
|
173
|
+
icon: meta.icon,
|
|
224
174
|
label,
|
|
225
175
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
226
176
|
},
|
|
@@ -256,6 +206,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
206
|
menuItemType: 'toggle',
|
|
257
207
|
isChecked: viewState.showResolvedThreads,
|
|
258
208
|
testId: 'threadPlugin.toggleShowResolved',
|
|
209
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
210
|
},
|
|
260
211
|
},
|
|
261
212
|
];
|
|
@@ -287,8 +238,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
238
|
},
|
|
288
239
|
properties: {
|
|
289
240
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
241
|
+
icon: 'ph--chat--regular',
|
|
292
242
|
testId: 'threadPlugin.createObject',
|
|
293
243
|
},
|
|
294
244
|
},
|
|
@@ -314,8 +264,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
314
264
|
case 'article':
|
|
315
265
|
case 'complementary': {
|
|
316
266
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
267
|
|
|
320
268
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
269
|
const channel = data.object;
|
|
@@ -348,85 +296,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
348
296
|
'threads' in data.subject &&
|
|
349
297
|
Array.isArray(data.subject.threads)
|
|
350
298
|
) {
|
|
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);
|
|
299
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
300
|
|
|
371
301
|
return (
|
|
372
|
-
<
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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>
|
|
302
|
+
<ThreadComplementary
|
|
303
|
+
role={role}
|
|
304
|
+
subject={data.subject}
|
|
305
|
+
stagedThreads={state.staging[fullyQualifiedId(data.subject)]}
|
|
306
|
+
current={state.current}
|
|
307
|
+
focus
|
|
308
|
+
showResolvedThreads={showResolvedThreads}
|
|
309
|
+
/>
|
|
430
310
|
);
|
|
431
311
|
}
|
|
432
312
|
}
|
|
@@ -442,6 +322,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
322
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
323
|
const { cursor, name, subject } = intent.data;
|
|
444
324
|
|
|
325
|
+
// Seed the threads array if it does not exist.
|
|
326
|
+
if (subject?.threads === undefined) {
|
|
327
|
+
try {
|
|
328
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
329
|
+
subject.threads = [];
|
|
330
|
+
} catch (e) {
|
|
331
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
445
336
|
const subjectId = fullyQualifiedId(subject);
|
|
446
337
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
338
|
const stagingArea = state.staging[subjectId];
|
|
@@ -455,17 +346,9 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
455
346
|
data: thread,
|
|
456
347
|
intents: [
|
|
457
348
|
[
|
|
458
|
-
{
|
|
459
|
-
action: NavigationAction.OPEN,
|
|
460
|
-
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
349
|
{
|
|
467
350
|
action: ThreadAction.SELECT,
|
|
468
|
-
data: { current: thread
|
|
351
|
+
data: { current: fullyQualifiedId(thread), focus: true },
|
|
469
352
|
},
|
|
470
353
|
{
|
|
471
354
|
action: LayoutAction.SET_LAYOUT,
|
|
@@ -519,7 +402,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
402
|
|
|
520
403
|
case ThreadAction.DELETE: {
|
|
521
404
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
405
|
+
if (
|
|
406
|
+
!(thread instanceof ThreadType) ||
|
|
407
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
408
|
+
) {
|
|
523
409
|
return;
|
|
524
410
|
}
|
|
525
411
|
|
|
@@ -531,7 +417,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
531
417
|
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
532
418
|
if (index !== -1) {
|
|
533
419
|
stagingArea.splice(index, 1);
|
|
534
|
-
return;
|
|
420
|
+
return { data: true };
|
|
535
421
|
}
|
|
536
422
|
}
|
|
537
423
|
|
|
@@ -541,7 +427,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
427
|
}
|
|
542
428
|
|
|
543
429
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
430
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
431
|
const cursor = subject.threads[index]?.anchor;
|
|
546
432
|
if (index !== -1) {
|
|
547
433
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +473,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
473
|
const { thread, subject } = intent.data ?? {};
|
|
588
474
|
if (
|
|
589
475
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
476
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
477
|
) {
|
|
592
478
|
return;
|
|
593
479
|
}
|
|
@@ -627,7 +513,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
513
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
514
|
const space = getSpace(subject);
|
|
629
515
|
|
|
630
|
-
if (
|
|
516
|
+
if (
|
|
517
|
+
!(thread instanceof ThreadType) ||
|
|
518
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
519
|
+
) {
|
|
631
520
|
return;
|
|
632
521
|
}
|
|
633
522
|
|
|
@@ -658,7 +547,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
547
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
548
|
data: {
|
|
660
549
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
550
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
551
|
},
|
|
663
552
|
},
|
|
664
553
|
],
|
|
@@ -682,7 +571,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
571
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
572
|
data: {
|
|
684
573
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
574
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
575
|
},
|
|
687
576
|
},
|
|
688
577
|
],
|
|
@@ -733,7 +622,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
733
622
|
() =>
|
|
734
623
|
threads.value
|
|
735
624
|
.filter((thread) => thread?.anchor)
|
|
736
|
-
.map((thread) => ({ id: thread
|
|
625
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
737
626
|
),
|
|
738
627
|
comments({
|
|
739
628
|
id: doc.id,
|
|
@@ -754,7 +643,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
754
643
|
// If the thread is in the staging area, remove it.
|
|
755
644
|
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
756
645
|
if (stagingArea) {
|
|
757
|
-
const index = stagingArea.findIndex((thread) => thread
|
|
646
|
+
const index = stagingArea.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
758
647
|
if (index !== -1) {
|
|
759
648
|
stagingArea.splice(index, 1);
|
|
760
649
|
}
|
|
@@ -767,7 +656,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
767
656
|
},
|
|
768
657
|
onUpdate: ({ id, cursor }) => {
|
|
769
658
|
const thread =
|
|
770
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread
|
|
659
|
+
state.staging[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
771
660
|
doc.threads.find((thread) => thread?.id === id);
|
|
772
661
|
|
|
773
662
|
if (thread instanceof ThreadType && thread.anchor) {
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Chat } from '@phosphor-icons/react';
|
|
6
5
|
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
7
6
|
|
|
8
7
|
import { create } from '@dxos/echo-schema';
|
|
9
8
|
import { MessageType } from '@dxos/plugin-space/types';
|
|
10
|
-
import { getSpace, useMembers } from '@dxos/react-client/echo';
|
|
9
|
+
import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
|
|
11
10
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
12
|
-
import { ScrollArea, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
13
|
-
import { PlankHeading
|
|
11
|
+
import { Icon, ScrollArea, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { PlankHeading } from '@dxos/react-ui-deck';
|
|
14
13
|
import { createBasicExtensions, createThemeExtensions, listener } from '@dxos/react-ui-editor';
|
|
15
14
|
import { mx } from '@dxos/react-ui-theme';
|
|
16
15
|
import { MessageTextbox, type MessageTextboxProps, Thread, ThreadFooter, threadLayout } from '@dxos/react-ui-thread';
|
|
@@ -28,7 +27,7 @@ export const ChatHeading = ({ attendableId }: { attendableId?: string }) => {
|
|
|
28
27
|
return (
|
|
29
28
|
<div role='none' className='flex items-center'>
|
|
30
29
|
<PlankHeading.Button attendableId={attendableId}>
|
|
31
|
-
<
|
|
30
|
+
<Icon icon='ph--chat--regular' size={5} />
|
|
32
31
|
</PlankHeading.Button>
|
|
33
32
|
<PlankHeading.Label attendableId={attendableId}>{t('chat heading')}</PlankHeading.Label>
|
|
34
33
|
</div>
|
|
@@ -39,7 +38,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
39
38
|
const identity = useIdentity()!;
|
|
40
39
|
const space = getSpace(thread);
|
|
41
40
|
const members = useMembers(space?.key);
|
|
42
|
-
const activity = useStatus(space, thread
|
|
41
|
+
const activity = useStatus(space, fullyQualifiedId(thread));
|
|
43
42
|
const { t } = useTranslation(THREAD_PLUGIN);
|
|
44
43
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
45
44
|
const [_count, _setCount] = useState(0);
|
|
@@ -48,7 +47,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
48
47
|
const threadScrollRef = useRef<HTMLDivElement | null>(null);
|
|
49
48
|
const { themeMode } = useThemeContext();
|
|
50
49
|
|
|
51
|
-
const textboxMetadata = getMessageMetadata(thread
|
|
50
|
+
const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
|
|
52
51
|
const messageRef = useRef('');
|
|
53
52
|
const extensions = useMemo(
|
|
54
53
|
() => [
|
|
@@ -103,7 +102,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
103
102
|
return (
|
|
104
103
|
<Thread
|
|
105
104
|
current={current}
|
|
106
|
-
id={thread
|
|
105
|
+
id={fullyQualifiedId(thread)}
|
|
107
106
|
classNames='bs-full grid-rows-[1fr_min-content_min-content] overflow-hidden transition-[padding-block-end] [[data-sidebar-inline-start-state=open]_&]:lg:pbe-0'
|
|
108
107
|
>
|
|
109
108
|
<ScrollArea.Root classNames='col-span-2'>
|
|
@@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
7
7
|
|
|
8
8
|
import { create } from '@dxos/echo-schema';
|
|
9
9
|
import { MessageType } from '@dxos/plugin-space/types';
|
|
10
|
-
import { getSpace, useMembers } from '@dxos/react-client/echo';
|
|
10
|
+
import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
|
|
11
11
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
12
12
|
import { Button, Tag, Tooltip, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
13
13
|
import { createBasicExtensions, createThemeExtensions, listener } from '@dxos/react-ui-editor';
|
|
@@ -103,13 +103,13 @@ export const CommentContainer = ({
|
|
|
103
103
|
const identity = useIdentity()!;
|
|
104
104
|
const space = getSpace(thread);
|
|
105
105
|
const members = useMembers(space?.key);
|
|
106
|
-
const activity = useStatus(space, thread
|
|
106
|
+
const activity = useStatus(space, fullyQualifiedId(thread));
|
|
107
107
|
const { t } = useTranslation(THREAD_PLUGIN);
|
|
108
108
|
const threadScrollRef = useRef<HTMLDivElement | null>(null);
|
|
109
109
|
const [autoFocus, setAutoFocus] = useState(!!autoFocusTextbox);
|
|
110
110
|
const { themeMode } = useThemeContext();
|
|
111
111
|
|
|
112
|
-
const textboxMetadata = getMessageMetadata(thread
|
|
112
|
+
const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
|
|
113
113
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
114
114
|
const [_count, _setCount] = useState(0);
|
|
115
115
|
const rerenderEditor = () => _setCount((count) => count + 1);
|
|
@@ -159,10 +159,8 @@ export const CommentContainer = ({
|
|
|
159
159
|
return true;
|
|
160
160
|
}, [thread, identity]);
|
|
161
161
|
|
|
162
|
-
const handleDeleteMessage = (id: string) => onMessageDelete?.(id);
|
|
163
|
-
|
|
164
162
|
return (
|
|
165
|
-
<Thread onClickCapture={onAttend} onFocusCapture={onAttend} current={current} id={thread
|
|
163
|
+
<Thread onClickCapture={onAttend} onFocusCapture={onAttend} current={current} id={fullyQualifiedId(thread)}>
|
|
166
164
|
<div
|
|
167
165
|
role='none'
|
|
168
166
|
className={mx(
|
|
@@ -195,7 +193,12 @@ export const CommentContainer = ({
|
|
|
195
193
|
</div>
|
|
196
194
|
</div>
|
|
197
195
|
{thread.messages.filter(nonNullable).map((message) => (
|
|
198
|
-
<MessageContainer
|
|
196
|
+
<MessageContainer
|
|
197
|
+
key={message.id}
|
|
198
|
+
message={message}
|
|
199
|
+
members={members}
|
|
200
|
+
onDelete={(id: string) => onMessageDelete?.(id)}
|
|
201
|
+
/>
|
|
199
202
|
))}
|
|
200
203
|
<MessageTextbox extensions={extensions} autoFocus={autoFocus} onSend={handleCreate} {...textboxMetadata} />
|
|
201
204
|
<ThreadFooter activity={activity}>{t('activity message')}</ThreadFooter>
|
|
@@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
8
8
|
|
|
9
9
|
import { MessageType, ThreadType } from '@dxos/plugin-space/types';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
|
-
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
11
|
+
import { Filter, fullyQualifiedId, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
12
12
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
13
13
|
import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
|
|
14
14
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
@@ -30,7 +30,7 @@ const Story = ({ spaceKey }: ClientRepeatedComponentProps) => {
|
|
|
30
30
|
const t = setTimeout(async () => {
|
|
31
31
|
space.db.add(createCommentThread(identity));
|
|
32
32
|
const thread = space.db.add(createCommentThread(identity));
|
|
33
|
-
setDetached([thread
|
|
33
|
+
setDetached([fullyQualifiedId(thread)]);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
return () => clearTimeout(t);
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { ChatText
|
|
5
|
+
import { ChatText } from '@phosphor-icons/react';
|
|
6
6
|
import React, { useEffect } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type ThreadType } from '@dxos/plugin-space/types';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
10
|
+
import { useTranslation, Icon, Trans } from '@dxos/react-ui';
|
|
11
|
+
import { PlankHeading } from '@dxos/react-ui-deck';
|
|
11
12
|
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
12
13
|
|
|
13
14
|
import { CommentContainer } from './CommentContainer';
|
|
@@ -39,7 +40,7 @@ export const CommentsHeading = ({ attendableId }: { attendableId?: string }) =>
|
|
|
39
40
|
return (
|
|
40
41
|
<div role='none' className='flex items-center'>
|
|
41
42
|
<PlankHeading.Button attendableId={attendableId}>
|
|
42
|
-
<
|
|
43
|
+
<Icon icon='ph--quotes--regular' size={5} />
|
|
43
44
|
</PlankHeading.Button>
|
|
44
45
|
<PlankHeading.Label attendableId={attendableId}>{t('comments heading')}</PlankHeading.Label>
|
|
45
46
|
</div>
|
|
@@ -73,20 +74,23 @@ export const CommentsContainer = ({
|
|
|
73
74
|
return (
|
|
74
75
|
<>
|
|
75
76
|
{filteredThreads.length > 0 ? (
|
|
76
|
-
filteredThreads.map((thread) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
filteredThreads.map((thread) => {
|
|
78
|
+
const threadId = fullyQualifiedId(thread);
|
|
79
|
+
return (
|
|
80
|
+
<CommentContainer
|
|
81
|
+
key={threadId}
|
|
82
|
+
thread={thread}
|
|
83
|
+
current={currentId === threadId}
|
|
84
|
+
detached={detached.includes(threadId)}
|
|
85
|
+
autoFocusTextbox={autoFocusCurrentTextbox && currentId === threadId}
|
|
86
|
+
{...(onThreadAttend && { onAttend: () => onThreadAttend(thread) })}
|
|
87
|
+
{...(onThreadDelete && { onThreadDelete: () => onThreadDelete(thread) })}
|
|
88
|
+
{...(onMessageDelete && { onMessageDelete: (messageId: string) => onMessageDelete(thread, messageId) })}
|
|
89
|
+
{...(onThreadToggleResolved && { onResolve: () => onThreadToggleResolved(thread) })}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
})
|
|
90
94
|
) : (
|
|
91
95
|
<div
|
|
92
96
|
role='alert'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
|
+
|
|
4
5
|
import { EditorView } from '@codemirror/view';
|
|
5
6
|
import { Check, PencilSimple, X } from '@phosphor-icons/react';
|
|
6
7
|
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
@@ -91,7 +92,7 @@ export const MessageContainer = ({
|
|
|
91
92
|
variant='ghost'
|
|
92
93
|
data-testid='thread.message.delete'
|
|
93
94
|
classNames={messageControlClassNames}
|
|
94
|
-
onClick={handleDelete}
|
|
95
|
+
onClick={() => handleDelete()}
|
|
95
96
|
>
|
|
96
97
|
<span className='sr-only'>{deleteLabel}</span>
|
|
97
98
|
<X className={getSize(4)} />
|