Skip to content

Commit 1f447a3

Browse files
committed
Add a Message object emitting a signal on update
1 parent 3bde20f commit 1f447a3

File tree

10 files changed

+175
-97
lines changed

10 files changed

+175
-97
lines changed

packages/jupyter-chat/src/__tests__/model.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import { AbstractChatModel, IChatContext, IChatModel } from '../model';
11-
import { IChatMessage, INewMessage } from '../types';
11+
import { IMessageContent, INewMessage } from '../types';
1212
import { MockChatModel, MockChatContext } from './mocks';
1313

1414
describe('test chat model', () => {
@@ -27,7 +27,7 @@ describe('test chat model', () => {
2727

2828
describe('incoming message', () => {
2929
class TestChat extends AbstractChatModel implements IChatModel {
30-
protected formatChatMessage(message: IChatMessage): IChatMessage {
30+
protected formatChatMessage(message: IMessageContent): IMessageContent {
3131
message.body = 'formatted msg';
3232
return message;
3333
}
@@ -43,14 +43,14 @@ describe('test chat model', () => {
4343
}
4444

4545
let model: IChatModel;
46-
let messages: IChatMessage[];
46+
let messages: IMessageContent[];
4747
const msg = {
4848
type: 'msg',
4949
id: 'message1',
5050
time: Date.now() / 1000,
5151
body: 'message test',
5252
sender: { username: 'user' }
53-
} as IChatMessage;
53+
} as IMessageContent;
5454

5555
beforeEach(() => {
5656
messages = [];
@@ -76,7 +76,7 @@ describe('test chat model', () => {
7676
model.messageAdded({ ...msg });
7777
expect(messages).toHaveLength(1);
7878
expect(messages[0]).not.toBe(msg);
79-
expect((messages[0] as IChatMessage).body).toBe('formatted msg');
79+
expect((messages[0] as IMessageContent).body).toBe('formatted msg');
8080
});
8181
});
8282

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { Box, Typography } from '@mui/material';
77
import React, { useEffect, useState } from 'react';
88

99
import { Avatar } from '../avatar';
10-
import { IChatModel } from '../../model';
11-
import { IChatMessage } from '../../types';
10+
import { IMessageContent, IMessage } from '../../types';
1211

1312
const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
1413
const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
@@ -20,11 +19,7 @@ type ChatMessageHeaderProps = {
2019
/**
2120
* The chat message.
2221
*/
23-
message: IChatMessage;
24-
/**
25-
* The chat model.
26-
*/
27-
model: IChatModel;
22+
message: IMessage;
2823
/**
2924
* Whether this message is from the current user.
3025
*/
@@ -40,8 +35,9 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
4035
return <></>;
4136
}
4237
const [datetime, setDatetime] = useState<Record<number, string>>({});
43-
const model = props.model;
44-
const [message, setMessage] = useState<IChatMessage>(props.message);
38+
const [message, setMessage] = useState<IMessageContent>(
39+
props.message.content
40+
);
4541
const sender = message.sender;
4642
/**
4743
* Effect: update cached datetime strings upon receiving a new message.
@@ -82,16 +78,14 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
8278

8379
// Listen for changes in the current message.
8480
useEffect(() => {
85-
function messageChanged(_: any, msg: IChatMessage) {
86-
if (msg.id === message.id) {
87-
setMessage({ ...msg });
88-
}
81+
function messageChanged() {
82+
setMessage(props.message.content);
8983
}
90-
model.messageChanged.connect(messageChanged);
84+
props.message.changed.connect(messageChanged);
9185
return () => {
92-
model.messageChanged.disconnect(messageChanged);
86+
props.message.changed.disconnect(messageChanged);
9387
};
94-
}, [model]);
88+
}, [props.message]);
9589

9690
const avatar = message.stacked ? null : Avatar({ user: sender });
9791

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { BaseMessageProps } from './messages';
1111
import { AttachmentPreviewList } from '../attachments';
1212
import { ChatInput } from '../input';
1313
import { IInputModel, InputModel } from '../../input-model';
14-
import { IChatMessage } from '../../types';
14+
import { IMessageContent, IMessage } from '../../types';
1515
import { replaceSpanToMention } from '../../utils';
1616

1717
/**
@@ -21,7 +21,7 @@ type ChatMessageProps = BaseMessageProps & {
2121
/**
2222
* The message to display.
2323
*/
24-
message: IChatMessage;
24+
message: IMessage;
2525
/**
2626
* The index of the message in the list.
2727
*/
@@ -38,7 +38,9 @@ type ChatMessageProps = BaseMessageProps & {
3838
export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
3939
(props, ref): JSX.Element => {
4040
const { model, rmRegistry } = props;
41-
const [message, setMessage] = useState<IChatMessage>(props.message);
41+
const [message, setMessage] = useState<IMessageContent>(
42+
props.message.content
43+
);
4244
const [edit, setEdit] = useState<boolean>(false);
4345
const [deleted, setDeleted] = useState<boolean>(false);
4446
const [canEdit, setCanEdit] = useState<boolean>(false);
@@ -65,16 +67,14 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
6567

6668
// Listen for changes in the current message.
6769
useEffect(() => {
68-
function messageChanged(_: any, msg: IChatMessage) {
69-
if (msg.id === message.id) {
70-
setMessage({ ...msg });
71-
}
70+
function messageChanged() {
71+
setMessage(props.message.content);
7272
}
73-
model.messageChanged.connect(messageChanged);
73+
props.message.changed.connect(messageChanged);
7474
return () => {
75-
model.messageChanged.disconnect(messageChanged);
75+
props.message.changed.disconnect(messageChanged);
7676
};
77-
}, [model]);
77+
}, [props.message]);
7878

7979
// Create an input model only if the message is edited.
8080
const startEdition = (): void => {

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import { Navigation } from './navigation';
1616
import { WelcomeMessage } from './welcome';
1717
import { IInputToolbarRegistry } from '../input';
1818
import { ScrollContainer } from '../scroll-container';
19-
import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
19+
import { Message } from '../../message';
2020
import { IChatModel } from '../../model';
21-
import { ChatArea, IChatMessage } from '../../types';
21+
import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
22+
import { ChatArea, IMessage } from '../../types';
2223

2324
export const MESSAGE_CLASS = 'jp-chat-message';
2425
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
@@ -63,7 +64,7 @@ export type BaseMessageProps = {
6364
*/
6465
export function ChatMessages(props: BaseMessageProps): JSX.Element {
6566
const { model } = props;
66-
const [messages, setMessages] = useState<IChatMessage[]>(model.messages);
67+
const [messages, setMessages] = useState<IMessage[]>(model.messages);
6768
const refMsgBox = useRef<HTMLDivElement>(null);
6869
const [allRendered, setAllRendered] = useState<boolean>(false);
6970

@@ -81,7 +82,11 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
8182
}
8283
model
8384
.getHistory()
84-
.then(history => setMessages(history.messages))
85+
.then(history =>
86+
setMessages(
87+
history.messages.map(message => new Message({ ...message }))
88+
)
89+
)
8590
.catch(e => console.error(e));
8691
}
8792

@@ -215,7 +220,6 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
215220
>
216221
<ChatMessageHeader
217222
message={message}
218-
model={model}
219223
isCurrentUser={isCurrentUser}
220224
/>
221225
<ChatMessage
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { ISignal, Signal } from '@lumino/signaling';
2+
import { IAttachment, IMessageContent, IMessage, IUser } from './types';
3+
4+
/**
5+
* The message object.
6+
*/
7+
export class Message implements IMessage {
8+
/**
9+
* The constructor of the message.
10+
*
11+
* @param content: the content of the message.
12+
*/
13+
constructor(content: IMessageContent) {
14+
this._content = content;
15+
}
16+
17+
/**
18+
* The message content.
19+
*/
20+
get content(): IMessageContent {
21+
return this._content;
22+
}
23+
24+
/**
25+
* Getters for each attribute individually.
26+
*/
27+
get type(): string {
28+
return this._content.type;
29+
}
30+
get body(): string {
31+
return this._content.body;
32+
}
33+
get id(): string {
34+
return this._content.id;
35+
}
36+
get time(): number {
37+
return this._content.time;
38+
}
39+
get sender(): IUser {
40+
return this._content.sender;
41+
}
42+
get attachments(): IAttachment[] | undefined {
43+
return this._content.attachments;
44+
}
45+
get mentions(): IUser[] | undefined {
46+
return this._content.mentions;
47+
}
48+
get raw_time(): boolean | undefined {
49+
return this._content.raw_time;
50+
}
51+
get deleted(): boolean | undefined {
52+
return this._content.deleted;
53+
}
54+
get edited(): boolean | undefined {
55+
return this._content.edited;
56+
}
57+
get stacked(): boolean | undefined {
58+
return this._content.stacked;
59+
}
60+
61+
/**
62+
* A signal emitting when the message has been updated.
63+
*/
64+
get changed(): ISignal<IMessage, void> {
65+
return this._changed;
66+
}
67+
68+
/**
69+
* Update one or several fields of the message.
70+
*/
71+
update(updated: Partial<IMessageContent>) {
72+
this._content = { ...this._content, ...updated };
73+
this._changed.emit();
74+
}
75+
76+
private _content: IMessageContent;
77+
private _changed = new Signal<IMessage, void>(this);
78+
}

0 commit comments

Comments
 (0)