@dxos/react-ui-thread 0.3.11-main.0e02eb0
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/LICENSE +8 -0
- package/README.md +3 -0
- package/dist/lib/browser/index.mjs +246 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/types/src/Message/Message.d.ts +30 -0
- package/dist/types/src/Message/Message.d.ts.map +1 -0
- package/dist/types/src/Message/Message.stories.d.ts +28 -0
- package/dist/types/src/Message/Message.stories.d.ts.map +1 -0
- package/dist/types/src/Message/command-extension.d.ts +3 -0
- package/dist/types/src/Message/command-extension.d.ts.map +1 -0
- package/dist/types/src/Message/index.d.ts +2 -0
- package/dist/types/src/Message/index.d.ts.map +1 -0
- package/dist/types/src/Thread/Comments.stories.d.ts +61 -0
- package/dist/types/src/Thread/Comments.stories.d.ts.map +1 -0
- package/dist/types/src/Thread/Thread.d.ts +14 -0
- package/dist/types/src/Thread/Thread.d.ts.map +1 -0
- package/dist/types/src/Thread/Thread.stories.d.ts +29 -0
- package/dist/types/src/Thread/Thread.stories.d.ts.map +1 -0
- package/dist/types/src/Thread/index.d.ts +2 -0
- package/dist/types/src/Thread/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +19 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +19 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/Message/Message.stories.tsx +45 -0
- package/src/Message/Message.tsx +186 -0
- package/src/Message/command-extension.ts +40 -0
- package/src/Message/index.ts +5 -0
- package/src/Thread/Comments.stories.tsx +368 -0
- package/src/Thread/Thread.stories.tsx +77 -0
- package/src/Thread/Thread.tsx +82 -0
- package/src/Thread/index.ts +5 -0
- package/src/index.ts +10 -0
- package/src/translations.ts +24 -0
- package/src/types.ts +26 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const translationKey = "thread";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
'en-US': {
|
|
4
|
+
thread: {
|
|
5
|
+
'plugin name': string;
|
|
6
|
+
'thread title placeholder': string;
|
|
7
|
+
'thread title label': string;
|
|
8
|
+
'delete thread label': string;
|
|
9
|
+
'create thread label': string;
|
|
10
|
+
'message placeholder': string;
|
|
11
|
+
'comment placeholder': string;
|
|
12
|
+
'settings standalone label': string;
|
|
13
|
+
'anonymous label': string;
|
|
14
|
+
'enter to send message': string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}[];
|
|
18
|
+
export default _default;
|
|
19
|
+
//# sourceMappingURL=translations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,WAAW,CAAC;;;;;;;;;;;;;;;;;AAEvC,wBAiBE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AvatarStatus } from '@dxos/react-ui';
|
|
2
|
+
export type MessageMetadata = {
|
|
3
|
+
id: string;
|
|
4
|
+
authorId: string;
|
|
5
|
+
authorName?: string;
|
|
6
|
+
authorImgSrc?: string;
|
|
7
|
+
authorStatus?: AvatarStatus;
|
|
8
|
+
};
|
|
9
|
+
export type MessageEntityBlock<BlockValue> = BlockValue & {
|
|
10
|
+
timestamp?: string;
|
|
11
|
+
};
|
|
12
|
+
export type MessageEntity<BlockValue> = MessageMetadata & {
|
|
13
|
+
blocks: MessageEntityBlock<BlockValue>[];
|
|
14
|
+
};
|
|
15
|
+
export type ThreadEntity = {
|
|
16
|
+
topicId?: string;
|
|
17
|
+
topicTitle?: string;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,CAAC,UAAU,IAAI,UAAU,GAAG;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,UAAU,IAAI,eAAe,GAAG;IACxD,MAAM,EAAE,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/react-ui-thread",
|
|
3
|
+
"version": "0.3.11-main.0e02eb0",
|
|
4
|
+
"description": "A set of components to use with threads, including comments and messages.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"main": "dist/lib/node/index.cjs",
|
|
10
|
+
"browser": {
|
|
11
|
+
"./dist/lib/node/index.cjs": "./dist/lib/browser/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"types": "dist/types/src/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@codemirror/language": "^6.10.0",
|
|
20
|
+
"@codemirror/state": "^6.4.0",
|
|
21
|
+
"@radix-ui/react-primitive": "^1.0.2",
|
|
22
|
+
"date-fns": "^3.3.1",
|
|
23
|
+
"@dxos/react-ui": "0.3.11-main.0e02eb0",
|
|
24
|
+
"@dxos/react-ui-mosaic": "0.3.11-main.0e02eb0",
|
|
25
|
+
"@dxos/react-ui-editor": "0.3.11-main.0e02eb0",
|
|
26
|
+
"@dxos/react-ui-theme": "0.3.11-main.0e02eb0",
|
|
27
|
+
"@dxos/util": "0.3.11-main.0e02eb0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@faker-js/faker": "^8.3.1",
|
|
31
|
+
"@phosphor-icons/react": "^2.0.5",
|
|
32
|
+
"@types/react": "^18.0.21",
|
|
33
|
+
"@types/react-dom": "^18.0.6",
|
|
34
|
+
"react": "^18.2.0",
|
|
35
|
+
"react-dom": "^18.2.0",
|
|
36
|
+
"vite": "^5.0.12",
|
|
37
|
+
"vite-plugin-turbosnap": "^1.0.2",
|
|
38
|
+
"@dxos/keys": "0.3.11-main.0e02eb0",
|
|
39
|
+
"@dxos/echo-schema": "0.3.11-main.0e02eb0",
|
|
40
|
+
"@dxos/log": "0.3.11-main.0e02eb0",
|
|
41
|
+
"@dxos/storybook-utils": "0.3.11-main.0e02eb0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@phosphor-icons/react": "^2.0.5",
|
|
45
|
+
"react": "^18.2.0",
|
|
46
|
+
"react-dom": "^18.2.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { PublicKey } from '@dxos/keys';
|
|
10
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
11
|
+
|
|
12
|
+
import { Message } from './Message';
|
|
13
|
+
import translations from '../translations';
|
|
14
|
+
import { type MessageEntity } from '../types';
|
|
15
|
+
|
|
16
|
+
const Story = () => {
|
|
17
|
+
const [identityKey] = useState(PublicKey.random());
|
|
18
|
+
const [message] = useState<MessageEntity<{ id: string; text: string }>>({
|
|
19
|
+
id: 'm1',
|
|
20
|
+
authorId: identityKey.toHex(),
|
|
21
|
+
blocks: [
|
|
22
|
+
{
|
|
23
|
+
id: 'b1',
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
text: 'hello',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className='grid grid-cols-[3rem_1fr]'>
|
|
32
|
+
<Message {...message} />
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
title: 'react-ui-thread/Message',
|
|
39
|
+
component: Message,
|
|
40
|
+
render: Story,
|
|
41
|
+
decorators: [withTheme],
|
|
42
|
+
parameters: { translations },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Default = {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { X } from '@phosphor-icons/react';
|
|
6
|
+
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
|
|
7
|
+
import React, { type ComponentPropsWithRef, type FC, forwardRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Avatar, Button, type ThemedClassName, useJdenticonHref, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { TextEditor, type TextEditorProps, keymap, type EditorView, listener } from '@dxos/react-ui-editor';
|
|
11
|
+
import {
|
|
12
|
+
focusRing,
|
|
13
|
+
hoverableControlItem,
|
|
14
|
+
hoverableControls,
|
|
15
|
+
hoverableFocusedWithinControls,
|
|
16
|
+
mx,
|
|
17
|
+
} from '@dxos/react-ui-theme';
|
|
18
|
+
import { safeParseJson } from '@dxos/util';
|
|
19
|
+
|
|
20
|
+
import { command } from './command-extension';
|
|
21
|
+
import { translationKey } from '../translations';
|
|
22
|
+
import { type MessageEntity, type MessageEntityBlock, type MessageMetadata } from '../types';
|
|
23
|
+
|
|
24
|
+
const avatarSize = 7;
|
|
25
|
+
|
|
26
|
+
const messageCell = 'plb-1 min-is-0';
|
|
27
|
+
|
|
28
|
+
export type MessageMetaProps = ThemedClassName<ComponentPropsWithRef<'div'>> &
|
|
29
|
+
MessageMetadata &
|
|
30
|
+
Partial<{ continues: boolean }>;
|
|
31
|
+
|
|
32
|
+
export const MessageMeta = forwardRef<HTMLDivElement, MessageMetaProps>(
|
|
33
|
+
(
|
|
34
|
+
{ authorImgSrc, authorStatus, authorId, authorName, continues = true, children, classNames, ...rootProps },
|
|
35
|
+
forwardedRef,
|
|
36
|
+
) => {
|
|
37
|
+
const jdenticon = useJdenticonHref(authorId ?? '', avatarSize);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Avatar.Root status={authorStatus ?? 'inactive'} size={avatarSize}>
|
|
41
|
+
<div
|
|
42
|
+
role='none'
|
|
43
|
+
{...rootProps}
|
|
44
|
+
className={mx('grid grid-cols-subgrid col-span-2', classNames)}
|
|
45
|
+
ref={forwardedRef}
|
|
46
|
+
>
|
|
47
|
+
<div role='none' className={'flex flex-col items-center gap-2 ' + messageCell}>
|
|
48
|
+
<Avatar.Frame>
|
|
49
|
+
<Avatar.Fallback href={jdenticon} />
|
|
50
|
+
{authorImgSrc && <Avatar.Image href={authorImgSrc} />}
|
|
51
|
+
</Avatar.Frame>
|
|
52
|
+
{continues && <div role='none' className='is-px grow surface-separator' />}
|
|
53
|
+
</div>
|
|
54
|
+
<div role='none' className={messageCell}>
|
|
55
|
+
{children}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</Avatar.Root>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export type MessageBlockProps<BlockValue> = {
|
|
64
|
+
block: MessageEntityBlock<BlockValue>;
|
|
65
|
+
onBlockDelete?: () => void;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const DefaultMessageBlock = ({ block, onBlockDelete }: MessageBlockProps<{ data?: any; text?: string }>) => {
|
|
69
|
+
const contentWidth = onBlockDelete ? 'col-span-2' : 'col-span-3';
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
role='none'
|
|
73
|
+
className={mx('grid grid-cols-subgrid col-span-3', hoverableControls, hoverableFocusedWithinControls)}
|
|
74
|
+
>
|
|
75
|
+
{block.data ? (
|
|
76
|
+
<pre className={mx('font-mono max-is-full overflow-x-auto', contentWidth)}>
|
|
77
|
+
<code>{JSON.stringify(safeParseJson(block.data), undefined, 2)}</code>
|
|
78
|
+
</pre>
|
|
79
|
+
) : (
|
|
80
|
+
<p className={contentWidth}>{block.text ?? ''}</p>
|
|
81
|
+
)}
|
|
82
|
+
{onBlockDelete && (
|
|
83
|
+
<Button
|
|
84
|
+
variant='ghost'
|
|
85
|
+
classNames={['p-1 min-bs-0 mie-1 place-self-start transition-opacity', hoverableControlItem]}
|
|
86
|
+
onClick={onBlockDelete}
|
|
87
|
+
>
|
|
88
|
+
<X />
|
|
89
|
+
</Button>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type MessageProps<BlockValue> = MessageEntity<BlockValue> & {
|
|
96
|
+
onDelete?: (messageId: string, blockIndex: number) => void;
|
|
97
|
+
MessageBlockComponent?: FC<MessageBlockProps<BlockValue>>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const Message = <BlockValue,>(props: MessageProps<BlockValue>) => {
|
|
101
|
+
const { t, dtLocale } = useTranslation(translationKey);
|
|
102
|
+
|
|
103
|
+
const { authorName, onDelete, blocks, id, MessageBlockComponent = DefaultMessageBlock, ...metaProps } = props;
|
|
104
|
+
|
|
105
|
+
const firstBlock = blocks[0];
|
|
106
|
+
const dt = firstBlock.timestamp ? new Date(firstBlock.timestamp) : undefined;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<MessageMeta {...metaProps} id={id} continues>
|
|
110
|
+
<p className='grid grid-cols-[1fr_max-content] gap-2 pie-2'>
|
|
111
|
+
<Avatar.Label classNames={['truncate font-semibold', !authorName && 'fg-description']}>
|
|
112
|
+
{authorName ?? t('anonymous label')}
|
|
113
|
+
</Avatar.Label>
|
|
114
|
+
<time className='fg-description text-xs pbs-1' dateTime={dt?.toISOString()}>
|
|
115
|
+
{dt ? formatDistanceToNow(dt, { locale: dtLocale, addSuffix: true }) : ''}
|
|
116
|
+
</time>
|
|
117
|
+
</p>
|
|
118
|
+
<div role='none' className='grid gap-y-1 grid-cols-[min-content_1fr_min-content]'>
|
|
119
|
+
{blocks.map((block, i) => (
|
|
120
|
+
<MessageBlockComponent key={i} block={block} onBlockDelete={onDelete && (() => onDelete(id, i))} />
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
</MessageMeta>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type MessageTextboxProps = {
|
|
128
|
+
onSend?: () => void;
|
|
129
|
+
onClear?: () => void;
|
|
130
|
+
onEditorFocus?: () => void;
|
|
131
|
+
disabled?: boolean;
|
|
132
|
+
} & Omit<MessageMetadata, 'id' | 'authorStatus'> &
|
|
133
|
+
TextEditorProps;
|
|
134
|
+
|
|
135
|
+
export const MessageTextbox = forwardRef<EditorView, MessageTextboxProps>(
|
|
136
|
+
({ onSend, onClear, onEditorFocus, authorId, authorName, authorImgSrc, disabled, ...editorProps }, forwardedRef) => {
|
|
137
|
+
return (
|
|
138
|
+
<MessageMeta
|
|
139
|
+
{...{ id: editorProps.model.id, authorId, authorName, authorImgSrc }}
|
|
140
|
+
authorStatus='active'
|
|
141
|
+
continues={false}
|
|
142
|
+
>
|
|
143
|
+
<TextEditor
|
|
144
|
+
slots={{ root: { className: mx('plb-1 mie-1 rounded-sm', focusRing, disabled && 'opacity-50') } }}
|
|
145
|
+
readonly={disabled}
|
|
146
|
+
extensions={[
|
|
147
|
+
command,
|
|
148
|
+
keymap.of([
|
|
149
|
+
{
|
|
150
|
+
key: 'Enter',
|
|
151
|
+
run: () => {
|
|
152
|
+
if (onSend) {
|
|
153
|
+
onSend();
|
|
154
|
+
return true;
|
|
155
|
+
} else {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
key: 'Meta+Backspace',
|
|
162
|
+
run: () => {
|
|
163
|
+
if (onClear) {
|
|
164
|
+
onClear();
|
|
165
|
+
return true;
|
|
166
|
+
} else {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
]),
|
|
172
|
+
listener({
|
|
173
|
+
onFocus: (focused) => {
|
|
174
|
+
if (focused) {
|
|
175
|
+
onEditorFocus?.();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
]}
|
|
180
|
+
{...editorProps}
|
|
181
|
+
ref={forwardedRef}
|
|
182
|
+
/>
|
|
183
|
+
</MessageMeta>
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { StreamLanguage, HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
6
|
+
import { type Extension } from '@codemirror/state';
|
|
7
|
+
|
|
8
|
+
import { tags } from '@dxos/react-ui-editor';
|
|
9
|
+
import { tagRoot } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* https://github.com/codemirror/stream-parser/blob/main/test/test-stream-parser.ts
|
|
13
|
+
*/
|
|
14
|
+
const parser = StreamLanguage.define<{ count: number }>({
|
|
15
|
+
startState: () => ({ count: 0 }),
|
|
16
|
+
token: (stream, state) => {
|
|
17
|
+
state.count++;
|
|
18
|
+
if (stream.eatSpace()) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (state.count === 1 && stream.match(/^(\/\w+)/)) {
|
|
22
|
+
return 'tagName';
|
|
23
|
+
}
|
|
24
|
+
stream.next();
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* https://codemirror.net/examples/styling
|
|
31
|
+
* https://lezer.codemirror.net/docs/ref/#highlight
|
|
32
|
+
*/
|
|
33
|
+
const styles = HighlightStyle.define([
|
|
34
|
+
{
|
|
35
|
+
tag: tags.tagName,
|
|
36
|
+
class: tagRoot({}),
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const command: Extension = [parser, syntaxHighlighting(styles)];
|