@dxos/plugin-thread 0.6.11 → 0.6.12-main.568932b
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 +233 -234
- 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 +313 -316
- 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 +1102 -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 +1 -1
- package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
- package/dist/types/src/components/CommentsContainer.d.ts +1 -5
- package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
- package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
- package/dist/types/src/components/ThreadComplementary.d.ts +10 -0
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -0
- package/dist/types/src/components/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 +40 -37
- package/src/ThreadPlugin.tsx +70 -182
- package/src/components/ChatContainer.tsx +7 -8
- package/src/components/CommentContainer.tsx +18 -19
- package/src/components/Comments.stories.tsx +2 -2
- package/src/components/CommentsContainer.tsx +18 -31
- package/src/components/MessageContainer.tsx +2 -1
- package/src/components/ThreadComplementary.tsx +113 -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,57 +15,40 @@ 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. */
|
|
67
50
|
staging: Record<string, ThreadType[]>;
|
|
68
51
|
current?: string | undefined;
|
|
69
|
-
focus?: boolean;
|
|
70
52
|
};
|
|
71
53
|
|
|
72
54
|
type SubjectId = string;
|
|
@@ -74,8 +56,6 @@ const initialViewState = { showResolvedThreads: false };
|
|
|
74
56
|
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
57
|
|
|
76
58
|
// 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
59
|
|
|
80
60
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
81
61
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
@@ -90,10 +70,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
90
70
|
return viewStore[subjectId];
|
|
91
71
|
};
|
|
92
72
|
|
|
93
|
-
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
94
73
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
95
74
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
-
let dispatch: IntentDispatcher | undefined;
|
|
97
75
|
|
|
98
76
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
99
77
|
|
|
@@ -102,45 +80,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
102
80
|
ready: async (plugins) => {
|
|
103
81
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
104
82
|
|
|
105
|
-
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
106
83
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
107
84
|
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
85
|
},
|
|
145
86
|
unload: async () => {
|
|
146
87
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -151,8 +92,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
151
92
|
records: {
|
|
152
93
|
[ChannelType.typename]: {
|
|
153
94
|
placeholder: ['channel title placeholder', { ns: THREAD_PLUGIN }],
|
|
154
|
-
icon:
|
|
155
|
-
iconSymbol: 'ph--chat--regular',
|
|
95
|
+
icon: 'ph--chat--regular',
|
|
156
96
|
// TODO(wittjosiah): Move out of metadata.
|
|
157
97
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
158
98
|
},
|
|
@@ -198,31 +138,42 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
198
138
|
return;
|
|
199
139
|
}
|
|
200
140
|
|
|
201
|
-
// TODO(Zan): Find util (or make one)
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] =
|
|
141
|
+
// TODO(Zan): Find util (or make one).
|
|
142
|
+
const subjectId = id.split('~').at(0);
|
|
143
|
+
const [spaceId, objectId] = subjectId?.split(':') ?? [];
|
|
204
144
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
const
|
|
206
|
-
|
|
145
|
+
const object = toSignal(
|
|
146
|
+
(onChange) => {
|
|
147
|
+
const timeout = setTimeout(async () => {
|
|
148
|
+
await space?.db.loadObjectById(objectId);
|
|
149
|
+
onChange();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return () => clearTimeout(timeout);
|
|
153
|
+
},
|
|
154
|
+
() => space?.db.getObjectById(objectId),
|
|
155
|
+
subjectId,
|
|
156
|
+
);
|
|
157
|
+
if (!object || !subjectId) {
|
|
207
158
|
return;
|
|
208
159
|
}
|
|
209
160
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
161
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
162
|
+
const label = meta.label?.(object) ||
|
|
163
|
+
object.name ||
|
|
164
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
165
|
|
|
216
|
-
const viewState = getViewState(
|
|
166
|
+
const viewState = getViewState(subjectId);
|
|
217
167
|
|
|
218
168
|
return {
|
|
219
169
|
id,
|
|
220
170
|
type: 'orphan-comments-for-subject',
|
|
221
|
-
data:
|
|
171
|
+
data: null,
|
|
222
172
|
properties: {
|
|
223
|
-
icon:
|
|
173
|
+
icon: 'ph--chat-text--regular',
|
|
224
174
|
label,
|
|
225
175
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
176
|
+
object,
|
|
226
177
|
},
|
|
227
178
|
};
|
|
228
179
|
},
|
|
@@ -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,16 @@ 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
|
-
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>
|
|
303
|
+
<ThreadComplementary
|
|
304
|
+
role={role}
|
|
305
|
+
subject={data.subject}
|
|
306
|
+
stagedThreads={state.staging[fullyQualifiedId(data.subject)]}
|
|
307
|
+
current={state.current}
|
|
308
|
+
showResolvedThreads={showResolvedThreads}
|
|
309
|
+
/>
|
|
430
310
|
);
|
|
431
311
|
}
|
|
432
312
|
}
|
|
@@ -442,6 +322,17 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
322
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
323
|
const { cursor, name, subject } = intent.data;
|
|
444
324
|
|
|
325
|
+
// Seed the threads array if it does not exist.
|
|
326
|
+
if (subject?.threads === undefined) {
|
|
327
|
+
try {
|
|
328
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
329
|
+
subject.threads = [];
|
|
330
|
+
} catch (e) {
|
|
331
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
445
336
|
const subjectId = fullyQualifiedId(subject);
|
|
446
337
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
338
|
const stagingArea = state.staging[subjectId];
|
|
@@ -455,17 +346,9 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
455
346
|
data: thread,
|
|
456
347
|
intents: [
|
|
457
348
|
[
|
|
458
|
-
{
|
|
459
|
-
action: NavigationAction.OPEN,
|
|
460
|
-
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
349
|
{
|
|
467
350
|
action: ThreadAction.SELECT,
|
|
468
|
-
data: { current: thread
|
|
351
|
+
data: { current: fullyQualifiedId(thread) },
|
|
469
352
|
},
|
|
470
353
|
{
|
|
471
354
|
action: LayoutAction.SET_LAYOUT,
|
|
@@ -485,7 +368,6 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
485
368
|
}
|
|
486
369
|
|
|
487
370
|
case ThreadAction.SELECT: {
|
|
488
|
-
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
489
371
|
state.current = intent.data?.current;
|
|
490
372
|
return { data: true };
|
|
491
373
|
}
|
|
@@ -519,7 +401,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
401
|
|
|
520
402
|
case ThreadAction.DELETE: {
|
|
521
403
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
404
|
+
if (
|
|
405
|
+
!(thread instanceof ThreadType) ||
|
|
406
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
407
|
+
) {
|
|
523
408
|
return;
|
|
524
409
|
}
|
|
525
410
|
|
|
@@ -531,7 +416,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
531
416
|
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
532
417
|
if (index !== -1) {
|
|
533
418
|
stagingArea.splice(index, 1);
|
|
534
|
-
return;
|
|
419
|
+
return { data: true };
|
|
535
420
|
}
|
|
536
421
|
}
|
|
537
422
|
|
|
@@ -541,7 +426,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
426
|
}
|
|
542
427
|
|
|
543
428
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
429
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
430
|
const cursor = subject.threads[index]?.anchor;
|
|
546
431
|
if (index !== -1) {
|
|
547
432
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +472,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
472
|
const { thread, subject } = intent.data ?? {};
|
|
588
473
|
if (
|
|
589
474
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
475
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
476
|
) {
|
|
592
477
|
return;
|
|
593
478
|
}
|
|
@@ -627,7 +512,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
512
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
513
|
const space = getSpace(subject);
|
|
629
514
|
|
|
630
|
-
if (
|
|
515
|
+
if (
|
|
516
|
+
!(thread instanceof ThreadType) ||
|
|
517
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
518
|
+
) {
|
|
631
519
|
return;
|
|
632
520
|
}
|
|
633
521
|
|
|
@@ -658,7 +546,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
546
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
547
|
data: {
|
|
660
548
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
549
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
550
|
},
|
|
663
551
|
},
|
|
664
552
|
],
|
|
@@ -682,7 +570,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
570
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
571
|
data: {
|
|
684
572
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
573
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
574
|
},
|
|
687
575
|
},
|
|
688
576
|
],
|
|
@@ -728,15 +616,15 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
728
616
|
},
|
|
729
617
|
}),
|
|
730
618
|
createExternalCommentSync(
|
|
731
|
-
doc
|
|
619
|
+
fullyQualifiedId(doc),
|
|
732
620
|
(sink) => effect(() => sink()),
|
|
733
621
|
() =>
|
|
734
622
|
threads.value
|
|
735
623
|
.filter((thread) => thread?.anchor)
|
|
736
|
-
.map((thread) => ({ id: thread
|
|
624
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
737
625
|
),
|
|
738
626
|
comments({
|
|
739
|
-
id: doc
|
|
627
|
+
id: fullyQualifiedId(doc),
|
|
740
628
|
onCreate: ({ cursor }) => {
|
|
741
629
|
const [start, end] = cursor.split(':');
|
|
742
630
|
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
@@ -754,7 +642,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
754
642
|
// If the thread is in the staging area, remove it.
|
|
755
643
|
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
756
644
|
if (stagingArea) {
|
|
757
|
-
const index = stagingArea.findIndex((thread) => thread
|
|
645
|
+
const index = stagingArea.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
758
646
|
if (index !== -1) {
|
|
759
647
|
stagingArea.splice(index, 1);
|
|
760
648
|
}
|
|
@@ -767,7 +655,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
767
655
|
},
|
|
768
656
|
onUpdate: ({ id, cursor }) => {
|
|
769
657
|
const thread =
|
|
770
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread
|
|
658
|
+
state.staging[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
771
659
|
doc.threads.find((thread) => thread?.id === id);
|
|
772
660
|
|
|
773
661
|
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'>
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { CheckCircle, X } from '@phosphor-icons/react';
|
|
6
|
-
import React, { useCallback,
|
|
6
|
+
import React, { useCallback, 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';
|
|
@@ -93,7 +93,6 @@ export const CommentContainer = ({
|
|
|
93
93
|
detached,
|
|
94
94
|
context,
|
|
95
95
|
current,
|
|
96
|
-
autoFocusTextbox,
|
|
97
96
|
onAttend,
|
|
98
97
|
onThreadDelete,
|
|
99
98
|
onMessageDelete,
|
|
@@ -103,13 +102,12 @@ export const CommentContainer = ({
|
|
|
103
102
|
const identity = useIdentity()!;
|
|
104
103
|
const space = getSpace(thread);
|
|
105
104
|
const members = useMembers(space?.key);
|
|
106
|
-
const activity = useStatus(space, thread
|
|
105
|
+
const activity = useStatus(space, fullyQualifiedId(thread));
|
|
107
106
|
const { t } = useTranslation(THREAD_PLUGIN);
|
|
108
107
|
const threadScrollRef = useRef<HTMLDivElement | null>(null);
|
|
109
|
-
const [autoFocus, setAutoFocus] = useState(!!autoFocusTextbox);
|
|
110
108
|
const { themeMode } = useThemeContext();
|
|
111
109
|
|
|
112
|
-
const textboxMetadata = getMessageMetadata(thread
|
|
110
|
+
const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
|
|
113
111
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
114
112
|
const [_count, _setCount] = useState(0);
|
|
115
113
|
const rerenderEditor = () => _setCount((count) => count + 1);
|
|
@@ -124,13 +122,6 @@ export const CommentContainer = ({
|
|
|
124
122
|
[_count],
|
|
125
123
|
);
|
|
126
124
|
|
|
127
|
-
// TODO(thure): Because of the way the `autoFocus` property is handled by TextEditor,
|
|
128
|
-
// this is the least-bad way of moving focus at the right time, though it is an anti-pattern.
|
|
129
|
-
// Refactor to behave more like <input/>’s `autoFocus` or `autofocus` (yes, they’re different).
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
setAutoFocus(!!autoFocusTextbox);
|
|
132
|
-
}, [autoFocusTextbox]);
|
|
133
|
-
|
|
134
125
|
// TODO(thure): Factor out.
|
|
135
126
|
const scrollToEnd = (behavior: ScrollBehavior) =>
|
|
136
127
|
setTimeout(() => threadScrollRef.current?.scrollIntoView({ behavior, block: 'end' }), 10);
|
|
@@ -151,7 +142,6 @@ export const CommentContainer = ({
|
|
|
151
142
|
onComment?.(thread);
|
|
152
143
|
|
|
153
144
|
messageRef.current = '';
|
|
154
|
-
setAutoFocus(true);
|
|
155
145
|
scrollToEnd('instant');
|
|
156
146
|
rerenderEditor();
|
|
157
147
|
|
|
@@ -159,10 +149,8 @@ export const CommentContainer = ({
|
|
|
159
149
|
return true;
|
|
160
150
|
}, [thread, identity]);
|
|
161
151
|
|
|
162
|
-
const handleDeleteMessage = (id: string) => onMessageDelete?.(id);
|
|
163
|
-
|
|
164
152
|
return (
|
|
165
|
-
<Thread onClickCapture={onAttend} onFocusCapture={onAttend} current={current} id={thread
|
|
153
|
+
<Thread onClickCapture={onAttend} onFocusCapture={onAttend} current={current} id={fullyQualifiedId(thread)}>
|
|
166
154
|
<div
|
|
167
155
|
role='none'
|
|
168
156
|
className={mx(
|
|
@@ -195,9 +183,20 @@ export const CommentContainer = ({
|
|
|
195
183
|
</div>
|
|
196
184
|
</div>
|
|
197
185
|
{thread.messages.filter(nonNullable).map((message) => (
|
|
198
|
-
<MessageContainer
|
|
186
|
+
<MessageContainer
|
|
187
|
+
key={message.id}
|
|
188
|
+
message={message}
|
|
189
|
+
members={members}
|
|
190
|
+
onDelete={(id: string) => onMessageDelete?.(id)}
|
|
191
|
+
/>
|
|
199
192
|
))}
|
|
200
|
-
|
|
193
|
+
{/*
|
|
194
|
+
TODO(wittjosiah): Can't autofocus this generally.
|
|
195
|
+
There can be multiple threads with inputs and they can't all be focused.
|
|
196
|
+
Also, it steals focus from documents when first rendered.
|
|
197
|
+
Need to find a way to autofocus in one scenario only: when a new thread is created.
|
|
198
|
+
*/}
|
|
199
|
+
<MessageTextbox extensions={extensions} onSend={handleCreate} {...textboxMetadata} />
|
|
201
200
|
<ThreadFooter activity={activity}>{t('activity message')}</ThreadFooter>
|
|
202
201
|
{/* NOTE(thure): This can’t also be the `overflow-anchor` because `ScrollArea` injects an interceding node that contains this necessary ref’d element. */}
|
|
203
202
|
<div role='none' className='bs-px -mbs-px' ref={threadScrollRef} />
|
|
@@ -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);
|