@dxos/plugin-thread 0.6.11 → 0.6.12-main.5a87ad5
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 +68 -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,39 @@ 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
|
+
subjectId,
|
|
157
|
+
);
|
|
158
|
+
if (!object || !subjectId) {
|
|
207
159
|
return;
|
|
208
160
|
}
|
|
209
161
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
162
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
163
|
+
const label = meta.label?.(object) ||
|
|
164
|
+
object.name ||
|
|
165
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
166
|
|
|
216
|
-
const viewState = getViewState(
|
|
167
|
+
const viewState = getViewState(subjectId);
|
|
217
168
|
|
|
218
169
|
return {
|
|
219
170
|
id,
|
|
220
171
|
type: 'orphan-comments-for-subject',
|
|
221
|
-
data:
|
|
172
|
+
data: object,
|
|
222
173
|
properties: {
|
|
223
|
-
icon: meta.
|
|
174
|
+
icon: meta.icon,
|
|
224
175
|
label,
|
|
225
176
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
226
177
|
},
|
|
@@ -256,6 +207,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
207
|
menuItemType: 'toggle',
|
|
257
208
|
isChecked: viewState.showResolvedThreads,
|
|
258
209
|
testId: 'threadPlugin.toggleShowResolved',
|
|
210
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
211
|
},
|
|
260
212
|
},
|
|
261
213
|
];
|
|
@@ -287,8 +239,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
239
|
},
|
|
288
240
|
properties: {
|
|
289
241
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
242
|
+
icon: 'ph--chat--regular',
|
|
292
243
|
testId: 'threadPlugin.createObject',
|
|
293
244
|
},
|
|
294
245
|
},
|
|
@@ -314,8 +265,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
314
265
|
case 'article':
|
|
315
266
|
case 'complementary': {
|
|
316
267
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
268
|
|
|
320
269
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
270
|
const channel = data.object;
|
|
@@ -348,85 +297,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
348
297
|
'threads' in data.subject &&
|
|
349
298
|
Array.isArray(data.subject.threads)
|
|
350
299
|
) {
|
|
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);
|
|
300
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
301
|
|
|
371
302
|
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>
|
|
303
|
+
<ThreadComplementary
|
|
304
|
+
role={role}
|
|
305
|
+
subject={data.subject}
|
|
306
|
+
stagedThreads={state.staging[fullyQualifiedId(data.subject)]}
|
|
307
|
+
current={state.current}
|
|
308
|
+
focus
|
|
309
|
+
showResolvedThreads={showResolvedThreads}
|
|
310
|
+
/>
|
|
430
311
|
);
|
|
431
312
|
}
|
|
432
313
|
}
|
|
@@ -442,6 +323,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
323
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
324
|
const { cursor, name, subject } = intent.data;
|
|
444
325
|
|
|
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
|
+
|
|
445
337
|
const subjectId = fullyQualifiedId(subject);
|
|
446
338
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
339
|
const stagingArea = state.staging[subjectId];
|
|
@@ -455,17 +347,9 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
455
347
|
data: thread,
|
|
456
348
|
intents: [
|
|
457
349
|
[
|
|
458
|
-
{
|
|
459
|
-
action: NavigationAction.OPEN,
|
|
460
|
-
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
350
|
{
|
|
467
351
|
action: ThreadAction.SELECT,
|
|
468
|
-
data: { current: thread
|
|
352
|
+
data: { current: fullyQualifiedId(thread), focus: true },
|
|
469
353
|
},
|
|
470
354
|
{
|
|
471
355
|
action: LayoutAction.SET_LAYOUT,
|
|
@@ -519,7 +403,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
403
|
|
|
520
404
|
case ThreadAction.DELETE: {
|
|
521
405
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
406
|
+
if (
|
|
407
|
+
!(thread instanceof ThreadType) ||
|
|
408
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
409
|
+
) {
|
|
523
410
|
return;
|
|
524
411
|
}
|
|
525
412
|
|
|
@@ -531,7 +418,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
531
418
|
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
532
419
|
if (index !== -1) {
|
|
533
420
|
stagingArea.splice(index, 1);
|
|
534
|
-
return;
|
|
421
|
+
return { data: true };
|
|
535
422
|
}
|
|
536
423
|
}
|
|
537
424
|
|
|
@@ -541,7 +428,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
428
|
}
|
|
542
429
|
|
|
543
430
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
431
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
432
|
const cursor = subject.threads[index]?.anchor;
|
|
546
433
|
if (index !== -1) {
|
|
547
434
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +474,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
474
|
const { thread, subject } = intent.data ?? {};
|
|
588
475
|
if (
|
|
589
476
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
477
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
478
|
) {
|
|
592
479
|
return;
|
|
593
480
|
}
|
|
@@ -627,7 +514,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
514
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
515
|
const space = getSpace(subject);
|
|
629
516
|
|
|
630
|
-
if (
|
|
517
|
+
if (
|
|
518
|
+
!(thread instanceof ThreadType) ||
|
|
519
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
520
|
+
) {
|
|
631
521
|
return;
|
|
632
522
|
}
|
|
633
523
|
|
|
@@ -658,7 +548,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
548
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
549
|
data: {
|
|
660
550
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
551
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
552
|
},
|
|
663
553
|
},
|
|
664
554
|
],
|
|
@@ -682,7 +572,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
572
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
573
|
data: {
|
|
684
574
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
575
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
576
|
},
|
|
687
577
|
},
|
|
688
578
|
],
|
|
@@ -733,7 +623,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
733
623
|
() =>
|
|
734
624
|
threads.value
|
|
735
625
|
.filter((thread) => thread?.anchor)
|
|
736
|
-
.map((thread) => ({ id: thread
|
|
626
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
737
627
|
),
|
|
738
628
|
comments({
|
|
739
629
|
id: doc.id,
|
|
@@ -754,7 +644,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
754
644
|
// If the thread is in the staging area, remove it.
|
|
755
645
|
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
756
646
|
if (stagingArea) {
|
|
757
|
-
const index = stagingArea.findIndex((thread) => thread
|
|
647
|
+
const index = stagingArea.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
758
648
|
if (index !== -1) {
|
|
759
649
|
stagingArea.splice(index, 1);
|
|
760
650
|
}
|
|
@@ -767,7 +657,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
767
657
|
},
|
|
768
658
|
onUpdate: ({ id, cursor }) => {
|
|
769
659
|
const thread =
|
|
770
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread
|
|
660
|
+
state.staging[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
771
661
|
doc.threads.find((thread) => thread?.id === id);
|
|
772
662
|
|
|
773
663
|
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)} />
|