@jupyter/chat 0.13.0 → 0.14.0
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/lib/active-cell-manager.d.ts +2 -0
- package/lib/active-cell-manager.js +7 -2
- package/lib/components/avatar.d.ts +20 -0
- package/lib/components/avatar.js +29 -0
- package/lib/components/chat.d.ts +1 -3
- package/lib/components/chat.js +2 -3
- package/lib/components/index.d.ts +2 -3
- package/lib/components/index.js +2 -3
- package/lib/components/input/buttons/send-button.js +15 -5
- package/lib/components/{chat-input.d.ts → input/chat-input.d.ts} +3 -3
- package/lib/components/{chat-input.js → input/chat-input.js} +7 -4
- package/lib/components/input/index.d.ts +1 -0
- package/lib/components/input/index.js +1 -0
- package/lib/components/input/toolbar-registry.d.ts +6 -0
- package/lib/components/input/use-chat-commands.d.ts +1 -1
- package/lib/components/input/use-chat-commands.js +23 -12
- package/lib/components/messages/footer.d.ts +2 -2
- package/lib/components/messages/footer.js +1 -1
- package/lib/components/messages/header.d.ts +16 -0
- package/lib/components/messages/header.js +85 -0
- package/lib/components/messages/index.d.ts +9 -0
- package/lib/components/messages/index.js +13 -0
- package/lib/components/messages/message-renderer.js +1 -1
- package/lib/components/messages/message.d.ts +21 -0
- package/lib/components/messages/message.js +102 -0
- package/lib/components/messages/messages.d.ts +38 -0
- package/lib/components/messages/messages.js +139 -0
- package/lib/components/messages/navigation.d.ts +20 -0
- package/lib/components/messages/navigation.js +98 -0
- package/lib/components/messages/writers.d.ts +16 -0
- package/lib/components/messages/writers.js +39 -0
- package/lib/context.d.ts +1 -1
- package/lib/index.d.ts +2 -6
- package/lib/index.js +2 -6
- package/lib/{registry.d.ts → registers/attachment-openers.d.ts} +1 -1
- package/lib/registers/chat-commands.d.ts +108 -0
- package/lib/{chat-commands/registry.js → registers/chat-commands.js} +8 -8
- package/lib/{footers/registry.d.ts → registers/footers.d.ts} +30 -5
- package/lib/registers/index.d.ts +3 -0
- package/lib/{footers → registers}/index.js +3 -2
- package/lib/selection-watcher.d.ts +11 -1
- package/lib/selection-watcher.js +10 -4
- package/lib/widgets/index.d.ts +3 -0
- package/lib/{chat-commands → widgets}/index.js +3 -2
- package/package.json +3 -1
- package/src/active-cell-manager.ts +10 -1
- package/src/components/avatar.tsx +68 -0
- package/src/components/chat.tsx +11 -6
- package/src/components/index.ts +2 -3
- package/src/components/input/buttons/send-button.tsx +17 -5
- package/src/components/{chat-input.tsx → input/chat-input.tsx} +12 -7
- package/src/components/input/index.ts +1 -0
- package/src/components/input/toolbar-registry.tsx +6 -0
- package/src/components/input/use-chat-commands.tsx +30 -15
- package/src/components/messages/footer.tsx +5 -2
- package/src/components/messages/header.tsx +133 -0
- package/src/components/messages/index.ts +14 -0
- package/src/components/messages/message-renderer.tsx +1 -1
- package/src/components/messages/message.tsx +156 -0
- package/src/components/messages/messages.tsx +218 -0
- package/src/components/messages/navigation.tsx +167 -0
- package/src/components/messages/welcome.tsx +1 -0
- package/src/components/messages/writers.tsx +81 -0
- package/src/context.ts +1 -1
- package/src/index.ts +2 -6
- package/src/{registry.ts → registers/attachment-openers.ts} +2 -1
- package/src/registers/chat-commands.ts +142 -0
- package/src/{footers/registry.ts → registers/footers.ts} +35 -8
- package/src/{footers → registers}/index.ts +3 -2
- package/src/selection-watcher.ts +28 -5
- package/src/{chat-commands → widgets}/index.ts +3 -2
- package/style/chat.css +82 -0
- package/lib/chat-commands/index.d.ts +0 -2
- package/lib/chat-commands/registry.d.ts +0 -28
- package/lib/chat-commands/types.d.ts +0 -52
- package/lib/chat-commands/types.js +0 -5
- package/lib/components/chat-messages.d.ts +0 -119
- package/lib/components/chat-messages.js +0 -446
- package/lib/footers/index.d.ts +0 -2
- package/lib/footers/types.d.ts +0 -26
- package/lib/footers/types.js +0 -5
- package/src/chat-commands/registry.ts +0 -60
- package/src/chat-commands/types.ts +0 -67
- package/src/components/chat-messages.tsx +0 -739
- package/src/footers/types.ts +0 -33
- package/lib/components/{toolbar.d.ts → messages/toolbar.d.ts} +0 -0
- package/lib/components/{toolbar.js → messages/toolbar.js} +0 -0
- package/lib/{registry.js → registers/attachment-openers.js} +0 -0
- package/lib/{footers/registry.js → registers/footers.js} +4 -4
- /package/src/components/{toolbar.tsx → messages/toolbar.tsx} +0 -0
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { IInputToolbarRegistry } from './input';
|
|
5
|
-
import { IChatCommandRegistry } from '../chat-commands';
|
|
6
|
-
import { IMessageFooterRegistry } from '../footers';
|
|
7
|
-
import { IChatModel } from '../model';
|
|
8
|
-
import { IChatMessage, IUser } from '../types';
|
|
9
|
-
/**
|
|
10
|
-
* The base components props.
|
|
11
|
-
*/
|
|
12
|
-
type BaseMessageProps = {
|
|
13
|
-
/**
|
|
14
|
-
* The mime renderer registry.
|
|
15
|
-
*/
|
|
16
|
-
rmRegistry: IRenderMimeRegistry;
|
|
17
|
-
/**
|
|
18
|
-
* The chat model.
|
|
19
|
-
*/
|
|
20
|
-
model: IChatModel;
|
|
21
|
-
/**
|
|
22
|
-
* The chat commands registry.
|
|
23
|
-
*/
|
|
24
|
-
chatCommandRegistry?: IChatCommandRegistry;
|
|
25
|
-
/**
|
|
26
|
-
* The input toolbar registry.
|
|
27
|
-
*/
|
|
28
|
-
inputToolbarRegistry: IInputToolbarRegistry;
|
|
29
|
-
/**
|
|
30
|
-
* The footer registry.
|
|
31
|
-
*/
|
|
32
|
-
messageFooterRegistry?: IMessageFooterRegistry;
|
|
33
|
-
/**
|
|
34
|
-
* The welcome message.
|
|
35
|
-
*/
|
|
36
|
-
welcomeMessage?: string;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* The messages list component.
|
|
40
|
-
*/
|
|
41
|
-
export declare function ChatMessages(props: BaseMessageProps): JSX.Element;
|
|
42
|
-
/**
|
|
43
|
-
* The message header props.
|
|
44
|
-
*/
|
|
45
|
-
type ChatMessageHeaderProps = {
|
|
46
|
-
/**
|
|
47
|
-
* The chat message.
|
|
48
|
-
*/
|
|
49
|
-
message: IChatMessage;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* The message header component.
|
|
53
|
-
*/
|
|
54
|
-
export declare function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element;
|
|
55
|
-
/**
|
|
56
|
-
* The message component body.
|
|
57
|
-
*/
|
|
58
|
-
export declare const ChatMessage: React.ForwardRefExoticComponent<BaseMessageProps & {
|
|
59
|
-
/**
|
|
60
|
-
* The message to display.
|
|
61
|
-
*/
|
|
62
|
-
message: IChatMessage;
|
|
63
|
-
/**
|
|
64
|
-
* The index of the message in the list.
|
|
65
|
-
*/
|
|
66
|
-
index: number;
|
|
67
|
-
/**
|
|
68
|
-
* The promise to resolve when the message is rendered.
|
|
69
|
-
*/
|
|
70
|
-
renderedPromise: PromiseDelegate<void>;
|
|
71
|
-
} & React.RefAttributes<HTMLDivElement>>;
|
|
72
|
-
/**
|
|
73
|
-
* The writers component props.
|
|
74
|
-
*/
|
|
75
|
-
type writersProps = {
|
|
76
|
-
/**
|
|
77
|
-
* The list of users currently writing.
|
|
78
|
-
*/
|
|
79
|
-
writers: IUser[];
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* The writers component, displaying the current writers.
|
|
83
|
-
*/
|
|
84
|
-
export declare function Writers(props: writersProps): JSX.Element | null;
|
|
85
|
-
/**
|
|
86
|
-
* The navigation component props.
|
|
87
|
-
*/
|
|
88
|
-
type NavigationProps = BaseMessageProps & {
|
|
89
|
-
/**
|
|
90
|
-
* The reference to the messages container.
|
|
91
|
-
*/
|
|
92
|
-
refMsgBox: React.RefObject<HTMLDivElement>;
|
|
93
|
-
/**
|
|
94
|
-
* Whether all the messages has been rendered once on first display.
|
|
95
|
-
*/
|
|
96
|
-
allRendered: boolean;
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* The navigation component, to navigate to unread messages.
|
|
100
|
-
*/
|
|
101
|
-
export declare function Navigation(props: NavigationProps): JSX.Element;
|
|
102
|
-
/**
|
|
103
|
-
* The avatar props.
|
|
104
|
-
*/
|
|
105
|
-
type AvatarProps = {
|
|
106
|
-
/**
|
|
107
|
-
* The user to display an avatar.
|
|
108
|
-
*/
|
|
109
|
-
user: IUser;
|
|
110
|
-
/**
|
|
111
|
-
* Whether the avatar should be small.
|
|
112
|
-
*/
|
|
113
|
-
small?: boolean;
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* The avatar component.
|
|
117
|
-
*/
|
|
118
|
-
export declare function Avatar(props: AvatarProps): JSX.Element | null;
|
|
119
|
-
export {};
|
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
import { Button } from '@jupyter/react-components';
|
|
6
|
-
import { LabIcon, caretDownEmptyIcon, classes } from '@jupyterlab/ui-components';
|
|
7
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
8
|
-
import { Avatar as MuiAvatar, Box, Typography } from '@mui/material';
|
|
9
|
-
import clsx from 'clsx';
|
|
10
|
-
import React, { useEffect, useState, useRef, forwardRef } from 'react';
|
|
11
|
-
import { AttachmentPreviewList } from './attachments';
|
|
12
|
-
import { ChatInput } from './chat-input';
|
|
13
|
-
import { MessageFooter } from './messages/footer';
|
|
14
|
-
import { MessageRenderer } from './messages/message-renderer';
|
|
15
|
-
import { WelcomeMessage } from './messages/welcome';
|
|
16
|
-
import { ScrollContainer } from './scroll-container';
|
|
17
|
-
import { InputModel } from '../input-model';
|
|
18
|
-
import { replaceSpanToMention } from '../utils';
|
|
19
|
-
const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
|
|
20
|
-
const MESSAGE_CLASS = 'jp-chat-message';
|
|
21
|
-
const MESSAGE_STACKED_CLASS = 'jp-chat-message-stacked';
|
|
22
|
-
const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
|
|
23
|
-
const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
|
|
24
|
-
const WRITERS_CLASS = 'jp-chat-writers';
|
|
25
|
-
const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation';
|
|
26
|
-
const NAVIGATION_UNREAD_CLASS = 'jp-chat-navigation-unread';
|
|
27
|
-
const NAVIGATION_TOP_CLASS = 'jp-chat-navigation-top';
|
|
28
|
-
const NAVIGATION_BOTTOM_CLASS = 'jp-chat-navigation-bottom';
|
|
29
|
-
/**
|
|
30
|
-
* The messages list component.
|
|
31
|
-
*/
|
|
32
|
-
export function ChatMessages(props) {
|
|
33
|
-
const { model } = props;
|
|
34
|
-
const [messages, setMessages] = useState(model.messages);
|
|
35
|
-
const refMsgBox = useRef(null);
|
|
36
|
-
const [currentWriters, setCurrentWriters] = useState([]);
|
|
37
|
-
const [allRendered, setAllRendered] = useState(false);
|
|
38
|
-
// The list of message DOM and their rendered promises.
|
|
39
|
-
const listRef = useRef([]);
|
|
40
|
-
const renderedPromise = useRef([]);
|
|
41
|
-
/**
|
|
42
|
-
* Effect: fetch history and config on initial render
|
|
43
|
-
*/
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
async function fetchHistory() {
|
|
46
|
-
if (!model.getHistory) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
model
|
|
50
|
-
.getHistory()
|
|
51
|
-
.then(history => setMessages(history.messages))
|
|
52
|
-
.catch(e => console.error(e));
|
|
53
|
-
}
|
|
54
|
-
fetchHistory();
|
|
55
|
-
setCurrentWriters([]);
|
|
56
|
-
}, [model]);
|
|
57
|
-
/**
|
|
58
|
-
* Effect: listen to chat messages.
|
|
59
|
-
*/
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
var _a;
|
|
62
|
-
function handleChatEvents() {
|
|
63
|
-
setMessages([...model.messages]);
|
|
64
|
-
}
|
|
65
|
-
function handleWritersChange(_, writers) {
|
|
66
|
-
setCurrentWriters(writers.map(writer => writer.user));
|
|
67
|
-
}
|
|
68
|
-
model.messagesUpdated.connect(handleChatEvents);
|
|
69
|
-
(_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(handleWritersChange);
|
|
70
|
-
return function cleanup() {
|
|
71
|
-
var _a;
|
|
72
|
-
model.messagesUpdated.disconnect(handleChatEvents);
|
|
73
|
-
(_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(handleChatEvents);
|
|
74
|
-
};
|
|
75
|
-
}, [model]);
|
|
76
|
-
/**
|
|
77
|
-
* Observe the messages to update the current viewport and the unread messages.
|
|
78
|
-
*/
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
const observer = new IntersectionObserver(entries => {
|
|
81
|
-
var _a;
|
|
82
|
-
// Used on first rendering, to ensure all the message as been rendered once.
|
|
83
|
-
if (!allRendered) {
|
|
84
|
-
Promise.all(renderedPromise.current.map(p => p.promise)).then(() => {
|
|
85
|
-
setAllRendered(true);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const unread = [...model.unreadMessages];
|
|
89
|
-
let unreadModified = false;
|
|
90
|
-
const inViewport = [...((_a = model.messagesInViewport) !== null && _a !== void 0 ? _a : [])];
|
|
91
|
-
entries.forEach(entry => {
|
|
92
|
-
var _a;
|
|
93
|
-
const index = parseInt((_a = entry.target.getAttribute('data-index')) !== null && _a !== void 0 ? _a : '');
|
|
94
|
-
if (!isNaN(index)) {
|
|
95
|
-
const viewportIdx = inViewport.indexOf(index);
|
|
96
|
-
if (!entry.isIntersecting && viewportIdx !== -1) {
|
|
97
|
-
inViewport.splice(viewportIdx, 1);
|
|
98
|
-
}
|
|
99
|
-
else if (entry.isIntersecting && viewportIdx === -1) {
|
|
100
|
-
inViewport.push(index);
|
|
101
|
-
}
|
|
102
|
-
if (unread.length) {
|
|
103
|
-
const unreadIdx = unread.indexOf(index);
|
|
104
|
-
if (unreadIdx !== -1 && entry.isIntersecting) {
|
|
105
|
-
unread.splice(unreadIdx, 1);
|
|
106
|
-
unreadModified = true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
props.model.messagesInViewport = inViewport;
|
|
112
|
-
// Ensure that all messages are rendered before updating unread messages, otherwise
|
|
113
|
-
// it can lead to wrong assumption , because more message are in the viewport
|
|
114
|
-
// before they are rendered.
|
|
115
|
-
if (allRendered && unreadModified) {
|
|
116
|
-
model.unreadMessages = unread;
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
/**
|
|
120
|
-
* Observe the messages.
|
|
121
|
-
*/
|
|
122
|
-
listRef.current.forEach(item => {
|
|
123
|
-
if (item) {
|
|
124
|
-
observer.observe(item);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return () => {
|
|
128
|
-
listRef.current.forEach(item => {
|
|
129
|
-
if (item) {
|
|
130
|
-
observer.unobserve(item);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
}, [messages, allRendered]);
|
|
135
|
-
return (React.createElement(React.Fragment, null,
|
|
136
|
-
React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
|
|
137
|
-
props.welcomeMessage && (React.createElement(WelcomeMessage, { rmRegistry: props.rmRegistry, content: props.welcomeMessage })),
|
|
138
|
-
React.createElement(Box, { ref: refMsgBox, className: clsx(MESSAGES_BOX_CLASS) }, messages.map((message, i) => {
|
|
139
|
-
renderedPromise.current[i] = new PromiseDelegate();
|
|
140
|
-
return (
|
|
141
|
-
// extra div needed to ensure each bubble is on a new line
|
|
142
|
-
React.createElement(Box, { key: i, className: clsx(MESSAGE_CLASS, message.stacked ? MESSAGE_STACKED_CLASS : '') },
|
|
143
|
-
React.createElement(ChatMessageHeader, { message: message }),
|
|
144
|
-
React.createElement(ChatMessage, { ...props, message: message, index: i, renderedPromise: renderedPromise.current[i], ref: el => (listRef.current[i] = el) }),
|
|
145
|
-
props.messageFooterRegistry && (React.createElement(MessageFooter, { registry: props.messageFooterRegistry, message: message, model: model }))));
|
|
146
|
-
})),
|
|
147
|
-
React.createElement(Writers, { writers: currentWriters })),
|
|
148
|
-
React.createElement(Navigation, { ...props, refMsgBox: refMsgBox, allRendered: allRendered })));
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* The message header component.
|
|
152
|
-
*/
|
|
153
|
-
export function ChatMessageHeader(props) {
|
|
154
|
-
var _a, _b;
|
|
155
|
-
const [datetime, setDatetime] = useState({});
|
|
156
|
-
const message = props.message;
|
|
157
|
-
const sender = message.sender;
|
|
158
|
-
/**
|
|
159
|
-
* Effect: update cached datetime strings upon receiving a new message.
|
|
160
|
-
*/
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
if (!datetime[message.time]) {
|
|
163
|
-
const newDatetime = {};
|
|
164
|
-
let datetime;
|
|
165
|
-
const currentDate = new Date();
|
|
166
|
-
const sameDay = (date) => date.getFullYear() === currentDate.getFullYear() &&
|
|
167
|
-
date.getMonth() === currentDate.getMonth() &&
|
|
168
|
-
date.getDate() === currentDate.getDate();
|
|
169
|
-
const msgDate = new Date(message.time * 1000); // Convert message time to milliseconds
|
|
170
|
-
// Display only the time if the day of the message is the current one.
|
|
171
|
-
if (sameDay(msgDate)) {
|
|
172
|
-
// Use the browser's default locale
|
|
173
|
-
datetime = msgDate.toLocaleTimeString([], {
|
|
174
|
-
hour: 'numeric',
|
|
175
|
-
minute: '2-digit'
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
// Use the browser's default locale
|
|
180
|
-
datetime = msgDate.toLocaleString([], {
|
|
181
|
-
day: 'numeric',
|
|
182
|
-
month: 'numeric',
|
|
183
|
-
year: 'numeric',
|
|
184
|
-
hour: 'numeric',
|
|
185
|
-
minute: '2-digit'
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
newDatetime[message.time] = datetime;
|
|
189
|
-
setDatetime(newDatetime);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
const avatar = message.stacked ? null : Avatar({ user: sender });
|
|
193
|
-
const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
|
|
194
|
-
return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
|
|
195
|
-
display: 'flex',
|
|
196
|
-
alignItems: 'center',
|
|
197
|
-
'& > :not(:last-child)': {
|
|
198
|
-
marginRight: 3
|
|
199
|
-
},
|
|
200
|
-
marginBottom: message.stacked ? '0px' : '12px'
|
|
201
|
-
} },
|
|
202
|
-
avatar,
|
|
203
|
-
React.createElement(Box, { sx: {
|
|
204
|
-
display: 'flex',
|
|
205
|
-
flexGrow: 1,
|
|
206
|
-
flexWrap: 'wrap',
|
|
207
|
-
justifyContent: 'space-between',
|
|
208
|
-
alignItems: 'center'
|
|
209
|
-
} },
|
|
210
|
-
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
211
|
-
!message.stacked && (React.createElement(Typography, { sx: {
|
|
212
|
-
fontWeight: 700,
|
|
213
|
-
color: 'var(--jp-ui-font-color1)',
|
|
214
|
-
paddingRight: '0.5em'
|
|
215
|
-
} }, name)),
|
|
216
|
-
(message.deleted || message.edited) && (React.createElement(Typography, { sx: {
|
|
217
|
-
fontStyle: 'italic',
|
|
218
|
-
fontSize: 'var(--jp-content-font-size0)'
|
|
219
|
-
} }, message.deleted ? '(message deleted)' : '(edited)'))),
|
|
220
|
-
React.createElement(Typography, { className: MESSAGE_TIME_CLASS, sx: {
|
|
221
|
-
fontSize: '0.8em',
|
|
222
|
-
color: 'var(--jp-ui-font-color2)',
|
|
223
|
-
fontWeight: 300
|
|
224
|
-
}, title: message.raw_time ? 'Unverified time' : '' }, `${datetime[message.time]}${message.raw_time ? '*' : ''}`))));
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* The message component body.
|
|
228
|
-
*/
|
|
229
|
-
export const ChatMessage = forwardRef((props, ref) => {
|
|
230
|
-
const { message, model, rmRegistry } = props;
|
|
231
|
-
const [edit, setEdit] = useState(false);
|
|
232
|
-
const [deleted, setDeleted] = useState(false);
|
|
233
|
-
const [canEdit, setCanEdit] = useState(false);
|
|
234
|
-
const [canDelete, setCanDelete] = useState(false);
|
|
235
|
-
// Look if the message can be deleted or edited.
|
|
236
|
-
useEffect(() => {
|
|
237
|
-
var _a;
|
|
238
|
-
// Init canDelete and canEdit state.
|
|
239
|
-
setDeleted((_a = message.deleted) !== null && _a !== void 0 ? _a : false);
|
|
240
|
-
if (model.user !== undefined && !message.deleted) {
|
|
241
|
-
if (model.user.username === message.sender.username) {
|
|
242
|
-
setCanEdit(model.updateMessage !== undefined);
|
|
243
|
-
setCanDelete(model.deleteMessage !== undefined);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (message.sender.bot) {
|
|
247
|
-
setCanDelete(model.deleteMessage !== undefined);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
setCanEdit(false);
|
|
252
|
-
setCanDelete(false);
|
|
253
|
-
}
|
|
254
|
-
}, [model, message]);
|
|
255
|
-
// Create an input model only if the message is edited.
|
|
256
|
-
const startEdition = () => {
|
|
257
|
-
var _a;
|
|
258
|
-
if (!canEdit) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
let body = message.body;
|
|
262
|
-
(_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
|
|
263
|
-
body = replaceSpanToMention(body, user);
|
|
264
|
-
});
|
|
265
|
-
const inputModel = new InputModel({
|
|
266
|
-
chatContext: model.createChatContext(),
|
|
267
|
-
onSend: (input, model) => updateMessage(message.id, input, model),
|
|
268
|
-
onCancel: () => cancelEdition(),
|
|
269
|
-
value: body,
|
|
270
|
-
activeCellManager: model.activeCellManager,
|
|
271
|
-
selectionWatcher: model.selectionWatcher,
|
|
272
|
-
documentManager: model.documentManager,
|
|
273
|
-
config: {
|
|
274
|
-
sendWithShiftEnter: model.config.sendWithShiftEnter
|
|
275
|
-
},
|
|
276
|
-
attachments: message.attachments,
|
|
277
|
-
mentions: message.mentions
|
|
278
|
-
});
|
|
279
|
-
model.addEditionModel(message.id, inputModel);
|
|
280
|
-
setEdit(true);
|
|
281
|
-
};
|
|
282
|
-
// Cancel the current edition of the message.
|
|
283
|
-
const cancelEdition = () => {
|
|
284
|
-
var _a;
|
|
285
|
-
(_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
286
|
-
setEdit(false);
|
|
287
|
-
};
|
|
288
|
-
// Update the content of the message.
|
|
289
|
-
const updateMessage = (id, input, inputModel) => {
|
|
290
|
-
var _a;
|
|
291
|
-
if (!canEdit || !inputModel) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
// Update the message
|
|
295
|
-
const updatedMessage = { ...message };
|
|
296
|
-
updatedMessage.body = input;
|
|
297
|
-
updatedMessage.attachments = inputModel.attachments;
|
|
298
|
-
updatedMessage.mentions = inputModel.mentions;
|
|
299
|
-
model.updateMessage(id, updatedMessage);
|
|
300
|
-
(_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
301
|
-
setEdit(false);
|
|
302
|
-
};
|
|
303
|
-
// Delete the message.
|
|
304
|
-
const deleteMessage = (id) => {
|
|
305
|
-
if (!canDelete) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
model.deleteMessage(id);
|
|
309
|
-
};
|
|
310
|
-
// Empty if the message has been deleted.
|
|
311
|
-
return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
|
|
312
|
-
edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: props.inputToolbarRegistry })) : (React.createElement(MessageRenderer, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
|
|
313
|
-
message.attachments && !edit && (
|
|
314
|
-
// Display the attachments only if message is not edited, otherwise the
|
|
315
|
-
// input component display them.
|
|
316
|
-
React.createElement(AttachmentPreviewList, { attachments: message.attachments }))));
|
|
317
|
-
});
|
|
318
|
-
/**
|
|
319
|
-
* The writers component, displaying the current writers.
|
|
320
|
-
*/
|
|
321
|
-
export function Writers(props) {
|
|
322
|
-
const { writers } = props;
|
|
323
|
-
return writers.length > 0 ? (React.createElement(Box, { className: WRITERS_CLASS },
|
|
324
|
-
writers.map((writer, index) => {
|
|
325
|
-
var _a, _b;
|
|
326
|
-
return (React.createElement("div", null,
|
|
327
|
-
React.createElement(Avatar, { user: writer, small: true }),
|
|
328
|
-
React.createElement("span", null, (_b = (_a = writer.display_name) !== null && _a !== void 0 ? _a : writer.name) !== null && _b !== void 0 ? _b : (writer.username || 'User undefined')),
|
|
329
|
-
React.createElement("span", null, index < writers.length - 1
|
|
330
|
-
? index < writers.length - 2
|
|
331
|
-
? ', '
|
|
332
|
-
: ' and '
|
|
333
|
-
: '')));
|
|
334
|
-
}),
|
|
335
|
-
React.createElement("span", null, (writers.length > 1 ? ' are' : ' is') + ' writing'))) : null;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* The navigation component, to navigate to unread messages.
|
|
339
|
-
*/
|
|
340
|
-
export function Navigation(props) {
|
|
341
|
-
const { model } = props;
|
|
342
|
-
const [lastInViewport, setLastInViewport] = useState(true);
|
|
343
|
-
const [unreadBefore, setUnreadBefore] = useState(null);
|
|
344
|
-
const [unreadAfter, setUnreadAfter] = useState(null);
|
|
345
|
-
const gotoMessage = (msgIdx, alignToTop = true) => {
|
|
346
|
-
var _a, _b;
|
|
347
|
-
(_b = (_a = props.refMsgBox.current) === null || _a === void 0 ? void 0 : _a.children.item(msgIdx)) === null || _b === void 0 ? void 0 : _b.scrollIntoView(alignToTop);
|
|
348
|
-
};
|
|
349
|
-
// Listen for change in unread messages, and find the first unread message before or
|
|
350
|
-
// after the current viewport, to display navigation buttons.
|
|
351
|
-
useEffect(() => {
|
|
352
|
-
var _a;
|
|
353
|
-
// Do not attempt to display navigation until messages are rendered, it can lead to
|
|
354
|
-
// wrong assumption, because more messages are in the viewport before they are
|
|
355
|
-
// rendered.
|
|
356
|
-
if (!props.allRendered) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
const unreadChanged = (model, unreadIndexes) => {
|
|
360
|
-
const viewport = model.messagesInViewport;
|
|
361
|
-
if (!viewport) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
// Initialize the next values with the current values if there still relevant.
|
|
365
|
-
let before = unreadBefore !== null &&
|
|
366
|
-
unreadIndexes.includes(unreadBefore) &&
|
|
367
|
-
unreadBefore < Math.min(...viewport)
|
|
368
|
-
? unreadBefore
|
|
369
|
-
: null;
|
|
370
|
-
let after = unreadAfter !== null &&
|
|
371
|
-
unreadIndexes.includes(unreadAfter) &&
|
|
372
|
-
unreadAfter > Math.max(...viewport)
|
|
373
|
-
? unreadAfter
|
|
374
|
-
: null;
|
|
375
|
-
unreadIndexes.forEach(unread => {
|
|
376
|
-
if (viewport === null || viewport === void 0 ? void 0 : viewport.includes(unread)) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (unread < (before !== null && before !== void 0 ? before : Math.min(...viewport))) {
|
|
380
|
-
before = unread;
|
|
381
|
-
}
|
|
382
|
-
else if (unread > Math.max(...viewport) &&
|
|
383
|
-
unread < (after !== null && after !== void 0 ? after : model.messages.length)) {
|
|
384
|
-
after = unread;
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
setUnreadBefore(before);
|
|
388
|
-
setUnreadAfter(after);
|
|
389
|
-
};
|
|
390
|
-
(_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.connect(unreadChanged);
|
|
391
|
-
unreadChanged(model, model.unreadMessages);
|
|
392
|
-
// Move to the last the message after all the messages have been first rendered.
|
|
393
|
-
gotoMessage(model.messages.length - 1, false);
|
|
394
|
-
return () => {
|
|
395
|
-
var _a;
|
|
396
|
-
(_a = model.unreadChanged) === null || _a === void 0 ? void 0 : _a.disconnect(unreadChanged);
|
|
397
|
-
};
|
|
398
|
-
}, [model, props.allRendered]);
|
|
399
|
-
// Listen for change in the viewport, to add a navigation button if the last is not
|
|
400
|
-
// in viewport.
|
|
401
|
-
useEffect(() => {
|
|
402
|
-
var _a, _b;
|
|
403
|
-
const viewportChanged = (model, viewport) => {
|
|
404
|
-
setLastInViewport(model.messages.length === 0 ||
|
|
405
|
-
viewport.includes(model.messages.length - 1));
|
|
406
|
-
};
|
|
407
|
-
(_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.connect(viewportChanged);
|
|
408
|
-
viewportChanged(model, (_b = model.messagesInViewport) !== null && _b !== void 0 ? _b : []);
|
|
409
|
-
return () => {
|
|
410
|
-
var _a;
|
|
411
|
-
(_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.disconnect(viewportChanged);
|
|
412
|
-
};
|
|
413
|
-
}, [model]);
|
|
414
|
-
return (React.createElement(React.Fragment, null,
|
|
415
|
-
unreadBefore !== null && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${NAVIGATION_UNREAD_CLASS} ${NAVIGATION_TOP_CLASS}`, onClick: () => gotoMessage(unreadBefore), title: 'Go to unread messages' },
|
|
416
|
-
React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') }))),
|
|
417
|
-
(unreadAfter !== null || !lastInViewport) && (React.createElement(Button, { className: `${NAVIGATION_BUTTON_CLASS} ${unreadAfter !== null ? NAVIGATION_UNREAD_CLASS : ''} ${NAVIGATION_BOTTOM_CLASS}`, onClick: unreadAfter === null
|
|
418
|
-
? () => gotoMessage(model.messages.length - 1, false)
|
|
419
|
-
: () => gotoMessage(unreadAfter), title: unreadAfter !== null
|
|
420
|
-
? 'Go to unread messages'
|
|
421
|
-
: 'Go to last message' },
|
|
422
|
-
React.createElement(LabIcon.resolveReact, { display: 'flex', icon: caretDownEmptyIcon, iconClass: classes('jp-Icon') })))));
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* The avatar component.
|
|
426
|
-
*/
|
|
427
|
-
export function Avatar(props) {
|
|
428
|
-
var _a, _b;
|
|
429
|
-
const { user } = props;
|
|
430
|
-
const sharedStyles = {
|
|
431
|
-
height: `${props.small ? '16' : '24'}px`,
|
|
432
|
-
width: `${props.small ? '16' : '24'}px`,
|
|
433
|
-
bgcolor: user.color,
|
|
434
|
-
fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
|
|
435
|
-
};
|
|
436
|
-
const name = (_b = (_a = user.display_name) !== null && _a !== void 0 ? _a : user.name) !== null && _b !== void 0 ? _b : (user.username || 'User undefined');
|
|
437
|
-
return user.avatar_url ? (React.createElement(MuiAvatar, { sx: {
|
|
438
|
-
...sharedStyles
|
|
439
|
-
}, src: user.avatar_url, alt: name, title: name })) : user.initials ? (React.createElement(MuiAvatar, { sx: {
|
|
440
|
-
...sharedStyles
|
|
441
|
-
}, alt: name, title: name },
|
|
442
|
-
React.createElement(Typography, { sx: {
|
|
443
|
-
fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`,
|
|
444
|
-
color: 'var(--jp-ui-inverse-font-color1)'
|
|
445
|
-
} }, user.initials))) : null;
|
|
446
|
-
}
|
package/lib/footers/index.d.ts
DELETED
package/lib/footers/types.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
import { IChatModel } from '../model';
|
|
3
|
-
import { IChatMessage } from '../types';
|
|
4
|
-
/**
|
|
5
|
-
* The props sent passed to each `MessageFooterSection` React component.
|
|
6
|
-
*/
|
|
7
|
-
export type MessageFooterSectionProps = {
|
|
8
|
-
model: IChatModel;
|
|
9
|
-
message: IChatMessage;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* A message footer section which can be added to the footer registry.
|
|
13
|
-
*/
|
|
14
|
-
export type MessageFooterSection = {
|
|
15
|
-
component: React.FC<MessageFooterSectionProps>;
|
|
16
|
-
position: 'left' | 'center' | 'right';
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* The message footer returned by the registry, composed of 'left', 'center',
|
|
20
|
-
* and 'right' sections.
|
|
21
|
-
*/
|
|
22
|
-
export type MessageFooter = {
|
|
23
|
-
left?: MessageFooterSection;
|
|
24
|
-
center?: MessageFooterSection;
|
|
25
|
-
right?: MessageFooterSection;
|
|
26
|
-
};
|
package/lib/footers/types.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Token } from '@lumino/coreutils';
|
|
7
|
-
import { ChatCommand, IChatCommandProvider } from './types';
|
|
8
|
-
import { IInputModel } from '../input-model';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Interface of a chat command registry, which tracks a list of chat command
|
|
12
|
-
* providers. Providers provide a list of commands given a user's partial input,
|
|
13
|
-
* and define how commands are handled when accepted in the chat commands menu.
|
|
14
|
-
*/
|
|
15
|
-
export interface IChatCommandRegistry {
|
|
16
|
-
addProvider(provider: IChatCommandProvider): void;
|
|
17
|
-
getProviders(): IChatCommandProvider[];
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Handles a chat command by calling `handleChatCommand()` on the provider
|
|
21
|
-
* corresponding to this chat command.
|
|
22
|
-
*/
|
|
23
|
-
handleChatCommand(command: ChatCommand, inputModel: IInputModel): void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Default chat command registry implementation.
|
|
28
|
-
*/
|
|
29
|
-
export class ChatCommandRegistry implements IChatCommandRegistry {
|
|
30
|
-
constructor() {
|
|
31
|
-
this._providers = new Map<string, IChatCommandProvider>();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
addProvider(provider: IChatCommandProvider): void {
|
|
35
|
-
this._providers.set(provider.id, provider);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getProviders(): IChatCommandProvider[] {
|
|
39
|
-
return Array.from(this._providers.values());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
handleChatCommand(command: ChatCommand, inputModel: IInputModel) {
|
|
43
|
-
const provider = this._providers.get(command.providerId);
|
|
44
|
-
if (!provider) {
|
|
45
|
-
console.error(
|
|
46
|
-
'Error in handling chat command: No command provider has an ID of ' +
|
|
47
|
-
command.providerId
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
provider.handleChatCommand(command, inputModel);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private _providers: Map<string, IChatCommandProvider>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const IChatCommandRegistry = new Token<IChatCommandRegistry>(
|
|
59
|
-
'@jupyter/chat:IChatCommandRegistry'
|
|
60
|
-
);
|