@droppii-org/chat-sdk 0.0.21 → 0.0.22

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.
Files changed (68) hide show
  1. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  2. package/dist/components/conversation/DeskConversationList.js +5 -0
  3. package/dist/components/message/MessageList.d.ts.map +1 -1
  4. package/dist/components/message/MessageList.js +10 -32
  5. package/dist/components/message/footer/ActionBar.d.ts +3 -0
  6. package/dist/components/message/footer/ActionBar.d.ts.map +1 -0
  7. package/dist/components/message/footer/ActionBar.js +143 -0
  8. package/dist/components/message/footer/EmojiPicker.d.ts +7 -0
  9. package/dist/components/message/footer/EmojiPicker.d.ts.map +1 -0
  10. package/dist/components/message/footer/EmojiPicker.js +151 -0
  11. package/dist/components/message/footer/EnterHandler.d.ts +2 -0
  12. package/dist/components/message/footer/EnterHandler.d.ts.map +1 -0
  13. package/dist/components/message/footer/EnterHandler.js +106 -0
  14. package/dist/components/message/footer/FilePreview.d.ts +10 -0
  15. package/dist/components/message/footer/FilePreview.d.ts.map +1 -0
  16. package/dist/components/message/footer/FilePreview.js +48 -0
  17. package/dist/components/message/footer/ToolbarPlugin.d.ts +2 -0
  18. package/dist/components/message/footer/ToolbarPlugin.d.ts.map +1 -0
  19. package/dist/components/message/footer/ToolbarPlugin.js +169 -0
  20. package/dist/components/message/footer/index.d.ts +9 -2
  21. package/dist/components/message/footer/index.d.ts.map +1 -1
  22. package/dist/components/message/footer/index.js +48 -8
  23. package/dist/components/message/item/FileMessage.d.ts +7 -0
  24. package/dist/components/message/item/FileMessage.d.ts.map +1 -0
  25. package/dist/components/message/item/FileMessage.js +27 -0
  26. package/dist/components/message/item/ImageMessage.d.ts +7 -0
  27. package/dist/components/message/item/ImageMessage.d.ts.map +1 -0
  28. package/dist/components/message/item/ImageMessage.js +23 -0
  29. package/dist/components/message/item/TextMessage.d.ts +7 -0
  30. package/dist/components/message/item/TextMessage.d.ts.map +1 -0
  31. package/dist/components/message/item/TextMessage.js +21 -0
  32. package/dist/components/message/item/VideoMessage.d.ts +7 -0
  33. package/dist/components/message/item/VideoMessage.d.ts.map +1 -0
  34. package/dist/components/message/item/VideoMessage.js +20 -0
  35. package/dist/components/message/{MessageItem.d.ts → item/index.d.ts} +2 -2
  36. package/dist/components/message/item/index.d.ts.map +1 -0
  37. package/dist/components/message/item/index.js +41 -0
  38. package/dist/context/ChatContext.d.ts.map +1 -1
  39. package/dist/context/ChatContext.js +9 -7
  40. package/dist/hooks/conversation/useConversation.d.ts.map +1 -1
  41. package/dist/hooks/conversation/useConversation.js +12 -4
  42. package/dist/hooks/conversation/useConversationStore.d.ts +3 -2
  43. package/dist/hooks/conversation/useConversationStore.d.ts.map +1 -1
  44. package/dist/hooks/message/useSendMessage.d.ts +22 -10
  45. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  46. package/dist/hooks/message/useSendMessage.js +209 -21
  47. package/dist/hooks/zustand/useMessageStore.d.ts +9 -0
  48. package/dist/hooks/zustand/useMessageStore.d.ts.map +1 -0
  49. package/dist/hooks/zustand/useMessageStore.js +8 -0
  50. package/dist/screens/deskMessage/index.d.ts.map +1 -1
  51. package/dist/screens/deskMessage/index.js +2 -8
  52. package/dist/styles/global.css +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/dist/types/chat.d.ts +36 -2
  55. package/dist/types/chat.d.ts.map +1 -1
  56. package/package.json +7 -1
  57. package/dist/assets/openIM.wasm +0 -0
  58. package/dist/components/ChatBubble.d.ts +0 -10
  59. package/dist/components/ChatBubble.d.ts.map +0 -1
  60. package/dist/components/ChatBubble.js +0 -28
  61. package/dist/components/message/MessageItem.d.ts.map +0 -1
  62. package/dist/components/message/MessageItem.js +0 -21
  63. package/dist/components/message/footer/BottomSection.d.ts +0 -3
  64. package/dist/components/message/footer/BottomSection.d.ts.map +0 -1
  65. package/dist/components/message/footer/BottomSection.js +0 -6
  66. package/dist/screens/desk-message/index.d.ts +0 -3
  67. package/dist/screens/desk-message/index.d.ts.map +0 -1
  68. package/dist/screens/desk-message/index.js +0 -15
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AAuGA,UAAU,yBAAyB;IACjC,oBAAoB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,oBAAoB,GAAI,sCAG3B,yBAAyB,4CAuL3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AAuGA,UAAU,yBAAyB;IACjC,oBAAoB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,oBAAoB,GAAI,sCAG3B,yBAAyB,4CA8L3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -104,9 +104,14 @@ const DeskConversationList = ({ onConversationSelect, className = "", }) => {
104
104
  const threadId = searchParams.get("threadId");
105
105
  if (threadId) {
106
106
  setSelectedThreadId(threadId);
107
+ const selectedConversation = conversations.find((conv) => conv.id === threadId);
108
+ if (selectedConversation) {
109
+ setConversationData(selectedConversation);
110
+ }
107
111
  }
108
112
  else if (conversations.length > 0) {
109
113
  setSelectedThreadId(conversations[0].id);
114
+ setConversationData(conversations[0]);
110
115
  const newSearchParams = new URLSearchParams(searchParams);
111
116
  newSearchParams.set("threadId", conversations[0].id);
112
117
  router.replace(`${pathname}?${newSearchParams.toString()}`);
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAe,MAAM,yBAAyB,CAAC;AAgBxE,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAqH3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAc3D,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAkE3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,33 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { SessionType } from "@openim/wasm-client-sdk";
4
3
  import { useMessage } from "../../hooks/message/useMessage";
5
- import { Button, Input, Spin, Tooltip } from "antd";
6
- import { Icon } from "../icon";
7
- import { useCallback, useEffect, useRef, useState } from "react";
8
- import { useSendMessage } from "../../hooks/message/useSendMessage";
4
+ import { Spin } from "antd";
5
+ import { useEffect, useMemo, useRef } from "react";
9
6
  import dayjs from "dayjs";
10
7
  import isToday from "dayjs/plugin/isToday";
11
8
  import emitter from "../../utils/events";
12
- import MessageItem from "./MessageItem";
9
+ import MessageItem from "./item";
13
10
  import InfiniteScroll from "react-infinite-scroll-component";
14
11
  import MessageHeader from "./MessageHeader";
12
+ import MessageFooter from "./footer";
15
13
  dayjs.extend(isToday);
16
14
  const MessageList = (props) => {
17
15
  var _a, _b;
18
16
  const { conversationData, onClose, conversationId } = props;
19
17
  const scrollRef = useRef(null);
20
18
  const { getMoreOldMessages, moreOldLoading, loadState, latestLoadState } = useMessage(conversationId);
21
- const { sendTextMessage } = useSendMessage({
22
- recvID: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType) !== SessionType.Single
23
- ? ""
24
- : (conversationData === null || conversationData === void 0 ? void 0 : conversationData.userID) || "",
25
- groupID: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType) === SessionType.Single
26
- ? ""
27
- : (conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID) || "",
28
- });
29
- const [textMessage, setTextMessage] = useState("");
30
- const [composing, setComposing] = useState(false);
19
+ const lastMessage = useMemo(() => {
20
+ var _a;
21
+ const messageList = (_a = latestLoadState.current) === null || _a === void 0 ? void 0 : _a.messageList;
22
+ return messageList === null || messageList === void 0 ? void 0 : messageList[(messageList === null || messageList === void 0 ? void 0 : messageList.length) - 1];
23
+ }, [latestLoadState]);
31
24
  const scrollToBottom = () => {
32
25
  setTimeout(() => {
33
26
  var _a, _b;
@@ -37,13 +30,6 @@ const MessageList = (props) => {
37
30
  });
38
31
  });
39
32
  };
40
- const onSendTextMessage = useCallback(async () => {
41
- var _a;
42
- const messageList = (_a = latestLoadState.current) === null || _a === void 0 ? void 0 : _a.messageList;
43
- setTextMessage("");
44
- const lastMessage = messageList === null || messageList === void 0 ? void 0 : messageList[(messageList === null || messageList === void 0 ? void 0 : messageList.length) - 1];
45
- sendTextMessage(textMessage, lastMessage);
46
- }, [textMessage, sendTextMessage, latestLoadState]);
47
33
  const loadMoreMessage = () => {
48
34
  if (!loadState.hasMoreOld || moreOldLoading)
49
35
  return;
@@ -51,7 +37,6 @@ const MessageList = (props) => {
51
37
  };
52
38
  useEffect(() => {
53
39
  emitter.on("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
54
- console.log("CHAT_LIST_SCROLL_TO_BOTTOM");
55
40
  return () => {
56
41
  emitter.off("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
57
42
  };
@@ -61,13 +46,6 @@ const MessageList = (props) => {
61
46
  overflow: "auto",
62
47
  display: "flex",
63
48
  flexDirection: "column-reverse",
64
- }, children: _jsx(InfiniteScroll, { dataLength: ((_a = loadState.groupMessageList) === null || _a === void 0 ? void 0 : _a.length) || 0, next: loadMoreMessage, style: { display: "flex", flexDirection: "column-reverse" }, inverse: true, hasMore: loadState.hasMoreOld, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableDiv", children: (_b = loadState.groupMessageList) === null || _b === void 0 ? void 0 : _b.toReversed().map((message) => _jsx(MessageItem, { groupMessage: message })) }) }), _jsx("div", { className: "border-t px-4 py-3", children: _jsx("div", { className: "border rounded-lg bg-gray-50", children: _jsxs("div", { className: "px-4 py-3 flex items-center gap-4", children: [_jsx(Input, { placeholder: "Nh\u1EADp tin nh\u1EAFn", size: "small", variant: "borderless", value: textMessage, onChange: (e) => setTextMessage(e.target.value), onKeyDown: (e) => {
65
- if (composing) {
66
- return;
67
- }
68
- if (e.key === "Enter") {
69
- onSendTextMessage();
70
- }
71
- }, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }), _jsx(Tooltip, { title: "G\u1EEDi tin nh\u1EAFn", children: _jsx(Button, { type: "primary", shape: "circle", size: "middle", icon: _jsx(Icon, { icon: "send-b", color: "white", size: 16 }), disabled: textMessage.length === 0, onClick: onSendTextMessage }) })] }) }) })] }));
49
+ }, children: _jsx(InfiniteScroll, { dataLength: ((_a = loadState.groupMessageList) === null || _a === void 0 ? void 0 : _a.length) || 0, next: loadMoreMessage, style: { display: "flex", flexDirection: "column-reverse" }, inverse: true, hasMore: loadState.hasMoreOld, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableDiv", children: (_b = loadState.groupMessageList) === null || _b === void 0 ? void 0 : _b.toReversed().map((message) => _jsx(MessageItem, { groupMessage: message })) }) }), _jsx(MessageFooter, { lastMessage: lastMessage })] }));
72
50
  };
73
51
  export default MessageList;
@@ -0,0 +1,3 @@
1
+ declare const ActionBar: () => import("react/jsx-runtime").JSX.Element;
2
+ export default ActionBar;
3
+ //# sourceMappingURL=ActionBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAgEA,QAAA,MAAM,SAAS,+CA+Nd,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -0,0 +1,143 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { Button, Upload, message, } from "antd";
5
+ import clsx from "clsx";
6
+ import EmojiPicker from "./EmojiPicker";
7
+ import { Icon } from "../../icon";
8
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
9
+ import { $createParagraphNode, $getRoot, $getSelection, $isRangeSelection, } from "lexical";
10
+ import { $generateHtmlFromNodes } from "@lexical/html";
11
+ import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
12
+ import { $isQuoteNode } from "@lexical/rich-text";
13
+ import { useMessageFooterContext } from ".";
14
+ const documentTypes = [
15
+ "application/pdf",
16
+ "application/msword",
17
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
18
+ ];
19
+ const EmojiIcon = (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M14 8.39997C14 8.8418 13.6418 9.19997 13.2 9.19997C12.7581 9.19997 12.4 8.8418 12.4 8.39997C12.4 7.95814 12.7581 7.59997 13.2 7.59997C13.6418 7.59997 14 7.95814 14 8.39997Z", fill: "white" }), _jsx("path", { d: "M7.59997 8.39997C7.59997 8.8418 7.2418 9.19997 6.79997 9.19997C6.35814 9.19997 5.99997 8.8418 5.99997 8.39997C5.99997 7.95814 6.35814 7.59997 6.79997 7.59997C7.2418 7.59997 7.59997 7.95814 7.59997 8.39997Z", fill: "white" }), _jsx("path", { d: "M7.59997 12.4C7.59997 12.4 8.49997 13.2 9.99997 13.2C11.5 13.2 12.4 12.4 12.4 12.4M14 8.39997C14 8.8418 13.6418 9.19997 13.2 9.19997C12.7581 9.19997 12.4 8.8418 12.4 8.39997C12.4 7.95814 12.7581 7.59997 13.2 7.59997C13.6418 7.59997 14 7.95814 14 8.39997ZM18 9.99997C18 14.4182 14.4182 18 9.99997 18C5.58169 18 1.99997 14.4182 1.99997 9.99997C1.99997 5.58169 5.58169 1.99997 9.99997 1.99997C14.4182 1.99997 18 5.58169 18 9.99997ZM7.59997 8.39997C7.59997 8.8418 7.2418 9.19997 6.79997 9.19997C6.35814 9.19997 5.99997 8.8418 5.99997 8.39997C5.99997 7.95814 6.35814 7.59997 6.79997 7.59997C7.2418 7.59997 7.59997 7.95814 7.59997 8.39997Z", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" })] }));
20
+ const ActionBar = () => {
21
+ const [editor] = useLexicalComposerContext();
22
+ const { onSendMessage, setListUploadFiles } = useMessageFooterContext();
23
+ const containerRef = useRef(null);
24
+ const [showEmojiPicker, setShowEmojiPicker] = useState(false);
25
+ const { listUploadFiles } = useMessageFooterContext();
26
+ const handleSend = useCallback(() => {
27
+ let plainText = "";
28
+ let richText = "";
29
+ // lấy plain text & html
30
+ editor.getEditorState().read(() => {
31
+ plainText = $getRoot().getTextContent();
32
+ richText = $generateHtmlFromNodes(editor);
33
+ });
34
+ if (plainText.trim().length > 0 || listUploadFiles.length > 0) {
35
+ onSendMessage({
36
+ plainText,
37
+ richText,
38
+ type: listUploadFiles.length > 0 ? "file" : "text",
39
+ });
40
+ }
41
+ editor.update(() => {
42
+ const root = $getRoot();
43
+ root.clear();
44
+ const paragraph = $createParagraphNode();
45
+ root.append(paragraph);
46
+ paragraph.select();
47
+ const selection = $getSelection();
48
+ if ($isRangeSelection(selection)) {
49
+ const anchorNode = selection.anchor.getNode();
50
+ const topLevelNode = anchorNode.getTopLevelElementOrThrow();
51
+ const isListNode = $isListNode(topLevelNode);
52
+ const isQuoteNode = $isQuoteNode(topLevelNode);
53
+ const listType = isListNode ? topLevelNode.getListType() : undefined;
54
+ if (selection.hasFormat("bold")) {
55
+ selection.formatText("bold");
56
+ }
57
+ if (selection.hasFormat("italic")) {
58
+ selection.formatText("italic");
59
+ }
60
+ if (selection.hasFormat("strikethrough")) {
61
+ selection.formatText("strikethrough");
62
+ }
63
+ if (isQuoteNode) {
64
+ // Nếu đang là QuoteNode → chuyển về Paragraph (bình thường)
65
+ const paragraph = $createParagraphNode();
66
+ // copy con của quote sang paragraph
67
+ const children = topLevelNode.getChildren();
68
+ children.forEach((child) => paragraph.append(child));
69
+ topLevelNode.replace(paragraph);
70
+ }
71
+ if (isListNode && listType) {
72
+ editor.dispatchCommand(listType === "number"
73
+ ? INSERT_ORDERED_LIST_COMMAND
74
+ : INSERT_UNORDERED_LIST_COMMAND, undefined);
75
+ }
76
+ }
77
+ });
78
+ }, [editor, onSendMessage]);
79
+ const handleEmojiSelect = useCallback((emoji) => {
80
+ editor.update(() => {
81
+ const selection = $getSelection();
82
+ if ($isRangeSelection(selection)) {
83
+ selection.insertText(emoji); // chèn emoji như text bình thường
84
+ }
85
+ });
86
+ setShowEmojiPicker(false);
87
+ }, [editor]);
88
+ const beforeUploadImagesAndVideo = (file, fileList) => {
89
+ const isImage = file.type === "image/jpeg" ||
90
+ file.type === "image/png" ||
91
+ file.type === "image/jpg";
92
+ const isVideo = file.type.startsWith("video/");
93
+ // check format
94
+ if (!isImage && !isVideo) {
95
+ message.error(`${file.name} không đúng định dạng JPG, JPEG, PNG hoặc VIDEO`);
96
+ return Upload.LIST_IGNORE;
97
+ }
98
+ // check size
99
+ const maxSize = isImage ? 5 : 200; // MB
100
+ if (file.size / 1024 / 1024 > maxSize) {
101
+ message.error(`${file.name} có kích thước tập tin vượt quá ${maxSize}MB`);
102
+ return Upload.LIST_IGNORE;
103
+ }
104
+ // nếu là video thì chỉ cho 1 cái duy nhất
105
+ if (isVideo) {
106
+ const hasVideo = listUploadFiles.some((f) => { var _a; return (_a = f.type) === null || _a === void 0 ? void 0 : _a.startsWith("video/"); });
107
+ if (hasVideo) {
108
+ message.error("Chỉ được phép tải lên 1 video duy nhất");
109
+ return Upload.LIST_IGNORE;
110
+ }
111
+ }
112
+ return true;
113
+ };
114
+ const beforeUploadFile = (file) => {
115
+ const isAllowed = documentTypes.includes(file.type);
116
+ if (!isAllowed) {
117
+ message.error(`${file.name} không đúng định dạng (chỉ hỗ trợ PDF, DOC, DOCX)`);
118
+ }
119
+ return isAllowed;
120
+ };
121
+ const handleChange = (info) => {
122
+ let newList = [...info.fileList];
123
+ // Nếu file mới là tài liệu -> chỉ giữ 1 cái (file cuối)
124
+ const lastFile = info.file;
125
+ if (documentTypes.includes(lastFile.type || "")) {
126
+ newList = newList.filter((f) => documentTypes.includes(f.type || "") === false); // remove doc cũ
127
+ newList.push(lastFile); // add doc mới
128
+ }
129
+ setListUploadFiles(newList);
130
+ };
131
+ useEffect(() => {
132
+ const handleClickOutside = (event) => {
133
+ if (containerRef.current &&
134
+ !containerRef.current.contains(event.target)) {
135
+ setShowEmojiPicker(false);
136
+ }
137
+ };
138
+ document.addEventListener("mousedown", handleClickOutside);
139
+ return () => document.removeEventListener("mousedown", handleClickOutside);
140
+ }, []);
141
+ return (_jsxs("div", { className: "flex items-center justify-between px-4", ref: containerRef, children: [_jsxs("div", { className: "flex items-center gap-3 relative", children: [_jsx(Button, { type: "text", shape: "default", className: clsx("text-gray-500 w-8 h-8 p-0", showEmojiPicker ? "bg-blue-100 text-blue-600" : "text-gray-500"), onClick: () => setShowEmojiPicker(!showEmojiPicker), children: EmojiIcon }), _jsx(Upload, { accept: "image/jpeg, image/png, image/jpg, video/*", beforeUpload: beforeUploadImagesAndVideo, multiple: true, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0 text-gray-500", children: _jsx(Icon, { icon: "image-02-o", size: 22 }) }) }), _jsx(Upload, { accept: ".doc,.docx,.pdf", beforeUpload: beforeUploadFile, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0 text-gray-500", children: _jsx(Icon, { icon: "link-o", size: 22 }) }) }), showEmojiPicker && (_jsx(EmojiPicker, { onEmojiSelect: handleEmojiSelect, onClose: () => setShowEmojiPicker(false) }))] }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleSend, children: _jsx(Icon, { icon: "send-b", size: 28, className: "text-blue-500" }) })] }));
142
+ };
143
+ export default ActionBar;
@@ -0,0 +1,7 @@
1
+ interface EmojiPickerProps {
2
+ onEmojiSelect: (emoji: string) => void;
3
+ onClose: () => void;
4
+ }
5
+ declare const EmojiPicker: ({ onEmojiSelect, onClose }: EmojiPickerProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default EmojiPicker;
7
+ //# sourceMappingURL=EmojiPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmojiPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EmojiPicker.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACxB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,QAAA,MAAM,WAAW,GAAI,4BAA4B,gBAAgB,4CAyLhE,CAAA;AAED,eAAe,WAAW,CAAA"}
@@ -0,0 +1,151 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ const EmojiPicker = ({ onEmojiSelect, onClose }) => {
5
+ const [activeCategory, setActiveCategory] = useState("smileys");
6
+ const emojiCategories = {
7
+ smileys: {
8
+ name: "Smileys & People",
9
+ emojis: [
10
+ "😀",
11
+ "😃",
12
+ "😄",
13
+ "😁",
14
+ "😆",
15
+ "😅",
16
+ "😂",
17
+ "🤣",
18
+ "😊",
19
+ "😇",
20
+ "🙂",
21
+ "🙃",
22
+ "😉",
23
+ "😌",
24
+ "😍",
25
+ "🥰",
26
+ "😘",
27
+ "😗",
28
+ "😙",
29
+ "😚",
30
+ "😋",
31
+ "😛",
32
+ "😝",
33
+ "😜",
34
+ "🤪",
35
+ "🤨",
36
+ "🧐",
37
+ "🤓",
38
+ "😎",
39
+ "🤩",
40
+ "🥳",
41
+ ],
42
+ },
43
+ nature: {
44
+ name: "Animals & Nature",
45
+ emojis: [
46
+ "🐶",
47
+ "🐱",
48
+ "🐭",
49
+ "🐹",
50
+ "🐰",
51
+ "🦊",
52
+ "🐻",
53
+ "🐼",
54
+ "🐨",
55
+ "🐯",
56
+ "🦁",
57
+ "🐮",
58
+ "🐷",
59
+ "🐸",
60
+ "🐵",
61
+ "🙈",
62
+ "🙉",
63
+ "🙊",
64
+ "🐒",
65
+ "🐔",
66
+ "🐧",
67
+ "🐦",
68
+ "🐤",
69
+ "🐣",
70
+ "🐥",
71
+ "🦆",
72
+ "🦅",
73
+ "🦉",
74
+ "🦇",
75
+ "🐺",
76
+ ],
77
+ },
78
+ food: {
79
+ name: "Food & Drink",
80
+ emojis: [
81
+ "🍎",
82
+ "🍐",
83
+ "🍊",
84
+ "🍋",
85
+ "🍌",
86
+ "🍉",
87
+ "🍇",
88
+ "🍓",
89
+ "🍈",
90
+ "🍒",
91
+ "🍑",
92
+ "🥭",
93
+ "🍍",
94
+ "🥥",
95
+ "🥝",
96
+ "🍅",
97
+ "🍆",
98
+ "🥑",
99
+ "🥦",
100
+ "🥬",
101
+ "🥒",
102
+ "🌶️",
103
+ "🌽",
104
+ "🥕",
105
+ "🧄",
106
+ "🧅",
107
+ "🥔",
108
+ "🍠",
109
+ "🥐",
110
+ "🍞",
111
+ ],
112
+ },
113
+ activities: {
114
+ name: "Activities",
115
+ emojis: [
116
+ "⚽",
117
+ "🏀",
118
+ "🏈",
119
+ "⚾",
120
+ "🥎",
121
+ "🎾",
122
+ "🏐",
123
+ "🏉",
124
+ "🥏",
125
+ "🎱",
126
+ "🪀",
127
+ "🏓",
128
+ "🏸",
129
+ "🏒",
130
+ "🏑",
131
+ "🥍",
132
+ "🏏",
133
+ "🪃",
134
+ "🥅",
135
+ "⛳",
136
+ "🪁",
137
+ "🏹",
138
+ "🎣",
139
+ "🤿",
140
+ "🥊",
141
+ "🥋",
142
+ "🎽",
143
+ "🛹",
144
+ "🛷",
145
+ "⛸️",
146
+ ],
147
+ },
148
+ };
149
+ return (_jsx("div", { className: "absolute bottom-full left-0 mb-2 bg-white border border-gray-200 rounded-xl shadow-lg w-80 z-50", children: _jsxs("div", { className: "p-3", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-medium text-gray-900", children: "Choose an emoji" }), _jsx("button", { onClick: onClose, className: "text-gray-400 hover:text-gray-600 text-xl", children: "\u00D7" })] }), _jsx("div", { className: "flex gap-1 mb-3", children: Object.entries(emojiCategories).map(([key, category]) => (_jsx("button", { onClick: () => setActiveCategory(key), className: `px-3 py-1 text-xs rounded-full transition-colors ${activeCategory === key ? "bg-blue-100 text-blue-600" : "text-gray-500 hover:text-gray-700"}`, children: category.name.split(" ")[0] }, key))) }), _jsx("div", { className: "grid grid-cols-8 gap-1 max-h-48 overflow-y-auto", children: emojiCategories[activeCategory].emojis.map((emoji, index) => (_jsx("button", { onClick: () => onEmojiSelect(emoji), className: "p-2 text-xl hover:bg-gray-100 rounded-lg transition-colors", children: emoji }, index))) })] }) }));
150
+ };
151
+ export default EmojiPicker;
@@ -0,0 +1,2 @@
1
+ export default function EnterHandler(): null;
2
+ //# sourceMappingURL=EnterHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAsBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAqHnC"}
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
+ import { KEY_ENTER_COMMAND, COMMAND_PRIORITY_NORMAL, $getSelection, $isRangeSelection, $createParagraphNode, $createLineBreakNode, $getRoot, } from "lexical";
4
+ import { useEffect, useRef } from "react";
5
+ import { $generateHtmlFromNodes } from "@lexical/html";
6
+ import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
7
+ import { $isQuoteNode } from "@lexical/rich-text";
8
+ import { useMessageFooterContext } from ".";
9
+ export default function EnterHandler() {
10
+ const [editor] = useLexicalComposerContext();
11
+ const { onSendMessage } = useMessageFooterContext();
12
+ const shiftEnterCount = useRef(0);
13
+ const { listUploadFiles } = useMessageFooterContext();
14
+ useEffect(() => {
15
+ return editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
16
+ // Case 1: Enter (submit, chặn xuống dòng)
17
+ if (!event.shiftKey) {
18
+ event.preventDefault();
19
+ shiftEnterCount.current = 0;
20
+ let plainText = "";
21
+ let richText = "";
22
+ // lấy plain text & html
23
+ editor.getEditorState().read(() => {
24
+ plainText = $getRoot().getTextContent();
25
+ richText = $generateHtmlFromNodes(editor);
26
+ });
27
+ if (plainText.trim().length > 0 || listUploadFiles.length > 0) {
28
+ onSendMessage({
29
+ plainText,
30
+ richText,
31
+ type: listUploadFiles.length > 0 ? "file" : "text",
32
+ });
33
+ }
34
+ editor.update(() => {
35
+ const root = $getRoot();
36
+ root.clear();
37
+ const paragraph = $createParagraphNode();
38
+ root.append(paragraph);
39
+ paragraph.select();
40
+ const selection = $getSelection();
41
+ if ($isRangeSelection(selection)) {
42
+ const anchorNode = selection.anchor.getNode();
43
+ const topLevelNode = anchorNode.getTopLevelElementOrThrow();
44
+ const isListNode = $isListNode(topLevelNode);
45
+ const isQuoteNode = $isQuoteNode(topLevelNode);
46
+ const listType = isListNode
47
+ ? topLevelNode.getListType()
48
+ : undefined;
49
+ if (selection.hasFormat("bold")) {
50
+ selection.formatText("bold");
51
+ }
52
+ if (selection.hasFormat("italic")) {
53
+ selection.formatText("italic");
54
+ }
55
+ if (selection.hasFormat("strikethrough")) {
56
+ selection.formatText("strikethrough");
57
+ }
58
+ if (isQuoteNode) {
59
+ // Nếu đang là QuoteNode → chuyển về Paragraph (bình thường)
60
+ const paragraph = $createParagraphNode();
61
+ // copy con của quote sang paragraph
62
+ const children = topLevelNode.getChildren();
63
+ children.forEach((child) => paragraph.append(child));
64
+ topLevelNode.replace(paragraph);
65
+ }
66
+ if (isListNode && listType) {
67
+ editor.dispatchCommand(listType === "number"
68
+ ? INSERT_ORDERED_LIST_COMMAND
69
+ : INSERT_UNORDERED_LIST_COMMAND, undefined);
70
+ }
71
+ }
72
+ });
73
+ return true;
74
+ }
75
+ // Case 2: Shift+Enter
76
+ if (event.shiftKey) {
77
+ event.preventDefault();
78
+ shiftEnterCount.current += 1;
79
+ if (shiftEnterCount.current >= 2) {
80
+ // Tách block mới
81
+ shiftEnterCount.current = 0;
82
+ editor.update(() => {
83
+ const selection = $getSelection();
84
+ if ($isRangeSelection(selection)) {
85
+ const paragraph = $createParagraphNode();
86
+ selection.insertNodes([paragraph]);
87
+ paragraph.select(); // con trỏ nhảy vào block mới
88
+ }
89
+ });
90
+ }
91
+ else {
92
+ // Xuống dòng trong block
93
+ editor.update(() => {
94
+ const selection = $getSelection();
95
+ if ($isRangeSelection(selection)) {
96
+ selection.insertNodes([$createLineBreakNode()]);
97
+ }
98
+ });
99
+ }
100
+ return true;
101
+ }
102
+ return false;
103
+ }, COMMAND_PRIORITY_NORMAL);
104
+ }, [editor, onSendMessage, listUploadFiles]);
105
+ return null;
106
+ }
@@ -0,0 +1,10 @@
1
+ export declare const documentIcon: import("react/jsx-runtime").JSX.Element;
2
+ interface ShortenOptions {
3
+ maxLength?: number;
4
+ keepStart?: number;
5
+ keepEnd?: number;
6
+ }
7
+ export declare const shortenFileName: (name: string, options?: ShortenOptions) => string;
8
+ declare const FilePreview: () => import("react/jsx-runtime").JSX.Element;
9
+ export default FilePreview;
10
+ //# sourceMappingURL=FilePreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/FilePreview.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,YAAY,yCAoCxB,CAAC;AAEF,UAAU,cAAc;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,UAAS,cAAmB,WAkBzE,CAAC;AAEF,QAAA,MAAM,WAAW,+CAgFhB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback } from "react";
4
+ import { useMessageFooterContext } from ".";
5
+ import { Button } from "antd";
6
+ import { Icon } from "../../icon";
7
+ const documentTypes = [
8
+ "application/pdf",
9
+ "application/msword",
10
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
11
+ ];
12
+ export const documentIcon = (_jsxs("svg", { width: "40", height: "40", viewBox: "0 0 40 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M10.1923 5.83301L23.4363 5.83301L34.1656 14.2054V29.8639C34.1656 31.8997 32.5286 34.1663 29.8068 34.1663C27.0849 34.1663 10.1923 34.1663 10.1923 34.1663C7.4704 34.1663 5.8335 31.8997 5.8335 29.8639V10.0305C5.8335 7.99475 7.4704 5.83301 10.1923 5.83301Z", fill: "#24B0FF" }), _jsx("path", { d: "M10.8335 28.1394V20.833H16.0309V22.4222H12.7026V23.5916H15.3612V25.1708H12.7026V28.1394H10.8335Z", fill: "#edf6ff" }), _jsx("path", { d: "M19.0073 28.0885V20.833H20.8613V26.5118H24.0146V28.0885L19.0073 28.0885Z", fill: "#edf6ff" }), _jsx("path", { d: "M16.5918 28.1001V20.833H18.4485V28.1001H16.5918Z", fill: "#edf6ff" }), _jsx("path", { d: "M24.5737 20.833V28.1393H29.7723V26.5573H26.439L26.4422 25.1708H29.1017V23.5916H26.4422V22.4222H29.7723V20.833H24.5737Z", fill: "#edf6ff" }), _jsx("path", { opacity: "0.302", "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M23.2686 5.83301V14.2281H34.1655L23.2686 5.83301Z", fill: "#edf6ff" })] }));
13
+ export const shortenFileName = (name, options = {}) => {
14
+ const { maxLength = 20, keepStart = 8, keepEnd = 3 } = options;
15
+ if (name.length <= maxLength)
16
+ return name;
17
+ const dotIndex = name.lastIndexOf(".");
18
+ const ext = dotIndex !== -1 ? name.slice(dotIndex) : "";
19
+ const base = dotIndex !== -1 ? name.slice(0, dotIndex) : name;
20
+ // Tính toán độ dài phần đầu & cuối
21
+ const available = maxLength - ext.length - 3; // trừ "...”
22
+ const startLen = keepStart !== null && keepStart !== void 0 ? keepStart : Math.floor(available / 2);
23
+ const endLen = keepEnd !== null && keepEnd !== void 0 ? keepEnd : Math.floor(available / 2);
24
+ const start = base.slice(0, startLen);
25
+ const end = base.slice(-endLen);
26
+ return `${start}...${end}${ext}`;
27
+ };
28
+ const FilePreview = () => {
29
+ const { listUploadFiles, setListUploadFiles } = useMessageFooterContext();
30
+ const onRemoveFile = (file) => {
31
+ setListUploadFiles(listUploadFiles.filter((f) => f.uid !== file.uid));
32
+ };
33
+ const renderFilePreview = useCallback((file) => {
34
+ var _a;
35
+ const isDocument = documentTypes.includes(file.type || "");
36
+ const isVideo = (_a = file.type) === null || _a === void 0 ? void 0 : _a.startsWith("video/");
37
+ let src = file.url;
38
+ if (!src && file.originFileObj) {
39
+ src = URL.createObjectURL(file.originFileObj);
40
+ }
41
+ if (isDocument) {
42
+ return (_jsxs("div", { className: "relative flex flex-row items-center gap-2 align-center bg-gray-100 rounded-md p-1 pr-2", children: [documentIcon, _jsx("span", { className: "text-xs text-gray-500", children: shortenFileName(file.name) }), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemoveFile(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) })] }, file.uid));
43
+ }
44
+ return (_jsxs("div", { className: "relative rounded-md border", children: [isVideo ? (_jsx("video", { src: src, className: "w-[48px] h-[48px] object-cover rounded-lg", autoPlay: false })) : (_jsx("img", { src: src, alt: file.name, className: "w-[48px] h-[48px] object-cover rounded-lg" })), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemoveFile(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) }), isVideo && (_jsx(Icon, { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", icon: "play-b", size: 20, color: "white" }))] }, file.uid));
45
+ }, [listUploadFiles]);
46
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsx("div", { className: "border-b py-2 px-4", children: _jsx("div", { className: "flex items-center gap-2", children: listUploadFiles.map((file) => renderFilePreview(file)) }) }) }));
47
+ };
48
+ export default FilePreview;
@@ -0,0 +1,2 @@
1
+ export declare const ToolbarPlugin: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ToolbarPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolbarPlugin.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ToolbarPlugin.tsx"],"names":[],"mappings":"AAwFA,eAAO,MAAM,aAAa,+CAyNzB,CAAC"}