@dxos/plugin-thread 0.6.11-staging.e6894a4 → 0.6.12-main.5cc132e
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-6L6BGE4Z.mjs} +2 -2
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-DU6LBSN3.mjs} +2 -2
- package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-AVC4CGJM.mjs} +6 -6
- package/dist/lib/browser/chunk-AVC4CGJM.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-U5UVX4ZP.mjs} +1 -1
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-U5UVX4ZP.mjs.map} +1 -1
- package/dist/lib/browser/index.mjs +217 -156
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs → ThreadArticle-7BWXVIJ4.cjs} +6 -6
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-XSRB6NKJ.cjs} +6 -6
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-4B2ARV7L.cjs} +4 -4
- package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-4B2ARV7L.cjs.map} +1 -1
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-T7TXCNHL.cjs} +8 -8
- package/dist/lib/node/chunk-T7TXCNHL.cjs.map +7 -0
- package/dist/lib/node/index.cjs +258 -199
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +2 -2
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/ThreadArticle-6L6BGE4Z.mjs +18 -0
- package/dist/lib/node-esm/ThreadArticle-6L6BGE4Z.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-DU6LBSN3.mjs +26 -0
- package/dist/lib/node-esm/ThreadMain-DU6LBSN3.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-5IBJO53M.mjs +21 -0
- package/dist/lib/node-esm/chunk-5IBJO53M.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-AVC4CGJM.mjs +410 -0
- package/dist/lib/node-esm/chunk-AVC4CGJM.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-U5UVX4ZP.mjs +20 -0
- package/dist/lib/node-esm/chunk-U5UVX4ZP.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1166 -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 +11 -0
- package/dist/lib/node-esm/meta.mjs.map +7 -0
- package/dist/lib/node-esm/types.mjs +8 -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 +0 -1
- package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
- package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
- package/dist/types/src/components/Comments.stories.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/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +35 -32
- package/src/ThreadPlugin.tsx +49 -123
- package/src/components/Chat.stories.tsx +3 -3
- package/src/components/ChatContainer.tsx +4 -4
- package/src/components/CommentContainer.tsx +10 -7
- package/src/components/Comments.stories.tsx +10 -15
- package/src/components/CommentsContainer.tsx +18 -14
- package/src/components/MessageContainer.tsx +2 -1
- package/src/components/ThreadComplementary.tsx +120 -0
- package/src/types.ts +1 -1
- package/dist/lib/browser/chunk-P7ZE5N5M.mjs.map +0 -7
- package/dist/lib/node/chunk-5PZNPPQF.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-6L6BGE4Z.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-DU6LBSN3.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-7BWXVIJ4.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-XSRB6NKJ.cjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Chat, type IconProps } from '@phosphor-icons/react';
|
|
6
6
|
import { computed, effect } from '@preact/signals-core';
|
|
7
|
-
import React
|
|
7
|
+
import React from 'react';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
type IntentPluginProvides,
|
|
@@ -21,17 +21,15 @@ import {
|
|
|
21
21
|
SLUG_COLLECTION_INDICATOR,
|
|
22
22
|
isLayoutParts,
|
|
23
23
|
parseMetadataResolverPlugin,
|
|
24
|
-
type IntentDispatcher,
|
|
25
|
-
useResolvePlugins,
|
|
26
24
|
} from '@dxos/app-framework';
|
|
27
25
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
28
26
|
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
27
|
import { create } from '@dxos/echo-schema';
|
|
30
28
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
29
|
+
import { log } from '@dxos/log';
|
|
31
30
|
import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin-attention';
|
|
32
31
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
33
32
|
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
-
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
35
33
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
36
34
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
37
35
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
@@ -43,24 +41,15 @@ import {
|
|
|
43
41
|
fullyQualifiedId,
|
|
44
42
|
loadObjectReferences,
|
|
45
43
|
} from '@dxos/react-client/echo';
|
|
46
|
-
import { ScrollArea } from '@dxos/react-ui';
|
|
47
|
-
import { createAttendableAttributes } from '@dxos/react-ui-attention';
|
|
48
44
|
import { comments, createExternalCommentSync, listener } from '@dxos/react-ui-editor';
|
|
49
45
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
46
|
import { nonNullable } from '@dxos/util';
|
|
51
47
|
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
ThreadSettings,
|
|
55
|
-
CommentsContainer,
|
|
56
|
-
CommentsHeading,
|
|
57
|
-
ChatContainer,
|
|
58
|
-
ChatHeading,
|
|
59
|
-
ThreadArticle,
|
|
60
|
-
} from './components';
|
|
48
|
+
import { ThreadMain, ThreadSettings, ChatContainer, ChatHeading, ThreadArticle } from './components';
|
|
49
|
+
import { ThreadComplementary } from './components/ThreadComplementary';
|
|
61
50
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
62
51
|
import translations from './translations';
|
|
63
|
-
import { ThreadAction, type
|
|
52
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
|
|
64
53
|
|
|
65
54
|
type ThreadState = {
|
|
66
55
|
/** An in-memory staging area for threads that are being drafted. */
|
|
@@ -73,6 +62,8 @@ type SubjectId = string;
|
|
|
73
62
|
const initialViewState = { showResolvedThreads: false };
|
|
74
63
|
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
64
|
|
|
65
|
+
// TODO(Zan): More robust runtime check.
|
|
66
|
+
|
|
76
67
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
77
68
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
78
69
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
@@ -89,7 +80,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
89
80
|
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
90
81
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
91
82
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
92
|
-
let dispatch: IntentDispatcher | undefined;
|
|
93
83
|
|
|
94
84
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
95
85
|
|
|
@@ -101,7 +91,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
101
91
|
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
102
92
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
103
93
|
intentPlugin = resolvePlugin(plugins, parseIntentPlugin)!;
|
|
104
|
-
dispatch = intentPlugin?.provides.intent.dispatch;
|
|
105
94
|
|
|
106
95
|
const graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
|
|
107
96
|
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
@@ -121,7 +110,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
121
110
|
|
|
122
111
|
const firstAttendedNodeWithComments = Array.from(attention.attended)
|
|
123
112
|
.map((id) => graphPlugin?.provides.graph.findNode(id))
|
|
124
|
-
.find((node) =>
|
|
113
|
+
.find((node) => {
|
|
114
|
+
const data = node?.data as { threads?: unknown };
|
|
115
|
+
return Array.isArray(data?.threads) && data.threads.length > 0;
|
|
116
|
+
});
|
|
125
117
|
|
|
126
118
|
if (firstAttendedNodeWithComments) {
|
|
127
119
|
void intentPlugin?.provides.intent.dispatch({
|
|
@@ -199,7 +191,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
199
191
|
const [spaceId, objectId] = docId?.split(':') ?? [];
|
|
200
192
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
201
193
|
const doc = space?.db.getObjectById(objectId);
|
|
202
|
-
|
|
203
194
|
if (!doc || !docId) {
|
|
204
195
|
return;
|
|
205
196
|
}
|
|
@@ -208,7 +199,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
208
199
|
const label = docMeta.label?.(doc) ||
|
|
209
200
|
doc.name ||
|
|
210
201
|
docMeta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
211
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
212
202
|
|
|
213
203
|
const viewState = getViewState(docId);
|
|
214
204
|
|
|
@@ -225,7 +215,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
225
215
|
},
|
|
226
216
|
actions: ({ node }) => {
|
|
227
217
|
const dataId = node.id.split('~').at(0);
|
|
228
|
-
|
|
229
218
|
if (!node.id.endsWith('~comments') || !dataId) {
|
|
230
219
|
return;
|
|
231
220
|
}
|
|
@@ -254,6 +243,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
254
243
|
menuItemType: 'toggle',
|
|
255
244
|
isChecked: viewState.showResolvedThreads,
|
|
256
245
|
testId: 'threadPlugin.toggleShowResolved',
|
|
246
|
+
iconSymbol: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
257
247
|
},
|
|
258
248
|
},
|
|
259
249
|
];
|
|
@@ -313,24 +303,12 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
313
303
|
case 'complementary': {
|
|
314
304
|
const location = navigationPlugin?.provides.location;
|
|
315
305
|
|
|
316
|
-
const providesThreadsConfig = useCallback(
|
|
317
|
-
(plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
318
|
-
// TODO(Zan): More robust runtime check.
|
|
319
|
-
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined,
|
|
320
|
-
[],
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
324
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
325
|
-
|
|
326
306
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
327
307
|
const channel = data.object;
|
|
328
308
|
// TODO(zan): Maybe we should have utility for positional main object ids.
|
|
329
309
|
if (isLayoutParts(location?.active) && location.active.main) {
|
|
330
310
|
const layoutEntries = location.active.main;
|
|
331
|
-
|
|
332
311
|
const currentPosition = layoutEntries.findIndex((entry) => channel.id === entry.id);
|
|
333
|
-
|
|
334
312
|
if (currentPosition > 0) {
|
|
335
313
|
const objectToTheLeft = layoutEntries[currentPosition - 1];
|
|
336
314
|
const context = getSpace(data.object)?.db.getObjectById(objectToTheLeft.id);
|
|
@@ -356,85 +334,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
356
334
|
'threads' in data.subject &&
|
|
357
335
|
Array.isArray(data.subject.threads)
|
|
358
336
|
) {
|
|
359
|
-
const
|
|
360
|
-
.concat(state.staging[fullyQualifiedId(data.subject)])
|
|
361
|
-
.filter(nonNullable);
|
|
362
|
-
|
|
363
|
-
const createSort = threadProvides.find((p) => p.predicate(data.subject))?.createSort;
|
|
364
|
-
if (createSort) {
|
|
365
|
-
const sort = createSort(data.subject);
|
|
366
|
-
threads.sort((a, b) => sort(a.anchor, b.anchor));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const detached = data.subject.threads
|
|
370
|
-
.filter(nonNullable)
|
|
371
|
-
.filter(({ anchor }) => !anchor)
|
|
372
|
-
.map((thread) => thread.id);
|
|
373
|
-
|
|
374
|
-
const qualifiedSubjectId = fullyQualifiedId(data.subject);
|
|
375
|
-
const attention = attentionPlugin?.provides.attention?.attended ?? new Set([qualifiedSubjectId]);
|
|
376
|
-
const attendableAttrs = createAttendableAttributes(qualifiedSubjectId);
|
|
377
|
-
const { showResolvedThreads } = getViewState(qualifiedSubjectId);
|
|
337
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
378
338
|
|
|
379
339
|
return (
|
|
380
|
-
<
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
autoFocusCurrentTextbox={state.focus}
|
|
389
|
-
showResolvedThreads={showResolvedThreads}
|
|
390
|
-
onThreadAttend={(thread) => {
|
|
391
|
-
if (state.current !== thread.id) {
|
|
392
|
-
state.current = thread.id;
|
|
393
|
-
void dispatch?.({
|
|
394
|
-
action: LayoutAction.SCROLL_INTO_VIEW,
|
|
395
|
-
data: {
|
|
396
|
-
id: fullyQualifiedId(data.subject!),
|
|
397
|
-
thread: fullyQualifiedId(thread),
|
|
398
|
-
cursor: thread.anchor,
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}}
|
|
403
|
-
onThreadDelete={(thread) =>
|
|
404
|
-
dispatch?.({
|
|
405
|
-
plugin: THREAD_PLUGIN,
|
|
406
|
-
action: ThreadAction.DELETE,
|
|
407
|
-
data: { subject: data.subject, thread },
|
|
408
|
-
})
|
|
409
|
-
}
|
|
410
|
-
onMessageDelete={(thread, messageId) =>
|
|
411
|
-
dispatch?.({
|
|
412
|
-
plugin: THREAD_PLUGIN,
|
|
413
|
-
action: ThreadAction.DELETE_MESSAGE,
|
|
414
|
-
data: { subject: data.subject, thread, messageId },
|
|
415
|
-
})
|
|
416
|
-
}
|
|
417
|
-
onThreadToggleResolved={(thread) =>
|
|
418
|
-
dispatch?.({
|
|
419
|
-
plugin: THREAD_PLUGIN,
|
|
420
|
-
action: ThreadAction.TOGGLE_RESOLVED,
|
|
421
|
-
data: { thread },
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
onComment={(thread) => {
|
|
425
|
-
void intentPlugin?.provides.intent.dispatch({
|
|
426
|
-
action: ThreadAction.ON_MESSAGE_ADD,
|
|
427
|
-
data: { thread, subject: data.subject },
|
|
428
|
-
});
|
|
429
|
-
}}
|
|
430
|
-
/>
|
|
431
|
-
<div role='none' className='bs-10' />
|
|
432
|
-
<ScrollArea.Scrollbar>
|
|
433
|
-
<ScrollArea.Thumb />
|
|
434
|
-
</ScrollArea.Scrollbar>
|
|
435
|
-
</ScrollArea.Viewport>
|
|
436
|
-
</ScrollArea.Root>
|
|
437
|
-
</div>
|
|
340
|
+
<ThreadComplementary
|
|
341
|
+
role={role}
|
|
342
|
+
subject={data.subject}
|
|
343
|
+
stagedThreads={state.staging[fullyQualifiedId(data.subject)]}
|
|
344
|
+
current={state.current}
|
|
345
|
+
focus
|
|
346
|
+
showResolvedThreads={showResolvedThreads}
|
|
347
|
+
/>
|
|
438
348
|
);
|
|
439
349
|
}
|
|
440
350
|
}
|
|
@@ -450,9 +360,19 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
450
360
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
451
361
|
const { cursor, name, subject } = intent.data;
|
|
452
362
|
|
|
363
|
+
// Seed the threads array if it does not exist.
|
|
364
|
+
if (subject?.threads === undefined) {
|
|
365
|
+
try {
|
|
366
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
367
|
+
subject.threads = [];
|
|
368
|
+
} catch (e) {
|
|
369
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
453
374
|
const subjectId = fullyQualifiedId(subject);
|
|
454
375
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
455
|
-
|
|
456
376
|
const stagingArea = state.staging[subjectId];
|
|
457
377
|
if (stagingArea) {
|
|
458
378
|
stagingArea.push(thread);
|
|
@@ -474,7 +394,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
474
394
|
},
|
|
475
395
|
{
|
|
476
396
|
action: ThreadAction.SELECT,
|
|
477
|
-
data: { current: thread
|
|
397
|
+
data: { current: fullyQualifiedId(thread), focus: true },
|
|
478
398
|
},
|
|
479
399
|
{
|
|
480
400
|
action: LayoutAction.SET_LAYOUT,
|
|
@@ -528,7 +448,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
528
448
|
|
|
529
449
|
case ThreadAction.DELETE: {
|
|
530
450
|
const { subject, thread } = intent.data ?? {};
|
|
531
|
-
if (
|
|
451
|
+
if (
|
|
452
|
+
!(thread instanceof ThreadType) ||
|
|
453
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
454
|
+
) {
|
|
532
455
|
return;
|
|
533
456
|
}
|
|
534
457
|
|
|
@@ -540,7 +463,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
540
463
|
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
541
464
|
if (index !== -1) {
|
|
542
465
|
stagingArea.splice(index, 1);
|
|
543
|
-
return;
|
|
466
|
+
return { data: true };
|
|
544
467
|
}
|
|
545
468
|
}
|
|
546
469
|
|
|
@@ -550,7 +473,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
550
473
|
}
|
|
551
474
|
|
|
552
475
|
if (!intent.undo) {
|
|
553
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
476
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
554
477
|
const cursor = subject.threads[index]?.anchor;
|
|
555
478
|
if (index !== -1) {
|
|
556
479
|
subject.threads?.splice(index, 1);
|
|
@@ -596,7 +519,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
596
519
|
const { thread, subject } = intent.data ?? {};
|
|
597
520
|
if (
|
|
598
521
|
!(thread instanceof ThreadType) ||
|
|
599
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
522
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
600
523
|
) {
|
|
601
524
|
return;
|
|
602
525
|
}
|
|
@@ -636,7 +559,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
636
559
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
637
560
|
const space = getSpace(subject);
|
|
638
561
|
|
|
639
|
-
if (
|
|
562
|
+
if (
|
|
563
|
+
!(thread instanceof ThreadType) ||
|
|
564
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
565
|
+
) {
|
|
640
566
|
return;
|
|
641
567
|
}
|
|
642
568
|
|
|
@@ -667,7 +593,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
667
593
|
action: ObservabilityAction.SEND_EVENT,
|
|
668
594
|
data: {
|
|
669
595
|
name: 'threads.message.delete',
|
|
670
|
-
properties: { threadId: thread.id, spaceId: space
|
|
596
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
671
597
|
},
|
|
672
598
|
},
|
|
673
599
|
],
|
|
@@ -691,7 +617,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
691
617
|
action: ObservabilityAction.SEND_EVENT,
|
|
692
618
|
data: {
|
|
693
619
|
name: 'threads.message.undo-delete',
|
|
694
|
-
properties: { threadId: thread.id, spaceId: space
|
|
620
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
695
621
|
},
|
|
696
622
|
},
|
|
697
623
|
],
|
|
@@ -742,7 +668,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
742
668
|
() =>
|
|
743
669
|
threads.value
|
|
744
670
|
.filter((thread) => thread?.anchor)
|
|
745
|
-
.map((thread) => ({ id: thread
|
|
671
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
746
672
|
),
|
|
747
673
|
comments({
|
|
748
674
|
id: doc.id,
|
|
@@ -763,7 +689,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
763
689
|
// If the thread is in the staging area, remove it.
|
|
764
690
|
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
765
691
|
if (stagingArea) {
|
|
766
|
-
const index = stagingArea.findIndex((thread) => thread
|
|
692
|
+
const index = stagingArea.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
767
693
|
if (index !== -1) {
|
|
768
694
|
stagingArea.splice(index, 1);
|
|
769
695
|
}
|
|
@@ -776,7 +702,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
776
702
|
},
|
|
777
703
|
onUpdate: ({ id, cursor }) => {
|
|
778
704
|
const thread =
|
|
779
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread
|
|
705
|
+
state.staging[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
780
706
|
doc.threads.find((thread) => thread?.id === id);
|
|
781
707
|
|
|
782
708
|
if (thread instanceof ThreadType && thread.anchor) {
|
|
@@ -15,7 +15,7 @@ import { useIdentity } from '@dxos/react-client/halo';
|
|
|
15
15
|
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
16
16
|
import { Mosaic } from '@dxos/react-ui-mosaic';
|
|
17
17
|
import { Thread } from '@dxos/react-ui-thread';
|
|
18
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
18
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
19
19
|
|
|
20
20
|
import { ChatContainer } from './ChatContainer';
|
|
21
21
|
import { createChatThread } from './testing';
|
|
@@ -69,8 +69,8 @@ export default {
|
|
|
69
69
|
component: Thread,
|
|
70
70
|
// TODO(wittjosiah): Register schemas.
|
|
71
71
|
render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType]} />,
|
|
72
|
-
decorators: [withTheme],
|
|
73
|
-
parameters: { translations
|
|
72
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
73
|
+
parameters: { translations },
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
export const Default = {};
|
|
@@ -7,7 +7,7 @@ import React, { useLayoutEffect, 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 { ScrollArea, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
13
13
|
import { PlankHeading, plankHeadingIconProps } from '@dxos/react-ui-deck';
|
|
@@ -39,7 +39,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
39
39
|
const identity = useIdentity()!;
|
|
40
40
|
const space = getSpace(thread);
|
|
41
41
|
const members = useMembers(space?.key);
|
|
42
|
-
const activity = useStatus(space, thread
|
|
42
|
+
const activity = useStatus(space, fullyQualifiedId(thread));
|
|
43
43
|
const { t } = useTranslation(THREAD_PLUGIN);
|
|
44
44
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
45
45
|
const [_count, _setCount] = useState(0);
|
|
@@ -48,7 +48,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
48
48
|
const threadScrollRef = useRef<HTMLDivElement | null>(null);
|
|
49
49
|
const { themeMode } = useThemeContext();
|
|
50
50
|
|
|
51
|
-
const textboxMetadata = getMessageMetadata(thread
|
|
51
|
+
const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
|
|
52
52
|
const messageRef = useRef('');
|
|
53
53
|
const extensions = useMemo(
|
|
54
54
|
() => [
|
|
@@ -103,7 +103,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
|
|
|
103
103
|
return (
|
|
104
104
|
<Thread
|
|
105
105
|
current={current}
|
|
106
|
-
id={thread
|
|
106
|
+
id={fullyQualifiedId(thread)}
|
|
107
107
|
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
108
|
>
|
|
109
109
|
<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,12 +8,10 @@ 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 {
|
|
12
|
-
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
11
|
+
import { Filter, fullyQualifiedId, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
13
12
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
14
|
-
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
15
|
-
import {
|
|
16
|
-
import { withFullscreen, withTheme } from '@dxos/storybook-utils';
|
|
13
|
+
import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
|
|
14
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
17
15
|
|
|
18
16
|
import { CommentsContainer } from './CommentsContainer';
|
|
19
17
|
import { createCommentThread } from './testing';
|
|
@@ -21,7 +19,7 @@ import translations from '../translations';
|
|
|
21
19
|
|
|
22
20
|
faker.seed(1);
|
|
23
21
|
|
|
24
|
-
const Story = ({ spaceKey }:
|
|
22
|
+
const Story = ({ spaceKey }: ClientRepeatedComponentProps) => {
|
|
25
23
|
const identity = useIdentity();
|
|
26
24
|
const space = useSpace(spaceKey);
|
|
27
25
|
const threads = useQuery(space, Filter.schema(ThreadType));
|
|
@@ -32,7 +30,7 @@ const Story = ({ spaceKey }: { spaceKey: PublicKey }) => {
|
|
|
32
30
|
const t = setTimeout(async () => {
|
|
33
31
|
space.db.add(createCommentThread(identity));
|
|
34
32
|
const thread = space.db.add(createCommentThread(identity));
|
|
35
|
-
setDetached([thread
|
|
33
|
+
setDetached([fullyQualifiedId(thread)]);
|
|
36
34
|
});
|
|
37
35
|
|
|
38
36
|
return () => clearTimeout(t);
|
|
@@ -43,15 +41,12 @@ const Story = ({ spaceKey }: { spaceKey: PublicKey }) => {
|
|
|
43
41
|
return null;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
// TODO(wittjosiah): Include Tooltip.Provider in `withTheme` decorator?
|
|
47
44
|
return (
|
|
48
|
-
<
|
|
49
|
-
<div className='flex
|
|
50
|
-
<
|
|
51
|
-
<CommentsContainer threads={threads} detached={detached} onThreadDelete={console.log} />
|
|
52
|
-
</div>
|
|
45
|
+
<div className='flex justify-center overflow-y-auto bg-white dark:bg-black'>
|
|
46
|
+
<div className='flex flex-col w-[400px]'>
|
|
47
|
+
<CommentsContainer threads={threads} detached={detached} onThreadDelete={console.log} />
|
|
53
48
|
</div>
|
|
54
|
-
</
|
|
49
|
+
</div>
|
|
55
50
|
);
|
|
56
51
|
};
|
|
57
52
|
|
|
@@ -59,7 +54,7 @@ export default {
|
|
|
59
54
|
title: 'plugin-thread/Comments',
|
|
60
55
|
// TODO(wittjosiah): Register schemas.
|
|
61
56
|
render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType, MessageType]} />,
|
|
62
|
-
decorators: [withTheme,
|
|
57
|
+
decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
|
|
63
58
|
parameters: { translations },
|
|
64
59
|
};
|
|
65
60
|
|
|
@@ -6,6 +6,7 @@ import { ChatText, Quotes } 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 { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
9
10
|
import { useTranslation, Trans } from '@dxos/react-ui';
|
|
10
11
|
import { PlankHeading, plankHeadingIconProps } from '@dxos/react-ui-deck';
|
|
11
12
|
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
@@ -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)} />
|