Skip to content

Commit decceae

Browse files
committed
Add a Message object emitting a signal on update
1 parent 4cc87d9 commit decceae

File tree

10 files changed

+176
-97
lines changed

10 files changed

+176
-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,20 +19,17 @@ 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

3025
/**
3126
* The message header component.
3227
*/
3328
export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
3429
const [datetime, setDatetime] = useState<Record<number, string>>({});
35-
const model = props.model;
36-
const [message, setMessage] = useState<IChatMessage>(props.message);
30+
const [message, setMessage] = useState<IMessageContent>(
31+
props.message.content
32+
);
3733
const sender = message.sender;
3834
/**
3935
* Effect: update cached datetime strings upon receiving a new message.
@@ -74,16 +70,14 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
7470

7571
// Listen for changes in the current message.
7672
useEffect(() => {
77-
function messageChanged(_: any, msg: IChatMessage) {
78-
if (msg.id === message.id) {
79-
setMessage({ ...msg });
80-
}
73+
function messageChanged() {
74+
setMessage(props.message.content);
8175
}
82-
model.messageChanged.connect(messageChanged);
76+
props.message.changed.connect(messageChanged);
8377
return () => {
84-
model.messageChanged.disconnect(messageChanged);
78+
props.message.changed.disconnect(messageChanged);
8579
};
86-
}, [model]);
80+
}, [props.message]);
8781

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

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: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import { WelcomeMessage } from './welcome';
1717
import { WritingUsersList } from './writers';
1818
import { IInputToolbarRegistry } from '../input';
1919
import { ScrollContainer } from '../scroll-container';
20-
import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
20+
import { Message } from '../../message';
2121
import { IChatModel } from '../../model';
22-
import { IChatMessage, IUser } from '../../types';
22+
import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
23+
import { IMessage, IUser } from '../../types';
2324

2425
export const MESSAGE_CLASS = 'jp-chat-message';
2526
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
@@ -60,7 +61,7 @@ export type BaseMessageProps = {
6061
*/
6162
export function ChatMessages(props: BaseMessageProps): JSX.Element {
6263
const { model } = props;
63-
const [messages, setMessages] = useState<IChatMessage[]>(model.messages);
64+
const [messages, setMessages] = useState<IMessage[]>(model.messages);
6465
const refMsgBox = useRef<HTMLDivElement>(null);
6566
const [currentWriters, setCurrentWriters] = useState<IUser[]>([]);
6667
const [allRendered, setAllRendered] = useState<boolean>(false);
@@ -79,7 +80,11 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
7980
}
8081
model
8182
.getHistory()
82-
.then(history => setMessages(history.messages))
83+
.then(history =>
84+
setMessages(
85+
history.messages.map(message => new Message({ ...message }))
86+
)
87+
)
8388
.catch(e => console.error(e));
8489
}
8590

@@ -191,7 +196,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
191196
message.stacked ? MESSAGE_STACKED_CLASS : ''
192197
)}
193198
>
194-
<ChatMessageHeader message={message} model={model} />
199+
<ChatMessageHeader message={message} />
195200
<ChatMessage
196201
{...props}
197202
message={message}
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)