@xipkg/calls-chat 0.1.22 → 0.1.25
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/dist/index.d.ts +10 -2
- package/dist/index.mjs +498 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -15
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { JSONContent } from '@tiptap/core';
|
|
2
3
|
import * as zustand from 'zustand';
|
|
3
4
|
|
|
4
5
|
type ChatProps = {
|
|
5
6
|
/** В компакт-режиме: классы позиционирования (как у CompactCall: top-16 bottom-4 left-4 и т.д.) */
|
|
6
7
|
compactPositionClassName?: string;
|
|
8
|
+
/** Встроить чат в родителя (PiP): без fixed/modal, на всю доступную высоту */
|
|
9
|
+
embedded?: boolean;
|
|
7
10
|
};
|
|
8
|
-
declare const Chat: ({ compactPositionClassName }?: ChatProps) => react.JSX.Element | null;
|
|
11
|
+
declare const Chat: ({ compactPositionClassName, embedded }?: ChatProps) => react.JSX.Element | null;
|
|
9
12
|
|
|
10
13
|
type ChatButtonProps = {
|
|
11
14
|
/** Дополнительные классы (например, для компактной панели: h-8 w-8 rounded-xl) */
|
|
@@ -14,7 +17,8 @@ type ChatButtonProps = {
|
|
|
14
17
|
declare const ChatButton: ({ className }: ChatButtonProps) => react.JSX.Element;
|
|
15
18
|
|
|
16
19
|
declare const useChat: () => {
|
|
17
|
-
sendChatMessage: (text: string) => void;
|
|
20
|
+
sendChatMessage: (text: string, content?: JSONContent) => void;
|
|
21
|
+
deleteChatMessage: (messageId: string) => void;
|
|
18
22
|
toggleChat: () => void;
|
|
19
23
|
openChat: () => void;
|
|
20
24
|
closeChat: () => void;
|
|
@@ -23,15 +27,19 @@ declare const useChat: () => {
|
|
|
23
27
|
type ChatMessageT = {
|
|
24
28
|
id: string;
|
|
25
29
|
text: string;
|
|
30
|
+
/** TipTap JSON. Старые клиенты поле игнорируют и показывают `text`. */
|
|
31
|
+
content?: JSONContent;
|
|
26
32
|
senderId: string;
|
|
27
33
|
senderName: string;
|
|
28
34
|
timestamp: number;
|
|
29
35
|
};
|
|
36
|
+
|
|
30
37
|
type useChatStoreT = {
|
|
31
38
|
isChatOpen: boolean;
|
|
32
39
|
chatMessages: ChatMessageT[];
|
|
33
40
|
unreadMessagesCount: number;
|
|
34
41
|
addChatMessage: (message: ChatMessageT) => void;
|
|
42
|
+
removeChatMessage: (messageId: string) => void;
|
|
35
43
|
clearUnreadMessages: () => void;
|
|
36
44
|
updateStore: (type: keyof useChatStoreT, value: unknown) => void;
|
|
37
45
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useRef, useEffect } from 'react';
|
|
2
2
|
import { Button } from '@xipkg/button';
|
|
3
|
-
import {
|
|
4
|
-
import { Close, Send, Chat as Chat$1 } from '@xipkg/icons';
|
|
3
|
+
import { Close, Trash, Chat as Chat$1, Send, Bold as Bold$1, Italic, Underline as Underline$1, Stroke } from '@xipkg/icons';
|
|
5
4
|
import { UserProfile } from '@xipkg/userprofile';
|
|
6
5
|
import { ScrollArea } from '@xipkg/scrollarea';
|
|
7
6
|
import { Modal, ModalContent, ModalTitle } from '@xipkg/modal';
|
|
@@ -11,10 +10,87 @@ import { useRoom, useCalls } from '@xipkg/calls-providers';
|
|
|
11
10
|
import { useSoundEffectsStore } from '@xipkg/calls-store';
|
|
12
11
|
import { playSound } from '@xipkg/calls-utils';
|
|
13
12
|
import { create } from 'zustand';
|
|
14
|
-
import {
|
|
13
|
+
import { cn, useMediaQuery } from '@xipkg/utils';
|
|
14
|
+
import { useEditor, useEditorState, EditorContent } from '@tiptap/react';
|
|
15
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
16
|
+
import { markPasteRule, markInputRule } from '@tiptap/core';
|
|
17
|
+
import { Bold, starPasteRegex, starInputRegex } from '@tiptap/extension-bold';
|
|
18
|
+
import { Underline } from '@tiptap/extension-underline';
|
|
19
|
+
import { TextSelection } from '@tiptap/pm/state';
|
|
20
|
+
import { BubbleMenu } from '@tiptap/react/menus';
|
|
15
21
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
16
22
|
|
|
17
23
|
// src/ui/Chat.tsx
|
|
24
|
+
|
|
25
|
+
// src/editor/sanitizeContent.ts
|
|
26
|
+
var ALLOWED_NODES = /* @__PURE__ */ new Set(["doc", "paragraph", "text", "hardBreak", "codeBlock", "blockquote"]);
|
|
27
|
+
var ALLOWED_MARKS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline", "code", "link"]);
|
|
28
|
+
var MAX_NODES = 400;
|
|
29
|
+
var MAX_TEXT = 8e3;
|
|
30
|
+
var MAX_DEPTH = 16;
|
|
31
|
+
var MAX_HREF_LENGTH = 2e3;
|
|
32
|
+
var isSafeHref = (href) => {
|
|
33
|
+
if (typeof href !== "string" || href.length === 0 || href.length > MAX_HREF_LENGTH) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(href);
|
|
38
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var sanitizeChatContent = (input) => {
|
|
44
|
+
if (!input || typeof input !== "object") return void 0;
|
|
45
|
+
let nodeCount = 0;
|
|
46
|
+
let textLength = 0;
|
|
47
|
+
const walk = (node, depth) => {
|
|
48
|
+
if (nodeCount > MAX_NODES || textLength > MAX_TEXT || depth > MAX_DEPTH) return null;
|
|
49
|
+
if (!node || typeof node !== "object") return null;
|
|
50
|
+
const raw = node;
|
|
51
|
+
if (typeof raw.type !== "string" || !ALLOWED_NODES.has(raw.type)) return null;
|
|
52
|
+
nodeCount += 1;
|
|
53
|
+
const result = { type: raw.type };
|
|
54
|
+
if (raw.type === "text") {
|
|
55
|
+
if (typeof raw.text !== "string") return null;
|
|
56
|
+
textLength += raw.text.length;
|
|
57
|
+
result.text = raw.text;
|
|
58
|
+
}
|
|
59
|
+
if (raw.type === "codeBlock" && raw.attrs && typeof raw.attrs.language === "string") {
|
|
60
|
+
result.attrs = { language: raw.attrs.language.slice(0, 32) };
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(raw.marks) && raw.marks.length > 0) {
|
|
63
|
+
const marks = [];
|
|
64
|
+
for (const mark of raw.marks) {
|
|
65
|
+
if (!mark || typeof mark !== "object" || typeof mark.type !== "string") continue;
|
|
66
|
+
if (!ALLOWED_MARKS.has(mark.type)) continue;
|
|
67
|
+
if (mark.type === "link") {
|
|
68
|
+
const href = mark.attrs?.href;
|
|
69
|
+
if (!isSafeHref(href)) continue;
|
|
70
|
+
marks.push({
|
|
71
|
+
type: "link",
|
|
72
|
+
attrs: { href, target: "_blank", rel: "noopener noreferrer" }
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
marks.push({ type: mark.type });
|
|
77
|
+
}
|
|
78
|
+
if (marks.length > 0) result.marks = marks;
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(raw.content)) {
|
|
81
|
+
const content = [];
|
|
82
|
+
for (const child of raw.content) {
|
|
83
|
+
const sanitized2 = walk(child, depth + 1);
|
|
84
|
+
if (sanitized2) content.push(sanitized2);
|
|
85
|
+
}
|
|
86
|
+
if (content.length > 0) result.content = content;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
const sanitized = walk(input, 0);
|
|
91
|
+
if (!sanitized || sanitized.type !== "doc") return void 0;
|
|
92
|
+
return sanitized;
|
|
93
|
+
};
|
|
18
94
|
var useChatStore = create()((set, get) => ({
|
|
19
95
|
isChatOpen: false,
|
|
20
96
|
chatMessages: [],
|
|
@@ -31,14 +107,20 @@ var useChatStore = create()((set, get) => ({
|
|
|
31
107
|
unreadMessagesCount: isChatOpen ? unreadMessagesCount : unreadMessagesCount + 1
|
|
32
108
|
}));
|
|
33
109
|
},
|
|
110
|
+
removeChatMessage: (messageId) => {
|
|
111
|
+
set((state) => ({
|
|
112
|
+
chatMessages: state.chatMessages.filter((msg) => msg.id !== messageId)
|
|
113
|
+
}));
|
|
114
|
+
},
|
|
34
115
|
clearUnreadMessages: () => set({ unreadMessagesCount: 0 })
|
|
35
116
|
}));
|
|
36
117
|
|
|
37
118
|
// src/hooks/useChat.ts
|
|
38
119
|
var CHAT_MESSAGE_TYPE = "chat_message";
|
|
120
|
+
var CHAT_MESSAGE_DELETE_TYPE = "chat_message_delete";
|
|
39
121
|
var useChat = () => {
|
|
40
122
|
const { sendMessage } = useLiveKitDataChannel();
|
|
41
|
-
const { addChatMessage, clearUnreadMessages, updateStore } = useChatStore();
|
|
123
|
+
const { addChatMessage, removeChatMessage, clearUnreadMessages, updateStore } = useChatStore();
|
|
42
124
|
const chatSoundVolume = useSoundEffectsStore((s) => s.chatMessageVolume);
|
|
43
125
|
const { room } = useRoom();
|
|
44
126
|
const getCurrentParticipantInfo = useCallback(() => {
|
|
@@ -72,34 +154,60 @@ var useChat = () => {
|
|
|
72
154
|
const payload = message.payload;
|
|
73
155
|
const currentParticipantInfo = getCurrentParticipantInfo();
|
|
74
156
|
if (payload.senderId === currentParticipantInfo.senderId) {
|
|
75
|
-
console.log("\u{1F4AC} Ignoring own message:", payload);
|
|
76
157
|
return;
|
|
77
158
|
}
|
|
78
|
-
|
|
79
|
-
|
|
159
|
+
addChatMessage({
|
|
160
|
+
...payload,
|
|
161
|
+
content: sanitizeChatContent(payload.content)
|
|
162
|
+
});
|
|
80
163
|
playSound("chatMessage", chatSoundVolume);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (message.type === CHAT_MESSAGE_DELETE_TYPE) {
|
|
167
|
+
const payload = message.payload;
|
|
168
|
+
if (!payload?.id || !payload?.senderId) return;
|
|
169
|
+
const existing = useChatStore.getState().chatMessages.find((msg) => msg.id === payload.id);
|
|
170
|
+
if (!existing) return;
|
|
171
|
+
if (String(existing.senderId) !== String(payload.senderId)) return;
|
|
172
|
+
removeChatMessage(payload.id);
|
|
81
173
|
}
|
|
82
174
|
},
|
|
83
|
-
[addChatMessage, getCurrentParticipantInfo, chatSoundVolume]
|
|
175
|
+
[addChatMessage, removeChatMessage, getCurrentParticipantInfo, chatSoundVolume]
|
|
84
176
|
);
|
|
85
177
|
useLiveKitDataChannelListener(handleChatMessage);
|
|
86
178
|
const sendChatMessage = useCallback(
|
|
87
|
-
(text) => {
|
|
179
|
+
(text, content) => {
|
|
88
180
|
if (!text.trim()) return;
|
|
89
181
|
const participantInfo = getCurrentParticipantInfo();
|
|
182
|
+
const sanitizedContent = sanitizeChatContent(content);
|
|
90
183
|
const message = {
|
|
91
184
|
id: `${Date.now()}-${Math.random()}`,
|
|
92
185
|
text: text.trim(),
|
|
186
|
+
...sanitizedContent ? { content: sanitizedContent } : {},
|
|
93
187
|
senderId: participantInfo.senderId,
|
|
94
188
|
senderName: participantInfo.senderName,
|
|
95
189
|
timestamp: Date.now()
|
|
96
190
|
};
|
|
97
|
-
console.log("\u{1F4E4} Sending chat message:", message);
|
|
98
191
|
addChatMessage(message);
|
|
99
192
|
sendMessage(CHAT_MESSAGE_TYPE, message);
|
|
100
193
|
},
|
|
101
194
|
[sendMessage, getCurrentParticipantInfo, addChatMessage]
|
|
102
195
|
);
|
|
196
|
+
const deleteChatMessage = useCallback(
|
|
197
|
+
(messageId) => {
|
|
198
|
+
const existing = useChatStore.getState().chatMessages.find((msg) => msg.id === messageId);
|
|
199
|
+
if (!existing) return;
|
|
200
|
+
const participantInfo = getCurrentParticipantInfo();
|
|
201
|
+
if (String(existing.senderId) !== String(participantInfo.senderId)) return;
|
|
202
|
+
const payload = {
|
|
203
|
+
id: messageId,
|
|
204
|
+
senderId: participantInfo.senderId
|
|
205
|
+
};
|
|
206
|
+
removeChatMessage(messageId);
|
|
207
|
+
sendMessage(CHAT_MESSAGE_DELETE_TYPE, payload);
|
|
208
|
+
},
|
|
209
|
+
[sendMessage, getCurrentParticipantInfo, removeChatMessage]
|
|
210
|
+
);
|
|
103
211
|
const toggleChat = useCallback(() => {
|
|
104
212
|
updateStore("isChatOpen", !useChatStore.getState().isChatOpen);
|
|
105
213
|
clearUnreadMessages();
|
|
@@ -113,11 +221,349 @@ var useChat = () => {
|
|
|
113
221
|
}, [updateStore]);
|
|
114
222
|
return {
|
|
115
223
|
sendChatMessage,
|
|
224
|
+
deleteChatMessage,
|
|
116
225
|
toggleChat,
|
|
117
226
|
openChat,
|
|
118
227
|
closeChat
|
|
119
228
|
};
|
|
120
229
|
};
|
|
230
|
+
var underlineInputRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
|
|
231
|
+
var underlinePasteRegex = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
|
|
232
|
+
var DiscordBold = Bold.extend({
|
|
233
|
+
addInputRules() {
|
|
234
|
+
return [
|
|
235
|
+
markInputRule({
|
|
236
|
+
find: starInputRegex,
|
|
237
|
+
type: this.type
|
|
238
|
+
})
|
|
239
|
+
];
|
|
240
|
+
},
|
|
241
|
+
addPasteRules() {
|
|
242
|
+
return [
|
|
243
|
+
markPasteRule({
|
|
244
|
+
find: starPasteRegex,
|
|
245
|
+
type: this.type
|
|
246
|
+
})
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
var DiscordUnderline = Underline.extend({
|
|
251
|
+
addInputRules() {
|
|
252
|
+
return [
|
|
253
|
+
markInputRule({
|
|
254
|
+
find: underlineInputRegex,
|
|
255
|
+
type: this.type
|
|
256
|
+
})
|
|
257
|
+
];
|
|
258
|
+
},
|
|
259
|
+
addPasteRules() {
|
|
260
|
+
return [
|
|
261
|
+
markPasteRule({
|
|
262
|
+
find: underlinePasteRegex,
|
|
263
|
+
type: this.type
|
|
264
|
+
})
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// src/editor/extensions.ts
|
|
270
|
+
var LINK_CLASS = "text-text-link hover:text-text-link cursor-pointer underline";
|
|
271
|
+
var chatExtensions = [
|
|
272
|
+
StarterKit.configure({
|
|
273
|
+
bold: false,
|
|
274
|
+
underline: false,
|
|
275
|
+
heading: false,
|
|
276
|
+
horizontalRule: false,
|
|
277
|
+
bulletList: false,
|
|
278
|
+
orderedList: false,
|
|
279
|
+
listItem: false,
|
|
280
|
+
listKeymap: false,
|
|
281
|
+
trailingNode: false,
|
|
282
|
+
link: {
|
|
283
|
+
openOnClick: false,
|
|
284
|
+
autolink: true,
|
|
285
|
+
defaultProtocol: "https",
|
|
286
|
+
HTMLAttributes: {
|
|
287
|
+
target: "_blank",
|
|
288
|
+
rel: "noopener noreferrer",
|
|
289
|
+
class: LINK_CLASS
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}),
|
|
293
|
+
DiscordBold,
|
|
294
|
+
DiscordUnderline
|
|
295
|
+
];
|
|
296
|
+
var chatProseClassName = cn(
|
|
297
|
+
"[&_.ProseMirror]:min-h-6 [&_.ProseMirror]:text-sm [&_.ProseMirror]:break-words [&_.ProseMirror]:outline-none",
|
|
298
|
+
"[&_.ProseMirror_p]:m-0 [&_.ProseMirror_p+p]:mt-1",
|
|
299
|
+
"[&_.ProseMirror_strong]:font-semibold",
|
|
300
|
+
"[&_.ProseMirror_em]:italic",
|
|
301
|
+
"[&_.ProseMirror_s]:line-through",
|
|
302
|
+
"[&_.ProseMirror_u]:underline",
|
|
303
|
+
"[&_.ProseMirror_code]:bg-background-page [&_.ProseMirror_code]:rounded [&_.ProseMirror_code]:px-1 [&_.ProseMirror_code]:py-0.5 [&_.ProseMirror_code]:font-mono [&_.ProseMirror_code]:text-[0.85em]",
|
|
304
|
+
"[&_.ProseMirror_pre]:bg-background-page [&_.ProseMirror_pre]:my-1 [&_.ProseMirror_pre]:overflow-x-auto [&_.ProseMirror_pre]:rounded-lg [&_.ProseMirror_pre]:p-2 [&_.ProseMirror_pre]:font-mono [&_.ProseMirror_pre]:text-xs [&_.ProseMirror_pre]:whitespace-pre-wrap",
|
|
305
|
+
"[&_.ProseMirror_blockquote]:border-border-default [&_.ProseMirror_blockquote]:text-text-secondary [&_.ProseMirror_blockquote]:my-1 [&_.ProseMirror_blockquote]:border-l-2 [&_.ProseMirror_blockquote]:pl-2",
|
|
306
|
+
"[&_.ProseMirror_a]:text-text-link [&_.ProseMirror_a]:cursor-pointer [&_.ProseMirror_a]:underline"
|
|
307
|
+
);
|
|
308
|
+
var chatMessageProseClassName = cn(
|
|
309
|
+
"text-sm wrap-break-word",
|
|
310
|
+
"[&_p]:m-0 [&_p+p]:mt-1",
|
|
311
|
+
"[&_strong]:font-semibold",
|
|
312
|
+
"[&_em]:italic",
|
|
313
|
+
"[&_s]:line-through",
|
|
314
|
+
"[&_u]:underline"
|
|
315
|
+
);
|
|
316
|
+
var isValidTextSelection = (state) => {
|
|
317
|
+
const { doc, selection } = state;
|
|
318
|
+
if (!(selection instanceof TextSelection) || selection.empty) return false;
|
|
319
|
+
if (selection.from === 0 || selection.to === 0) return false;
|
|
320
|
+
try {
|
|
321
|
+
const $from = doc.resolve(selection.from);
|
|
322
|
+
const $to = doc.resolve(selection.to);
|
|
323
|
+
return $from.parent.isTextblock && $to.parent.isTextblock;
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
var FormatButton = ({ editor, type, isActive, ariaLabel, children }) => {
|
|
329
|
+
return /* @__PURE__ */ jsx(
|
|
330
|
+
Button,
|
|
331
|
+
{
|
|
332
|
+
type: "button",
|
|
333
|
+
variant: "none",
|
|
334
|
+
"aria-label": ariaLabel,
|
|
335
|
+
"aria-pressed": isActive,
|
|
336
|
+
className: cn(
|
|
337
|
+
"[&_svg]:fill-icon-primary h-6 w-6 rounded-sm p-1",
|
|
338
|
+
isActive && "bg-status-info-background [&_svg]:fill-icon-brand"
|
|
339
|
+
),
|
|
340
|
+
onMouseDown: (event) => {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
editor.chain().focus().toggleMark(type).run();
|
|
343
|
+
},
|
|
344
|
+
children
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
var ChatBubbleMenu = ({ editor }) => {
|
|
349
|
+
const { t } = useTranslation("calls");
|
|
350
|
+
const activeStates = useEditorState({
|
|
351
|
+
editor,
|
|
352
|
+
selector: ({ editor: current }) => ({
|
|
353
|
+
bold: current.isActive("bold"),
|
|
354
|
+
italic: current.isActive("italic"),
|
|
355
|
+
underline: current.isActive("underline"),
|
|
356
|
+
strike: current.isActive("strike")
|
|
357
|
+
})
|
|
358
|
+
});
|
|
359
|
+
return /* @__PURE__ */ jsxs(
|
|
360
|
+
BubbleMenu,
|
|
361
|
+
{
|
|
362
|
+
editor,
|
|
363
|
+
appendTo: () => editor.view.dom.ownerDocument.body,
|
|
364
|
+
shouldShow: ({ state }) => isValidTextSelection(state),
|
|
365
|
+
options: {
|
|
366
|
+
placement: "top",
|
|
367
|
+
strategy: "fixed",
|
|
368
|
+
offset: 8
|
|
369
|
+
},
|
|
370
|
+
className: "border-border-default bg-background-surface z-200 flex gap-1 rounded-lg border p-1 shadow-lg",
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ jsx(
|
|
373
|
+
FormatButton,
|
|
374
|
+
{
|
|
375
|
+
editor,
|
|
376
|
+
type: "bold",
|
|
377
|
+
isActive: activeStates.bold,
|
|
378
|
+
ariaLabel: t("chat.formatBold"),
|
|
379
|
+
children: /* @__PURE__ */ jsx(Bold$1, {})
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
383
|
+
FormatButton,
|
|
384
|
+
{
|
|
385
|
+
editor,
|
|
386
|
+
type: "italic",
|
|
387
|
+
isActive: activeStates.italic,
|
|
388
|
+
ariaLabel: t("chat.formatItalic"),
|
|
389
|
+
children: /* @__PURE__ */ jsx(Italic, {})
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
/* @__PURE__ */ jsx(
|
|
393
|
+
FormatButton,
|
|
394
|
+
{
|
|
395
|
+
editor,
|
|
396
|
+
type: "underline",
|
|
397
|
+
isActive: activeStates.underline,
|
|
398
|
+
ariaLabel: t("chat.formatUnderline"),
|
|
399
|
+
children: /* @__PURE__ */ jsx(Underline$1, {})
|
|
400
|
+
}
|
|
401
|
+
),
|
|
402
|
+
/* @__PURE__ */ jsx(
|
|
403
|
+
FormatButton,
|
|
404
|
+
{
|
|
405
|
+
editor,
|
|
406
|
+
type: "strike",
|
|
407
|
+
isActive: activeStates.strike,
|
|
408
|
+
ariaLabel: t("chat.formatStrike"),
|
|
409
|
+
children: /* @__PURE__ */ jsx(Stroke, {})
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
var ChatComposer = ({ placeholder, onSend }) => {
|
|
417
|
+
const onSendRef = useRef(onSend);
|
|
418
|
+
onSendRef.current = onSend;
|
|
419
|
+
const editorRef = useRef(null);
|
|
420
|
+
const sendFromEditor = useCallback(() => {
|
|
421
|
+
const current = editorRef.current;
|
|
422
|
+
if (!current || current.isDestroyed) return;
|
|
423
|
+
const text = current.getText({ blockSeparator: "\n" }).trim();
|
|
424
|
+
if (!text) return;
|
|
425
|
+
onSendRef.current(text, current.getJSON());
|
|
426
|
+
current.commands.clearContent(true);
|
|
427
|
+
current.commands.focus();
|
|
428
|
+
}, []);
|
|
429
|
+
const sendFromEditorRef = useRef(sendFromEditor);
|
|
430
|
+
sendFromEditorRef.current = sendFromEditor;
|
|
431
|
+
const editor = useEditor({
|
|
432
|
+
immediatelyRender: false,
|
|
433
|
+
extensions: chatExtensions,
|
|
434
|
+
autofocus: true,
|
|
435
|
+
editorProps: {
|
|
436
|
+
attributes: {
|
|
437
|
+
class: "min-h-6 text-sm break-words outline-none",
|
|
438
|
+
"aria-label": placeholder
|
|
439
|
+
},
|
|
440
|
+
handleKeyDown: (_view, event) => {
|
|
441
|
+
if (event.key !== "Enter" || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (event.isComposing || event.keyCode === 229) return false;
|
|
445
|
+
event.preventDefault();
|
|
446
|
+
sendFromEditorRef.current();
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
editorRef.current = editor;
|
|
453
|
+
}, [editor]);
|
|
454
|
+
const isEmpty = useEditorState({
|
|
455
|
+
editor,
|
|
456
|
+
selector: ({ editor: current }) => !current?.getText({ blockSeparator: "\n" }).trim()
|
|
457
|
+
}) ?? true;
|
|
458
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-end gap-2 pr-3", children: /* @__PURE__ */ jsxs(
|
|
459
|
+
"div",
|
|
460
|
+
{
|
|
461
|
+
className: cn(
|
|
462
|
+
"border-border-default flex max-h-40 w-full flex-1 items-center rounded-xl border pl-4",
|
|
463
|
+
chatProseClassName
|
|
464
|
+
),
|
|
465
|
+
children: [
|
|
466
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
|
|
467
|
+
isEmpty && /* @__PURE__ */ jsx("span", { className: "text-text-secondary pointer-events-none absolute top-3 left-0 text-sm", children: placeholder }),
|
|
468
|
+
/* @__PURE__ */ jsx(EditorContent, { editor, className: "my-3 max-h-32 overflow-y-auto pr-2" }),
|
|
469
|
+
editor && /* @__PURE__ */ jsx(ChatBubbleMenu, { editor })
|
|
470
|
+
] }),
|
|
471
|
+
/* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(
|
|
472
|
+
Button,
|
|
473
|
+
{
|
|
474
|
+
size: "icon",
|
|
475
|
+
variant: "primary",
|
|
476
|
+
onClick: sendFromEditor,
|
|
477
|
+
disabled: isEmpty,
|
|
478
|
+
className: "rounded-xl p-2",
|
|
479
|
+
children: /* @__PURE__ */ jsx(
|
|
480
|
+
Send,
|
|
481
|
+
{
|
|
482
|
+
className: cn(
|
|
483
|
+
"fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6",
|
|
484
|
+
isEmpty && "fill-icon-secondary"
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
) })
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
) });
|
|
493
|
+
};
|
|
494
|
+
var LINK_CLASS2 = "text-text-link hover:text-text-link cursor-pointer underline";
|
|
495
|
+
var wrapMarks = (text, marks) => {
|
|
496
|
+
return (marks ?? []).reduce((acc, mark) => {
|
|
497
|
+
switch (mark.type) {
|
|
498
|
+
case "bold":
|
|
499
|
+
return /* @__PURE__ */ jsx("strong", { children: acc });
|
|
500
|
+
case "italic":
|
|
501
|
+
return /* @__PURE__ */ jsx("em", { children: acc });
|
|
502
|
+
case "strike":
|
|
503
|
+
return /* @__PURE__ */ jsx("s", { children: acc });
|
|
504
|
+
case "underline":
|
|
505
|
+
return /* @__PURE__ */ jsx("u", { children: acc });
|
|
506
|
+
case "code":
|
|
507
|
+
return /* @__PURE__ */ jsx("code", { className: "bg-background-page rounded px-1 py-0.5 font-mono text-[0.85em]", children: acc });
|
|
508
|
+
case "link": {
|
|
509
|
+
const href = typeof mark.attrs?.href === "string" ? mark.attrs.href : void 0;
|
|
510
|
+
if (!href) return acc;
|
|
511
|
+
return /* @__PURE__ */ jsx(
|
|
512
|
+
"a",
|
|
513
|
+
{
|
|
514
|
+
href,
|
|
515
|
+
target: "_blank",
|
|
516
|
+
rel: "noopener noreferrer",
|
|
517
|
+
className: LINK_CLASS2,
|
|
518
|
+
onClick: (event) => event.stopPropagation(),
|
|
519
|
+
"data-umami-event": "outbound-link-click",
|
|
520
|
+
"data-umami-event-url": href,
|
|
521
|
+
"data-umami-event-source": "chat",
|
|
522
|
+
children: acc
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
default:
|
|
527
|
+
return acc;
|
|
528
|
+
}
|
|
529
|
+
}, text);
|
|
530
|
+
};
|
|
531
|
+
var renderNode = (node, key) => {
|
|
532
|
+
const children = node.content?.map((child, index) => renderNode(child, `${key}-${index}`));
|
|
533
|
+
switch (node.type) {
|
|
534
|
+
case "doc":
|
|
535
|
+
return children;
|
|
536
|
+
case "paragraph":
|
|
537
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 min-h-[1em]", children: children ?? /* @__PURE__ */ jsx("br", {}) }, key);
|
|
538
|
+
case "blockquote":
|
|
539
|
+
return /* @__PURE__ */ jsx(
|
|
540
|
+
"blockquote",
|
|
541
|
+
{
|
|
542
|
+
className: "border-border-default text-text-secondary my-1 border-l-2 pl-2",
|
|
543
|
+
children
|
|
544
|
+
},
|
|
545
|
+
key
|
|
546
|
+
);
|
|
547
|
+
case "codeBlock":
|
|
548
|
+
return /* @__PURE__ */ jsx(
|
|
549
|
+
"pre",
|
|
550
|
+
{
|
|
551
|
+
className: "bg-background-page my-1 overflow-x-auto rounded-lg p-2 font-mono text-xs whitespace-pre-wrap",
|
|
552
|
+
children: /* @__PURE__ */ jsx("code", { children })
|
|
553
|
+
},
|
|
554
|
+
key
|
|
555
|
+
);
|
|
556
|
+
case "hardBreak":
|
|
557
|
+
return /* @__PURE__ */ jsx("br", {}, key);
|
|
558
|
+
case "text":
|
|
559
|
+
return /* @__PURE__ */ jsx("span", { children: wrapMarks(node.text ?? "", node.marks) }, key);
|
|
560
|
+
default:
|
|
561
|
+
return children;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
var renderChatJsonContent = (content) => {
|
|
565
|
+
return renderNode(content, "chat-doc");
|
|
566
|
+
};
|
|
121
567
|
var parseLinks = (message) => {
|
|
122
568
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
123
569
|
const messageParts = message.split(urlRegex);
|
|
@@ -142,18 +588,19 @@ var parseLinks = (message) => {
|
|
|
142
588
|
return messagePart;
|
|
143
589
|
});
|
|
144
590
|
};
|
|
145
|
-
var
|
|
591
|
+
var ChatMessageContent = ({ text, content, className }) => {
|
|
592
|
+
return /* @__PURE__ */ jsx("div", { className: cn(chatMessageProseClassName, className), children: content ? renderChatJsonContent(content) : parseLinks(text) });
|
|
593
|
+
};
|
|
594
|
+
var Chat = ({ compactPositionClassName, embedded = false } = {}) => {
|
|
146
595
|
const { t, i18n } = useTranslation("calls");
|
|
147
|
-
const [messageText, setMessageText] = useState("");
|
|
148
596
|
const messagesEndRef = useRef(null);
|
|
149
|
-
const { sendChatMessage, closeChat } = useChat();
|
|
597
|
+
const { sendChatMessage, deleteChatMessage, closeChat } = useChat();
|
|
150
598
|
const { chatMessages, isChatOpen } = useChatStore();
|
|
151
599
|
const { data: currentUser } = useCalls().auth.useCurrentUser();
|
|
152
600
|
const timeLocale = i18n.language?.startsWith("ru") ? "ru-RU" : i18n.language?.startsWith("en") ? "en-US" : i18n.language;
|
|
153
601
|
const scrollToBottom = (behavior = "smooth") => {
|
|
154
602
|
messagesEndRef.current?.scrollIntoView({ behavior });
|
|
155
603
|
};
|
|
156
|
-
const textareaRef = useRef(null);
|
|
157
604
|
useEffect(() => {
|
|
158
605
|
if (isChatOpen) {
|
|
159
606
|
requestAnimationFrame(() => {
|
|
@@ -168,26 +615,15 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
168
615
|
});
|
|
169
616
|
}
|
|
170
617
|
}, [chatMessages.length, isChatOpen]);
|
|
171
|
-
const handleSendMessage = () => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
scrollToBottom("smooth");
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
if (textareaRef.current) {
|
|
180
|
-
textareaRef.current.style.height = "auto";
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const handleKeyDownSendMessage = (e) => {
|
|
184
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
185
|
-
e.preventDefault();
|
|
186
|
-
handleSendMessage();
|
|
187
|
-
}
|
|
618
|
+
const handleSendMessage = (text, content) => {
|
|
619
|
+
sendChatMessage(text, content);
|
|
620
|
+
requestAnimationFrame(() => {
|
|
621
|
+
scrollToBottom("smooth");
|
|
622
|
+
});
|
|
188
623
|
};
|
|
189
624
|
const isMobile = useMediaQuery("(max-width: 639px)");
|
|
190
625
|
if (!isChatOpen) return null;
|
|
626
|
+
const chatPanelClassName = "border-border-default bg-background-surface flex min-h-0 flex-col overflow-hidden rounded-2xl border p-4 pr-1";
|
|
191
627
|
const chatContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
192
628
|
/* @__PURE__ */ jsxs("div", { className: "border-border-default flex items-center justify-between pr-3", children: [
|
|
193
629
|
/* @__PURE__ */ jsx("h3", { className: "text-text-primary text-lg font-medium", children: t("chat.title") }),
|
|
@@ -199,7 +635,7 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
199
635
|
return /* @__PURE__ */ jsx(
|
|
200
636
|
"div",
|
|
201
637
|
{
|
|
202
|
-
className: `flex ${isOwnMessage ? "justify-end" : "justify-start"}`,
|
|
638
|
+
className: `group flex ${isOwnMessage ? "justify-end" : "justify-start"}`,
|
|
203
639
|
children: /* @__PURE__ */ jsxs("div", { className: "text-text-primary flex max-w-[90%] flex-col gap-1 rounded-lg select-text", children: [
|
|
204
640
|
/* @__PURE__ */ jsxs("div", { className: "text-text-primary flex flex-row items-center gap-1 text-xs font-medium", children: [
|
|
205
641
|
!isOwnMessage && /* @__PURE__ */ jsx(
|
|
@@ -222,16 +658,31 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
222
658
|
}
|
|
223
659
|
)
|
|
224
660
|
] }),
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
661
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
662
|
+
/* @__PURE__ */ jsx(
|
|
663
|
+
ChatMessageContent,
|
|
664
|
+
{
|
|
665
|
+
text: message.text,
|
|
666
|
+
content: message.content,
|
|
667
|
+
className: cn(
|
|
668
|
+
"cursor-text rounded-lg px-3 py-2 select-text",
|
|
669
|
+
isOwnMessage ? "bg-action-primary-background-disabled" : "bg-background-page"
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
),
|
|
673
|
+
isOwnMessage && /* @__PURE__ */ jsx(
|
|
674
|
+
Button,
|
|
675
|
+
{
|
|
676
|
+
type: "button",
|
|
677
|
+
size: "icon",
|
|
678
|
+
variant: "none",
|
|
679
|
+
className: "bg-background-surface border-border-default absolute -top-2 -left-2 h-7 w-7 rounded-full border p-1 opacity-100 shadow-sm sm:opacity-0 sm:group-focus-within:opacity-100 sm:group-hover:opacity-100",
|
|
680
|
+
onClick: () => deleteChatMessage(message.id),
|
|
681
|
+
"aria-label": t("chat.deleteAria"),
|
|
682
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "fill-icon-secondary h-4 w-4" })
|
|
683
|
+
}
|
|
684
|
+
)
|
|
685
|
+
] })
|
|
235
686
|
] })
|
|
236
687
|
},
|
|
237
688
|
message.id
|
|
@@ -239,39 +690,11 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
239
690
|
}),
|
|
240
691
|
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
241
692
|
] }) }),
|
|
242
|
-
/* @__PURE__ */ jsx(
|
|
243
|
-
/* @__PURE__ */ jsx(
|
|
244
|
-
Textarea,
|
|
245
|
-
{
|
|
246
|
-
ref: textareaRef,
|
|
247
|
-
value: messageText,
|
|
248
|
-
onChange: (e) => setMessageText(e.target.value),
|
|
249
|
-
placeholder: t("chat.placeholder"),
|
|
250
|
-
className: "my-3 max-h-32 min-w-full rounded-none border-none p-0 pr-2",
|
|
251
|
-
onKeyDown: handleKeyDownSendMessage
|
|
252
|
-
}
|
|
253
|
-
),
|
|
254
|
-
/* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(
|
|
255
|
-
Button,
|
|
256
|
-
{
|
|
257
|
-
size: "icon",
|
|
258
|
-
variant: "primary",
|
|
259
|
-
onClick: handleSendMessage,
|
|
260
|
-
disabled: !messageText.trim(),
|
|
261
|
-
className: "rounded-xl p-2",
|
|
262
|
-
children: /* @__PURE__ */ jsx(
|
|
263
|
-
Send,
|
|
264
|
-
{
|
|
265
|
-
className: cn(
|
|
266
|
-
"fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6",
|
|
267
|
-
!messageText.trim() && "fill-icon-secondary"
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
)
|
|
271
|
-
}
|
|
272
|
-
) })
|
|
273
|
-
] }) })
|
|
693
|
+
/* @__PURE__ */ jsx(ChatComposer, { placeholder: t("chat.placeholder"), onSend: handleSendMessage })
|
|
274
694
|
] });
|
|
695
|
+
if (embedded) {
|
|
696
|
+
return /* @__PURE__ */ jsx("div", { className: cn(chatPanelClassName, "h-full w-full"), children: chatContent });
|
|
697
|
+
}
|
|
275
698
|
if (isMobile) {
|
|
276
699
|
return /* @__PURE__ */ jsx(Modal, { open: isChatOpen, onOpenChange: (open) => !open && closeChat(), children: /* @__PURE__ */ jsxs(
|
|
277
700
|
ModalContent,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/chatStore.ts","../src/hooks/useChat.ts","../src/utils/chat.tsx","../src/ui/Chat.tsx","../src/ui/ChatButton.tsx"],"names":["jsx","jsxs","Button","cn","Chat"],"mappings":"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","file":"index.mjs","sourcesContent":["import { create } from 'zustand';\n\ntype ChatMessageT = {\n id: string;\n text: string;\n senderId: string;\n senderName: string;\n timestamp: number;\n};\n\nexport type useChatStoreT = {\n isChatOpen: boolean;\n chatMessages: ChatMessageT[];\n unreadMessagesCount: number;\n addChatMessage: (message: ChatMessageT) => void;\n clearUnreadMessages: () => void;\n updateStore: (type: keyof useChatStoreT, value: unknown) => void;\n};\n\n// Чат передаётся только через LiveKit DataChannel (P2P/broadcast в рамках текущего звонка),\n// серверной истории сообщений нет. Раньше стор персистился в localStorage без привязки\n// к конкретному звонку — сообщения предыдущего звонка «утекали» в следующий на том же\n// устройстве (и только у того пользователя, у кого localStorage не был очищен штатным\n// disconnect-флоу). Стор намеренно НЕ персистится: чат живёт только в памяти на время\n// звонка и одинаково пуст у всех при входе в новый звонок.\nexport const useChatStore = create<useChatStoreT>()((set, get) => ({\n isChatOpen: false,\n chatMessages: [],\n unreadMessagesCount: 0,\n updateStore: (type: keyof useChatStoreT, value: unknown) => set({ [type]: value }),\n\n addChatMessage: (message: ChatMessageT) => {\n const { isChatOpen, unreadMessagesCount, chatMessages } = get();\n\n // Проверяем, нет ли уже сообщения с таким ID (дедупликация)\n const messageExists = chatMessages.some((msg) => msg.id === message.id);\n if (messageExists) {\n return;\n }\n\n set((state) => ({\n chatMessages: [...state.chatMessages, message],\n unreadMessagesCount: isChatOpen ? unreadMessagesCount : unreadMessagesCount + 1,\n }));\n },\n\n clearUnreadMessages: () => set({ unreadMessagesCount: 0 }),\n}));\n","import { useCallback } from 'react';\nimport { useLiveKitDataChannel, useLiveKitDataChannelListener } from '@xipkg/calls-hooks';\nimport { useRoom } from '@xipkg/calls-providers';\nimport { useSoundEffectsStore } from '@xipkg/calls-store';\nimport { playSound } from '@xipkg/calls-utils';\nimport { useChatStore } from '../store';\n\nconst CHAT_MESSAGE_TYPE = 'chat_message';\n\ntype ChatMessagePayload = {\n id: string;\n text: string;\n senderId: string;\n senderName: string;\n timestamp: number;\n};\n\nexport const useChat = () => {\n const { sendMessage } = useLiveKitDataChannel();\n const { addChatMessage, clearUnreadMessages, updateStore } = useChatStore();\n const chatSoundVolume = useSoundEffectsStore((s) => s.chatMessageVolume);\n const { room } = useRoom();\n\n // Получаем информацию о текущем участнике из LiveKit\n const getCurrentParticipantInfo = useCallback(() => {\n if (!room?.localParticipant) {\n return {\n senderId: 'unknown',\n senderName: 'Unknown User',\n };\n }\n\n const participant = room.localParticipant;\n\n try {\n // Парсим метаданные участника\n const metadata = participant.metadata;\n if (metadata) {\n const userInfo = JSON.parse(metadata);\n return {\n senderId: userInfo?.user_id || userInfo?.id || participant.identity,\n senderName:\n userInfo?.display_name ||\n userInfo?.name ||\n userInfo?.username ||\n participant.name ||\n participant.identity,\n };\n }\n } catch (error) {\n console.warn('⚠️ Failed to parse participant metadata:', error);\n }\n\n // Fallback на стандартные поля LiveKit\n return {\n senderId: participant.identity,\n senderName: participant.name || participant.identity,\n };\n }, [room]);\n\n const handleChatMessage = useCallback(\n (message: { type: string; payload: unknown }) => {\n if (message.type === CHAT_MESSAGE_TYPE) {\n const payload = message.payload as ChatMessagePayload;\n\n // Проверяем, что это не наше собственное сообщение\n const currentParticipantInfo = getCurrentParticipantInfo();\n if (payload.senderId === currentParticipantInfo.senderId) {\n console.log('💬 Ignoring own message:', payload);\n return;\n }\n\n console.log('💬 Received chat message:', payload);\n addChatMessage(payload);\n playSound('chatMessage', chatSoundVolume);\n }\n },\n [addChatMessage, getCurrentParticipantInfo, chatSoundVolume],\n );\n\n // Слушаем сообщения чата\n useLiveKitDataChannelListener(handleChatMessage);\n\n const sendChatMessage = useCallback(\n (text: string) => {\n if (!text.trim()) return;\n\n const participantInfo = getCurrentParticipantInfo();\n const message: ChatMessagePayload = {\n id: `${Date.now()}-${Math.random()}`,\n text: text.trim(),\n senderId: participantInfo.senderId,\n senderName: participantInfo.senderName,\n timestamp: Date.now(),\n };\n\n console.log('📤 Sending chat message:', message);\n\n // Добавляем сообщение в локальный store отправителя\n addChatMessage(message);\n\n // Отправляем через DataChannel\n sendMessage(CHAT_MESSAGE_TYPE, message);\n },\n [sendMessage, getCurrentParticipantInfo, addChatMessage],\n );\n\n const toggleChat = useCallback(() => {\n updateStore('isChatOpen', !useChatStore.getState().isChatOpen);\n clearUnreadMessages();\n }, [updateStore, clearUnreadMessages]);\n\n const openChat = useCallback(() => {\n updateStore('isChatOpen', true);\n clearUnreadMessages();\n }, [updateStore, clearUnreadMessages]);\n\n const closeChat = useCallback(() => {\n updateStore('isChatOpen', false);\n }, [updateStore]);\n\n return {\n sendChatMessage,\n toggleChat,\n openChat,\n closeChat,\n };\n};\n","export const parseLinks = (message: string) => {\n const urlRegex = /(https?:\\/\\/[^\\s]+)/g;\n const messageParts = message.split(urlRegex);\n\n return messageParts.map((messagePart, index) => {\n if (urlRegex.test(messagePart)) {\n return (\n <a\n key={index}\n href={messagePart}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-text-link hover:text-text-link cursor-pointer underline\"\n onClick={(e) => e.stopPropagation()}\n data-umami-event=\"outbound-link-click\"\n data-umami-event-url={messagePart}\n data-umami-event-source=\"chat\"\n >\n {messagePart}\n </a>\n );\n }\n\n return messagePart;\n });\n};\n","import { useState, useRef, useEffect } from 'react';\nimport { Button } from '@xipkg/button';\nimport { Textarea } from '@xipkg/textarea';\nimport { Send, Close } from '@xipkg/icons';\nimport { UserProfile } from '@xipkg/userprofile';\nimport { ScrollArea } from '@xipkg/scrollarea';\nimport { Modal, ModalContent, ModalTitle } from '@xipkg/modal';\nimport { useTranslation } from 'react-i18next';\nimport { useChat } from '../hooks';\nimport { useCalls } from '@xipkg/calls-providers';\nimport { useChatStore } from '../store';\nimport { cn, useMediaQuery } from '@xipkg/utils';\nimport { parseLinks } from '../utils/chat';\n\ntype ChatProps = {\n /** В компакт-режиме: классы позиционирования (как у CompactCall: top-16 bottom-4 left-4 и т.д.) */\n compactPositionClassName?: string;\n};\n\nexport const Chat = ({ compactPositionClassName }: ChatProps = {}) => {\n const { t, i18n } = useTranslation('calls');\n const [messageText, setMessageText] = useState('');\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const { sendChatMessage, closeChat } = useChat();\n const { chatMessages, isChatOpen } = useChatStore();\n const { data: currentUser } = useCalls().auth.useCurrentUser();\n const timeLocale = i18n.language?.startsWith('ru')\n ? 'ru-RU'\n : i18n.language?.startsWith('en')\n ? 'en-US'\n : i18n.language;\n\n const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {\n messagesEndRef.current?.scrollIntoView({ behavior });\n };\n\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (isChatOpen) {\n requestAnimationFrame(() => {\n scrollToBottom('auto');\n });\n }\n }, [isChatOpen]);\n\n // Автоматическая прокрутка при получении новых сообщений\n useEffect(() => {\n if (isChatOpen && chatMessages.length > 0) {\n requestAnimationFrame(() => {\n scrollToBottom('smooth');\n });\n }\n }, [chatMessages.length, isChatOpen]);\n\n const handleSendMessage = () => {\n if (messageText.trim()) {\n sendChatMessage(messageText);\n setMessageText('');\n requestAnimationFrame(() => {\n scrollToBottom('smooth');\n });\n }\n\n if (textareaRef.current) {\n textareaRef.current.style.height = 'auto';\n }\n };\n\n const handleKeyDownSendMessage = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n // Enter без Shift - отправка сообщения\n // Shift+Enter - перенос строки (разрешаем стандартное поведение)\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n // Если Shift+Enter, не предотвращаем стандартное поведение - будет перенос строки\n };\n\n const isMobile = useMediaQuery('(max-width: 639px)');\n if (!isChatOpen) return null;\n\n const chatContent = (\n <>\n {/* Заголовок */}\n <div className=\"border-border-default flex items-center justify-between pr-3\">\n <h3 className=\"text-text-primary text-lg font-medium\">{t('chat.title')}</h3>\n <Button size=\"icon\" variant=\"none\" onClick={closeChat}>\n <Close className=\"h-6 w-6\" aria-label={t('chat.close')} />\n </Button>\n </div>\n\n {/* Сообщения */}\n <ScrollArea className=\"min-h-0 flex-1 py-2 pr-3\">\n <div className=\"space-y-4\">\n {chatMessages.length === 0 ? (\n <div className=\"text-text-secondary text-center\">\n <p>{t('chat.empty')}</p>\n </div>\n ) : (\n chatMessages.map((message) => {\n const isOwnMessage = Number(message.senderId) === Number(currentUser?.id);\n return (\n <div\n key={message.id}\n className={`flex ${isOwnMessage ? 'justify-end' : 'justify-start'}`}\n >\n <div className=\"text-text-primary flex max-w-[90%] flex-col gap-1 rounded-lg select-text\">\n <div className=\"text-text-primary flex flex-row items-center gap-1 text-xs font-medium\">\n {!isOwnMessage && (\n <UserProfile\n size=\"s\"\n userId={Number(message.senderId)}\n text={message.senderName}\n src={`https://api.sovlium.ru/files/users/${message.senderId}/avatar.webp`}\n />\n )}\n <div\n className={`text-xs-base ${isOwnMessage ? 'text-text-disabled ml-auto' : 'text-text-secondary'}`}\n >\n {new Date(message.timestamp).toLocaleTimeString(timeLocale, {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </div>\n </div>\n <div\n className={cn(\n 'cursor-text rounded-lg px-3 py-2 text-sm wrap-break-word whitespace-pre-wrap select-text',\n isOwnMessage\n ? 'bg-action-primary-background-disabled'\n : 'bg-background-page',\n )}\n >\n {parseLinks(message.text)}\n </div>\n </div>\n </div>\n );\n })\n )}\n <div ref={messagesEndRef} />\n </div>\n </ScrollArea>\n\n {/* Поле ввода */}\n <div className=\"flex items-end gap-2 pr-3\">\n <div className=\"border-border-default flex max-h-40 w-full flex-1 items-center rounded-xl border pl-4\">\n <Textarea\n ref={textareaRef}\n value={messageText}\n onChange={(e) => setMessageText(e.target.value)}\n placeholder={t('chat.placeholder')}\n className=\"my-3 max-h-32 min-w-full rounded-none border-none p-0 pr-2\"\n onKeyDown={handleKeyDownSendMessage}\n />\n <div className=\"pr-1\">\n <Button\n size=\"icon\"\n variant=\"primary\"\n onClick={handleSendMessage}\n disabled={!messageText.trim()}\n className=\"rounded-xl p-2\"\n >\n <Send\n className={cn(\n 'fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6',\n !messageText.trim() && 'fill-icon-secondary',\n )}\n />\n </Button>\n </div>\n </div>\n </div>\n </>\n );\n\n if (isMobile) {\n return (\n <Modal open={isChatOpen} onOpenChange={(open) => !open && closeChat()}>\n <ModalContent\n className=\"border-border-default bg-background-surface flex h-[85dvh] max-h-[85dvh] w-[calc(100vw-32px)] max-w-[calc(100vw-32px)] flex-col gap-0 overflow-hidden rounded-2xl border p-4 pr-1\"\n aria-describedby={undefined}\n >\n <ModalTitle className=\"sr-only\">{t('chat.title')}</ModalTitle>\n <div className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">{chatContent}</div>\n </ModalContent>\n </Modal>\n );\n }\n\n if (compactPositionClassName) {\n return (\n <div\n className={cn(\n 'border-border-default bg-background-surface fixed z-100 flex min-h-0 w-[328px] flex-col overflow-hidden rounded-2xl border p-4 pr-1 shadow-lg',\n compactPositionClassName,\n )}\n >\n {chatContent}\n </div>\n );\n }\n\n return (\n <div className=\"bg-background-surface border-border-default sm:border-border-default fixed flex h-full min-h-0 w-full max-w-none min-w-[328px] flex-col overflow-hidden rounded-2xl border p-4 pr-1 sm:relative sm:max-w-[328px]\">\n {chatContent}\n </div>\n );\n};\n","import { Button } from '@xipkg/button';\nimport { Chat } from '@xipkg/icons';\nimport { cn } from '@xipkg/utils';\nimport { useChat } from '../hooks/useChat';\nimport { useChatStore } from '../store/chatStore';\n\ntype ChatButtonProps = {\n /** Дополнительные классы (например, для компактной панели: h-8 w-8 rounded-xl) */\n className?: string;\n};\n\nexport const ChatButton = ({ className }: ChatButtonProps) => {\n const { toggleChat } = useChat();\n const { isChatOpen, unreadMessagesCount } = useChatStore();\n\n return (\n <Button\n size=\"icon\"\n variant=\"none\"\n onClick={toggleChat}\n className={cn(\n 'bg-background-surface hover:bg-background-page text-text-primary relative m-0 rounded-xl p-0',\n !className && 'h-10 w-10 min-w-10',\n isChatOpen && 'text-text-link',\n className,\n )}\n data-umami-event=\"call-toggle-chat\"\n data-umami-event-state={isChatOpen ? 'close' : 'open'}\n >\n <Chat className={cn('h-6 w-6', isChatOpen ? 'fill-icon-brand' : 'fill-icon-primary')} />\n {unreadMessagesCount > 0 && (\n <div className=\"bg-action-primary-background-default text-action-primary-text absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium\">\n {unreadMessagesCount > 99 ? '99+' : unreadMessagesCount}\n </div>\n )}\n </Button>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/editor/sanitizeContent.ts","../src/store/chatStore.ts","../src/hooks/useChat.ts","../src/editor/discordMarks.ts","../src/editor/extensions.ts","../src/editor/proseClassName.ts","../src/ui/ChatBubbleMenu.tsx","../src/ui/ChatComposer.tsx","../src/editor/renderJsonContent.tsx","../src/utils/chat.tsx","../src/ui/ChatMessageContent.tsx","../src/ui/Chat.tsx","../src/ui/ChatButton.tsx"],"names":["sanitized","cn","Bold","UnderlineIcon","useCallback","useEditorState","jsx","jsxs","Button","LINK_CLASS","useTranslation","useRef","useEffect","Chat"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { JSONContent } from '@tiptap/core';\n\nconst ALLOWED_NODES = new Set(['doc', 'paragraph', 'text', 'hardBreak', 'codeBlock', 'blockquote']);\n\nconst ALLOWED_MARKS = new Set(['bold', 'italic', 'strike', 'underline', 'code', 'link']);\n\nconst MAX_NODES = 400;\nconst MAX_TEXT = 8000;\nconst MAX_DEPTH = 16;\nconst MAX_HREF_LENGTH = 2000;\n\nconst isSafeHref = (href: unknown): href is string => {\n if (typeof href !== 'string' || href.length === 0 || href.length > MAX_HREF_LENGTH) {\n return false;\n }\n\n try {\n const url = new URL(href);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n};\n\nexport const sanitizeChatContent = (input: unknown): JSONContent | undefined => {\n if (!input || typeof input !== 'object') return undefined;\n\n let nodeCount = 0;\n let textLength = 0;\n\n const walk = (node: unknown, depth: number): JSONContent | null => {\n if (nodeCount > MAX_NODES || textLength > MAX_TEXT || depth > MAX_DEPTH) return null;\n if (!node || typeof node !== 'object') return null;\n\n const raw = node as JSONContent;\n if (typeof raw.type !== 'string' || !ALLOWED_NODES.has(raw.type)) return null;\n\n nodeCount += 1;\n const result: JSONContent = { type: raw.type };\n\n if (raw.type === 'text') {\n if (typeof raw.text !== 'string') return null;\n textLength += raw.text.length;\n result.text = raw.text;\n }\n\n if (raw.type === 'codeBlock' && raw.attrs && typeof raw.attrs.language === 'string') {\n result.attrs = { language: raw.attrs.language.slice(0, 32) };\n }\n\n if (Array.isArray(raw.marks) && raw.marks.length > 0) {\n const marks: NonNullable<JSONContent['marks']> = [];\n\n for (const mark of raw.marks) {\n if (!mark || typeof mark !== 'object' || typeof mark.type !== 'string') continue;\n if (!ALLOWED_MARKS.has(mark.type)) continue;\n\n if (mark.type === 'link') {\n const href = mark.attrs?.href;\n if (!isSafeHref(href)) continue;\n marks.push({\n type: 'link',\n attrs: { href, target: '_blank', rel: 'noopener noreferrer' },\n });\n continue;\n }\n\n marks.push({ type: mark.type });\n }\n\n if (marks.length > 0) result.marks = marks;\n }\n\n if (Array.isArray(raw.content)) {\n const content: JSONContent[] = [];\n for (const child of raw.content) {\n const sanitized = walk(child, depth + 1);\n if (sanitized) content.push(sanitized);\n }\n if (content.length > 0) result.content = content;\n }\n\n return result;\n };\n\n const sanitized = walk(input, 0);\n if (!sanitized || sanitized.type !== 'doc') return undefined;\n return sanitized;\n};\n","import { create } from 'zustand';\nimport type { ChatMessageT } from '../types';\n\nexport type { ChatMessageT };\n\nexport type useChatStoreT = {\n isChatOpen: boolean;\n chatMessages: ChatMessageT[];\n unreadMessagesCount: number;\n addChatMessage: (message: ChatMessageT) => void;\n removeChatMessage: (messageId: string) => void;\n clearUnreadMessages: () => void;\n updateStore: (type: keyof useChatStoreT, value: unknown) => void;\n};\n\n// Чат передаётся только через LiveKit DataChannel (P2P/broadcast в рамках текущего звонка),\n// серверной истории сообщений нет. Раньше стор персистился в localStorage без привязки\n// к конкретному звонку — сообщения предыдущего звонка «утекали» в следующий на том же\n// устройстве (и только у того пользователя, у кого localStorage не был очищен штатным\n// disconnect-флоу). Стор намеренно НЕ персистится: чат живёт только в памяти на время\n// звонка и одинаково пуст у всех при входе в новый звонок.\nexport const useChatStore = create<useChatStoreT>()((set, get) => ({\n isChatOpen: false,\n chatMessages: [],\n unreadMessagesCount: 0,\n updateStore: (type: keyof useChatStoreT, value: unknown) => set({ [type]: value }),\n\n addChatMessage: (message: ChatMessageT) => {\n const { isChatOpen, unreadMessagesCount, chatMessages } = get();\n\n // Проверяем, нет ли уже сообщения с таким ID (дедупликация)\n const messageExists = chatMessages.some((msg) => msg.id === message.id);\n if (messageExists) {\n return;\n }\n\n set((state) => ({\n chatMessages: [...state.chatMessages, message],\n unreadMessagesCount: isChatOpen ? unreadMessagesCount : unreadMessagesCount + 1,\n }));\n },\n\n removeChatMessage: (messageId: string) => {\n set((state) => ({\n chatMessages: state.chatMessages.filter((msg) => msg.id !== messageId),\n }));\n },\n\n clearUnreadMessages: () => set({ unreadMessagesCount: 0 }),\n}));\n","import { useCallback } from 'react';\nimport type { JSONContent } from '@tiptap/core';\nimport { useLiveKitDataChannel, useLiveKitDataChannelListener } from '@xipkg/calls-hooks';\nimport { useRoom } from '@xipkg/calls-providers';\nimport { useSoundEffectsStore } from '@xipkg/calls-store';\nimport { playSound } from '@xipkg/calls-utils';\nimport { sanitizeChatContent } from '../editor/sanitizeContent';\nimport { useChatStore } from '../store';\nimport type { ChatMessageT } from '../types';\n\nconst CHAT_MESSAGE_TYPE = 'chat_message';\nconst CHAT_MESSAGE_DELETE_TYPE = 'chat_message_delete';\n\ntype ChatMessagePayload = ChatMessageT;\n\ntype ChatMessageDeletePayload = {\n id: string;\n senderId: string;\n};\n\nexport const useChat = () => {\n const { sendMessage } = useLiveKitDataChannel();\n const { addChatMessage, removeChatMessage, clearUnreadMessages, updateStore } = useChatStore();\n const chatSoundVolume = useSoundEffectsStore((s) => s.chatMessageVolume);\n const { room } = useRoom();\n\n // Получаем информацию о текущем участнике из LiveKit\n const getCurrentParticipantInfo = useCallback(() => {\n if (!room?.localParticipant) {\n return {\n senderId: 'unknown',\n senderName: 'Unknown User',\n };\n }\n\n const participant = room.localParticipant;\n\n try {\n // Парсим метаданные участника\n const metadata = participant.metadata;\n if (metadata) {\n const userInfo = JSON.parse(metadata);\n return {\n senderId: userInfo?.user_id || userInfo?.id || participant.identity,\n senderName:\n userInfo?.display_name ||\n userInfo?.name ||\n userInfo?.username ||\n participant.name ||\n participant.identity,\n };\n }\n } catch (error) {\n console.warn('⚠️ Failed to parse participant metadata:', error);\n }\n\n // Fallback на стандартные поля LiveKit\n return {\n senderId: participant.identity,\n senderName: participant.name || participant.identity,\n };\n }, [room]);\n\n const handleChatMessage = useCallback(\n (message: { type: string; payload: unknown }) => {\n if (message.type === CHAT_MESSAGE_TYPE) {\n const payload = message.payload as ChatMessagePayload;\n\n // Проверяем, что это не наше собственное сообщение\n const currentParticipantInfo = getCurrentParticipantInfo();\n if (payload.senderId === currentParticipantInfo.senderId) {\n return;\n }\n\n addChatMessage({\n ...payload,\n content: sanitizeChatContent(payload.content),\n });\n playSound('chatMessage', chatSoundVolume);\n return;\n }\n\n if (message.type === CHAT_MESSAGE_DELETE_TYPE) {\n const payload = message.payload as ChatMessageDeletePayload;\n if (!payload?.id || !payload?.senderId) return;\n\n // Удаляем только если автор удаления совпадает с автором сообщения —\n // чужой клиент не может удалить чужие сообщения через поддельный payload.\n const existing = useChatStore.getState().chatMessages.find((msg) => msg.id === payload.id);\n if (!existing) return;\n if (String(existing.senderId) !== String(payload.senderId)) return;\n\n removeChatMessage(payload.id);\n }\n },\n [addChatMessage, removeChatMessage, getCurrentParticipantInfo, chatSoundVolume],\n );\n\n // Слушаем сообщения чата\n useLiveKitDataChannelListener(handleChatMessage);\n\n const sendChatMessage = useCallback(\n (text: string, content?: JSONContent) => {\n if (!text.trim()) return;\n\n const participantInfo = getCurrentParticipantInfo();\n const sanitizedContent = sanitizeChatContent(content);\n const message: ChatMessagePayload = {\n id: `${Date.now()}-${Math.random()}`,\n text: text.trim(),\n ...(sanitizedContent ? { content: sanitizedContent } : {}),\n senderId: participantInfo.senderId,\n senderName: participantInfo.senderName,\n timestamp: Date.now(),\n };\n\n // Добавляем сообщение в локальный store отправителя\n addChatMessage(message);\n\n // Отправляем через DataChannel\n sendMessage(CHAT_MESSAGE_TYPE, message);\n },\n [sendMessage, getCurrentParticipantInfo, addChatMessage],\n );\n\n const deleteChatMessage = useCallback(\n (messageId: string) => {\n const existing = useChatStore.getState().chatMessages.find((msg) => msg.id === messageId);\n if (!existing) return;\n\n const participantInfo = getCurrentParticipantInfo();\n if (String(existing.senderId) !== String(participantInfo.senderId)) return;\n\n const payload: ChatMessageDeletePayload = {\n id: messageId,\n senderId: participantInfo.senderId,\n };\n\n // Optimistic: сразу убираем у себя, затем broadcast для остальных\n removeChatMessage(messageId);\n sendMessage(CHAT_MESSAGE_DELETE_TYPE, payload);\n },\n [sendMessage, getCurrentParticipantInfo, removeChatMessage],\n );\n\n const toggleChat = useCallback(() => {\n updateStore('isChatOpen', !useChatStore.getState().isChatOpen);\n clearUnreadMessages();\n }, [updateStore, clearUnreadMessages]);\n\n const openChat = useCallback(() => {\n updateStore('isChatOpen', true);\n clearUnreadMessages();\n }, [updateStore, clearUnreadMessages]);\n\n const closeChat = useCallback(() => {\n updateStore('isChatOpen', false);\n }, [updateStore]);\n\n return {\n sendChatMessage,\n deleteChatMessage,\n toggleChat,\n openChat,\n closeChat,\n };\n};\n","import { markInputRule, markPasteRule } from '@tiptap/core';\nimport { Bold, starInputRegex, starPasteRegex } from '@tiptap/extension-bold';\nimport { Underline } from '@tiptap/extension-underline';\n\n/**\n * Discord: `**жирный**`, `__подчёркнутый__`.\n * Стандартный markdown / StarterKit мапит `__` на bold — переназначаем.\n */\nconst underlineInputRegex = /(?:^|\\s)(__(?!\\s+__)((?:[^_]+))__(?!\\s+__))$/;\nconst underlinePasteRegex = /(?:^|\\s)(__(?!\\s+__)((?:[^_]+))__(?!\\s+__))/g;\n\nexport const DiscordBold = Bold.extend({\n addInputRules() {\n return [\n markInputRule({\n find: starInputRegex,\n type: this.type,\n }),\n ];\n },\n addPasteRules() {\n return [\n markPasteRule({\n find: starPasteRegex,\n type: this.type,\n }),\n ];\n },\n});\n\nexport const DiscordUnderline = Underline.extend({\n addInputRules() {\n return [\n markInputRule({\n find: underlineInputRegex,\n type: this.type,\n }),\n ];\n },\n addPasteRules() {\n return [\n markPasteRule({\n find: underlinePasteRegex,\n type: this.type,\n }),\n ];\n },\n});\n","import StarterKit from '@tiptap/starter-kit';\nimport { DiscordBold, DiscordUnderline } from './discordMarks';\n\nconst LINK_CLASS = 'text-text-link hover:text-text-link cursor-pointer underline';\n\n/** Схема чата: Discord-шорткаты + тот же набор марок, что у текста на доске. */\nexport const chatExtensions = [\n StarterKit.configure({\n bold: false,\n underline: false,\n heading: false,\n horizontalRule: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n listKeymap: false,\n trailingNode: false,\n link: {\n openOnClick: false,\n autolink: true,\n defaultProtocol: 'https',\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer',\n class: LINK_CLASS,\n },\n },\n }),\n DiscordBold,\n DiscordUnderline,\n];\n","import { cn } from '@xipkg/utils';\n\n/** Общие стили ProseMirror для композера и пузырьков сообщений. */\nexport const chatProseClassName = cn(\n '[&_.ProseMirror]:min-h-6 [&_.ProseMirror]:text-sm [&_.ProseMirror]:break-words [&_.ProseMirror]:outline-none',\n '[&_.ProseMirror_p]:m-0 [&_.ProseMirror_p+p]:mt-1',\n '[&_.ProseMirror_strong]:font-semibold',\n '[&_.ProseMirror_em]:italic',\n '[&_.ProseMirror_s]:line-through',\n '[&_.ProseMirror_u]:underline',\n '[&_.ProseMirror_code]:bg-background-page [&_.ProseMirror_code]:rounded [&_.ProseMirror_code]:px-1 [&_.ProseMirror_code]:py-0.5 [&_.ProseMirror_code]:font-mono [&_.ProseMirror_code]:text-[0.85em]',\n '[&_.ProseMirror_pre]:bg-background-page [&_.ProseMirror_pre]:my-1 [&_.ProseMirror_pre]:overflow-x-auto [&_.ProseMirror_pre]:rounded-lg [&_.ProseMirror_pre]:p-2 [&_.ProseMirror_pre]:font-mono [&_.ProseMirror_pre]:text-xs [&_.ProseMirror_pre]:whitespace-pre-wrap',\n '[&_.ProseMirror_blockquote]:border-border-default [&_.ProseMirror_blockquote]:text-text-secondary [&_.ProseMirror_blockquote]:my-1 [&_.ProseMirror_blockquote]:border-l-2 [&_.ProseMirror_blockquote]:pl-2',\n '[&_.ProseMirror_a]:text-text-link [&_.ProseMirror_a]:cursor-pointer [&_.ProseMirror_a]:underline',\n);\n\nexport const chatMessageProseClassName = cn(\n 'text-sm wrap-break-word',\n '[&_p]:m-0 [&_p+p]:mt-1',\n '[&_strong]:font-semibold',\n '[&_em]:italic',\n '[&_s]:line-through',\n '[&_u]:underline',\n);\n","import type { Editor } from '@tiptap/core';\nimport { TextSelection } from '@tiptap/pm/state';\nimport type { EditorState } from '@tiptap/pm/state';\nimport { BubbleMenu } from '@tiptap/react/menus';\nimport { useEditorState } from '@tiptap/react';\nimport { Bold, Italic, Stroke, Underline as UnderlineIcon } from '@xipkg/icons';\nimport { Button } from '@xipkg/button';\nimport { cn } from '@xipkg/utils';\nimport { useTranslation } from 'react-i18next';\nimport type { ReactNode } from 'react';\n\ntype FormatTypeT = 'bold' | 'italic' | 'underline' | 'strike';\n\ntype ChatBubbleMenuProps = {\n editor: Editor;\n};\n\nconst isValidTextSelection = (state: EditorState): boolean => {\n const { doc, selection } = state;\n if (!(selection instanceof TextSelection) || selection.empty) return false;\n if (selection.from === 0 || selection.to === 0) return false;\n\n try {\n const $from = doc.resolve(selection.from);\n const $to = doc.resolve(selection.to);\n return $from.parent.isTextblock && $to.parent.isTextblock;\n } catch {\n return false;\n }\n};\n\ntype FormatButtonProps = {\n editor: Editor;\n type: FormatTypeT;\n isActive: boolean;\n ariaLabel: string;\n children: ReactNode;\n};\n\nconst FormatButton = ({ editor, type, isActive, ariaLabel, children }: FormatButtonProps) => {\n return (\n <Button\n type=\"button\"\n variant=\"none\"\n aria-label={ariaLabel}\n aria-pressed={isActive}\n className={cn(\n '[&_svg]:fill-icon-primary h-6 w-6 rounded-sm p-1',\n isActive && 'bg-status-info-background [&_svg]:fill-icon-brand',\n )}\n onMouseDown={(event) => {\n event.preventDefault();\n editor.chain().focus().toggleMark(type).run();\n }}\n >\n {children}\n </Button>\n );\n};\n\nexport const ChatBubbleMenu = ({ editor }: ChatBubbleMenuProps) => {\n const { t } = useTranslation('calls');\n const activeStates = useEditorState({\n editor,\n selector: ({ editor: current }) => ({\n bold: current.isActive('bold'),\n italic: current.isActive('italic'),\n underline: current.isActive('underline'),\n strike: current.isActive('strike'),\n }),\n });\n\n return (\n <BubbleMenu\n editor={editor}\n appendTo={() => editor.view.dom.ownerDocument.body}\n shouldShow={({ state }) => isValidTextSelection(state)}\n options={{\n placement: 'top',\n strategy: 'fixed',\n offset: 8,\n }}\n className=\"border-border-default bg-background-surface z-200 flex gap-1 rounded-lg border p-1 shadow-lg\"\n >\n <FormatButton\n editor={editor}\n type=\"bold\"\n isActive={activeStates.bold}\n ariaLabel={t('chat.formatBold')}\n >\n <Bold />\n </FormatButton>\n <FormatButton\n editor={editor}\n type=\"italic\"\n isActive={activeStates.italic}\n ariaLabel={t('chat.formatItalic')}\n >\n <Italic />\n </FormatButton>\n <FormatButton\n editor={editor}\n type=\"underline\"\n isActive={activeStates.underline}\n ariaLabel={t('chat.formatUnderline')}\n >\n <UnderlineIcon />\n </FormatButton>\n <FormatButton\n editor={editor}\n type=\"strike\"\n isActive={activeStates.strike}\n ariaLabel={t('chat.formatStrike')}\n >\n <Stroke />\n </FormatButton>\n </BubbleMenu>\n );\n};\n","import { useCallback, useEffect, useRef } from 'react';\nimport type { Editor, JSONContent } from '@tiptap/core';\nimport { EditorContent, useEditor, useEditorState } from '@tiptap/react';\nimport { Button } from '@xipkg/button';\nimport { Send } from '@xipkg/icons';\nimport { cn } from '@xipkg/utils';\nimport { chatExtensions } from '../editor/extensions';\nimport { chatProseClassName } from '../editor/proseClassName';\nimport { ChatBubbleMenu } from './ChatBubbleMenu';\n\ntype ChatComposerProps = {\n placeholder: string;\n onSend: (text: string, content: JSONContent) => void;\n};\n\nexport const ChatComposer = ({ placeholder, onSend }: ChatComposerProps) => {\n const onSendRef = useRef(onSend);\n onSendRef.current = onSend;\n\n const editorRef = useRef<Editor | null>(null);\n\n const sendFromEditor = useCallback(() => {\n const current = editorRef.current;\n if (!current || current.isDestroyed) return;\n\n const text = current.getText({ blockSeparator: '\\n' }).trim();\n if (!text) return;\n\n onSendRef.current(text, current.getJSON());\n current.commands.clearContent(true);\n current.commands.focus();\n }, []);\n\n const sendFromEditorRef = useRef(sendFromEditor);\n sendFromEditorRef.current = sendFromEditor;\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: chatExtensions,\n autofocus: true,\n editorProps: {\n attributes: {\n class: 'min-h-6 text-sm break-words outline-none',\n 'aria-label': placeholder,\n },\n handleKeyDown: (_view, event) => {\n if (\n event.key !== 'Enter' ||\n event.shiftKey ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey\n ) {\n return false;\n }\n if (event.isComposing || event.keyCode === 229) return false;\n\n event.preventDefault();\n sendFromEditorRef.current();\n return true;\n },\n },\n });\n\n useEffect(() => {\n editorRef.current = editor;\n }, [editor]);\n\n const isEmpty =\n useEditorState({\n editor,\n selector: ({ editor: current }) => !current?.getText({ blockSeparator: '\\n' }).trim(),\n }) ?? true;\n\n return (\n <div className=\"flex items-end gap-2 pr-3\">\n <div\n className={cn(\n 'border-border-default flex max-h-40 w-full flex-1 items-center rounded-xl border pl-4',\n chatProseClassName,\n )}\n >\n <div className=\"relative min-w-0 flex-1\">\n {isEmpty && (\n <span className=\"text-text-secondary pointer-events-none absolute top-3 left-0 text-sm\">\n {placeholder}\n </span>\n )}\n <EditorContent editor={editor} className=\"my-3 max-h-32 overflow-y-auto pr-2\" />\n {editor && <ChatBubbleMenu editor={editor} />}\n </div>\n <div className=\"pr-1\">\n <Button\n size=\"icon\"\n variant=\"primary\"\n onClick={sendFromEditor}\n disabled={isEmpty}\n className=\"rounded-xl p-2\"\n >\n <Send\n className={cn(\n 'fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6',\n isEmpty && 'fill-icon-secondary',\n )}\n />\n </Button>\n </div>\n </div>\n </div>\n );\n};\n","import type { JSONContent } from '@tiptap/core';\nimport type { ReactNode } from 'react';\n\nconst LINK_CLASS = 'text-text-link hover:text-text-link cursor-pointer underline';\n\nconst wrapMarks = (text: string, marks: JSONContent['marks']): ReactNode => {\n return (marks ?? []).reduce<ReactNode>((acc, mark) => {\n switch (mark.type) {\n case 'bold':\n return <strong>{acc}</strong>;\n case 'italic':\n return <em>{acc}</em>;\n case 'strike':\n return <s>{acc}</s>;\n case 'underline':\n return <u>{acc}</u>;\n case 'code':\n return (\n <code className=\"bg-background-page rounded px-1 py-0.5 font-mono text-[0.85em]\">\n {acc}\n </code>\n );\n case 'link': {\n const href = typeof mark.attrs?.href === 'string' ? mark.attrs.href : undefined;\n if (!href) return acc;\n return (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={LINK_CLASS}\n onClick={(event) => event.stopPropagation()}\n data-umami-event=\"outbound-link-click\"\n data-umami-event-url={href}\n data-umami-event-source=\"chat\"\n >\n {acc}\n </a>\n );\n }\n default:\n return acc;\n }\n }, text);\n};\n\nconst renderNode = (node: JSONContent, key: string): ReactNode => {\n const children = node.content?.map((child, index) => renderNode(child, `${key}-${index}`));\n\n switch (node.type) {\n case 'doc':\n return children;\n case 'paragraph':\n return (\n <p key={key} className=\"m-0 min-h-[1em]\">\n {children ?? <br />}\n </p>\n );\n case 'blockquote':\n return (\n <blockquote\n key={key}\n className=\"border-border-default text-text-secondary my-1 border-l-2 pl-2\"\n >\n {children}\n </blockquote>\n );\n case 'codeBlock':\n return (\n <pre\n key={key}\n className=\"bg-background-page my-1 overflow-x-auto rounded-lg p-2 font-mono text-xs whitespace-pre-wrap\"\n >\n <code>{children}</code>\n </pre>\n );\n case 'hardBreak':\n return <br key={key} />;\n case 'text':\n return <span key={key}>{wrapMarks(node.text ?? '', node.marks)}</span>;\n default:\n return children;\n }\n};\n\nexport const renderChatJsonContent = (content: JSONContent): ReactNode => {\n return renderNode(content, 'chat-doc');\n};\n","export const parseLinks = (message: string) => {\n const urlRegex = /(https?:\\/\\/[^\\s]+)/g;\n const messageParts = message.split(urlRegex);\n\n return messageParts.map((messagePart, index) => {\n if (urlRegex.test(messagePart)) {\n return (\n <a\n key={index}\n href={messagePart}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-text-link hover:text-text-link cursor-pointer underline\"\n onClick={(e) => e.stopPropagation()}\n data-umami-event=\"outbound-link-click\"\n data-umami-event-url={messagePart}\n data-umami-event-source=\"chat\"\n >\n {messagePart}\n </a>\n );\n }\n\n return messagePart;\n });\n};\n","import type { JSONContent } from '@tiptap/core';\nimport { cn } from '@xipkg/utils';\nimport { renderChatJsonContent } from '../editor/renderJsonContent';\nimport { chatMessageProseClassName } from '../editor/proseClassName';\nimport { parseLinks } from '../utils/chat';\n\ntype ChatMessageContentProps = {\n text: string;\n content?: JSONContent;\n className?: string;\n};\n\nexport const ChatMessageContent = ({ text, content, className }: ChatMessageContentProps) => {\n return (\n <div className={cn(chatMessageProseClassName, className)}>\n {content ? renderChatJsonContent(content) : parseLinks(text)}\n </div>\n );\n};\n","import { useRef, useEffect } from 'react';\nimport type { JSONContent } from '@tiptap/core';\nimport { Button } from '@xipkg/button';\nimport { Close, Trash } from '@xipkg/icons';\nimport { UserProfile } from '@xipkg/userprofile';\nimport { ScrollArea } from '@xipkg/scrollarea';\nimport { Modal, ModalContent, ModalTitle } from '@xipkg/modal';\nimport { useTranslation } from 'react-i18next';\nimport { useChat } from '../hooks';\nimport { useCalls } from '@xipkg/calls-providers';\nimport { useChatStore } from '../store';\nimport { cn, useMediaQuery } from '@xipkg/utils';\nimport { ChatComposer } from './ChatComposer';\nimport { ChatMessageContent } from './ChatMessageContent';\n\ntype ChatProps = {\n /** В компакт-режиме: классы позиционирования (как у CompactCall: top-16 bottom-4 left-4 и т.д.) */\n compactPositionClassName?: string;\n /** Встроить чат в родителя (PiP): без fixed/modal, на всю доступную высоту */\n embedded?: boolean;\n};\n\nexport const Chat = ({ compactPositionClassName, embedded = false }: ChatProps = {}) => {\n const { t, i18n } = useTranslation('calls');\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const { sendChatMessage, deleteChatMessage, closeChat } = useChat();\n const { chatMessages, isChatOpen } = useChatStore();\n const { data: currentUser } = useCalls().auth.useCurrentUser();\n const timeLocale = i18n.language?.startsWith('ru')\n ? 'ru-RU'\n : i18n.language?.startsWith('en')\n ? 'en-US'\n : i18n.language;\n\n const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {\n messagesEndRef.current?.scrollIntoView({ behavior });\n };\n\n useEffect(() => {\n if (isChatOpen) {\n requestAnimationFrame(() => {\n scrollToBottom('auto');\n });\n }\n }, [isChatOpen]);\n\n // Автоматическая прокрутка при получении новых сообщений\n useEffect(() => {\n if (isChatOpen && chatMessages.length > 0) {\n requestAnimationFrame(() => {\n scrollToBottom('smooth');\n });\n }\n }, [chatMessages.length, isChatOpen]);\n\n const handleSendMessage = (text: string, content?: JSONContent) => {\n sendChatMessage(text, content);\n requestAnimationFrame(() => {\n scrollToBottom('smooth');\n });\n };\n\n const isMobile = useMediaQuery('(max-width: 639px)');\n if (!isChatOpen) return null;\n\n const chatPanelClassName =\n 'border-border-default bg-background-surface flex min-h-0 flex-col overflow-hidden rounded-2xl border p-4 pr-1';\n\n const chatContent = (\n <>\n {/* Заголовок */}\n <div className=\"border-border-default flex items-center justify-between pr-3\">\n <h3 className=\"text-text-primary text-lg font-medium\">{t('chat.title')}</h3>\n <Button size=\"icon\" variant=\"none\" onClick={closeChat}>\n <Close className=\"h-6 w-6\" aria-label={t('chat.close')} />\n </Button>\n </div>\n\n {/* Сообщения */}\n <ScrollArea className=\"min-h-0 flex-1 py-2 pr-3\">\n <div className=\"space-y-4\">\n {chatMessages.length === 0 ? (\n <div className=\"text-text-secondary text-center\">\n <p>{t('chat.empty')}</p>\n </div>\n ) : (\n chatMessages.map((message) => {\n const isOwnMessage = Number(message.senderId) === Number(currentUser?.id);\n return (\n <div\n key={message.id}\n className={`group flex ${isOwnMessage ? 'justify-end' : 'justify-start'}`}\n >\n <div className=\"text-text-primary flex max-w-[90%] flex-col gap-1 rounded-lg select-text\">\n <div className=\"text-text-primary flex flex-row items-center gap-1 text-xs font-medium\">\n {!isOwnMessage && (\n <UserProfile\n size=\"s\"\n userId={Number(message.senderId)}\n text={message.senderName}\n src={`https://api.sovlium.ru/files/users/${message.senderId}/avatar.webp`}\n />\n )}\n <div\n className={`text-xs-base ${isOwnMessage ? 'text-text-disabled ml-auto' : 'text-text-secondary'}`}\n >\n {new Date(message.timestamp).toLocaleTimeString(timeLocale, {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </div>\n </div>\n <div className=\"relative\">\n <ChatMessageContent\n text={message.text}\n content={message.content}\n className={cn(\n 'cursor-text rounded-lg px-3 py-2 select-text',\n isOwnMessage\n ? 'bg-action-primary-background-disabled'\n : 'bg-background-page',\n )}\n />\n {isOwnMessage && (\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"none\"\n className=\"bg-background-surface border-border-default absolute -top-2 -left-2 h-7 w-7 rounded-full border p-1 opacity-100 shadow-sm sm:opacity-0 sm:group-focus-within:opacity-100 sm:group-hover:opacity-100\"\n onClick={() => deleteChatMessage(message.id)}\n aria-label={t('chat.deleteAria')}\n >\n <Trash className=\"fill-icon-secondary h-4 w-4\" />\n </Button>\n )}\n </div>\n </div>\n </div>\n );\n })\n )}\n <div ref={messagesEndRef} />\n </div>\n </ScrollArea>\n\n <ChatComposer placeholder={t('chat.placeholder')} onSend={handleSendMessage} />\n </>\n );\n\n if (embedded) {\n return <div className={cn(chatPanelClassName, 'h-full w-full')}>{chatContent}</div>;\n }\n\n if (isMobile) {\n return (\n <Modal open={isChatOpen} onOpenChange={(open) => !open && closeChat()}>\n <ModalContent\n className=\"border-border-default bg-background-surface flex h-[85dvh] max-h-[85dvh] w-[calc(100vw-32px)] max-w-[calc(100vw-32px)] flex-col gap-0 overflow-hidden rounded-2xl border p-4 pr-1\"\n aria-describedby={undefined}\n >\n <ModalTitle className=\"sr-only\">{t('chat.title')}</ModalTitle>\n <div className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">{chatContent}</div>\n </ModalContent>\n </Modal>\n );\n }\n\n if (compactPositionClassName) {\n return (\n <div\n className={cn(\n 'border-border-default bg-background-surface fixed z-100 flex min-h-0 w-[328px] flex-col overflow-hidden rounded-2xl border p-4 pr-1 shadow-lg',\n compactPositionClassName,\n )}\n >\n {chatContent}\n </div>\n );\n }\n\n return (\n <div className=\"bg-background-surface border-border-default sm:border-border-default fixed flex h-full min-h-0 w-full max-w-none min-w-[328px] flex-col overflow-hidden rounded-2xl border p-4 pr-1 sm:relative sm:max-w-[328px]\">\n {chatContent}\n </div>\n );\n};\n","import { Button } from '@xipkg/button';\nimport { Chat } from '@xipkg/icons';\nimport { cn } from '@xipkg/utils';\nimport { useChat } from '../hooks/useChat';\nimport { useChatStore } from '../store/chatStore';\n\ntype ChatButtonProps = {\n /** Дополнительные классы (например, для компактной панели: h-8 w-8 rounded-xl) */\n className?: string;\n};\n\nexport const ChatButton = ({ className }: ChatButtonProps) => {\n const { toggleChat } = useChat();\n const { isChatOpen, unreadMessagesCount } = useChatStore();\n\n return (\n <Button\n size=\"icon\"\n variant=\"none\"\n onClick={toggleChat}\n className={cn(\n 'bg-background-surface hover:bg-background-page text-text-primary relative m-0 rounded-xl p-0',\n !className && 'h-10 w-10 min-w-10',\n isChatOpen && 'text-text-link',\n className,\n )}\n data-umami-event=\"call-toggle-chat\"\n data-umami-event-state={isChatOpen ? 'close' : 'open'}\n >\n <Chat className={cn('h-6 w-6', isChatOpen ? 'fill-icon-brand' : 'fill-icon-primary')} />\n {unreadMessagesCount > 0 && (\n <div className=\"bg-action-primary-background-default text-action-primary-text absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium\">\n {unreadMessagesCount > 99 ? '99+' : unreadMessagesCount}\n </div>\n )}\n </Button>\n );\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xipkg/calls-chat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -12,31 +12,38 @@
|
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@tiptap/core": "3.30.1",
|
|
16
|
+
"@tiptap/extension-bold": "3.30.1",
|
|
17
|
+
"@tiptap/extension-bubble-menu": "3.30.1",
|
|
18
|
+
"@tiptap/extension-underline": "3.30.1",
|
|
19
|
+
"@tiptap/pm": "3.30.1",
|
|
20
|
+
"@tiptap/react": "3.30.1",
|
|
21
|
+
"@tiptap/starter-kit": "3.30.1",
|
|
22
|
+
"@xipkg/button": "4.3.0",
|
|
23
|
+
"@xipkg/calls-config": "^0.1.25",
|
|
24
|
+
"@xipkg/calls-hooks": "^0.1.25",
|
|
25
|
+
"@xipkg/calls-providers": "^0.1.25",
|
|
26
|
+
"@xipkg/calls-store": "^0.1.25",
|
|
27
|
+
"@xipkg/calls-types": "^0.1.25",
|
|
28
|
+
"@xipkg/calls-ui": "^0.1.25",
|
|
29
|
+
"@xipkg/calls-utils": "^0.1.25",
|
|
30
|
+
"@xipkg/icons": "^3.1.0",
|
|
31
|
+
"@xipkg/modal": "4.6.0",
|
|
15
32
|
"@xipkg/scrollarea": "2.3.0",
|
|
16
33
|
"@xipkg/userprofile": "4.2.0",
|
|
17
|
-
"@xipkg/
|
|
18
|
-
"
|
|
19
|
-
"@xipkg/button": "4.3.0",
|
|
20
|
-
"@xipkg/calls-ui": "^0.1.22",
|
|
21
|
-
"@xipkg/calls-utils": "^0.1.22",
|
|
22
|
-
"@xipkg/calls-types": "^0.1.22",
|
|
23
|
-
"@xipkg/calls-config": "^0.1.22",
|
|
24
|
-
"@xipkg/calls-providers": "^0.1.22",
|
|
25
|
-
"@xipkg/calls-store": "^0.1.22",
|
|
26
|
-
"@xipkg/calls-hooks": "^0.1.22",
|
|
27
|
-
"zustand": "5.0.14",
|
|
28
|
-
"@xipkg/utils": "^1.8.0"
|
|
34
|
+
"@xipkg/utils": "^1.8.0",
|
|
35
|
+
"zustand": "5.0.14"
|
|
29
36
|
},
|
|
30
37
|
"devDependencies": {
|
|
31
|
-
"@tanstack/react-query-devtools": "5.101.4",
|
|
32
38
|
"@eslint/js": "^9.19.0",
|
|
33
|
-
"
|
|
39
|
+
"@tanstack/react-query-devtools": "5.101.4",
|
|
34
40
|
"@types/node": "^20.3.1",
|
|
35
41
|
"@types/react": "^19.2.17",
|
|
36
42
|
"@types/react-dom": "^19.0.2",
|
|
37
43
|
"@xipkg/eslint": "4.6.0",
|
|
38
44
|
"@xipkg/typescript": "0.2.0",
|
|
39
45
|
"common.eslint": "*",
|
|
46
|
+
"common.typescript": "*",
|
|
40
47
|
"eslint": "^9.19.0",
|
|
41
48
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
42
49
|
"eslint-plugin-react-refresh": "^0.4.18",
|