Skip to content

Commit 6981efc

Browse files
authored
Restore time for current user's messages, and allow showing deleted messages (#325)
* Add a setting to show the deleted messages in chat * Add a test for the setting * Restore a minimal header for current user, containing message time and status (deleted/edited) * ui-test on time visibility * Add the config option in the documentation * lint
1 parent e3ff747 commit 6981efc

File tree

7 files changed

+212
-72
lines changed

7 files changed

+212
-72
lines changed

docs/source/developers/developing_extensions/extension-providing-chat.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ interface IConfig {
189189
* Whether to enable or not the code toolbar.
190190
*/
191191
enableCodeToolbar?: boolean;
192+
/**
193+
* Whether to display deleted messages.
194+
*/
195+
showDeleted?: boolean;
192196
}
193197
```
194198

packages/jupyter-chat/src/components/messages/header.tsx

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ type ChatMessageHeaderProps = {
3030
* The message header component.
3131
*/
3232
export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
33-
// Don't render header for stacked messages or current user messages
34-
if (props.message.stacked || props.isCurrentUser) {
33+
const message = props.message;
34+
// Don't render header for stacked messages not deleted or edited.
35+
if (message.stacked && !message.deleted && !message.edited) {
3536
return <></>;
3637
}
38+
39+
// Flag to display only the deleted or edited information (stacked message).
40+
const onlyState = message.stacked && (message.deleted || message.edited);
41+
3742
const [datetime, setDatetime] = useState<Record<number, string>>({});
38-
const message = props.message;
3943
const sender = message.sender;
4044
/**
4145
* Effect: update cached datetime strings upon receiving a new message.
@@ -88,10 +92,10 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
8892
'& > :not(:last-child)': {
8993
marginRight: 3
9094
},
91-
marginBottom: message.stacked ? '0px' : '12px'
95+
marginBottom: message.stacked || props.isCurrentUser ? '0px' : '12px'
9296
}}
9397
>
94-
{avatar}
98+
{!props.isCurrentUser && !onlyState && avatar}
9599
<Box
96100
sx={{
97101
display: 'flex',
@@ -102,7 +106,7 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
102106
}}
103107
>
104108
<Box sx={{ display: 'flex', alignItems: 'center' }}>
105-
{!message.stacked && (
109+
{!onlyState && !props.isCurrentUser && (
106110
<Typography
107111
sx={{
108112
fontWeight: 700,
@@ -124,17 +128,19 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
124128
</Typography>
125129
)}
126130
</Box>
127-
<Typography
128-
className={MESSAGE_TIME_CLASS}
129-
sx={{
130-
fontSize: '0.8em',
131-
color: 'var(--jp-ui-font-color2)',
132-
fontWeight: 300
133-
}}
134-
title={message.raw_time ? 'Unverified time' : ''}
135-
>
136-
{`${datetime[message.time]}${message.raw_time ? '*' : ''}`}
137-
</Typography>
131+
{!onlyState && (
132+
<Typography
133+
className={MESSAGE_TIME_CLASS}
134+
sx={{
135+
fontSize: '0.8em',
136+
color: 'var(--jp-ui-font-color2)',
137+
fontWeight: 300
138+
}}
139+
title={message.raw_time ? 'Unverified time' : ''}
140+
>
141+
{`${datetime[message.time]}${message.raw_time ? '*' : ''}`}
142+
</Typography>
143+
)}
138144
</Box>
139145
</Box>
140146
);

packages/jupyter-chat/src/components/messages/messages.tsx

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { Navigation } from './navigation';
1515
import { WelcomeMessage } from './welcome';
1616
import { ScrollContainer } from '../scroll-container';
1717
import { useChatContext } from '../../context';
18-
import { IChatMessage } from '../../types';
18+
import { IChatMessage, IConfig } from '../../types';
19+
import { IChatModel } from '../../model';
1920

2021
export const MESSAGE_CLASS = 'jp-chat-message';
2122
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
@@ -31,6 +32,9 @@ export function ChatMessages(): JSX.Element {
3132
const [messages, setMessages] = useState<IChatMessage[]>(model.messages);
3233
const refMsgBox = useRef<HTMLDivElement>(null);
3334
const [allRendered, setAllRendered] = useState<boolean>(false);
35+
const [showDeleted, setShowDeleted] = useState<boolean>(
36+
model.config.showDeleted ?? false
37+
);
3438

3539
// The list of message DOM and their rendered promises.
3640
const listRef = useRef<(HTMLDivElement | null)[]>([]);
@@ -60,14 +64,29 @@ export function ChatMessages(): JSX.Element {
6064
function handleChatEvents() {
6165
setMessages([...model.messages]);
6266
}
63-
6467
model.messagesUpdated.connect(handleChatEvents);
6568

66-
return function cleanup() {
69+
return () => {
6770
model.messagesUpdated.disconnect(handleChatEvents);
6871
};
6972
}, [model]);
7073

74+
/**
75+
* Effect: Listen to the config change.
76+
*/
77+
useEffect(() => {
78+
function handleConfigChange(_: IChatModel, config: IConfig) {
79+
if (config.showDeleted !== showDeleted) {
80+
setShowDeleted(config.showDeleted ?? false);
81+
}
82+
}
83+
model.configChanged.connect(handleConfigChange);
84+
85+
return () => {
86+
model.configChanged.disconnect(handleConfigChange);
87+
};
88+
}, [model, showDeleted]);
89+
7190
/**
7291
* Observe the messages to update the current viewport and the unread messages.
7392
*/
@@ -148,47 +167,49 @@ export function ChatMessages(): JSX.Element {
148167
ref={refMsgBox}
149168
className={clsx(MESSAGES_BOX_CLASS)}
150169
>
151-
{messages
152-
.filter(message => !message.deleted)
153-
.map((message, i) => {
154-
renderedPromise.current[i] = new PromiseDelegate();
155-
const isCurrentUser =
156-
model.user !== undefined &&
157-
model.user.username === message.sender.username;
158-
return (
159-
// extra div needed to ensure each bubble is on a new line
160-
<Box
161-
key={i}
162-
sx={{
163-
...(isCurrentUser && {
164-
marginLeft: area === 'main' ? '25%' : '10%',
165-
backgroundColor: 'var(--jp-layout-color2)',
166-
border: 'none',
167-
borderRadius: 2,
168-
padding: 2
169-
})
170-
}}
171-
className={clsx(
172-
MESSAGE_CLASS,
173-
message.stacked ? MESSAGE_STACKED_CLASS : ''
174-
)}
175-
>
176-
<ChatMessageHeader
177-
message={message}
178-
isCurrentUser={isCurrentUser}
179-
/>
180-
<ChatMessage
181-
message={message}
182-
index={i}
183-
renderedPromise={renderedPromise.current[i]}
184-
ref={el => (listRef.current[i] = el)}
185-
/>
186-
{messageFooterRegistry && (
187-
<MessageFooterComponent message={message} />
188-
)}
189-
</Box>
190-
);
191-
})}
170+
{/* Filter the deleted message if user don't expect to see it. */}
171+
{(showDeleted
172+
? messages
173+
: messages.filter(message => !message.deleted)
174+
).map((message, i) => {
175+
renderedPromise.current[i] = new PromiseDelegate();
176+
const isCurrentUser =
177+
model.user !== undefined &&
178+
model.user.username === message.sender.username;
179+
return (
180+
// extra div needed to ensure each bubble is on a new line
181+
<Box
182+
key={i}
183+
sx={{
184+
...(isCurrentUser && {
185+
marginLeft: area === 'main' ? '25%' : '10%',
186+
backgroundColor: 'var(--jp-layout-color2)',
187+
border: 'none',
188+
borderRadius: 2,
189+
padding: 2
190+
})
191+
}}
192+
className={clsx(
193+
MESSAGE_CLASS,
194+
message.stacked ? MESSAGE_STACKED_CLASS : ''
195+
)}
196+
>
197+
<ChatMessageHeader
198+
message={message}
199+
isCurrentUser={isCurrentUser}
200+
/>
201+
<ChatMessage
202+
message={message}
203+
index={i}
204+
renderedPromise={renderedPromise.current[i]}
205+
ref={el => (listRef.current[i] = el)}
206+
/>
207+
{messageFooterRegistry && (
208+
<MessageFooterComponent message={message} />
209+
)}
210+
</Box>
211+
);
212+
})}
192213
</Box>
193214
</ScrollContainer>
194215
<Navigation refMsgBox={refMsgBox} allRendered={allRendered} />

packages/jupyter-chat/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export interface IConfig {
5252
* Whether to send typing notification.
5353
*/
5454
sendTypingNotification?: boolean;
55+
/**
56+
* Whether to display deleted messages.
57+
*/
58+
showDeleted?: boolean;
5559
}
5660

5761
/**

packages/jupyterlab-chat-extension/schema/factory.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
"default": true,
3838
"readOnly": false
3939
},
40+
"showDeleted": {
41+
"description": "Show the deleted messages.",
42+
"type": "boolean",
43+
"default": false,
44+
"readOnly": false
45+
},
4046
"defaultDirectory": {
4147
"description": "Default directory where to create and look for chat, the jupyter root directory if empty.",
4248
"type": "string",

packages/jupyterlab-chat-extension/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ const chatConfig: JupyterFrontEndPlugin<IWidgetConfig> = {
247247
.composite as boolean,
248248
sendTypingNotification: setting.get('sendTypingNotification')
249249
.composite as boolean,
250+
showDeleted: setting.get('showDeleted').composite as boolean,
250251
defaultDirectory: currentDirectory
251252
};
252253
});

0 commit comments

Comments
 (0)