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