@xipkg/calls-chat 0.1.24 → 0.1.26
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 +8 -2
- package/dist/index.mjs +449 -65
- 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,7 @@ 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;
|
|
18
21
|
deleteChatMessage: (messageId: string) => void;
|
|
19
22
|
toggleChat: () => void;
|
|
20
23
|
openChat: () => void;
|
|
@@ -24,10 +27,13 @@ declare const useChat: () => {
|
|
|
24
27
|
type ChatMessageT = {
|
|
25
28
|
id: string;
|
|
26
29
|
text: string;
|
|
30
|
+
/** TipTap JSON. Старые клиенты поле игнорируют и показывают `text`. */
|
|
31
|
+
content?: JSONContent;
|
|
27
32
|
senderId: string;
|
|
28
33
|
senderName: string;
|
|
29
34
|
timestamp: number;
|
|
30
35
|
};
|
|
36
|
+
|
|
31
37
|
type useChatStoreT = {
|
|
32
38
|
isChatOpen: boolean;
|
|
33
39
|
chatMessages: ChatMessageT[];
|
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, Trash, 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: [],
|
|
@@ -80,7 +156,10 @@ var useChat = () => {
|
|
|
80
156
|
if (payload.senderId === currentParticipantInfo.senderId) {
|
|
81
157
|
return;
|
|
82
158
|
}
|
|
83
|
-
addChatMessage(
|
|
159
|
+
addChatMessage({
|
|
160
|
+
...payload,
|
|
161
|
+
content: sanitizeChatContent(payload.content)
|
|
162
|
+
});
|
|
84
163
|
playSound("chatMessage", chatSoundVolume);
|
|
85
164
|
return;
|
|
86
165
|
}
|
|
@@ -97,12 +176,14 @@ var useChat = () => {
|
|
|
97
176
|
);
|
|
98
177
|
useLiveKitDataChannelListener(handleChatMessage);
|
|
99
178
|
const sendChatMessage = useCallback(
|
|
100
|
-
(text) => {
|
|
179
|
+
(text, content) => {
|
|
101
180
|
if (!text.trim()) return;
|
|
102
181
|
const participantInfo = getCurrentParticipantInfo();
|
|
182
|
+
const sanitizedContent = sanitizeChatContent(content);
|
|
103
183
|
const message = {
|
|
104
184
|
id: `${Date.now()}-${Math.random()}`,
|
|
105
185
|
text: text.trim(),
|
|
186
|
+
...sanitizedContent ? { content: sanitizedContent } : {},
|
|
106
187
|
senderId: participantInfo.senderId,
|
|
107
188
|
senderName: participantInfo.senderName,
|
|
108
189
|
timestamp: Date.now()
|
|
@@ -146,6 +227,346 @@ var useChat = () => {
|
|
|
146
227
|
closeChat
|
|
147
228
|
};
|
|
148
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 chatInlineMarksClassName = cn(
|
|
297
|
+
"[&_strong]:font-bold",
|
|
298
|
+
"[&_em]:italic [&_em]:pr-[0.18em]",
|
|
299
|
+
"[&_s]:line-through",
|
|
300
|
+
"[&_u]:underline"
|
|
301
|
+
);
|
|
302
|
+
var chatProseClassName = cn(
|
|
303
|
+
"[&_.ProseMirror]:min-h-6 [&_.ProseMirror]:text-sm [&_.ProseMirror]:break-words [&_.ProseMirror]:outline-none",
|
|
304
|
+
"[&_.ProseMirror_p]:m-0 [&_.ProseMirror_p+p]:mt-1",
|
|
305
|
+
"[&_.ProseMirror_strong]:font-bold",
|
|
306
|
+
"[&_.ProseMirror_em]:italic [&_.ProseMirror_em]:pr-[0.18em]",
|
|
307
|
+
"[&_.ProseMirror_s]:line-through",
|
|
308
|
+
"[&_.ProseMirror_u]:underline",
|
|
309
|
+
"[&_.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]",
|
|
310
|
+
"[&_.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",
|
|
311
|
+
"[&_.ProseMirror_blockquote]:border-border-default [&_.ProseMirror_blockquote]:text-text-secondary [&_.ProseMirror_blockquote]:my-1 [&_.ProseMirror_blockquote]:border-l-2 [&_.ProseMirror_blockquote]:pl-2",
|
|
312
|
+
"[&_.ProseMirror_a]:text-text-link [&_.ProseMirror_a]:cursor-pointer [&_.ProseMirror_a]:underline"
|
|
313
|
+
);
|
|
314
|
+
var chatMessageProseClassName = cn(
|
|
315
|
+
"text-sm wrap-break-word",
|
|
316
|
+
"[&_p]:m-0 [&_p+p]:mt-1",
|
|
317
|
+
chatInlineMarksClassName
|
|
318
|
+
);
|
|
319
|
+
var isValidTextSelection = (state) => {
|
|
320
|
+
const { doc, selection } = state;
|
|
321
|
+
if (!(selection instanceof TextSelection) || selection.empty) return false;
|
|
322
|
+
if (selection.from === 0 || selection.to === 0) return false;
|
|
323
|
+
try {
|
|
324
|
+
const $from = doc.resolve(selection.from);
|
|
325
|
+
const $to = doc.resolve(selection.to);
|
|
326
|
+
return $from.parent.isTextblock && $to.parent.isTextblock;
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var FormatButton = ({ editor, type, isActive, ariaLabel, children }) => {
|
|
332
|
+
return /* @__PURE__ */ jsx(
|
|
333
|
+
Button,
|
|
334
|
+
{
|
|
335
|
+
type: "button",
|
|
336
|
+
variant: "none",
|
|
337
|
+
"aria-label": ariaLabel,
|
|
338
|
+
"aria-pressed": isActive,
|
|
339
|
+
className: cn(
|
|
340
|
+
"[&_svg]:fill-icon-primary h-6 w-6 rounded-sm p-1",
|
|
341
|
+
isActive && "bg-status-info-background [&_svg]:fill-icon-brand"
|
|
342
|
+
),
|
|
343
|
+
onMouseDown: (event) => {
|
|
344
|
+
event.preventDefault();
|
|
345
|
+
editor.chain().focus().toggleMark(type).run();
|
|
346
|
+
},
|
|
347
|
+
children
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
var ChatBubbleMenu = ({ editor }) => {
|
|
352
|
+
const { t } = useTranslation("calls");
|
|
353
|
+
const activeStates = useEditorState({
|
|
354
|
+
editor,
|
|
355
|
+
selector: ({ editor: current }) => ({
|
|
356
|
+
bold: current.isActive("bold"),
|
|
357
|
+
italic: current.isActive("italic"),
|
|
358
|
+
underline: current.isActive("underline"),
|
|
359
|
+
strike: current.isActive("strike")
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
return /* @__PURE__ */ jsxs(
|
|
363
|
+
BubbleMenu,
|
|
364
|
+
{
|
|
365
|
+
editor,
|
|
366
|
+
appendTo: () => editor.view.dom.ownerDocument.body,
|
|
367
|
+
shouldShow: ({ state }) => isValidTextSelection(state),
|
|
368
|
+
options: {
|
|
369
|
+
placement: "top",
|
|
370
|
+
strategy: "fixed",
|
|
371
|
+
offset: 8
|
|
372
|
+
},
|
|
373
|
+
className: "border-border-default bg-background-surface z-200 flex gap-1 rounded-lg border p-1 shadow-lg",
|
|
374
|
+
children: [
|
|
375
|
+
/* @__PURE__ */ jsx(
|
|
376
|
+
FormatButton,
|
|
377
|
+
{
|
|
378
|
+
editor,
|
|
379
|
+
type: "bold",
|
|
380
|
+
isActive: activeStates.bold,
|
|
381
|
+
ariaLabel: t("chat.formatBold"),
|
|
382
|
+
children: /* @__PURE__ */ jsx(Bold$1, {})
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
FormatButton,
|
|
387
|
+
{
|
|
388
|
+
editor,
|
|
389
|
+
type: "italic",
|
|
390
|
+
isActive: activeStates.italic,
|
|
391
|
+
ariaLabel: t("chat.formatItalic"),
|
|
392
|
+
children: /* @__PURE__ */ jsx(Italic, {})
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
/* @__PURE__ */ jsx(
|
|
396
|
+
FormatButton,
|
|
397
|
+
{
|
|
398
|
+
editor,
|
|
399
|
+
type: "underline",
|
|
400
|
+
isActive: activeStates.underline,
|
|
401
|
+
ariaLabel: t("chat.formatUnderline"),
|
|
402
|
+
children: /* @__PURE__ */ jsx(Underline$1, {})
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
/* @__PURE__ */ jsx(
|
|
406
|
+
FormatButton,
|
|
407
|
+
{
|
|
408
|
+
editor,
|
|
409
|
+
type: "strike",
|
|
410
|
+
isActive: activeStates.strike,
|
|
411
|
+
ariaLabel: t("chat.formatStrike"),
|
|
412
|
+
children: /* @__PURE__ */ jsx(Stroke, {})
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
};
|
|
419
|
+
var ChatComposer = ({ placeholder, onSend }) => {
|
|
420
|
+
const onSendRef = useRef(onSend);
|
|
421
|
+
onSendRef.current = onSend;
|
|
422
|
+
const editorRef = useRef(null);
|
|
423
|
+
const sendFromEditor = useCallback(() => {
|
|
424
|
+
const current = editorRef.current;
|
|
425
|
+
if (!current || current.isDestroyed) return;
|
|
426
|
+
const text = current.getText({ blockSeparator: "\n" }).trim();
|
|
427
|
+
if (!text) return;
|
|
428
|
+
onSendRef.current(text, current.getJSON());
|
|
429
|
+
current.commands.clearContent(true);
|
|
430
|
+
current.commands.focus();
|
|
431
|
+
}, []);
|
|
432
|
+
const sendFromEditorRef = useRef(sendFromEditor);
|
|
433
|
+
sendFromEditorRef.current = sendFromEditor;
|
|
434
|
+
const editor = useEditor({
|
|
435
|
+
immediatelyRender: false,
|
|
436
|
+
extensions: chatExtensions,
|
|
437
|
+
autofocus: true,
|
|
438
|
+
editorProps: {
|
|
439
|
+
attributes: {
|
|
440
|
+
class: "min-h-6 text-sm break-words outline-none",
|
|
441
|
+
"aria-label": placeholder
|
|
442
|
+
},
|
|
443
|
+
handleKeyDown: (_view, event) => {
|
|
444
|
+
if (event.key !== "Enter" || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
if (event.isComposing || event.keyCode === 229) return false;
|
|
448
|
+
event.preventDefault();
|
|
449
|
+
sendFromEditorRef.current();
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
editorRef.current = editor;
|
|
456
|
+
}, [editor]);
|
|
457
|
+
const isEmpty = useEditorState({
|
|
458
|
+
editor,
|
|
459
|
+
selector: ({ editor: current }) => !current?.getText({ blockSeparator: "\n" }).trim()
|
|
460
|
+
}) ?? true;
|
|
461
|
+
return /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-end gap-2 pr-3", children: /* @__PURE__ */ jsxs(
|
|
462
|
+
"div",
|
|
463
|
+
{
|
|
464
|
+
className: cn(
|
|
465
|
+
"border-border-default flex max-h-40 w-full flex-1 items-center rounded-xl border pl-4",
|
|
466
|
+
chatProseClassName
|
|
467
|
+
),
|
|
468
|
+
children: [
|
|
469
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
|
|
470
|
+
isEmpty && /* @__PURE__ */ jsx("span", { className: "text-text-secondary pointer-events-none absolute top-3 left-0 text-sm", children: placeholder }),
|
|
471
|
+
/* @__PURE__ */ jsx(EditorContent, { editor, className: "my-3 max-h-32 overflow-y-auto pr-2" }),
|
|
472
|
+
editor && /* @__PURE__ */ jsx(ChatBubbleMenu, { editor })
|
|
473
|
+
] }),
|
|
474
|
+
/* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(
|
|
475
|
+
Button,
|
|
476
|
+
{
|
|
477
|
+
size: "icon",
|
|
478
|
+
variant: "primary",
|
|
479
|
+
onClick: sendFromEditor,
|
|
480
|
+
disabled: isEmpty,
|
|
481
|
+
className: "rounded-xl p-2",
|
|
482
|
+
children: /* @__PURE__ */ jsx(
|
|
483
|
+
Send,
|
|
484
|
+
{
|
|
485
|
+
className: cn(
|
|
486
|
+
"fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6",
|
|
487
|
+
isEmpty && "fill-icon-secondary"
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
) })
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
) });
|
|
496
|
+
};
|
|
497
|
+
var LINK_CLASS2 = "text-text-link hover:text-text-link cursor-pointer underline";
|
|
498
|
+
var wrapMarks = (text, marks) => {
|
|
499
|
+
return (marks ?? []).reduce((acc, mark) => {
|
|
500
|
+
switch (mark.type) {
|
|
501
|
+
case "bold":
|
|
502
|
+
return /* @__PURE__ */ jsx("strong", { className: "font-bold", children: acc });
|
|
503
|
+
case "italic":
|
|
504
|
+
return /* @__PURE__ */ jsx("em", { className: "pr-[0.18em] italic", children: acc });
|
|
505
|
+
case "strike":
|
|
506
|
+
return /* @__PURE__ */ jsx("s", { children: acc });
|
|
507
|
+
case "underline":
|
|
508
|
+
return /* @__PURE__ */ jsx("u", { children: acc });
|
|
509
|
+
case "code":
|
|
510
|
+
return /* @__PURE__ */ jsx("code", { className: "bg-background-page rounded px-1 py-0.5 font-mono text-[0.85em]", children: acc });
|
|
511
|
+
case "link": {
|
|
512
|
+
const href = typeof mark.attrs?.href === "string" ? mark.attrs.href : void 0;
|
|
513
|
+
if (!href) return acc;
|
|
514
|
+
return /* @__PURE__ */ jsx(
|
|
515
|
+
"a",
|
|
516
|
+
{
|
|
517
|
+
href,
|
|
518
|
+
target: "_blank",
|
|
519
|
+
rel: "noopener noreferrer",
|
|
520
|
+
className: LINK_CLASS2,
|
|
521
|
+
onClick: (event) => event.stopPropagation(),
|
|
522
|
+
"data-umami-event": "outbound-link-click",
|
|
523
|
+
"data-umami-event-url": href,
|
|
524
|
+
"data-umami-event-source": "chat",
|
|
525
|
+
children: acc
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
default:
|
|
530
|
+
return acc;
|
|
531
|
+
}
|
|
532
|
+
}, text);
|
|
533
|
+
};
|
|
534
|
+
var renderNode = (node, key) => {
|
|
535
|
+
const children = node.content?.map((child, index) => renderNode(child, `${key}-${index}`));
|
|
536
|
+
switch (node.type) {
|
|
537
|
+
case "doc":
|
|
538
|
+
return children;
|
|
539
|
+
case "paragraph":
|
|
540
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 min-h-[1em]", children: children ?? /* @__PURE__ */ jsx("br", {}) }, key);
|
|
541
|
+
case "blockquote":
|
|
542
|
+
return /* @__PURE__ */ jsx(
|
|
543
|
+
"blockquote",
|
|
544
|
+
{
|
|
545
|
+
className: "border-border-default text-text-secondary my-1 border-l-2 pl-2",
|
|
546
|
+
children
|
|
547
|
+
},
|
|
548
|
+
key
|
|
549
|
+
);
|
|
550
|
+
case "codeBlock":
|
|
551
|
+
return /* @__PURE__ */ jsx(
|
|
552
|
+
"pre",
|
|
553
|
+
{
|
|
554
|
+
className: "bg-background-page my-1 overflow-x-auto rounded-lg p-2 font-mono text-xs whitespace-pre-wrap",
|
|
555
|
+
children: /* @__PURE__ */ jsx("code", { children })
|
|
556
|
+
},
|
|
557
|
+
key
|
|
558
|
+
);
|
|
559
|
+
case "hardBreak":
|
|
560
|
+
return /* @__PURE__ */ jsx("br", {}, key);
|
|
561
|
+
case "text":
|
|
562
|
+
return /* @__PURE__ */ jsx("span", { children: wrapMarks(node.text ?? "", node.marks) }, key);
|
|
563
|
+
default:
|
|
564
|
+
return children;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
var renderChatJsonContent = (content) => {
|
|
568
|
+
return renderNode(content, "chat-doc");
|
|
569
|
+
};
|
|
149
570
|
var parseLinks = (message) => {
|
|
150
571
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
151
572
|
const messageParts = message.split(urlRegex);
|
|
@@ -170,9 +591,11 @@ var parseLinks = (message) => {
|
|
|
170
591
|
return messagePart;
|
|
171
592
|
});
|
|
172
593
|
};
|
|
173
|
-
var
|
|
594
|
+
var ChatMessageContent = ({ text, content, className }) => {
|
|
595
|
+
return /* @__PURE__ */ jsx("div", { className: cn(chatMessageProseClassName, className), children: content ? renderChatJsonContent(content) : parseLinks(text) });
|
|
596
|
+
};
|
|
597
|
+
var Chat = ({ compactPositionClassName, embedded = false } = {}) => {
|
|
174
598
|
const { t, i18n } = useTranslation("calls");
|
|
175
|
-
const [messageText, setMessageText] = useState("");
|
|
176
599
|
const messagesEndRef = useRef(null);
|
|
177
600
|
const { sendChatMessage, deleteChatMessage, closeChat } = useChat();
|
|
178
601
|
const { chatMessages, isChatOpen } = useChatStore();
|
|
@@ -181,7 +604,6 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
181
604
|
const scrollToBottom = (behavior = "smooth") => {
|
|
182
605
|
messagesEndRef.current?.scrollIntoView({ behavior });
|
|
183
606
|
};
|
|
184
|
-
const textareaRef = useRef(null);
|
|
185
607
|
useEffect(() => {
|
|
186
608
|
if (isChatOpen) {
|
|
187
609
|
requestAnimationFrame(() => {
|
|
@@ -196,32 +618,21 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
196
618
|
});
|
|
197
619
|
}
|
|
198
620
|
}, [chatMessages.length, isChatOpen]);
|
|
199
|
-
const handleSendMessage = () => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
scrollToBottom("smooth");
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
if (textareaRef.current) {
|
|
208
|
-
textareaRef.current.style.height = "auto";
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
const handleKeyDownSendMessage = (e) => {
|
|
212
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
213
|
-
e.preventDefault();
|
|
214
|
-
handleSendMessage();
|
|
215
|
-
}
|
|
621
|
+
const handleSendMessage = (text, content) => {
|
|
622
|
+
sendChatMessage(text, content);
|
|
623
|
+
requestAnimationFrame(() => {
|
|
624
|
+
scrollToBottom("smooth");
|
|
625
|
+
});
|
|
216
626
|
};
|
|
217
627
|
const isMobile = useMediaQuery("(max-width: 639px)");
|
|
218
628
|
if (!isChatOpen) return null;
|
|
629
|
+
const chatPanelClassName = "border-border-default bg-background-surface flex min-h-0 flex-col overflow-hidden rounded-2xl border p-4 pr-1";
|
|
219
630
|
const chatContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
220
|
-
/* @__PURE__ */ jsxs("div", { className: "border-border-default flex items-center justify-between pr-3", children: [
|
|
631
|
+
/* @__PURE__ */ jsxs("div", { className: "border-border-default flex shrink-0 items-center justify-between pr-3", children: [
|
|
221
632
|
/* @__PURE__ */ jsx("h3", { className: "text-text-primary text-lg font-medium", children: t("chat.title") }),
|
|
222
633
|
/* @__PURE__ */ jsx(Button, { size: "icon", variant: "none", onClick: closeChat, children: /* @__PURE__ */ jsx(Close, { className: "h-6 w-6", "aria-label": t("chat.close") }) })
|
|
223
634
|
] }),
|
|
224
|
-
/* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1 py-2 pr-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
635
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "h-full min-h-0 flex-1 py-2 pr-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
225
636
|
chatMessages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-text-secondary text-center", children: /* @__PURE__ */ jsx("p", { children: t("chat.empty") }) }) : chatMessages.map((message) => {
|
|
226
637
|
const isOwnMessage = Number(message.senderId) === Number(currentUser?.id);
|
|
227
638
|
return /* @__PURE__ */ jsx(
|
|
@@ -252,13 +663,14 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
252
663
|
] }),
|
|
253
664
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
254
665
|
/* @__PURE__ */ jsx(
|
|
255
|
-
|
|
666
|
+
ChatMessageContent,
|
|
256
667
|
{
|
|
668
|
+
text: message.text,
|
|
669
|
+
content: message.content,
|
|
257
670
|
className: cn(
|
|
258
|
-
"cursor-text rounded-lg px-3 py-2
|
|
671
|
+
"cursor-text rounded-lg px-3 py-2 select-text",
|
|
259
672
|
isOwnMessage ? "bg-action-primary-background-disabled" : "bg-background-page"
|
|
260
|
-
)
|
|
261
|
-
children: parseLinks(message.text)
|
|
673
|
+
)
|
|
262
674
|
}
|
|
263
675
|
),
|
|
264
676
|
isOwnMessage && /* @__PURE__ */ jsx(
|
|
@@ -267,7 +679,7 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
267
679
|
type: "button",
|
|
268
680
|
size: "icon",
|
|
269
681
|
variant: "none",
|
|
270
|
-
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-
|
|
682
|
+
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",
|
|
271
683
|
onClick: () => deleteChatMessage(message.id),
|
|
272
684
|
"aria-label": t("chat.deleteAria"),
|
|
273
685
|
children: /* @__PURE__ */ jsx(Trash, { className: "fill-icon-secondary h-4 w-4" })
|
|
@@ -281,39 +693,11 @@ var Chat = ({ compactPositionClassName } = {}) => {
|
|
|
281
693
|
}),
|
|
282
694
|
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
283
695
|
] }) }),
|
|
284
|
-
/* @__PURE__ */ jsx(
|
|
285
|
-
/* @__PURE__ */ jsx(
|
|
286
|
-
Textarea,
|
|
287
|
-
{
|
|
288
|
-
ref: textareaRef,
|
|
289
|
-
value: messageText,
|
|
290
|
-
onChange: (e) => setMessageText(e.target.value),
|
|
291
|
-
placeholder: t("chat.placeholder"),
|
|
292
|
-
className: "my-3 max-h-32 min-w-full rounded-none border-none p-0 pr-2",
|
|
293
|
-
onKeyDown: handleKeyDownSendMessage
|
|
294
|
-
}
|
|
295
|
-
),
|
|
296
|
-
/* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(
|
|
297
|
-
Button,
|
|
298
|
-
{
|
|
299
|
-
size: "icon",
|
|
300
|
-
variant: "primary",
|
|
301
|
-
onClick: handleSendMessage,
|
|
302
|
-
disabled: !messageText.trim(),
|
|
303
|
-
className: "rounded-xl p-2",
|
|
304
|
-
children: /* @__PURE__ */ jsx(
|
|
305
|
-
Send,
|
|
306
|
-
{
|
|
307
|
-
className: cn(
|
|
308
|
-
"fill-action-primary-text group-hover:fill-action-primary-text h-6 w-6",
|
|
309
|
-
!messageText.trim() && "fill-icon-secondary"
|
|
310
|
-
)
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
) })
|
|
315
|
-
] }) })
|
|
696
|
+
/* @__PURE__ */ jsx(ChatComposer, { placeholder: t("chat.placeholder"), onSend: handleSendMessage })
|
|
316
697
|
] });
|
|
698
|
+
if (embedded) {
|
|
699
|
+
return /* @__PURE__ */ jsx("div", { className: cn(chatPanelClassName, "h-full min-h-0 w-full"), children: chatContent });
|
|
700
|
+
}
|
|
317
701
|
if (isMobile) {
|
|
318
702
|
return /* @__PURE__ */ jsx(Modal, { open: isChatOpen, onOpenChange: (open) => !open && closeChat(), children: /* @__PURE__ */ jsxs(
|
|
319
703
|
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 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 { 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';\nconst CHAT_MESSAGE_DELETE_TYPE = 'chat_message_delete';\n\ntype ChatMessagePayload = {\n id: string;\n text: string;\n senderId: string;\n senderName: string;\n timestamp: number;\n};\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(payload);\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) => {\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 // Добавляем сообщение в локальный 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","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, 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 { 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, 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 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={`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 <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 {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-hover:opacity-100 sm:group-focus-within: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 {/* Поле ввода */}\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/**\n * Жирный — 700, не 600: при синтезе начертания (нет файла Inter Bold) 600 почти неотличим от 400.\n * Курсив — запас справа: наклон глифа вылезает из метрики и сталкивается со следующим символом.\n */\nconst chatInlineMarksClassName = cn(\n '[&_strong]:font-bold',\n '[&_em]:italic [&_em]:pr-[0.18em]',\n '[&_s]:line-through',\n '[&_u]:underline',\n);\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-bold',\n '[&_.ProseMirror_em]:italic [&_.ProseMirror_em]:pr-[0.18em]',\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 chatInlineMarksClassName,\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 shrink-0 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 className=\"font-bold\">{acc}</strong>;\n case 'italic':\n return <em className=\"pr-[0.18em] italic\">{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 shrink-0 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=\"h-full 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 min-h-0 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.26",
|
|
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.26",
|
|
24
|
+
"@xipkg/calls-hooks": "^0.1.26",
|
|
25
|
+
"@xipkg/calls-providers": "^0.1.26",
|
|
26
|
+
"@xipkg/calls-store": "^0.1.26",
|
|
27
|
+
"@xipkg/calls-types": "^0.1.26",
|
|
28
|
+
"@xipkg/calls-ui": "^0.1.26",
|
|
29
|
+
"@xipkg/calls-utils": "^0.1.26",
|
|
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.24",
|
|
21
|
-
"@xipkg/calls-utils": "^0.1.24",
|
|
22
|
-
"@xipkg/calls-types": "^0.1.24",
|
|
23
|
-
"@xipkg/calls-config": "^0.1.24",
|
|
24
|
-
"@xipkg/calls-providers": "^0.1.24",
|
|
25
|
-
"@xipkg/calls-store": "^0.1.24",
|
|
26
|
-
"@xipkg/calls-hooks": "^0.1.24",
|
|
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",
|