@dxos/plugin-thread 0.6.13 → 0.6.14-main.1366248
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-6RFOFTU2.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-45OFO3G6.mjs} +3 -3
- package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-OTCR7JTS.mjs} +17 -17
- package/dist/lib/browser/chunk-OTCR7JTS.mjs.map +7 -0
- package/dist/lib/browser/chunk-YLYCGZ62.mjs +17 -0
- package/dist/lib/browser/chunk-YLYCGZ62.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-YQUONHZJ.mjs} +2 -2
- package/dist/lib/browser/chunk-YQUONHZJ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +452 -388
- 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-IUK2EECC.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-CXIQPH4H.cjs} +7 -7
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-LGA276VA.cjs} +6 -6
- package/dist/lib/node/chunk-LGA276VA.cjs.map +7 -0
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-XOW5SL5M.cjs} +66 -66
- package/dist/lib/node/chunk-XOW5SL5M.cjs.map +7 -0
- package/dist/lib/node/{chunk-BWBONZGU.cjs → chunk-YWFYN3OY.cjs} +8 -22
- package/dist/lib/node/chunk-YWFYN3OY.cjs.map +7 -0
- package/dist/lib/node/index.cjs +501 -441
- 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-OA3T4XFJ.mjs +19 -0
- package/dist/lib/node-esm/ThreadArticle-OA3T4XFJ.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-VXFO6JL6.mjs +27 -0
- package/dist/lib/node-esm/ThreadMain-VXFO6JL6.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-3CZBPOEH.mjs +21 -0
- package/dist/lib/node-esm/chunk-3CZBPOEH.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-5R5QKQY4.mjs +411 -0
- package/dist/lib/node-esm/chunk-5R5QKQY4.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SSEJYCYQ.mjs +19 -0
- package/dist/lib/node-esm/chunk-SSEJYCYQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1167 -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/Chat.stories.d.ts +3 -42
- package/dist/types/src/components/Chat.stories.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/Comments.stories.d.ts +3 -41
- package/dist/types/src/components/Comments.stories.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/index.d.ts +6 -6
- package/dist/types/src/components/index.d.ts.map +1 -1
- 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/components/types.d.ts +2 -2
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/threads.d.ts +9 -0
- package/dist/types/src/extensions/threads.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +4 -9
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +8 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +42 -46
- package/src/ThreadPlugin.tsx +158 -309
- package/src/components/Chat.stories.tsx +7 -4
- package/src/components/ChatContainer.tsx +7 -8
- package/src/components/CommentContainer.tsx +25 -23
- package/src/components/Comments.stories.tsx +9 -6
- package/src/components/CommentsContainer.tsx +18 -31
- package/src/components/MessageContainer.tsx +15 -24
- package/src/components/ThreadComplementary.tsx +113 -0
- package/src/components/ThreadSettings.tsx +3 -3
- package/src/components/index.ts +6 -6
- package/src/components/types.ts +2 -2
- package/src/extensions/index.ts +5 -0
- package/src/extensions/threads.ts +117 -0
- package/src/meta.ts +16 -0
- package/src/translations.ts +1 -0
- package/src/types.ts +11 -4
- package/dist/lib/browser/chunk-5IBJO53M.mjs +0 -21
- package/dist/lib/browser/chunk-5IBJO53M.mjs.map +0 -7
- package/dist/lib/browser/chunk-GKPZHUT3.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/lib/node/chunk-D5GOVXHS.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/meta.tsx +0 -20
- package/src/types.test.ts +0 -13
- /package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs.map → ThreadArticle-6RFOFTU2.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-45OFO3G6.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-IUK2EECC.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-CXIQPH4H.cjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -2,85 +2,57 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Chat, type IconProps } from '@phosphor-icons/react';
|
|
6
|
-
import { computed, effect } from '@preact/signals-core';
|
|
7
5
|
import React from 'react';
|
|
8
6
|
|
|
9
7
|
import {
|
|
10
8
|
type IntentPluginProvides,
|
|
11
|
-
LayoutAction,
|
|
12
|
-
NavigationAction,
|
|
13
9
|
type LocationProvides,
|
|
10
|
+
NavigationAction,
|
|
14
11
|
type Plugin,
|
|
15
12
|
type PluginDefinition,
|
|
13
|
+
isLayoutParts,
|
|
16
14
|
parseIntentPlugin,
|
|
17
15
|
parseNavigationPlugin,
|
|
18
|
-
resolvePlugin,
|
|
19
|
-
parseGraphPlugin,
|
|
20
|
-
SLUG_PATH_SEPARATOR,
|
|
21
|
-
SLUG_COLLECTION_INDICATOR,
|
|
22
|
-
isLayoutParts,
|
|
23
16
|
parseMetadataResolverPlugin,
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
resolvePlugin,
|
|
18
|
+
LayoutAction,
|
|
26
19
|
} from '@dxos/app-framework';
|
|
27
20
|
import { type UnsubscribeCallback } from '@dxos/async';
|
|
28
|
-
import { type EchoReactiveObject, getTypename } from '@dxos/echo-schema';
|
|
29
|
-
import { create } from '@dxos/echo-schema';
|
|
30
21
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
31
|
-
import {
|
|
22
|
+
import { log } from '@dxos/log';
|
|
32
23
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
33
|
-
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
34
|
-
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
24
|
+
import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxos/plugin-graph';
|
|
35
25
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
36
26
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
37
27
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
getTextInRange,
|
|
41
|
-
Filter,
|
|
42
|
-
createDocAccessor,
|
|
43
|
-
fullyQualifiedId,
|
|
44
|
-
loadObjectReferences,
|
|
45
|
-
} from '@dxos/react-client/echo';
|
|
46
|
-
import { ScrollArea } from '@dxos/react-ui';
|
|
47
|
-
import { createAttendableAttributes } from '@dxos/react-ui-attention';
|
|
48
|
-
import { comments, createExternalCommentSync, listener } from '@dxos/react-ui-editor';
|
|
28
|
+
import { create, type EchoReactiveObject, getTypename } from '@dxos/react-client/echo';
|
|
29
|
+
import { getSpace, fullyQualifiedId, loadObjectReferences, parseId } from '@dxos/react-client/echo';
|
|
49
30
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
50
|
-
import { nonNullable } from '@dxos/util';
|
|
51
31
|
|
|
52
32
|
import {
|
|
53
|
-
ThreadMain,
|
|
54
|
-
ThreadSettings,
|
|
55
|
-
CommentsContainer,
|
|
56
|
-
CommentsHeading,
|
|
57
33
|
ChatContainer,
|
|
58
34
|
ChatHeading,
|
|
59
35
|
ThreadArticle,
|
|
36
|
+
ThreadComplementary,
|
|
37
|
+
ThreadMain,
|
|
38
|
+
ThreadSettings,
|
|
60
39
|
} from './components';
|
|
40
|
+
import { threads } from './extensions';
|
|
61
41
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
62
42
|
import translations from './translations';
|
|
63
|
-
import { ThreadAction, type
|
|
64
|
-
|
|
65
|
-
type ThreadState = {
|
|
66
|
-
/** An in-memory staging area for threads that are being drafted. */
|
|
67
|
-
staging: Record<string, ThreadType[]>;
|
|
68
|
-
current?: string | undefined;
|
|
69
|
-
focus?: boolean;
|
|
70
|
-
};
|
|
43
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps, type ThreadState } from './types';
|
|
71
44
|
|
|
72
45
|
type SubjectId = string;
|
|
46
|
+
|
|
73
47
|
const initialViewState = { showResolvedThreads: false };
|
|
74
|
-
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
78
|
-
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
49
|
+
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
79
50
|
|
|
80
51
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
52
|
+
// NOTE(burdon): Review/discuss CursorConverter semantics.
|
|
81
53
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
82
54
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
83
|
-
const state = create<ThreadState>({
|
|
55
|
+
const state = create<ThreadState>({ drafts: {} });
|
|
84
56
|
|
|
85
57
|
const viewStore = create<ViewStore>({});
|
|
86
58
|
const getViewState = (subjectId: string) => {
|
|
@@ -90,10 +62,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
90
62
|
return viewStore[subjectId];
|
|
91
63
|
};
|
|
92
64
|
|
|
93
|
-
let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
|
|
94
65
|
let navigationPlugin: Plugin<LocationProvides> | undefined;
|
|
95
66
|
let intentPlugin: Plugin<IntentPluginProvides> | undefined;
|
|
96
|
-
let dispatch: IntentDispatcher | undefined;
|
|
97
67
|
|
|
98
68
|
const unsubscribeCallbacks = [] as UnsubscribeCallback[];
|
|
99
69
|
|
|
@@ -102,45 +72,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
102
72
|
ready: async (plugins) => {
|
|
103
73
|
settings.prop({ key: 'standalone', type: LocalStorageStore.bool({ allowUndefined: true }) });
|
|
104
74
|
|
|
105
|
-
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
106
75
|
navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
|
|
107
76
|
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
77
|
},
|
|
145
78
|
unload: async () => {
|
|
146
79
|
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe());
|
|
@@ -150,9 +83,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
150
83
|
metadata: {
|
|
151
84
|
records: {
|
|
152
85
|
[ChannelType.typename]: {
|
|
153
|
-
placeholder: ['channel
|
|
154
|
-
icon:
|
|
155
|
-
iconSymbol: 'ph--chat--regular',
|
|
86
|
+
placeholder: ['channel name placeholder', { ns: THREAD_PLUGIN }],
|
|
87
|
+
icon: 'ph--chat--regular',
|
|
156
88
|
// TODO(wittjosiah): Move out of metadata.
|
|
157
89
|
loadReferences: (channel: ChannelType) => loadObjectReferences(channel, (channel) => channel.threads),
|
|
158
90
|
},
|
|
@@ -180,49 +112,88 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
180
112
|
},
|
|
181
113
|
translations: [...translations, ...threadTranslations],
|
|
182
114
|
echo: { schema: [ChannelType, ThreadType, MessageType] },
|
|
115
|
+
complementary: {
|
|
116
|
+
panels: [
|
|
117
|
+
{
|
|
118
|
+
id: 'comments',
|
|
119
|
+
label: ['open comments panel label', { ns: THREAD_PLUGIN }],
|
|
120
|
+
icon: 'ph--chat-text--regular',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
183
124
|
graph: {
|
|
184
125
|
builder: (plugins) => {
|
|
185
126
|
const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
|
|
186
127
|
const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
|
|
187
128
|
const metadataResolver = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
|
|
188
|
-
|
|
189
129
|
if (!client || !dispatch || !metadataResolver) {
|
|
190
130
|
return [];
|
|
191
131
|
}
|
|
192
132
|
|
|
133
|
+
const type = 'orphan-comments-for-subject';
|
|
134
|
+
const icon = 'ph--chat-text--regular';
|
|
135
|
+
|
|
193
136
|
return [
|
|
194
137
|
createExtension({
|
|
195
138
|
id: `${THREAD_PLUGIN}/comments-for-subject`,
|
|
196
139
|
resolver: ({ id }) => {
|
|
140
|
+
// TODO(Zan): Find util (or make one).
|
|
197
141
|
if (!id.endsWith('~comments')) {
|
|
198
142
|
return;
|
|
199
143
|
}
|
|
200
144
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const [spaceId, objectId] = docId?.split(':') ?? [];
|
|
145
|
+
const [subjectId] = id.split('~');
|
|
146
|
+
const { spaceId, objectId } = parseId(subjectId);
|
|
204
147
|
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
205
|
-
|
|
206
|
-
|
|
148
|
+
if (!objectId) {
|
|
149
|
+
// TODO(wittjosiah): Support comments for arbitrary subjects.
|
|
150
|
+
// This is to ensure that the comments panel is not stuck on an old object.
|
|
151
|
+
return {
|
|
152
|
+
id,
|
|
153
|
+
type,
|
|
154
|
+
data: null,
|
|
155
|
+
properties: {
|
|
156
|
+
icon,
|
|
157
|
+
label: ['unnamed object threads label', { ns: THREAD_PLUGIN }],
|
|
158
|
+
showResolvedThreads: false,
|
|
159
|
+
object: null,
|
|
160
|
+
space,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const object = toSignal(
|
|
166
|
+
(onChange) => {
|
|
167
|
+
const timeout = setTimeout(async () => {
|
|
168
|
+
await space?.db.loadObjectById(objectId);
|
|
169
|
+
onChange();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return () => clearTimeout(timeout);
|
|
173
|
+
},
|
|
174
|
+
() => space?.db.getObjectById(objectId),
|
|
175
|
+
subjectId,
|
|
176
|
+
);
|
|
177
|
+
if (!object || !subjectId) {
|
|
207
178
|
return;
|
|
208
179
|
}
|
|
209
180
|
|
|
210
|
-
const
|
|
211
|
-
const label =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// const label = docMeta.label?.(doc) || doc.name || docMeta.placeholder;
|
|
181
|
+
const meta = metadataResolver(getTypename(object) ?? '');
|
|
182
|
+
const label = meta.label?.(object) ||
|
|
183
|
+
object.name ||
|
|
184
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
215
185
|
|
|
216
|
-
const viewState = getViewState(
|
|
186
|
+
const viewState = getViewState(subjectId);
|
|
217
187
|
|
|
218
188
|
return {
|
|
219
189
|
id,
|
|
220
|
-
type
|
|
221
|
-
data:
|
|
190
|
+
type,
|
|
191
|
+
data: null,
|
|
222
192
|
properties: {
|
|
223
|
-
icon
|
|
193
|
+
icon,
|
|
224
194
|
label,
|
|
225
195
|
showResolvedThreads: viewState.showResolvedThreads,
|
|
196
|
+
object,
|
|
226
197
|
},
|
|
227
198
|
};
|
|
228
199
|
},
|
|
@@ -256,6 +227,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
256
227
|
menuItemType: 'toggle',
|
|
257
228
|
isChecked: viewState.showResolvedThreads,
|
|
258
229
|
testId: 'threadPlugin.toggleShowResolved',
|
|
230
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
259
231
|
},
|
|
260
232
|
},
|
|
261
233
|
];
|
|
@@ -287,8 +259,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
287
259
|
},
|
|
288
260
|
properties: {
|
|
289
261
|
label: ['create channel label', { ns: THREAD_PLUGIN }],
|
|
290
|
-
icon:
|
|
291
|
-
iconSymbol: 'ph--chat--regular',
|
|
262
|
+
icon: 'ph--chat--regular',
|
|
292
263
|
testId: 'threadPlugin.createObject',
|
|
293
264
|
},
|
|
294
265
|
},
|
|
@@ -311,11 +282,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
311
282
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
312
283
|
}
|
|
313
284
|
|
|
314
|
-
case 'article':
|
|
315
|
-
case 'complementary': {
|
|
285
|
+
case 'article': {
|
|
316
286
|
const location = navigationPlugin?.provides.location;
|
|
317
|
-
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
318
|
-
const threadProvides = threadsIntegrators.map((p) => p.provides.thread);
|
|
319
287
|
|
|
320
288
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
321
289
|
const channel = data.object;
|
|
@@ -342,93 +310,31 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
342
310
|
);
|
|
343
311
|
}
|
|
344
312
|
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'complementary--comments': {
|
|
345
317
|
if (
|
|
346
318
|
data.subject &&
|
|
347
319
|
typeof data.subject === 'object' &&
|
|
348
320
|
'threads' in data.subject &&
|
|
349
|
-
Array.isArray(data.subject.threads)
|
|
321
|
+
Array.isArray(data.subject.threads) &&
|
|
322
|
+
!(data.subject instanceof ChannelType)
|
|
350
323
|
) {
|
|
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);
|
|
324
|
+
const { showResolvedThreads } = getViewState(fullyQualifiedId(data.subject));
|
|
370
325
|
|
|
371
326
|
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>
|
|
327
|
+
<ThreadComplementary
|
|
328
|
+
role={role}
|
|
329
|
+
subject={data.subject}
|
|
330
|
+
drafts={state.drafts[fullyQualifiedId(data.subject)]}
|
|
331
|
+
current={state.current}
|
|
332
|
+
showResolvedThreads={showResolvedThreads}
|
|
333
|
+
/>
|
|
430
334
|
);
|
|
431
335
|
}
|
|
336
|
+
|
|
337
|
+
break;
|
|
432
338
|
}
|
|
433
339
|
}
|
|
434
340
|
|
|
@@ -442,36 +348,48 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
442
348
|
if (intent.data && intent.data.cursor !== undefined) {
|
|
443
349
|
const { cursor, name, subject } = intent.data;
|
|
444
350
|
|
|
351
|
+
// Seed the threads array if it does not exist.
|
|
352
|
+
if (subject?.threads === undefined) {
|
|
353
|
+
try {
|
|
354
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
355
|
+
subject.threads = [];
|
|
356
|
+
} catch (err) {
|
|
357
|
+
log.error('Subject does not support threads array', subject?.typename);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
445
362
|
const subjectId = fullyQualifiedId(subject);
|
|
446
363
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
449
|
-
|
|
364
|
+
const draft = state.drafts[subjectId];
|
|
365
|
+
if (draft) {
|
|
366
|
+
draft.push(thread);
|
|
450
367
|
} else {
|
|
451
|
-
state.
|
|
368
|
+
state.drafts[subjectId] = [thread];
|
|
452
369
|
}
|
|
453
370
|
|
|
454
371
|
return {
|
|
455
372
|
data: thread,
|
|
456
373
|
intents: [
|
|
374
|
+
[
|
|
375
|
+
{
|
|
376
|
+
action: ThreadAction.SELECT,
|
|
377
|
+
data: { current: fullyQualifiedId(thread) },
|
|
378
|
+
},
|
|
379
|
+
],
|
|
457
380
|
[
|
|
458
381
|
{
|
|
459
382
|
action: NavigationAction.OPEN,
|
|
460
383
|
data: {
|
|
461
|
-
activeParts: {
|
|
462
|
-
complementary: `${subjectId}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`,
|
|
463
|
-
},
|
|
384
|
+
activeParts: { complementary: 'comments' },
|
|
464
385
|
},
|
|
465
386
|
},
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
data: { current: thread.id, focus: true },
|
|
469
|
-
},
|
|
387
|
+
],
|
|
388
|
+
[
|
|
470
389
|
{
|
|
471
390
|
action: LayoutAction.SET_LAYOUT,
|
|
472
391
|
data: {
|
|
473
392
|
element: 'complementary',
|
|
474
|
-
subject: subjectId,
|
|
475
393
|
state: true,
|
|
476
394
|
},
|
|
477
395
|
},
|
|
@@ -480,14 +398,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
480
398
|
};
|
|
481
399
|
} else {
|
|
482
400
|
// NOTE: This is the standalone thread creation case.
|
|
483
|
-
return {
|
|
401
|
+
return {
|
|
402
|
+
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
403
|
+
};
|
|
484
404
|
}
|
|
485
405
|
}
|
|
486
406
|
|
|
487
407
|
case ThreadAction.SELECT: {
|
|
488
|
-
state.focus = intent.data?.current === state.current ? state.focus : intent.data?.focus;
|
|
489
408
|
state.current = intent.data?.current;
|
|
490
|
-
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
data: true,
|
|
412
|
+
intents: !intent.data?.skipOpen
|
|
413
|
+
? [
|
|
414
|
+
[
|
|
415
|
+
{
|
|
416
|
+
action: NavigationAction.OPEN,
|
|
417
|
+
data: {
|
|
418
|
+
activeParts: { complementary: 'comments' },
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
]
|
|
423
|
+
: [],
|
|
424
|
+
};
|
|
491
425
|
}
|
|
492
426
|
|
|
493
427
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -506,6 +440,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
506
440
|
const spaceId = space?.id;
|
|
507
441
|
|
|
508
442
|
return {
|
|
443
|
+
data: true,
|
|
509
444
|
intents: [
|
|
510
445
|
[
|
|
511
446
|
{
|
|
@@ -519,19 +454,21 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
519
454
|
|
|
520
455
|
case ThreadAction.DELETE: {
|
|
521
456
|
const { subject, thread } = intent.data ?? {};
|
|
522
|
-
if (
|
|
457
|
+
if (
|
|
458
|
+
!(thread instanceof ThreadType) ||
|
|
459
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
460
|
+
) {
|
|
523
461
|
return;
|
|
524
462
|
}
|
|
525
463
|
|
|
526
464
|
const subjectId = fullyQualifiedId(subject);
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
// Check if we're deleting a
|
|
530
|
-
|
|
531
|
-
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
465
|
+
const draft = state.drafts[subjectId];
|
|
466
|
+
if (draft) {
|
|
467
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
468
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
532
469
|
if (index !== -1) {
|
|
533
|
-
|
|
534
|
-
return;
|
|
470
|
+
draft.splice(index, 1);
|
|
471
|
+
return { data: true };
|
|
535
472
|
}
|
|
536
473
|
}
|
|
537
474
|
|
|
@@ -541,7 +478,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
541
478
|
}
|
|
542
479
|
|
|
543
480
|
if (!intent.undo) {
|
|
544
|
-
const index = subject.threads.findIndex((t) => t?.id === thread.id);
|
|
481
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
545
482
|
const cursor = subject.threads[index]?.anchor;
|
|
546
483
|
if (index !== -1) {
|
|
547
484
|
subject.threads?.splice(index, 1);
|
|
@@ -587,7 +524,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
587
524
|
const { thread, subject } = intent.data ?? {};
|
|
588
525
|
if (
|
|
589
526
|
!(thread instanceof ThreadType) ||
|
|
590
|
-
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
527
|
+
!(subject && typeof subject === 'object' && 'threads' in subject && Array.isArray(subject.threads))
|
|
591
528
|
) {
|
|
592
529
|
return;
|
|
593
530
|
}
|
|
@@ -597,12 +534,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
597
534
|
const intents = [];
|
|
598
535
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
599
536
|
|
|
600
|
-
if (state.
|
|
601
|
-
// Move
|
|
537
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
538
|
+
// Move draft to document.
|
|
602
539
|
thread.status = 'active';
|
|
603
540
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
604
|
-
state.
|
|
605
|
-
|
|
541
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
606
542
|
intents.push({
|
|
607
543
|
action: ObservabilityAction.SEND_EVENT,
|
|
608
544
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -627,7 +563,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
627
563
|
const { subject, thread, messageId } = intent.data ?? {};
|
|
628
564
|
const space = getSpace(subject);
|
|
629
565
|
|
|
630
|
-
if (
|
|
566
|
+
if (
|
|
567
|
+
!(thread instanceof ThreadType) ||
|
|
568
|
+
!(subject && typeof subject === 'object' && 'threads' in subject)
|
|
569
|
+
) {
|
|
631
570
|
return;
|
|
632
571
|
}
|
|
633
572
|
|
|
@@ -658,7 +597,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
658
597
|
action: ObservabilityAction.SEND_EVENT,
|
|
659
598
|
data: {
|
|
660
599
|
name: 'threads.message.delete',
|
|
661
|
-
properties: { threadId: thread.id, spaceId: space
|
|
600
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
662
601
|
},
|
|
663
602
|
},
|
|
664
603
|
],
|
|
@@ -667,13 +606,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
667
606
|
} else if (intent.undo) {
|
|
668
607
|
const message = intent.data?.message;
|
|
669
608
|
const messageIndex = intent.data?.messageIndex;
|
|
670
|
-
|
|
671
609
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
672
610
|
return;
|
|
673
611
|
}
|
|
674
612
|
|
|
675
613
|
thread.messages.splice(messageIndex, 0, message);
|
|
676
|
-
|
|
677
614
|
return {
|
|
678
615
|
data: true,
|
|
679
616
|
intents: [
|
|
@@ -682,7 +619,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
682
619
|
action: ObservabilityAction.SEND_EVENT,
|
|
683
620
|
data: {
|
|
684
621
|
name: 'threads.message.undo-delete',
|
|
685
|
-
properties: { threadId: thread.id, spaceId: space
|
|
622
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
686
623
|
},
|
|
687
624
|
},
|
|
688
625
|
],
|
|
@@ -694,96 +631,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
694
631
|
},
|
|
695
632
|
},
|
|
696
633
|
markdown: {
|
|
697
|
-
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
698
634
|
extensions: ({ document: doc }) => {
|
|
699
|
-
|
|
700
|
-
if (!doc || !space) {
|
|
701
|
-
// Include no-op comments extension here to ensure that the facets are always present when they are expected.
|
|
702
|
-
// TODO(wittjosiah): The Editor should only look for these facets when comments are available.
|
|
703
|
-
return [comments()];
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
|
|
707
|
-
const threads = computed(() =>
|
|
708
|
-
[...doc.threads.filter(nonNullable), ...(state.staging[fullyQualifiedId(doc)] ?? [])].filter(
|
|
709
|
-
(thread) => !(thread?.status === 'resolved'),
|
|
710
|
-
),
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
return [
|
|
714
|
-
listener({
|
|
715
|
-
onChange: () => {
|
|
716
|
-
doc.threads.forEach((thread) => {
|
|
717
|
-
if (thread?.anchor) {
|
|
718
|
-
const [start, end] = thread.anchor.split(':');
|
|
719
|
-
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
720
|
-
// TODO(burdon): This seems unsafe; review.
|
|
721
|
-
// Only update if the name has changed, otherwise this will cause an infinite loop.
|
|
722
|
-
// Skip if the name is empty - this means comment text was deleted, but thread name should remain.
|
|
723
|
-
if (name && name !== thread.name) {
|
|
724
|
-
thread.name = name;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
},
|
|
729
|
-
}),
|
|
730
|
-
createExternalCommentSync(
|
|
731
|
-
doc.id,
|
|
732
|
-
(sink) => effect(() => sink()),
|
|
733
|
-
() =>
|
|
734
|
-
threads.value
|
|
735
|
-
.filter((thread) => thread?.anchor)
|
|
736
|
-
.map((thread) => ({ id: thread.id, cursor: thread.anchor! })),
|
|
737
|
-
),
|
|
738
|
-
comments({
|
|
739
|
-
id: doc.id,
|
|
740
|
-
onCreate: ({ cursor }) => {
|
|
741
|
-
const [start, end] = cursor.split(':');
|
|
742
|
-
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
743
|
-
|
|
744
|
-
void intentPlugin?.provides.intent.dispatch({
|
|
745
|
-
action: ThreadAction.CREATE,
|
|
746
|
-
data: {
|
|
747
|
-
cursor,
|
|
748
|
-
name,
|
|
749
|
-
subject: doc,
|
|
750
|
-
},
|
|
751
|
-
});
|
|
752
|
-
},
|
|
753
|
-
onDelete: ({ id }) => {
|
|
754
|
-
// If the thread is in the staging area, remove it.
|
|
755
|
-
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
756
|
-
if (stagingArea) {
|
|
757
|
-
const index = stagingArea.findIndex((thread) => thread.id === id);
|
|
758
|
-
if (index !== -1) {
|
|
759
|
-
stagingArea.splice(index, 1);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
const thread = doc.threads.find((thread) => thread?.id === id);
|
|
764
|
-
if (thread) {
|
|
765
|
-
thread.anchor = undefined;
|
|
766
|
-
}
|
|
767
|
-
},
|
|
768
|
-
onUpdate: ({ id, cursor }) => {
|
|
769
|
-
const thread =
|
|
770
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => thread.id === id) ??
|
|
771
|
-
doc.threads.find((thread) => thread?.id === id);
|
|
772
|
-
|
|
773
|
-
if (thread instanceof ThreadType && thread.anchor) {
|
|
774
|
-
const [start, end] = thread.anchor.split(':');
|
|
775
|
-
thread.name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
776
|
-
thread.anchor = cursor;
|
|
777
|
-
}
|
|
778
|
-
},
|
|
779
|
-
onSelect: ({ selection: { current, closest } }) => {
|
|
780
|
-
const dispatch = intentPlugin?.provides.intent.dispatch;
|
|
781
|
-
if (dispatch) {
|
|
782
|
-
void dispatch([{ action: ThreadAction.SELECT, data: { current: current ?? closest } }]);
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
}),
|
|
786
|
-
];
|
|
635
|
+
return threads(state, doc, intentPlugin?.provides.intent.dispatch);
|
|
787
636
|
},
|
|
788
637
|
},
|
|
789
638
|
},
|