@dxos/plugin-thread 0.6.13 → 0.6.14-main.7bd9c89
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-3W6SWGHG.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs → ThreadMain-D7CU4Z57.mjs} +3 -3
- package/dist/lib/browser/chunk-5G3KL6TC.mjs +16 -0
- package/dist/lib/browser/chunk-5G3KL6TC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-P7ZE5N5M.mjs → chunk-AKS43EUV.mjs} +15 -15
- package/dist/lib/browser/chunk-AKS43EUV.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs → chunk-JFQZ5W2I.mjs} +2 -2
- package/dist/lib/browser/{chunk-GKPZHUT3.mjs.map → chunk-JFQZ5W2I.mjs.map} +1 -1
- package/dist/lib/browser/index.mjs +409 -382
- 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-LNYJIQRO.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-XJX75GHG.cjs → ThreadMain-PQWIG7M5.cjs} +7 -7
- package/dist/lib/node/{chunk-5PZNPPQF.cjs → chunk-DL2VJOPZ.cjs} +65 -65
- package/dist/lib/node/chunk-DL2VJOPZ.cjs.map +7 -0
- package/dist/lib/node/{chunk-D5GOVXHS.cjs → chunk-FOZUQZF4.cjs} +6 -6
- package/dist/lib/node/{chunk-D5GOVXHS.cjs.map → chunk-FOZUQZF4.cjs.map} +1 -1
- package/dist/lib/node/{chunk-BWBONZGU.cjs → chunk-JIJ7Z6NO.cjs} +7 -22
- package/dist/lib/node/chunk-JIJ7Z6NO.cjs.map +7 -0
- package/dist/lib/node/index.cjs +458 -435
- 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-RKAZPOU2.mjs +19 -0
- package/dist/lib/node-esm/ThreadArticle-RKAZPOU2.mjs.map +7 -0
- package/dist/lib/node-esm/ThreadMain-VTBJFLXN.mjs +27 -0
- package/dist/lib/node-esm/ThreadMain-VTBJFLXN.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-AQ5EMVXT.mjs +411 -0
- package/dist/lib/node-esm/chunk-AQ5EMVXT.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs +18 -0
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs +21 -0
- package/dist/lib/node-esm/chunk-ZXIPFXSA.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1130 -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 +3 -9
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +6 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +40 -37
- package/src/ThreadPlugin.tsx +125 -305
- package/src/components/Chat.stories.tsx +7 -4
- package/src/components/ChatContainer.tsx +7 -8
- package/src/components/CommentContainer.tsx +18 -19
- package/src/components/Comments.stories.tsx +9 -6
- package/src/components/CommentsContainer.tsx +18 -31
- package/src/components/MessageContainer.tsx +12 -22
- package/src/components/ThreadComplementary.tsx +113 -0
- 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 +15 -0
- package/src/types.ts +8 -3
- package/dist/lib/browser/chunk-5IBJO53M.mjs +0 -21
- 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/meta.tsx +0 -20
- package/src/types.test.ts +0 -13
- /package/dist/lib/browser/{ThreadArticle-O64ZECMF.mjs.map → ThreadArticle-3W6SWGHG.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ZDCRC6RG.mjs.map → ThreadMain-D7CU4Z57.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-DO56JXBU.cjs.map → ThreadArticle-LNYJIQRO.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-XJX75GHG.cjs.map → ThreadMain-PQWIG7M5.cjs.map} +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
7
8
|
import React, { useEffect, useState } from 'react';
|
|
8
9
|
|
|
9
10
|
import { SurfaceProvider } from '@dxos/app-framework';
|
|
@@ -64,13 +65,15 @@ const Story = () => {
|
|
|
64
65
|
);
|
|
65
66
|
};
|
|
66
67
|
|
|
67
|
-
export
|
|
68
|
-
|
|
68
|
+
export const Default = {};
|
|
69
|
+
|
|
70
|
+
const meta: Meta = {
|
|
71
|
+
title: 'plugins/plugin-thread/Chat',
|
|
69
72
|
component: Thread,
|
|
70
|
-
// TODO(
|
|
73
|
+
// TODO(burdon): Use decorator.
|
|
71
74
|
render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType]} />,
|
|
72
75
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
73
76
|
parameters: { translations },
|
|
74
77
|
};
|
|
75
78
|
|
|
76
|
-
export
|
|
79
|
+
export default meta;
|
|
@@ -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} />
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
7
8
|
import React, { useEffect, useState } from 'react';
|
|
8
9
|
|
|
9
10
|
import { MessageType, ThreadType } from '@dxos/plugin-space/types';
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
11
|
-
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
12
|
+
import { Filter, fullyQualifiedId, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
12
13
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
13
14
|
import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
|
|
14
15
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
@@ -30,7 +31,7 @@ const Story = ({ spaceKey }: ClientRepeatedComponentProps) => {
|
|
|
30
31
|
const t = setTimeout(async () => {
|
|
31
32
|
space.db.add(createCommentThread(identity));
|
|
32
33
|
const thread = space.db.add(createCommentThread(identity));
|
|
33
|
-
setDetached([thread
|
|
34
|
+
setDetached([fullyQualifiedId(thread)]);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
return () => clearTimeout(t);
|
|
@@ -50,12 +51,14 @@ const Story = ({ spaceKey }: ClientRepeatedComponentProps) => {
|
|
|
50
51
|
);
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
export const Default = {};
|
|
55
|
+
|
|
56
|
+
const meta: Meta = {
|
|
57
|
+
title: 'plugins/plugin-thread/Comments',
|
|
58
|
+
// TODO(wittjosiah): Use decorator.
|
|
56
59
|
render: () => <ClientRepeater component={Story} createIdentity createSpace types={[ThreadType, MessageType]} />,
|
|
57
60
|
decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
|
|
58
61
|
parameters: { translations },
|
|
59
62
|
};
|
|
60
63
|
|
|
61
|
-
export
|
|
64
|
+
export default meta;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { ChatText
|
|
5
|
+
import { ChatText } from '@phosphor-icons/react';
|
|
6
6
|
import React, { useEffect } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type ThreadType } from '@dxos/plugin-space/types';
|
|
9
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
9
10
|
import { useTranslation, Trans } from '@dxos/react-ui';
|
|
10
|
-
import { PlankHeading, plankHeadingIconProps } from '@dxos/react-ui-deck';
|
|
11
11
|
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
12
12
|
|
|
13
13
|
import { CommentContainer } from './CommentContainer';
|
|
@@ -24,7 +24,6 @@ export type ThreadsContainerProps = Omit<
|
|
|
24
24
|
*/
|
|
25
25
|
detached?: string[];
|
|
26
26
|
currentId?: string;
|
|
27
|
-
autoFocusCurrentTextbox?: boolean;
|
|
28
27
|
showResolvedThreads?: boolean;
|
|
29
28
|
onThreadAttend?: (thread: ThreadType) => void;
|
|
30
29
|
onThreadDelete?: (thread: ThreadType) => void;
|
|
@@ -33,19 +32,6 @@ export type ThreadsContainerProps = Omit<
|
|
|
33
32
|
onComment?: (thread: ThreadType) => void;
|
|
34
33
|
};
|
|
35
34
|
|
|
36
|
-
export const CommentsHeading = ({ attendableId }: { attendableId?: string }) => {
|
|
37
|
-
const { t } = useTranslation(THREAD_PLUGIN);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div role='none' className='flex items-center'>
|
|
41
|
-
<PlankHeading.Button attendableId={attendableId}>
|
|
42
|
-
<Quotes {...plankHeadingIconProps} />
|
|
43
|
-
</PlankHeading.Button>
|
|
44
|
-
<PlankHeading.Label attendableId={attendableId}>{t('comments heading')}</PlankHeading.Label>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
35
|
/**
|
|
50
36
|
* Comment threads.
|
|
51
37
|
*/
|
|
@@ -53,7 +39,6 @@ export const CommentsContainer = ({
|
|
|
53
39
|
threads,
|
|
54
40
|
detached = [],
|
|
55
41
|
currentId,
|
|
56
|
-
autoFocusCurrentTextbox,
|
|
57
42
|
showResolvedThreads,
|
|
58
43
|
onThreadAttend,
|
|
59
44
|
onThreadDelete,
|
|
@@ -73,20 +58,22 @@ export const CommentsContainer = ({
|
|
|
73
58
|
return (
|
|
74
59
|
<>
|
|
75
60
|
{filteredThreads.length > 0 ? (
|
|
76
|
-
filteredThreads.map((thread) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
filteredThreads.map((thread) => {
|
|
62
|
+
const threadId = fullyQualifiedId(thread);
|
|
63
|
+
return (
|
|
64
|
+
<CommentContainer
|
|
65
|
+
key={threadId}
|
|
66
|
+
thread={thread}
|
|
67
|
+
current={currentId === threadId}
|
|
68
|
+
detached={detached.includes(threadId)}
|
|
69
|
+
{...(onThreadAttend && { onAttend: () => onThreadAttend(thread) })}
|
|
70
|
+
{...(onThreadDelete && { onThreadDelete: () => onThreadDelete(thread) })}
|
|
71
|
+
{...(onMessageDelete && { onMessageDelete: (messageId: string) => onMessageDelete(thread, messageId) })}
|
|
72
|
+
{...(onThreadToggleResolved && { onResolve: () => onThreadToggleResolved(thread) })}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
})
|
|
90
77
|
) : (
|
|
91
78
|
<div
|
|
92
79
|
role='alert'
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
|
+
|
|
4
5
|
import { EditorView } from '@codemirror/view';
|
|
5
6
|
import { Check, PencilSimple, X } from '@phosphor-icons/react';
|
|
6
7
|
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
7
8
|
|
|
8
9
|
import { Surface } from '@dxos/app-framework';
|
|
9
|
-
import { type Expando, type EchoReactiveObject } from '@dxos/echo-schema';
|
|
10
10
|
import { type MessageType } from '@dxos/plugin-space/types';
|
|
11
11
|
import { PublicKey } from '@dxos/react-client';
|
|
12
|
-
import { type SpaceMember } from '@dxos/react-client/echo';
|
|
12
|
+
import { type EchoReactiveObject, type Expando, type SpaceMember } from '@dxos/react-client/echo';
|
|
13
13
|
import { useIdentity, type Identity } from '@dxos/react-client/halo';
|
|
14
|
-
import {
|
|
15
|
-
Button,
|
|
16
|
-
ButtonGroup,
|
|
17
|
-
DensityProvider,
|
|
18
|
-
Tooltip,
|
|
19
|
-
useOnTransition,
|
|
20
|
-
useThemeContext,
|
|
21
|
-
useTranslation,
|
|
22
|
-
} from '@dxos/react-ui';
|
|
14
|
+
import { Button, ButtonGroup, Tooltip, useOnTransition, useThemeContext, useTranslation } from '@dxos/react-ui';
|
|
23
15
|
import { createBasicExtensions, createThemeExtensions, useTextEditor } from '@dxos/react-ui-editor';
|
|
24
16
|
import { Mosaic, type MosaicTileComponent } from '@dxos/react-ui-mosaic';
|
|
25
17
|
import {
|
|
@@ -91,7 +83,7 @@ export const MessageContainer = ({
|
|
|
91
83
|
variant='ghost'
|
|
92
84
|
data-testid='thread.message.delete'
|
|
93
85
|
classNames={messageControlClassNames}
|
|
94
|
-
onClick={handleDelete}
|
|
86
|
+
onClick={() => handleDelete()}
|
|
95
87
|
>
|
|
96
88
|
<span className='sr-only'>{deleteLabel}</span>
|
|
97
89
|
<X className={getSize(4)} />
|
|
@@ -184,16 +176,14 @@ const MessageBlockObjectTile: MosaicTileComponent<EchoReactiveObject<any>> = for
|
|
|
184
176
|
style={draggableStyle}
|
|
185
177
|
ref={forwardedRef}
|
|
186
178
|
>
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/>
|
|
196
|
-
</DensityProvider>
|
|
179
|
+
<Surface
|
|
180
|
+
role='card'
|
|
181
|
+
limit={1}
|
|
182
|
+
data={{ content: item }}
|
|
183
|
+
draggableProps={draggableProps}
|
|
184
|
+
fallback={title}
|
|
185
|
+
{...props}
|
|
186
|
+
/>
|
|
197
187
|
</div>
|
|
198
188
|
);
|
|
199
189
|
},
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { LayoutAction, type Plugin, useIntentDispatcher, useResolvePlugins } from '@dxos/app-framework';
|
|
8
|
+
import { type ThreadType } from '@dxos/plugin-space';
|
|
9
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
10
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
11
|
+
import { useAttended } from '@dxos/react-ui-attention';
|
|
12
|
+
import { nonNullable } from '@dxos/util';
|
|
13
|
+
|
|
14
|
+
import { CommentsContainer } from '../components';
|
|
15
|
+
import { THREAD_PLUGIN } from '../meta';
|
|
16
|
+
import { ThreadAction, type ThreadProvides } from '../types';
|
|
17
|
+
|
|
18
|
+
const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undefined =>
|
|
19
|
+
'thread' in plugin.provides ? (plugin as Plugin<ThreadProvides<any>>) : undefined;
|
|
20
|
+
|
|
21
|
+
export const ThreadComplementary = ({
|
|
22
|
+
role,
|
|
23
|
+
subject,
|
|
24
|
+
drafts,
|
|
25
|
+
current,
|
|
26
|
+
showResolvedThreads,
|
|
27
|
+
}: {
|
|
28
|
+
role: string;
|
|
29
|
+
subject: any;
|
|
30
|
+
drafts: ThreadType[] | undefined;
|
|
31
|
+
current?: string;
|
|
32
|
+
showResolvedThreads?: boolean;
|
|
33
|
+
}) => {
|
|
34
|
+
const dispatch = useIntentDispatcher();
|
|
35
|
+
|
|
36
|
+
const threadsIntegrators = useResolvePlugins(providesThreadsConfig);
|
|
37
|
+
const threadProvides = useMemo(() => threadsIntegrators.map((p) => p.provides.thread), [threadsIntegrators]);
|
|
38
|
+
const createSort = useMemo(
|
|
39
|
+
() => threadProvides.find((p) => p.predicate(subject))?.createSort,
|
|
40
|
+
[threadProvides, subject],
|
|
41
|
+
);
|
|
42
|
+
const sort = useMemo(() => createSort?.(subject), [createSort, subject]);
|
|
43
|
+
|
|
44
|
+
const threads = useMemo(() => {
|
|
45
|
+
return subject.threads.concat(drafts ?? []).filter(nonNullable) as ThreadType[];
|
|
46
|
+
}, [JSON.stringify(subject.threads), JSON.stringify(drafts)]);
|
|
47
|
+
|
|
48
|
+
const detachedIds = useMemo(() => {
|
|
49
|
+
return threads.filter(({ anchor }) => !anchor).map((thread) => fullyQualifiedId(thread));
|
|
50
|
+
}, [threads]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!sort) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
threads.sort((a, b) => sort(a?.anchor, b?.anchor));
|
|
57
|
+
}, [sort, threads]);
|
|
58
|
+
|
|
59
|
+
const attended = useAttended();
|
|
60
|
+
const qualifiedSubjectId = fullyQualifiedId(subject);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div role='none' className='contents'>
|
|
64
|
+
<ScrollArea.Root classNames='row-span-2'>
|
|
65
|
+
<ScrollArea.Viewport>
|
|
66
|
+
<CommentsContainer
|
|
67
|
+
threads={threads}
|
|
68
|
+
detached={detachedIds}
|
|
69
|
+
currentId={attended.includes(qualifiedSubjectId) ? current : undefined}
|
|
70
|
+
showResolvedThreads={showResolvedThreads}
|
|
71
|
+
onThreadAttend={(thread) => {
|
|
72
|
+
const threadId = fullyQualifiedId(thread);
|
|
73
|
+
if (current !== threadId) {
|
|
74
|
+
current = threadId;
|
|
75
|
+
|
|
76
|
+
void dispatch?.([
|
|
77
|
+
{
|
|
78
|
+
action: LayoutAction.SCROLL_INTO_VIEW,
|
|
79
|
+
data: {
|
|
80
|
+
id: fullyQualifiedId(subject),
|
|
81
|
+
thread: threadId,
|
|
82
|
+
cursor: thread.anchor,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
}}
|
|
88
|
+
onThreadDelete={(thread) => {
|
|
89
|
+
return dispatch({ plugin: THREAD_PLUGIN, action: ThreadAction.DELETE, data: { subject, thread } });
|
|
90
|
+
}}
|
|
91
|
+
onMessageDelete={(thread, messageId) =>
|
|
92
|
+
dispatch({
|
|
93
|
+
plugin: THREAD_PLUGIN,
|
|
94
|
+
action: ThreadAction.DELETE_MESSAGE,
|
|
95
|
+
data: { subject, thread, messageId },
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
onThreadToggleResolved={(thread) =>
|
|
99
|
+
dispatch({ plugin: THREAD_PLUGIN, action: ThreadAction.TOGGLE_RESOLVED, data: { thread } })
|
|
100
|
+
}
|
|
101
|
+
onComment={(thread) => {
|
|
102
|
+
void dispatch({ action: ThreadAction.ON_MESSAGE_ADD, data: { thread, subject } });
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<div role='none' className='bs-10' />
|
|
106
|
+
<ScrollArea.Scrollbar>
|
|
107
|
+
<ScrollArea.Thumb />
|
|
108
|
+
</ScrollArea.Scrollbar>
|
|
109
|
+
</ScrollArea.Viewport>
|
|
110
|
+
</ScrollArea.Root>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
export const ThreadArticle = React.lazy(() => import('./ThreadArticle'));
|
|
7
|
+
export const ThreadMain = lazy(() => import('./ThreadMain'));
|
|
8
|
+
export const ThreadArticle = lazy(() => import('./ThreadArticle'));
|
|
10
9
|
|
|
11
10
|
// TODO(wittjosiah): Suspense boundary for sidebar?
|
|
12
|
-
export * from './ThreadSettings';
|
|
13
11
|
export * from './CommentContainer';
|
|
14
|
-
export * from './MessageContainer';
|
|
15
12
|
export * from './CommentsContainer';
|
|
16
13
|
export * from './ChatContainer';
|
|
14
|
+
export * from './MessageContainer';
|
|
15
|
+
export * from './ThreadComplementary';
|
|
16
|
+
export * from './ThreadSettings';
|
package/src/components/types.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type EchoReactiveObject } from '@dxos/echo
|
|
5
|
+
import { type EchoReactiveObject } from '@dxos/client/echo';
|
|
6
6
|
import { type ThreadType } from '@dxos/plugin-space/types';
|
|
7
|
-
import type
|
|
7
|
+
import { type ThreadProps } from '@dxos/react-ui-thread';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Props for components which connect an ECHO Thread object to the UI component Thread.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import { computed, effect } from '@preact/signals-core';
|
|
8
|
+
|
|
9
|
+
import { type IntentDispatcher } from '@dxos/app-framework';
|
|
10
|
+
import { type DocumentType } from '@dxos/plugin-markdown/types';
|
|
11
|
+
import { ThreadType } from '@dxos/plugin-space/types';
|
|
12
|
+
import { getSpace, getTextInRange, createDocAccessor, fullyQualifiedId } from '@dxos/react-client/echo';
|
|
13
|
+
import { comments, createExternalCommentSync } from '@dxos/react-ui-editor';
|
|
14
|
+
import { nonNullable } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { ThreadAction, type ThreadState } from '../types';
|
|
17
|
+
|
|
18
|
+
// TODO(burdon): Factor out.
|
|
19
|
+
const getName = (doc: DocumentType, anchor: string): string | undefined => {
|
|
20
|
+
if (doc.content) {
|
|
21
|
+
const [start, end] = anchor.split(':');
|
|
22
|
+
return getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Construct plugins.
|
|
28
|
+
*/
|
|
29
|
+
export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: IntentDispatcher): Extension => {
|
|
30
|
+
const space = doc && getSpace(doc);
|
|
31
|
+
if (!doc || !space || !dispatch) {
|
|
32
|
+
// Include no-op comments extension here to ensure that the facets are always present when they are expected.
|
|
33
|
+
// TODO(wittjosiah): The Editor should only look for these facets when comments are available.
|
|
34
|
+
return [comments()];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
|
|
38
|
+
const threads = computed(() =>
|
|
39
|
+
[...doc.threads.filter(nonNullable), ...(state.drafts[fullyQualifiedId(doc)] ?? [])].filter(
|
|
40
|
+
(thread) => !(thread?.status === 'resolved'),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
EditorView.updateListener.of((update) => {
|
|
46
|
+
if (update.docChanged) {
|
|
47
|
+
doc.threads.forEach((thread) => {
|
|
48
|
+
if (thread?.anchor) {
|
|
49
|
+
// Only update if the name has changed, otherwise this will cause an infinite loop.
|
|
50
|
+
// Skip if the name is empty; this means comment text was deleted, but thread name should remain.
|
|
51
|
+
const name = getName(doc, thread.anchor);
|
|
52
|
+
if (name && name !== thread.name) {
|
|
53
|
+
thread.name = name;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
createExternalCommentSync(
|
|
61
|
+
fullyQualifiedId(doc),
|
|
62
|
+
(sink) => effect(() => sink()),
|
|
63
|
+
() =>
|
|
64
|
+
threads.value
|
|
65
|
+
.filter((thread) => thread?.anchor)
|
|
66
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
comments({
|
|
70
|
+
id: fullyQualifiedId(doc),
|
|
71
|
+
onCreate: ({ cursor }) => {
|
|
72
|
+
const name = getName(doc, cursor);
|
|
73
|
+
void dispatch({
|
|
74
|
+
action: ThreadAction.CREATE,
|
|
75
|
+
data: {
|
|
76
|
+
cursor,
|
|
77
|
+
name,
|
|
78
|
+
subject: doc,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
onDelete: ({ id }) => {
|
|
83
|
+
const draft = state.drafts[fullyQualifiedId(doc)];
|
|
84
|
+
if (draft) {
|
|
85
|
+
const index = draft.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
86
|
+
if (index !== -1) {
|
|
87
|
+
draft.splice(index, 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const thread = doc.threads.find((thread) => thread?.id === id);
|
|
92
|
+
if (thread) {
|
|
93
|
+
thread.anchor = undefined;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onUpdate: ({ id, cursor }) => {
|
|
97
|
+
const thread =
|
|
98
|
+
state.drafts[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
99
|
+
doc.threads.find((thread) => thread?.id === id);
|
|
100
|
+
|
|
101
|
+
if (thread instanceof ThreadType && thread.anchor) {
|
|
102
|
+
thread.name = getName(doc, thread.anchor);
|
|
103
|
+
thread.anchor = cursor;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onSelect: ({ selection: { current, closest } }) => {
|
|
107
|
+
void dispatch({
|
|
108
|
+
action: ThreadAction.SELECT,
|
|
109
|
+
data: {
|
|
110
|
+
current: current ?? closest,
|
|
111
|
+
skipOpen: true,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
};
|
package/src/meta.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type PluginMeta } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
export const THREAD_PLUGIN = 'dxos.org/plugin/thread';
|
|
8
|
+
export const THREAD_ITEM = `${THREAD_PLUGIN}/item`;
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
id: THREAD_PLUGIN,
|
|
12
|
+
name: 'Threads',
|
|
13
|
+
description: 'Comment threads on documents.',
|
|
14
|
+
icon: 'ph--chat--regular',
|
|
15
|
+
} satisfies PluginMeta;
|