@dxos/plugin-thread 0.7.5-main.9d26e3a → 0.7.5-main.e9bb01b
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/ThreadContainer-OI5M262D.mjs +19 -0
- package/dist/lib/browser/ThreadContainer-OI5M262D.mjs.map +7 -0
- package/dist/lib/browser/app-graph-builder-Q7A2ZRUA.mjs +121 -0
- package/dist/lib/browser/app-graph-builder-Q7A2ZRUA.mjs.map +7 -0
- package/dist/lib/browser/{chunk-ENERBDYI.mjs → chunk-636QDHE3.mjs} +3 -3
- package/dist/lib/browser/chunk-636QDHE3.mjs.map +7 -0
- package/dist/lib/browser/chunk-BD3J6HEY.mjs +18 -0
- package/dist/lib/browser/chunk-BD3J6HEY.mjs.map +7 -0
- package/dist/lib/browser/chunk-BNEWNQBO.mjs +71 -0
- package/dist/lib/browser/chunk-BNEWNQBO.mjs.map +7 -0
- package/dist/lib/browser/{chunk-OD2IZKRP.mjs → chunk-GQVZK3BQ.mjs} +21 -87
- package/dist/lib/browser/chunk-GQVZK3BQ.mjs.map +7 -0
- package/dist/lib/browser/chunk-NAO3LZMZ.mjs +16 -0
- package/dist/lib/browser/chunk-NAO3LZMZ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-6KTMQPL4.mjs → chunk-NO5AZLUZ.mjs} +7 -3
- package/dist/lib/browser/chunk-NO5AZLUZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +149 -939
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/intent-resolver-QFP6L3AF.mjs +287 -0
- package/dist/lib/browser/intent-resolver-QFP6L3AF.mjs.map +7 -0
- package/dist/lib/browser/markdown-2NBXKXCY.mjs +112 -0
- package/dist/lib/browser/markdown-2NBXKXCY.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-PXYCYPWA.mjs +410 -0
- package/dist/lib/browser/react-surface-PXYCYPWA.mjs.map +7 -0
- package/dist/lib/browser/settings-TKOPRXSU.mjs +22 -0
- package/dist/lib/browser/settings-TKOPRXSU.mjs.map +7 -0
- package/dist/lib/browser/state-GNFUURIC.mjs +33 -0
- package/dist/lib/browser/state-GNFUURIC.mjs.map +7 -0
- package/dist/lib/browser/types.mjs +6 -4
- package/dist/lib/node/{ThreadArticle-ZCNMFBAO.cjs → ThreadContainer-L6TPQJWA.cjs} +14 -13
- package/dist/lib/node/ThreadContainer-L6TPQJWA.cjs.map +7 -0
- package/dist/lib/node/app-graph-builder-ZXW5KUDK.cjs +135 -0
- package/dist/lib/node/app-graph-builder-ZXW5KUDK.cjs.map +7 -0
- package/dist/lib/node/{chunk-2CB5AVBL.cjs → chunk-2TK6J6BN.cjs} +70 -138
- package/dist/lib/node/chunk-2TK6J6BN.cjs.map +7 -0
- package/dist/lib/node/{chunk-VW2P26MZ.cjs → chunk-AUVKOX5Q.cjs} +13 -8
- package/dist/lib/node/chunk-AUVKOX5Q.cjs.map +7 -0
- package/dist/lib/node/chunk-AWSTZ2KN.cjs +93 -0
- package/dist/lib/node/chunk-AWSTZ2KN.cjs.map +7 -0
- package/dist/lib/node/{chunk-IC2NICNC.cjs → chunk-F735VMTN.cjs} +7 -7
- package/dist/lib/node/chunk-F735VMTN.cjs.map +7 -0
- package/dist/lib/node/{meta.cjs → chunk-FPWNZ6TB.cjs} +13 -10
- package/dist/lib/node/chunk-FPWNZ6TB.cjs.map +7 -0
- package/dist/lib/node/{ThreadMain-5AYGCXXP.cjs → chunk-S5LH7AIT.cjs} +26 -23
- package/dist/lib/node/chunk-S5LH7AIT.cjs.map +7 -0
- package/dist/lib/node/index.cjs +147 -933
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/intent-resolver-ZSOQILPJ.cjs +299 -0
- package/dist/lib/node/intent-resolver-ZSOQILPJ.cjs.map +7 -0
- package/dist/lib/node/markdown-FWRKMSKV.cjs +122 -0
- package/dist/lib/node/markdown-FWRKMSKV.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/react-surface-CPDCOLUZ.cjs +412 -0
- package/dist/lib/node/react-surface-CPDCOLUZ.cjs.map +7 -0
- package/dist/lib/node/settings-TH4YSUVU.cjs +36 -0
- package/dist/lib/node/settings-TH4YSUVU.cjs.map +7 -0
- package/dist/lib/node/state-TQWEZ6B3.cjs +49 -0
- package/dist/lib/node/state-TQWEZ6B3.cjs.map +7 -0
- package/dist/lib/node/types.cjs +6 -4
- package/dist/lib/node/types.cjs.map +2 -2
- package/dist/lib/node-esm/ThreadContainer-HE46AQJH.mjs +20 -0
- package/dist/lib/node-esm/ThreadContainer-HE46AQJH.mjs.map +7 -0
- package/dist/lib/node-esm/app-graph-builder-ATS6HGE6.mjs +122 -0
- package/dist/lib/node-esm/app-graph-builder-ATS6HGE6.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-7YYL6QVU.mjs +20 -0
- package/dist/lib/node-esm/chunk-7YYL6QVU.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-AQ74G3GY.mjs → chunk-AZCU7SHI.mjs} +21 -87
- package/dist/lib/node-esm/chunk-AZCU7SHI.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RMYATBXM.mjs → chunk-B3CF35MY.mjs} +7 -3
- package/dist/lib/node-esm/chunk-B3CF35MY.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-7QRGG25F.mjs → chunk-FXUATCLW.mjs} +3 -3
- package/dist/lib/node-esm/chunk-FXUATCLW.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HLWJTUWZ.mjs +73 -0
- package/dist/lib/node-esm/chunk-HLWJTUWZ.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZJITX3TO.mjs +17 -0
- package/dist/lib/node-esm/chunk-ZJITX3TO.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +149 -939
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/intent-resolver-MHP4C2NZ.mjs +288 -0
- package/dist/lib/node-esm/intent-resolver-MHP4C2NZ.mjs.map +7 -0
- package/dist/lib/node-esm/markdown-6AQFDAW3.mjs +113 -0
- package/dist/lib/node-esm/markdown-6AQFDAW3.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/react-surface-RD4F2LRT.mjs +411 -0
- package/dist/lib/node-esm/react-surface-RD4F2LRT.mjs.map +7 -0
- package/dist/lib/node-esm/settings-KEDRR7RM.mjs +23 -0
- package/dist/lib/node-esm/settings-KEDRR7RM.mjs.map +7 -0
- package/dist/lib/node-esm/state-7MLR27YG.mjs +34 -0
- package/dist/lib/node-esm/state-7MLR27YG.mjs.map +7 -0
- package/dist/lib/node-esm/types.mjs +6 -4
- package/dist/types/src/ThreadPlugin.d.ts +1 -3
- package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +180 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/capabilities.d.ts +15 -0
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +187 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/markdown.d.ts +4 -0
- package/dist/types/src/capabilities/markdown.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +4 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +4 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/capabilities/state.d.ts +7 -0
- package/dist/types/src/capabilities/state.d.ts.map +1 -0
- package/dist/types/src/components/Chat.stories.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 +2 -7
- package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
- package/dist/types/src/components/ThreadComplementary.d.ts +1 -5
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
- package/dist/types/src/components/ThreadContainer.d.ts +7 -0
- package/dist/types/src/components/ThreadContainer.d.ts.map +1 -0
- package/dist/types/src/components/command-extension.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +3 -15
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +5 -5
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +10 -14
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -38
- package/src/ThreadPlugin.tsx +104 -470
- package/src/capabilities/app-graph-builder.ts +120 -0
- package/src/capabilities/capabilities.ts +19 -0
- package/src/capabilities/index.ts +14 -0
- package/src/capabilities/intent-resolver.ts +239 -0
- package/src/capabilities/markdown.ts +18 -0
- package/src/capabilities/react-surface.tsx +56 -0
- package/src/capabilities/settings.ts +14 -0
- package/src/capabilities/state.ts +25 -0
- package/src/components/Chat.stories.tsx +25 -23
- package/src/components/CommentContainer.tsx +16 -30
- package/src/components/CommentsContainer.tsx +1 -17
- package/src/components/ThreadComplementary.tsx +85 -48
- package/src/components/ThreadContainer.tsx +22 -0
- package/src/components/ThreadSettings.tsx +3 -3
- package/src/components/command-extension.ts +2 -5
- package/src/components/index.ts +1 -2
- package/src/components/types.ts +5 -5
- package/src/index.ts +1 -5
- package/src/meta.ts +1 -1
- package/src/translations.ts +1 -0
- package/src/types.ts +10 -29
- package/dist/lib/browser/ThreadArticle-EWLTNU6Z.mjs +0 -18
- package/dist/lib/browser/ThreadArticle-EWLTNU6Z.mjs.map +0 -7
- package/dist/lib/browser/ThreadMain-ABEDFWHC.mjs +0 -26
- package/dist/lib/browser/ThreadMain-ABEDFWHC.mjs.map +0 -7
- package/dist/lib/browser/chunk-6KTMQPL4.mjs.map +0 -7
- package/dist/lib/browser/chunk-ENERBDYI.mjs.map +0 -7
- package/dist/lib/browser/chunk-OD2IZKRP.mjs.map +0 -7
- package/dist/lib/browser/meta.mjs +0 -11
- package/dist/lib/browser/meta.mjs.map +0 -7
- package/dist/lib/node/ThreadArticle-ZCNMFBAO.cjs.map +0 -7
- package/dist/lib/node/ThreadMain-5AYGCXXP.cjs.map +0 -7
- package/dist/lib/node/chunk-2CB5AVBL.cjs.map +0 -7
- package/dist/lib/node/chunk-IC2NICNC.cjs.map +0 -7
- package/dist/lib/node/chunk-VW2P26MZ.cjs.map +0 -7
- package/dist/lib/node/meta.cjs.map +0 -7
- package/dist/lib/node-esm/ThreadArticle-F37HYTOU.mjs +0 -19
- package/dist/lib/node-esm/ThreadArticle-F37HYTOU.mjs.map +0 -7
- package/dist/lib/node-esm/ThreadMain-OL6ZHNZY.mjs +0 -27
- package/dist/lib/node-esm/ThreadMain-OL6ZHNZY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-7QRGG25F.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-AQ74G3GY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RMYATBXM.mjs.map +0 -7
- package/dist/lib/node-esm/meta.mjs +0 -12
- package/dist/lib/node-esm/meta.mjs.map +0 -7
- package/dist/types/src/components/ThreadArticle.d.ts +0 -5
- package/dist/types/src/components/ThreadArticle.d.ts.map +0 -1
- package/dist/types/src/components/ThreadMain.d.ts +0 -7
- package/dist/types/src/components/ThreadMain.d.ts.map +0 -1
- package/src/components/ThreadArticle.tsx +0 -18
- package/src/components/ThreadMain.tsx +0 -27
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes, createIntent, type PluginsContext } from '@dxos/app-framework';
|
|
6
|
+
import { ClientCapabilities } from '@dxos/plugin-client';
|
|
7
|
+
import { createExtension, toSignal } from '@dxos/plugin-graph';
|
|
8
|
+
import { ObservabilityAction } from '@dxos/plugin-observability/types';
|
|
9
|
+
import { memoizeQuery } from '@dxos/plugin-space';
|
|
10
|
+
import { getTypename, parseId, SpaceState } from '@dxos/react-client/echo';
|
|
11
|
+
|
|
12
|
+
import { ThreadCapabilities } from './capabilities';
|
|
13
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
14
|
+
|
|
15
|
+
const type = 'orphan-comments-for-subject';
|
|
16
|
+
const icon = 'ph--chat-text--regular';
|
|
17
|
+
|
|
18
|
+
export default (context: PluginsContext) => {
|
|
19
|
+
const resolver = (typename: string) =>
|
|
20
|
+
context.requestCapabilities(Capabilities.Metadata).find(({ id }) => id === typename)?.metadata ?? {};
|
|
21
|
+
|
|
22
|
+
return contributes(
|
|
23
|
+
Capabilities.AppGraphBuilder,
|
|
24
|
+
createExtension({
|
|
25
|
+
id: `${THREAD_PLUGIN}/comments-for-subject`,
|
|
26
|
+
resolver: ({ id }) => {
|
|
27
|
+
// TODO(Zan): Find util (or make one).
|
|
28
|
+
if (!id.endsWith('~comments')) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const client = context.requestCapability(ClientCapabilities.Client);
|
|
33
|
+
const [subjectId] = id.split('~');
|
|
34
|
+
const { spaceId, objectId } = parseId(subjectId);
|
|
35
|
+
const spaces = toSignal(
|
|
36
|
+
(onChange) => client.spaces.subscribe(() => onChange()).unsubscribe,
|
|
37
|
+
() => client.spaces.get(),
|
|
38
|
+
);
|
|
39
|
+
const space = spaces?.find((space) => space.id === spaceId && space.state.get() === SpaceState.SPACE_READY);
|
|
40
|
+
if (!objectId) {
|
|
41
|
+
// TODO(wittjosiah): Support comments for arbitrary subjects.
|
|
42
|
+
// This is to ensure that the comments panel is not stuck on an old object.
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
type,
|
|
46
|
+
data: null,
|
|
47
|
+
properties: {
|
|
48
|
+
icon,
|
|
49
|
+
label: ['unnamed object threads label', { ns: THREAD_PLUGIN }],
|
|
50
|
+
showResolvedThreads: false,
|
|
51
|
+
object: null,
|
|
52
|
+
space,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const [object] = memoizeQuery(space, { id: objectId });
|
|
58
|
+
if (!object || !subjectId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const meta = resolver(getTypename(object) ?? '');
|
|
63
|
+
const label = meta.label?.(object) ||
|
|
64
|
+
object.name ||
|
|
65
|
+
meta.placeholder || ['unnamed object threads label', { ns: THREAD_PLUGIN }];
|
|
66
|
+
|
|
67
|
+
const { getViewState } = context.requestCapability(ThreadCapabilities.State);
|
|
68
|
+
const viewState = getViewState(subjectId);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
type,
|
|
73
|
+
data: null,
|
|
74
|
+
properties: {
|
|
75
|
+
icon,
|
|
76
|
+
label,
|
|
77
|
+
showResolvedThreads: viewState.showResolvedThreads,
|
|
78
|
+
object,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
actions: ({ node }) => {
|
|
83
|
+
const dataId = node.id.split('~').at(0);
|
|
84
|
+
if (!node.id.endsWith('~comments') || !dataId) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const [spaceId, objectId] = dataId.split(':');
|
|
89
|
+
|
|
90
|
+
const { getViewState } = context.requestCapability(ThreadCapabilities.State);
|
|
91
|
+
const viewState = getViewState(dataId);
|
|
92
|
+
const toggle = async () => {
|
|
93
|
+
const newToggleState = !viewState.showResolvedThreads;
|
|
94
|
+
viewState.showResolvedThreads = newToggleState;
|
|
95
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
96
|
+
await dispatch(
|
|
97
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
98
|
+
name: 'threads.toggle-show-resolved',
|
|
99
|
+
properties: { spaceId, threadId: objectId, showResolved: newToggleState },
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
id: `${THREAD_PLUGIN}/toggle-show-resolved/${node.id}`,
|
|
107
|
+
data: toggle,
|
|
108
|
+
properties: {
|
|
109
|
+
label: ['toggle show resolved', { ns: THREAD_PLUGIN }],
|
|
110
|
+
menuItemType: 'toggle',
|
|
111
|
+
isChecked: viewState.showResolvedThreads,
|
|
112
|
+
testId: 'threadPlugin.toggleShowResolved',
|
|
113
|
+
icon: viewState.showResolvedThreads ? 'ph--eye-slash--regular' : 'ph--eye--regular',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { defineCapability } from '@dxos/app-framework';
|
|
6
|
+
import { type DeepReadonly } from '@dxos/util';
|
|
7
|
+
|
|
8
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
9
|
+
import { type ViewState, type ThreadState } from '../types';
|
|
10
|
+
|
|
11
|
+
export namespace ThreadCapabilities {
|
|
12
|
+
type GetViewState = (subjectId: string) => ViewState;
|
|
13
|
+
export const State = defineCapability<{ state: DeepReadonly<ThreadState>; getViewState: GetViewState }>(
|
|
14
|
+
`${THREAD_PLUGIN}/capability/state`,
|
|
15
|
+
);
|
|
16
|
+
export const MutableState = defineCapability<{ state: ThreadState; getViewState: GetViewState }>(
|
|
17
|
+
`${THREAD_PLUGIN}/capability/state`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { lazy } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
|
|
8
|
+
export const IntentResolver = lazy(() => import('./intent-resolver'));
|
|
9
|
+
export const Markdown = lazy(() => import('./markdown'));
|
|
10
|
+
export const ReactSurface = lazy(() => import('./react-surface'));
|
|
11
|
+
export const ThreadSettings = lazy(() => import('./settings'));
|
|
12
|
+
export const ThreadState = lazy(() => import('./state'));
|
|
13
|
+
|
|
14
|
+
export * from './capabilities';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Capabilities,
|
|
7
|
+
contributes,
|
|
8
|
+
createIntent,
|
|
9
|
+
createResolver,
|
|
10
|
+
LayoutAction,
|
|
11
|
+
type PluginsContext,
|
|
12
|
+
} from '@dxos/app-framework';
|
|
13
|
+
import { Ref } from '@dxos/echo-schema';
|
|
14
|
+
import { log } from '@dxos/log';
|
|
15
|
+
import { ObservabilityAction } from '@dxos/plugin-observability/types';
|
|
16
|
+
import { ChannelType, ThreadType } from '@dxos/plugin-space/types';
|
|
17
|
+
import { create, fullyQualifiedId, getSpace, makeRef } from '@dxos/react-client/echo';
|
|
18
|
+
|
|
19
|
+
import { ThreadCapabilities } from './capabilities';
|
|
20
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
21
|
+
import { ThreadAction } from '../types';
|
|
22
|
+
|
|
23
|
+
export default (context: PluginsContext) =>
|
|
24
|
+
contributes(Capabilities.IntentResolver, [
|
|
25
|
+
createResolver({
|
|
26
|
+
intent: ThreadAction.Create,
|
|
27
|
+
resolve: ({ name, cursor, subject }) => {
|
|
28
|
+
if (cursor && subject) {
|
|
29
|
+
// Seed the threads array if it does not exist.
|
|
30
|
+
if (subject?.threads === undefined) {
|
|
31
|
+
try {
|
|
32
|
+
// Static schema will throw an error if subject does not support threads array property.
|
|
33
|
+
subject.threads = [];
|
|
34
|
+
} catch (err) {
|
|
35
|
+
log.error('Subject does not support threads array', { typename: subject?.typename });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { state } = context.requestCapability(ThreadCapabilities.MutableState);
|
|
41
|
+
const subjectId = fullyQualifiedId(subject);
|
|
42
|
+
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
43
|
+
const draft = state.drafts[subjectId];
|
|
44
|
+
if (draft) {
|
|
45
|
+
draft.push(thread);
|
|
46
|
+
} else {
|
|
47
|
+
state.drafts[subjectId] = [thread];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
data: { object: thread },
|
|
52
|
+
intents: [
|
|
53
|
+
createIntent(ThreadAction.Select, { current: fullyQualifiedId(thread) }),
|
|
54
|
+
createIntent(LayoutAction.UpdateComplementary, { part: 'complementary', subject: 'comments' }),
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
// NOTE: This is the standalone thread creation case.
|
|
59
|
+
return {
|
|
60
|
+
data: { object: create(ChannelType, { threads: [makeRef(create(ThreadType, { messages: [] }))] }) },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
createResolver({
|
|
66
|
+
intent: ThreadAction.Select,
|
|
67
|
+
resolve: ({ current, skipOpen }) => {
|
|
68
|
+
const { state } = context.requestCapability(ThreadCapabilities.MutableState);
|
|
69
|
+
state.current = current;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
intents: !skipOpen
|
|
73
|
+
? [createIntent(LayoutAction.UpdateComplementary, { part: 'complementary', subject: 'comments' })]
|
|
74
|
+
: undefined,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
createResolver({
|
|
79
|
+
intent: ThreadAction.ToggleResolved,
|
|
80
|
+
resolve: ({ thread }) => {
|
|
81
|
+
if (thread.status === 'active' || thread.status === undefined) {
|
|
82
|
+
thread.status = 'resolved';
|
|
83
|
+
} else if (thread.status === 'resolved') {
|
|
84
|
+
thread.status = 'active';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const space = getSpace(thread);
|
|
88
|
+
const spaceId = space?.id;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
intents: [
|
|
92
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
93
|
+
name: 'threads.toggle-resolved',
|
|
94
|
+
properties: { threadId: thread.id, spaceId },
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
createResolver({
|
|
101
|
+
intent: ThreadAction.Delete,
|
|
102
|
+
resolve: ({ subject, thread }, undo) => {
|
|
103
|
+
const { state } = context.requestCapability(ThreadCapabilities.MutableState);
|
|
104
|
+
const subjectId = fullyQualifiedId(subject);
|
|
105
|
+
const draft = state.drafts[subjectId];
|
|
106
|
+
if (draft) {
|
|
107
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
108
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
109
|
+
if (index !== -1) {
|
|
110
|
+
draft.splice(index, 1);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const space = getSpace(thread);
|
|
116
|
+
if (!space || !subject.threads) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!undo) {
|
|
121
|
+
const index = subject.threads.findIndex((t: any) => t?.id === thread.id);
|
|
122
|
+
const cursor = subject.threads[index]?.anchor;
|
|
123
|
+
if (index !== -1) {
|
|
124
|
+
subject.threads?.splice(index, 1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
space.db.remove(thread);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
undoable: {
|
|
131
|
+
message: ['thread deleted label', { ns: THREAD_PLUGIN }],
|
|
132
|
+
data: { cursor },
|
|
133
|
+
},
|
|
134
|
+
intents: [
|
|
135
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
136
|
+
name: 'threads.delete',
|
|
137
|
+
properties: { threadId: thread.id, spaceId: space.id },
|
|
138
|
+
}),
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
// TODO(wittjosiah): SDK should do this automatically.
|
|
143
|
+
const savedThread = space.db.add(thread);
|
|
144
|
+
subject.threads.push(savedThread);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
intents: [
|
|
148
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
149
|
+
name: 'threads.undo-delete',
|
|
150
|
+
properties: { threadId: thread.id, spaceId: space.id },
|
|
151
|
+
}),
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
createResolver({
|
|
158
|
+
intent: ThreadAction.OnMessageAdd,
|
|
159
|
+
resolve: ({ thread, subject }) => {
|
|
160
|
+
const { state } = context.requestCapability(ThreadCapabilities.MutableState);
|
|
161
|
+
const subjectId = fullyQualifiedId(subject);
|
|
162
|
+
const space = getSpace(subject);
|
|
163
|
+
const intents = [];
|
|
164
|
+
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
165
|
+
|
|
166
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
167
|
+
// Move draft to document.
|
|
168
|
+
thread.status = 'active';
|
|
169
|
+
subject.threads ? subject.threads.push(makeRef(thread)) : (subject.threads = [makeRef(thread)]);
|
|
170
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
171
|
+
intents.push(
|
|
172
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
173
|
+
name: 'threads.thread-created',
|
|
174
|
+
properties: analyticsProperties,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
intents.push(
|
|
180
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
181
|
+
name: 'threads.message-added',
|
|
182
|
+
properties: { ...analyticsProperties, threadLength: thread.messages.length },
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return { intents };
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
createResolver({
|
|
190
|
+
intent: ThreadAction.DeleteMessage,
|
|
191
|
+
resolve: ({ subject, thread, messageId, message, messageIndex }, undo) => {
|
|
192
|
+
const space = getSpace(subject);
|
|
193
|
+
|
|
194
|
+
if (!undo) {
|
|
195
|
+
const messageIndex = thread.messages.findIndex(Ref.hasObjectId(messageId));
|
|
196
|
+
const message = thread.messages[messageIndex]?.target;
|
|
197
|
+
if (!message) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (messageIndex === 0 && thread.messages.length === 1) {
|
|
202
|
+
// If the message is the only message in the thread, delete the thread.
|
|
203
|
+
return {
|
|
204
|
+
intents: [createIntent(ThreadAction.Delete, { subject, thread })],
|
|
205
|
+
};
|
|
206
|
+
} else {
|
|
207
|
+
thread.messages.splice(messageIndex, 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
undoable: {
|
|
212
|
+
message: ['message deleted label', { ns: THREAD_PLUGIN }],
|
|
213
|
+
data: { message, messageIndex },
|
|
214
|
+
},
|
|
215
|
+
intents: [
|
|
216
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
217
|
+
name: 'threads.message.delete',
|
|
218
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
} else {
|
|
223
|
+
if (!messageIndex || !message) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
thread.messages.splice(messageIndex, 0, makeRef(message));
|
|
228
|
+
return {
|
|
229
|
+
intents: [
|
|
230
|
+
createIntent(ObservabilityAction.SendEvent, {
|
|
231
|
+
name: 'threads.message.undo-delete',
|
|
232
|
+
properties: { threadId: thread.id, spaceId: space?.id },
|
|
233
|
+
}),
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
]);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
|
|
6
|
+
import { MarkdownCapabilities } from '@dxos/plugin-markdown';
|
|
7
|
+
|
|
8
|
+
import { ThreadCapabilities } from './capabilities';
|
|
9
|
+
import { threads } from '../extensions';
|
|
10
|
+
|
|
11
|
+
export default (context: PluginsContext) =>
|
|
12
|
+
contributes(MarkdownCapabilities.Extensions, [
|
|
13
|
+
({ document: doc }) => {
|
|
14
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
15
|
+
const { state } = context.requestCapability(ThreadCapabilities.MutableState);
|
|
16
|
+
return threads(state, doc, dispatch);
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Capabilities, contributes, createSurface, useLayout } from '@dxos/app-framework';
|
|
8
|
+
import { type Ref } from '@dxos/echo-schema';
|
|
9
|
+
import { SettingsStore } from '@dxos/local-storage';
|
|
10
|
+
import { ChannelType, type ThreadType } from '@dxos/plugin-space/types';
|
|
11
|
+
import { getSpace } from '@dxos/react-client/echo';
|
|
12
|
+
|
|
13
|
+
import { ThreadContainer, ThreadComplementary, ThreadSettings } from '../components';
|
|
14
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
15
|
+
import { type ThreadSettingsProps } from '../types';
|
|
16
|
+
|
|
17
|
+
export default () =>
|
|
18
|
+
contributes(Capabilities.ReactSurface, [
|
|
19
|
+
createSurface({
|
|
20
|
+
id: `${THREAD_PLUGIN}/channel`,
|
|
21
|
+
role: 'article',
|
|
22
|
+
filter: (data): data is { subject: ChannelType } =>
|
|
23
|
+
data.subject instanceof ChannelType && !!data.subject.threads[0],
|
|
24
|
+
component: ({ data, role }) => {
|
|
25
|
+
const layout = useLayout();
|
|
26
|
+
const channel = data.subject;
|
|
27
|
+
const thread = channel.threads[0].target!;
|
|
28
|
+
const currentPosition = layout.active.findIndex((id) => id === channel.id);
|
|
29
|
+
if (currentPosition > 0) {
|
|
30
|
+
const objectToTheLeft = layout.active[currentPosition - 1];
|
|
31
|
+
const context = getSpace(channel)?.db.getObjectById(objectToTheLeft);
|
|
32
|
+
return <ThreadContainer role={role} thread={thread} context={context} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return <ThreadContainer role={role} thread={thread} />;
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
createSurface({
|
|
39
|
+
id: `${THREAD_PLUGIN}/thread`,
|
|
40
|
+
role: 'complementary--comments',
|
|
41
|
+
filter: (data): data is { subject: { threads: Ref<ThreadType>[] } } =>
|
|
42
|
+
!!data.subject &&
|
|
43
|
+
typeof data.subject === 'object' &&
|
|
44
|
+
'threads' in data.subject &&
|
|
45
|
+
Array.isArray(data.subject.threads) &&
|
|
46
|
+
!(data.subject instanceof ChannelType),
|
|
47
|
+
component: ({ data }) => <ThreadComplementary subject={data.subject} />,
|
|
48
|
+
}),
|
|
49
|
+
createSurface({
|
|
50
|
+
id: `${THREAD_PLUGIN}/settings`,
|
|
51
|
+
role: 'article',
|
|
52
|
+
filter: (data): data is { subject: SettingsStore<ThreadSettingsProps> } =>
|
|
53
|
+
data.subject instanceof SettingsStore && data.subject.prefix === THREAD_PLUGIN,
|
|
54
|
+
component: ({ data: { subject } }) => <ThreadSettings settings={subject.value} />,
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes } from '@dxos/app-framework';
|
|
6
|
+
import { create } from '@dxos/live-object';
|
|
7
|
+
|
|
8
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
9
|
+
import { type ThreadSettingsProps, ThreadSettingsSchema } from '../types';
|
|
10
|
+
|
|
11
|
+
export default () => {
|
|
12
|
+
const settings = create<ThreadSettingsProps>({});
|
|
13
|
+
return contributes(Capabilities.Settings, { schema: ThreadSettingsSchema, prefix: THREAD_PLUGIN, value: settings });
|
|
14
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { contributes } from '@dxos/app-framework';
|
|
6
|
+
import { create } from '@dxos/live-object';
|
|
7
|
+
|
|
8
|
+
import { ThreadCapabilities } from './capabilities';
|
|
9
|
+
import { type ThreadState, type ViewStore } from '../types';
|
|
10
|
+
|
|
11
|
+
const initialViewState = { showResolvedThreads: false };
|
|
12
|
+
|
|
13
|
+
export default () => {
|
|
14
|
+
const state = create<ThreadState>({ drafts: {} });
|
|
15
|
+
const viewStore = create<ViewStore>({});
|
|
16
|
+
|
|
17
|
+
const getViewState = (subjectId: string) => {
|
|
18
|
+
if (!viewStore[subjectId]) {
|
|
19
|
+
viewStore[subjectId] = { ...initialViewState };
|
|
20
|
+
}
|
|
21
|
+
return viewStore[subjectId];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return contributes(ThreadCapabilities.State, { state, getViewState });
|
|
25
|
+
};
|
|
@@ -7,13 +7,14 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta } from '@storybook/react';
|
|
8
8
|
import React, { useEffect, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
|
|
11
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
11
12
|
import { MessageType, ThreadType } from '@dxos/plugin-space/types';
|
|
12
13
|
import { faker } from '@dxos/random';
|
|
13
14
|
import { useClient } from '@dxos/react-client';
|
|
14
15
|
import { type Space } from '@dxos/react-client/echo';
|
|
15
16
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
16
|
-
import {
|
|
17
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
17
18
|
import { Mosaic } from '@dxos/react-ui-mosaic';
|
|
18
19
|
import { Thread } from '@dxos/react-ui-thread';
|
|
19
20
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
@@ -46,24 +47,10 @@ const Story = () => {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
return (
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
id: 'test',
|
|
54
|
-
role: 'card',
|
|
55
|
-
component: ({ role }) => <span>{JSON.stringify({ role })}</span>,
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
<Mosaic.Root debug>
|
|
61
|
-
<main className='max-is-prose mli-auto bs-dvh overflow-hidden'>
|
|
62
|
-
{space && <ChatContainer thread={thread} />}
|
|
63
|
-
</main>
|
|
64
|
-
<Mosaic.DragOverlay />
|
|
65
|
-
</Mosaic.Root>
|
|
66
|
-
</SurfaceProvider>
|
|
50
|
+
<Mosaic.Root debug>
|
|
51
|
+
<main className='max-is-prose mli-auto bs-dvh overflow-hidden'>{space && <ChatContainer thread={thread} />}</main>
|
|
52
|
+
<Mosaic.DragOverlay />
|
|
53
|
+
</Mosaic.Root>
|
|
67
54
|
);
|
|
68
55
|
};
|
|
69
56
|
|
|
@@ -72,9 +59,24 @@ export const Default = {};
|
|
|
72
59
|
const meta: Meta = {
|
|
73
60
|
title: 'plugins/plugin-thread/Chat',
|
|
74
61
|
component: Thread,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
render: () => <Story />,
|
|
63
|
+
decorators: [
|
|
64
|
+
withPluginManager({
|
|
65
|
+
capabilities: [
|
|
66
|
+
contributes(
|
|
67
|
+
Capabilities.ReactSurface,
|
|
68
|
+
createSurface({
|
|
69
|
+
id: 'test',
|
|
70
|
+
role: 'card',
|
|
71
|
+
component: ({ role }) => <span>{JSON.stringify({ role })}</span>,
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
withTheme,
|
|
77
|
+
withLayout({ fullscreen: true, tooltips: true }),
|
|
78
|
+
withClientProvider({ createSpace: true, types: [ThreadType, MessageType] }),
|
|
79
|
+
],
|
|
78
80
|
parameters: { translations },
|
|
79
81
|
};
|
|
80
82
|
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
import { CheckCircle, X } from '@phosphor-icons/react';
|
|
6
6
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { MessageType } from '@dxos/plugin-space/types';
|
|
8
|
+
import { RefArray } from '@dxos/live-object';
|
|
10
9
|
import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
|
|
11
10
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
12
11
|
import { Button, Tag, Tooltip, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
@@ -94,13 +93,12 @@ const DeleteThreadButton = ({ onDelete }: { onDelete: () => void }) => {
|
|
|
94
93
|
export const CommentContainer = ({
|
|
95
94
|
thread,
|
|
96
95
|
detached,
|
|
97
|
-
context,
|
|
98
96
|
current,
|
|
99
97
|
onAttend,
|
|
100
|
-
onThreadDelete,
|
|
101
|
-
onMessageDelete,
|
|
102
|
-
onResolve,
|
|
103
98
|
onComment,
|
|
99
|
+
onResolve,
|
|
100
|
+
onMessageDelete,
|
|
101
|
+
onThreadDelete,
|
|
104
102
|
}: ThreadContainerProps) => {
|
|
105
103
|
const identity = useIdentity()!;
|
|
106
104
|
const space = getSpace(thread);
|
|
@@ -129,33 +127,26 @@ export const CommentContainer = ({
|
|
|
129
127
|
const scrollToEnd = (behavior: ScrollBehavior) =>
|
|
130
128
|
setTimeout(() => threadScrollRef.current?.scrollIntoView({ behavior, block: 'end' }), 10);
|
|
131
129
|
|
|
132
|
-
const
|
|
130
|
+
const handleAttend = useCallback(() => onAttend?.(thread), [onAttend, thread]);
|
|
131
|
+
const handleResolve = useCallback(() => onResolve?.(thread), [onResolve, thread]);
|
|
132
|
+
const handleMessageDelete = useCallback((id: string) => onMessageDelete?.(thread, id), [onMessageDelete, thread]);
|
|
133
|
+
const handleThreadDelete = useCallback(() => onThreadDelete?.(thread), [onThreadDelete, thread]);
|
|
134
|
+
|
|
135
|
+
const handleComment: MessageTextboxProps['onSend'] = useCallback(() => {
|
|
133
136
|
if (!messageRef.current) {
|
|
134
137
|
return false;
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
thread.
|
|
138
|
-
makeRef(
|
|
139
|
-
create(MessageType, {
|
|
140
|
-
sender: { identityKey: identity.identityKey.toHex() },
|
|
141
|
-
timestamp: new Date().toISOString(),
|
|
142
|
-
text: messageRef.current,
|
|
143
|
-
context: context ? makeRef(context) : undefined,
|
|
144
|
-
}),
|
|
145
|
-
),
|
|
146
|
-
);
|
|
147
|
-
onComment?.(thread);
|
|
148
|
-
|
|
140
|
+
onComment?.(thread, messageRef.current);
|
|
149
141
|
messageRef.current = '';
|
|
150
142
|
scrollToEnd('instant');
|
|
151
143
|
rerenderEditor();
|
|
152
144
|
|
|
153
|
-
// TODO(burdon): Scroll to bottom.
|
|
154
145
|
return true;
|
|
155
146
|
}, [thread, identity]);
|
|
156
147
|
|
|
157
148
|
return (
|
|
158
|
-
<Thread onClickCapture={
|
|
149
|
+
<Thread onClickCapture={handleAttend} onFocusCapture={handleAttend} current={current} id={fullyQualifiedId(thread)}>
|
|
159
150
|
<div
|
|
160
151
|
role='none'
|
|
161
152
|
className={mx(
|
|
@@ -182,19 +173,14 @@ export const CommentContainer = ({
|
|
|
182
173
|
<div className='flex flex-row items-center pli-1'>
|
|
183
174
|
{thread.status === 'staged' && <Tag palette='neutral'>{t('draft button')}</Tag>}
|
|
184
175
|
{onResolve && !(thread?.status === 'staged') && (
|
|
185
|
-
<ToggleResolvedButton isResolved={thread?.status === 'resolved'} onResolve={
|
|
176
|
+
<ToggleResolvedButton isResolved={thread?.status === 'resolved'} onResolve={handleResolve} />
|
|
186
177
|
)}
|
|
187
|
-
{onThreadDelete && <DeleteThreadButton onDelete={
|
|
178
|
+
{onThreadDelete && <DeleteThreadButton onDelete={handleThreadDelete} />}
|
|
188
179
|
</div>
|
|
189
180
|
</div>
|
|
190
181
|
{/** TODO(dmaretskyi): How's `thread.messages` undefined? */}
|
|
191
182
|
{RefArray.allResolvedTargets(thread.messages?.filter(nonNullable) ?? []).map((message) => (
|
|
192
|
-
<MessageContainer
|
|
193
|
-
key={message.id}
|
|
194
|
-
message={message}
|
|
195
|
-
members={members}
|
|
196
|
-
onDelete={(id: string) => onMessageDelete?.(id)}
|
|
197
|
-
/>
|
|
183
|
+
<MessageContainer key={message.id} message={message} members={members} onDelete={handleMessageDelete} />
|
|
198
184
|
))}
|
|
199
185
|
{/*
|
|
200
186
|
TODO(wittjosiah): Can't autofocus this generally.
|
|
@@ -202,7 +188,7 @@ export const CommentContainer = ({
|
|
|
202
188
|
Also, it steals focus from documents when first rendered.
|
|
203
189
|
Need to find a way to autofocus in one scenario only: when a new thread is created.
|
|
204
190
|
*/}
|
|
205
|
-
<MessageTextbox extensions={extensions} onSend={
|
|
191
|
+
<MessageTextbox extensions={extensions} onSend={handleComment} {...textboxMetadata} />
|
|
206
192
|
<ThreadFooter activity={activity}>{t('activity message')}</ThreadFooter>
|
|
207
193
|
{/* NOTE(thure): This can’t also be the `overflow-anchor` because `ScrollArea` injects an interceding node that contains this necessary ref’d element. */}
|
|
208
194
|
<div role='none' className='bs-px -mbs-px' ref={threadScrollRef} />
|