@minhnd1010/chat-widget 1.0.1 → 1.0.4

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 (2) hide show
  1. package/index.js +282 -178
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  class ChatWidget extends HTMLElement {
2
2
  constructor() {
3
3
  super();
4
- this.attachShadow({ mode: 'open' });
4
+ this.attachShadow({ mode: "open" });
5
5
  this.connection = null;
6
6
  }
7
7
 
@@ -15,17 +15,19 @@ class ChatWidget extends HTMLElement {
15
15
 
16
16
  this.initLogic();
17
17
 
18
- if (!customElements.get('emoji-picker')) {
18
+ if (!customElements.get("emoji-picker")) {
19
19
  // Nếu chưa load thư viện → load từ CDN
20
- const script = document.createElement('script');
21
- script.type = 'module';
22
- script.src = 'https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js';
20
+ const script = document.createElement("script");
21
+ script.type = "module";
22
+ script.src =
23
+ "https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js";
23
24
  script.onload = () => {
24
- console.log('emoji-picker-element loaded');
25
+ console.log("emoji-picker-element loaded");
25
26
  // Sau khi load xong, khởi tạo picker (nếu cần)
26
27
  this.setupEmojiPickerAfterLoad();
27
28
  };
28
- script.onerror = () => console.error('Failed to load emoji-picker-element');
29
+ script.onerror = () =>
30
+ console.error("Failed to load emoji-picker-element");
29
31
  document.head.appendChild(script);
30
32
  } else {
31
33
  // Nếu đã load rồi (trang có nhiều widget) → khởi tạo luôn
@@ -34,29 +36,32 @@ class ChatWidget extends HTMLElement {
34
36
  }
35
37
 
36
38
  render() {
37
- const position = this.getAttribute('position') || 'bottom-left';
39
+ const position = this.getAttribute("position") || "bottom-left";
38
40
 
39
- let containerStyles = 'bottom: 20px; left: 20px;';
40
- let boxStyles = 'top: -78vh; left: 42px;';
41
+ let containerStyles = "bottom: 20px; left: 20px;";
42
+ let boxStyles = "top: -78vh; left: 42px;";
41
43
 
42
44
  switch (position) {
43
- case 'bottom-right':
44
- containerStyles = 'bottom: 20px; right: 20px; left: auto;';
45
- boxStyles = 'top: -78vh; right: 42px; left: auto; transform: translateX(-100%);';
45
+ case "bottom-right":
46
+ containerStyles = "bottom: 20px; right: 20px; left: auto;";
47
+ boxStyles =
48
+ "top: -78vh; right: 42px; left: auto; transform: translateX(-100%);";
46
49
  break;
47
- case 'top-left':
48
- containerStyles = 'top: 20px; left: 20px; bottom: auto;';
49
- boxStyles = 'bottom: 80px; top: auto; left: 42px; transform: translateY(100%);';
50
+ case "top-left":
51
+ containerStyles = "top: 20px; left: 20px; bottom: auto;";
52
+ boxStyles =
53
+ "bottom: 80px; top: auto; left: 42px; transform: translateY(100%);";
50
54
  break;
51
- case 'top-right':
52
- containerStyles = 'top: 20px; right: 20px; left: auto; bottom: auto;';
53
- boxStyles = 'bottom: 80px; top: auto; right: 42px; left: auto; transform: translateX(-100%) translateY(100%);';
55
+ case "top-right":
56
+ containerStyles = "top: 20px; right: 20px; left: auto; bottom: auto;";
57
+ boxStyles =
58
+ "bottom: 80px; top: auto; right: 42px; left: auto; transform: translateX(-100%) translateY(100%);";
54
59
  break;
55
60
  default:
56
61
  // bottom-left
57
62
  }
58
63
 
59
- const template = document.createElement('template');
64
+ const template = document.createElement("template");
60
65
  template.innerHTML = `
61
66
  <style>
62
67
  * {
@@ -381,6 +386,7 @@ class ChatWidget extends HTMLElement {
381
386
  padding: 10px;
382
387
  border-radius: 12px;
383
388
  margin: 0;
389
+ word-break: break-word;
384
390
  }
385
391
 
386
392
  .partner .message {
@@ -461,7 +467,7 @@ class ChatWidget extends HTMLElement {
461
467
  align-items: center;
462
468
  background-color: #e0e0e0ff;
463
469
  border-radius: 50px;
464
- padding: 10px 20px;
470
+ padding: 8px 20px;
465
471
  overflow-y: hidden;
466
472
  }
467
473
 
@@ -503,15 +509,38 @@ class ChatWidget extends HTMLElement {
503
509
  background-color: #4b4bda;
504
510
  }
505
511
 
512
+ /* ================================================================================
513
+ File attach styling
514
+ ================================================================================ */
515
+ .file-attach {
516
+ background: rgb(224, 224, 224);
517
+ border: none;
518
+ cursor: pointer;
519
+ border-radius: 50%;
520
+ display: flex;
521
+ align-items: center;
522
+ justify-content: center;
523
+ padding: 6px;
524
+ }
525
+
526
+ .file-attach:hover {
527
+ background-color: rgba(201, 201, 201, 1);
528
+ }
529
+
530
+ .file-attach svg {
531
+ width: 26px;
532
+ height: 26px;
533
+ fill: #5b5bdfff;
534
+ }
535
+
506
536
  /* ================================================================================
507
537
  EMOJI PICKER STYLING
508
538
  ================================================================================ */
509
539
  #emoji-button {
510
540
  background: none;
511
541
  border: none;
512
- font-size: 24px;
542
+ font-size: 18px;
513
543
  cursor: pointer;
514
- padding: 8px;
515
544
  border-radius: 50%;
516
545
  display: flex;
517
546
  align-items: center;
@@ -522,6 +551,12 @@ class ChatWidget extends HTMLElement {
522
551
  background-color: rgba(0, 0, 0, 0.1);
523
552
  }
524
553
 
554
+ #emoji-button svg {
555
+ width: 25px;
556
+ height: 25px;
557
+ fill: #5b5bdfff;
558
+ }
559
+
525
560
  emoji-picker {
526
561
  position: absolute;
527
562
  bottom: 70px;
@@ -754,6 +789,13 @@ class ChatWidget extends HTMLElement {
754
789
 
755
790
  <!-- Message input -->
756
791
  <div class="message-sending">
792
+ <div class="file-attach" title="Đính kèm file">
793
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
794
+ <path
795
+ d="M352 128C352 110.3 337.7 96 320 96C302.3 96 288 110.3 288 128L288 288L128 288C110.3 288 96 302.3 96 320C96 337.7 110.3 352 128 352L288 352L288 512C288 529.7 302.3 544 320 544C337.7 544 352 529.7 352 512L352 352L512 352C529.7 352 544 337.7 544 320C544 302.3 529.7 288 512 288L352 288L352 128z"
796
+ />
797
+ </svg>
798
+ </div>
757
799
  <div class="message-content">
758
800
  <textarea
759
801
  id="message-input"
@@ -761,7 +803,13 @@ class ChatWidget extends HTMLElement {
761
803
  placeholder="Nhập tin nhắn..."
762
804
  ></textarea>
763
805
  </div>
764
- <button id="emoji-button">😊</button>
806
+ <button id="emoji-button">
807
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
808
+ <path
809
+ d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM229.4 385.9C249.8 413.9 282.8 432 320 432C357.2 432 390.2 413.9 410.6 385.9C418.4 375.2 433.4 372.8 444.1 380.6C454.8 388.4 457.2 403.4 449.4 414.1C420.3 454 373.2 480 320 480C266.8 480 219.7 454 190.6 414.1C182.8 403.4 185.2 388.4 195.9 380.6C206.6 372.8 221.6 375.2 229.4 385.9zM208 272C208 254.3 222.3 240 240 240C257.7 240 272 254.3 272 272C272 289.7 257.7 304 240 304C222.3 304 208 289.7 208 272zM400 240C417.7 240 432 254.3 432 272C432 289.7 417.7 304 400 304C382.3 304 368 289.7 368 272C368 254.3 382.3 240 400 240z"
810
+ />
811
+ </svg>
812
+ </button>
765
813
  <button id="btn-send-message">
766
814
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
767
815
  <path
@@ -783,24 +831,20 @@ class ChatWidget extends HTMLElement {
783
831
  const shadowRoot = this.shadowRoot;
784
832
 
785
833
  // ================== CONFIGURATION ==================
786
- const BASE_URL = this.getAttribute('base-url');
834
+ const BASE_URL = this.getAttribute("base-url");
787
835
  const PAGE_SIZE = 10;
788
836
  const SCROLL_THRESHOLD = 20;
789
837
  const SCROLL_LOAD_MORE_THRESHOLD = 100;
790
838
  const DEBOUNCE_DELAY = 200;
791
839
 
792
840
  // ================== USER INPUT ==================
793
- let token = prompt("Nhập access token của bạn:", "");
794
- if (!token) token = "Anonymous";
841
+ let token = this.getAttribute("token");
795
842
 
796
- let userId = prompt("Nhập mã của bạn:", "");
797
- if (!userId) userId = "Anonymous";
843
+ let userId = this.getAttribute("ma_doi_tac_nsd");
798
844
 
799
- let fullName = prompt("Nhập tên đầy đủ của bạn:");
800
- if (!fullName) fullName = "Khách hàng";
845
+ let fullName = this.getAttribute("ten");
801
846
 
802
- let ePartnerCode = prompt("Nhập ePartnerCode của bạn:", "BENH_VIEN");
803
- if (!ePartnerCode) ePartnerCode = "PORTAL";
847
+ let ePartnerCode = this.getAttribute("e-partner-code");
804
848
 
805
849
  // ================== STATE MANAGEMENT ==================
806
850
  let selectedChat = null;
@@ -820,35 +864,35 @@ class ChatWidget extends HTMLElement {
820
864
 
821
865
  // ================== DOM ELEMENTS ==================
822
866
  const listEl = shadowRoot.querySelector("#list-items ul");
823
- const infoTitle = shadowRoot.querySelector('#info-input .info-header h4');
824
- const chatBoxIcon = shadowRoot.querySelector('#chat-icon');
825
- const chatBoxContainer = shadowRoot.querySelector('#chat-box');
826
- const chatBoxFilter = shadowRoot.querySelector('#chat-box-header');
827
- const searchChatInput = shadowRoot.querySelector('#search-chat-input');
828
- const searchChatIcon = shadowRoot.querySelector('#search-chat-icon');
829
- const chatBoxListContainer = shadowRoot.querySelector('#list-items');
830
- const chatBoxInputInfo = shadowRoot.querySelector('#info-input');
831
- const turnBackButton = shadowRoot.querySelector('#turn-back-button');
832
- const continueButton = shadowRoot.querySelector('#continue-button');
833
- const chatDetails = shadowRoot.querySelector('#chat-details');
834
- const btnX = shadowRoot.querySelector('#btn-x');
835
- const detailsBackButton = shadowRoot.querySelector('#btn-details-back');
836
- const messageContainer = shadowRoot.querySelector('#message-container');
837
- const hsblInput = shadowRoot.querySelector('#input-shsbl');
838
- const userNameInput = shadowRoot.querySelector('#input-username');
839
- const errorInfoInput = shadowRoot.querySelector('#error-info-input');
840
- const btnSendMessage = shadowRoot.querySelector('#btn-send-message');
841
- const textarea = shadowRoot.getElementById('message-input');
842
- const emojiButton = shadowRoot.getElementById('emoji-button');
843
- const messageInput = shadowRoot.getElementById('message-input');
867
+ const infoTitle = shadowRoot.querySelector("#info-input .info-header h4");
868
+ const chatBoxIcon = shadowRoot.querySelector("#chat-icon");
869
+ const chatBoxContainer = shadowRoot.querySelector("#chat-box");
870
+ const chatBoxFilter = shadowRoot.querySelector("#chat-box-header");
871
+ const searchChatInput = shadowRoot.querySelector("#search-chat-input");
872
+ const searchChatIcon = shadowRoot.querySelector("#search-chat-icon");
873
+ const chatBoxListContainer = shadowRoot.querySelector("#list-items");
874
+ const chatBoxInputInfo = shadowRoot.querySelector("#info-input");
875
+ const turnBackButton = shadowRoot.querySelector("#turn-back-button");
876
+ const continueButton = shadowRoot.querySelector("#continue-button");
877
+ const chatDetails = shadowRoot.querySelector("#chat-details");
878
+ const btnX = shadowRoot.querySelector("#btn-x");
879
+ const detailsBackButton = shadowRoot.querySelector("#btn-details-back");
880
+ const messageContainer = shadowRoot.querySelector("#message-container");
881
+ const hsblInput = shadowRoot.querySelector("#input-shsbl");
882
+ const userNameInput = shadowRoot.querySelector("#input-username");
883
+ const errorInfoInput = shadowRoot.querySelector("#error-info-input");
884
+ const btnSendMessage = shadowRoot.querySelector("#btn-send-message");
885
+ const textarea = shadowRoot.getElementById("message-input");
886
+ const emojiButton = shadowRoot.getElementById("emoji-button");
887
+ const messageInput = shadowRoot.getElementById("message-input");
844
888
 
845
889
  userNameInput.value = fullName;
846
890
 
847
891
  // ================== UTILITY FUNCTIONS ==================
848
892
  function parseCustomDateString(str) {
849
893
  // Expected format: dd/MM/yyyy HH:mm:ss
850
- const [date, time] = str.split(' ');
851
- const [day, month, year] = date.split('/');
894
+ const [date, time] = str.split(" ");
895
+ const [day, month, year] = date.split("/");
852
896
  return new Date(`${year}-${month}-${day}T${time}`);
853
897
  }
854
898
 
@@ -865,8 +909,8 @@ class ChatWidget extends HTMLElement {
865
909
  }
866
910
 
867
911
  function autoResize() {
868
- this.style.height = 'auto';
869
- this.style.height = this.scrollHeight + 'px';
912
+ this.style.height = "auto";
913
+ this.style.height = this.scrollHeight + "px";
870
914
  }
871
915
 
872
916
  function scrollToBottom() {
@@ -878,12 +922,14 @@ class ChatWidget extends HTMLElement {
878
922
  // ================== CHAT LIST FUNCTIONS ==================
879
923
  function renderChatList(items, append = false) {
880
924
  const htmlContent = items
881
- .map(item => `
925
+ .map(
926
+ (item) => `
882
927
  <li data-id="${item.MA}">
883
928
  <img class="logo-item" src="${item.LINK_LOGO}" alt="Logo" />
884
929
  <h4 class="title-item">${item.TEN}</h4>
885
930
  </li>
886
- `)
931
+ `
932
+ )
887
933
  .join("");
888
934
 
889
935
  if (append) {
@@ -897,8 +943,12 @@ class ChatWidget extends HTMLElement {
897
943
  const scrollHeight = chatBoxListContainer.scrollHeight;
898
944
  const clientHeight = chatBoxListContainer.clientHeight;
899
945
 
900
- if (scrollHeight <= clientHeight && chatListHasMore && !chatListIsLoading) {
901
- console.log('Danh sách chưa đủ để scroll, load thêm...');
946
+ if (
947
+ scrollHeight <= clientHeight &&
948
+ chatListHasMore &&
949
+ !chatListIsLoading
950
+ ) {
951
+ console.log("Danh sách chưa đủ để scroll, load thêm...");
902
952
  await loadMoreChatList();
903
953
  setTimeout(() => ensureChatListFilled(), 100);
904
954
  }
@@ -909,7 +959,7 @@ class ChatWidget extends HTMLElement {
909
959
  const body = {
910
960
  trang: page,
911
961
  so_dong: PAGE_SIZE,
912
- nd_tim: searchTerm || ""
962
+ nd_tim: searchTerm || "",
913
963
  };
914
964
 
915
965
  const header = {
@@ -918,17 +968,20 @@ class ChatWidget extends HTMLElement {
918
968
  };
919
969
 
920
970
  const response = await fetch(`${BASE_URL}api/chat/execute`, {
921
- method: 'POST',
971
+ method: "POST",
922
972
  headers: header,
923
- body: JSON.stringify(body)
973
+ body: JSON.stringify(body),
924
974
  });
925
975
 
926
- if (!response.ok) throw new Error('Failed to fetch chat list');
976
+ if (!response.ok) throw new Error("Failed to fetch chat list");
927
977
 
928
978
  const data = await response.json();
929
979
 
930
980
  // Check if there are more items
931
- if (data.data.length === 0 || data.data.length >= data.output.tong_so_dong) {
981
+ if (
982
+ data.data.length === 0 ||
983
+ data.data.length >= data.output.tong_so_dong
984
+ ) {
932
985
  chatListHasMore = false;
933
986
  } else {
934
987
  chatListHasMore = true;
@@ -944,9 +997,10 @@ class ChatWidget extends HTMLElement {
944
997
 
945
998
  return data.data.length;
946
999
  } catch (error) {
947
- console.error('Error loading chat list:', error);
1000
+ console.error("Error loading chat list:", error);
948
1001
  if (!append) {
949
- listEl.innerHTML = '<li style="padding: 20px; text-align: center;">Không thể tải danh sách chat</li>';
1002
+ listEl.innerHTML =
1003
+ '<li style="padding: 20px; text-align: center;">Không thể tải danh sách chat</li>';
950
1004
  }
951
1005
  return 0;
952
1006
  }
@@ -961,14 +1015,18 @@ class ChatWidget extends HTMLElement {
961
1015
 
962
1016
  try {
963
1017
  chatListCurrentPage++;
964
- const count = await loadChatList(currentSearchTerm, chatListCurrentPage, true);
1018
+ const count = await loadChatList(
1019
+ currentSearchTerm,
1020
+ chatListCurrentPage,
1021
+ true
1022
+ );
965
1023
 
966
1024
  if (count === 0) {
967
1025
  chatListHasMore = false;
968
- console.log('Đã tải hết danh sách chat');
1026
+ console.log("Đã tải hết danh sách chat");
969
1027
  }
970
1028
  } catch (error) {
971
- console.error('Lỗi khi load thêm chat list:', error);
1029
+ console.error("Lỗi khi load thêm chat list:", error);
972
1030
  chatListCurrentPage--;
973
1031
  } finally {
974
1032
  chatListIsLoading = false;
@@ -976,22 +1034,29 @@ class ChatWidget extends HTMLElement {
976
1034
  }
977
1035
 
978
1036
  function wireChatItemClicks() {
979
- const chatBoxListItem = shadowRoot.querySelectorAll('#list-items li');
1037
+ const chatBoxListItem = shadowRoot.querySelectorAll("#list-items li");
980
1038
  chatBoxListItem.forEach((item) => {
981
- item.addEventListener('click', function () {
1039
+ item.addEventListener("click", function () {
982
1040
  if (chatBoxInputInfo) {
983
1041
  chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
984
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
985
- chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1042
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay
1043
+ ? "none"
1044
+ : "flex";
1045
+ chatBoxFilter.style.display = chatBoxListContainerDisplay
1046
+ ? "none"
1047
+ : "flex";
986
1048
 
987
1049
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
988
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1050
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1051
+ ? "none"
1052
+ : "flex";
989
1053
 
990
1054
  const { id } = item.dataset;
991
- const name = item.querySelector('.title-item')?.textContent?.trim() || '';
1055
+ const name =
1056
+ item.querySelector(".title-item")?.textContent?.trim() || "";
992
1057
 
993
1058
  selectedChat = { id, name };
994
- infoTitle.textContent = name || 'Không tìm thấy cuộc trò chuyện!';
1059
+ infoTitle.textContent = name || "Không tìm thấy cuộc trò chuyện!";
995
1060
  userNameInput.value = fullName;
996
1061
  }
997
1062
  });
@@ -1004,7 +1069,7 @@ class ChatWidget extends HTMLElement {
1004
1069
  const body = {
1005
1070
  trang: page,
1006
1071
  so_dong: PAGE_SIZE,
1007
- nhom: groupName || ""
1072
+ nhom: groupName || "",
1008
1073
  };
1009
1074
 
1010
1075
  const header = {
@@ -1013,23 +1078,23 @@ class ChatWidget extends HTMLElement {
1013
1078
  };
1014
1079
 
1015
1080
  const response = await fetch(`${BASE_URL}api/chat/execute`, {
1016
- method: 'POST',
1081
+ method: "POST",
1017
1082
  headers: header,
1018
- body: JSON.stringify(body)
1083
+ body: JSON.stringify(body),
1019
1084
  });
1020
1085
 
1021
- if (!response.ok) throw new Error('Failed to fetch chat list');
1086
+ if (!response.ok) throw new Error("Failed to fetch chat list");
1022
1087
 
1023
1088
  const data = await response.json();
1024
1089
 
1025
1090
  // Sort messages by NGAY_GUI ascending
1026
- console.log('Loaded messages:', data.data);
1091
+ console.log("Loaded messages:", data.data);
1027
1092
  const sortedMessages = data.data.sort((a, b) => {
1028
1093
  const dateA = parseCustomDateString(a.NGAY_GUI);
1029
1094
  const dateB = parseCustomDateString(b.NGAY_GUI);
1030
1095
  return dateA - dateB;
1031
1096
  });
1032
- console.log('Sorted messages:', sortedMessages);
1097
+ console.log("Sorted messages:", sortedMessages);
1033
1098
 
1034
1099
  return sortedMessages;
1035
1100
  } catch {
@@ -1043,22 +1108,22 @@ class ChatWidget extends HTMLElement {
1043
1108
  for (const msg of messages) {
1044
1109
  const isMe = userId === msg.MA_DOI_TAC_NSD;
1045
1110
  const msgDiv = document.createElement("div");
1046
- msgDiv.classList.add(isMe ? 'me' : 'partner');
1111
+ msgDiv.classList.add(isMe ? "me" : "partner");
1047
1112
  msgDiv.innerHTML = `
1048
1113
  <div class="info">
1049
- ${isMe ?
1050
- `<div class="message-title">
1114
+ ${
1115
+ isMe
1116
+ ? `<div class="message-title">
1051
1117
  <h5>${msg.TEN}</h5>
1052
1118
  <p class="message-time">${msg.NGAY_GUI}</p>
1053
1119
  </div>
1054
1120
  <img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />`
1055
- :
1056
- `<img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />
1121
+ : `<img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />
1057
1122
  <div class="message-title">
1058
1123
  <h5>${msg.TEN}</h5>
1059
1124
  <p class="message-time">${msg.NGAY_GUI}</p>
1060
1125
  </div>`
1061
- }
1126
+ }
1062
1127
  </div>
1063
1128
  <div class="message">
1064
1129
  <p>${msg.NOI_DUNG_TIN_NHAN}</p>
@@ -1086,21 +1151,24 @@ class ChatWidget extends HTMLElement {
1086
1151
 
1087
1152
  try {
1088
1153
  currentPage++;
1089
- const messages = await loadMessageHistory(selectedChat.hsbl, currentPage);
1154
+ const messages = await loadMessageHistory(
1155
+ selectedChat.hsbl,
1156
+ currentPage
1157
+ );
1090
1158
 
1091
1159
  if (messages.length === 0) {
1092
1160
  hasMoreMessages = false;
1093
- console.log('Đã tải hết tin nhắn');
1161
+ console.log("Đã tải hết tin nhắn");
1094
1162
  return;
1095
1163
  }
1096
1164
 
1097
1165
  await appendMessageHistory(messages, true);
1098
1166
 
1099
1167
  const newScrollHeight = messageContainer.scrollHeight;
1100
- messageContainer.scrollTop = newScrollHeight - oldScrollHeight + oldScrollTop;
1101
-
1168
+ messageContainer.scrollTop =
1169
+ newScrollHeight - oldScrollHeight + oldScrollTop;
1102
1170
  } catch (error) {
1103
- console.error('Lỗi khi load thêm tin nhắn:', error);
1171
+ console.error("Lỗi khi load thêm tin nhắn:", error);
1104
1172
  currentPage--;
1105
1173
  } finally {
1106
1174
  isLoading = false;
@@ -1112,17 +1180,17 @@ class ChatWidget extends HTMLElement {
1112
1180
  let isValid = true;
1113
1181
 
1114
1182
  if (!hsblInput.value.trim()) {
1115
- hsblInput.classList.add('error');
1183
+ hsblInput.classList.add("error");
1116
1184
  isValid = false;
1117
1185
  } else {
1118
- hsblInput.classList.remove('error');
1186
+ hsblInput.classList.remove("error");
1119
1187
  }
1120
1188
 
1121
1189
  if (!userNameInput.value.trim()) {
1122
- userNameInput.classList.add('error');
1190
+ userNameInput.classList.add("error");
1123
1191
  isValid = false;
1124
1192
  } else {
1125
- userNameInput.classList.remove('error');
1193
+ userNameInput.classList.remove("error");
1126
1194
  }
1127
1195
 
1128
1196
  return isValid;
@@ -1130,7 +1198,7 @@ class ChatWidget extends HTMLElement {
1130
1198
 
1131
1199
  function validateContractId(contractId) {
1132
1200
  return fetch(`${BASE_URL}api/chat/execute`)
1133
- .then(response => {
1201
+ .then((response) => {
1134
1202
  if (!response.ok) {
1135
1203
  errorInfoInput.textContent = `Mã hợp đồng không tồn tại.`;
1136
1204
  return false;
@@ -1145,7 +1213,7 @@ class ChatWidget extends HTMLElement {
1145
1213
 
1146
1214
  // ================== EVENT LISTENERS ==================
1147
1215
  // Auto resize textarea
1148
- textarea.addEventListener('input', autoResize, false);
1216
+ textarea.addEventListener("input", autoResize, false);
1149
1217
 
1150
1218
  // Scroll handlers
1151
1219
  const handleScroll = debounce(function () {
@@ -1154,64 +1222,71 @@ class ChatWidget extends HTMLElement {
1154
1222
  }
1155
1223
  }, DEBOUNCE_DELAY);
1156
1224
 
1157
- messageContainer.addEventListener('scroll', handleScroll);
1225
+ messageContainer.addEventListener("scroll", handleScroll);
1158
1226
 
1159
1227
  const handleChatListScroll = debounce(function () {
1160
- console.log('Scroll chat list:', chatBoxListContainer.scrollTop);
1228
+ console.log("Scroll chat list:", chatBoxListContainer.scrollTop);
1161
1229
  const scrollTop = chatBoxListContainer.scrollTop;
1162
1230
  const scrollHeight = chatBoxListContainer.scrollHeight;
1163
1231
  const clientHeight = chatBoxListContainer.clientHeight;
1164
1232
 
1165
- if (scrollTop + clientHeight >= scrollHeight - SCROLL_LOAD_MORE_THRESHOLD) {
1233
+ if (
1234
+ scrollTop + clientHeight >=
1235
+ scrollHeight - SCROLL_LOAD_MORE_THRESHOLD
1236
+ ) {
1166
1237
  loadMoreChatList();
1167
1238
  }
1168
1239
  }, DEBOUNCE_DELAY);
1169
1240
 
1170
- chatBoxListContainer.addEventListener('scroll', handleChatListScroll);
1241
+ chatBoxListContainer.addEventListener("scroll", handleChatListScroll);
1171
1242
 
1172
1243
  // Input validation listeners
1173
- hsblInput.addEventListener('input', function () {
1244
+ hsblInput.addEventListener("input", function () {
1174
1245
  if (this.value.trim()) {
1175
- this.classList.remove('error');
1246
+ this.classList.remove("error");
1176
1247
  }
1177
1248
  });
1178
1249
 
1179
- userNameInput.addEventListener('input', function () {
1250
+ userNameInput.addEventListener("input", function () {
1180
1251
  if (this.value.trim()) {
1181
- this.classList.remove('error');
1252
+ this.classList.remove("error");
1182
1253
  }
1183
1254
  });
1184
1255
 
1185
1256
  // Display initialization
1186
- chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1187
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1188
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1189
- chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1257
+ chatBoxContainer.style.display = chatBoxContainerDisplay ? "none" : "flex";
1258
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay
1259
+ ? "none"
1260
+ : "flex";
1261
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? "none" : "flex";
1262
+ chatDetails.style.display = chatDetailsDisplay ? "none" : "flex";
1190
1263
 
1191
1264
  // Chat box icon click
1192
- chatBoxIcon.addEventListener('click', function () {
1265
+ chatBoxIcon.addEventListener("click", function () {
1193
1266
  if (chatBoxContainer) {
1194
1267
  chatListCurrentPage = 1;
1195
1268
  chatListIsLoading = false;
1196
1269
  currentSearchTerm = "";
1197
- searchChatInput.value = '';
1270
+ searchChatInput.value = "";
1198
1271
 
1199
1272
  loadChatList();
1200
1273
  chatBoxContainerDisplay = !chatBoxContainerDisplay;
1201
- chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1274
+ chatBoxContainer.style.display = chatBoxContainerDisplay
1275
+ ? "none"
1276
+ : "flex";
1202
1277
  }
1203
1278
  });
1204
1279
 
1205
1280
  // Close button
1206
- btnX.addEventListener('click', function () {
1281
+ btnX.addEventListener("click", function () {
1207
1282
  if (chatBoxContainer) {
1208
1283
  chatBoxContainerDisplay = true;
1209
- chatBoxContainer.style.display = 'none';
1284
+ chatBoxContainer.style.display = "none";
1210
1285
  }
1211
1286
  });
1212
1287
 
1213
1288
  // Search handlers
1214
- searchChatIcon.addEventListener('click', function () {
1289
+ searchChatIcon.addEventListener("click", function () {
1215
1290
  const searchTerm = searchChatInput.value.trim();
1216
1291
  currentSearchTerm = searchTerm;
1217
1292
 
@@ -1221,8 +1296,8 @@ class ChatWidget extends HTMLElement {
1221
1296
  loadChatList(currentSearchTerm);
1222
1297
  });
1223
1298
 
1224
- searchChatInput.addEventListener('keypress', function (event) {
1225
- if (event.key === 'Enter') {
1299
+ searchChatInput.addEventListener("keypress", function (event) {
1300
+ if (event.key === "Enter") {
1226
1301
  const searchTerm = searchChatInput.value.trim();
1227
1302
  currentSearchTerm = searchTerm;
1228
1303
 
@@ -1234,24 +1309,30 @@ class ChatWidget extends HTMLElement {
1234
1309
  });
1235
1310
 
1236
1311
  // Turn back button
1237
- turnBackButton.addEventListener('click', function () {
1312
+ turnBackButton.addEventListener("click", function () {
1238
1313
  if (chatBoxInputInfo) {
1239
1314
  chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
1240
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1241
- chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1315
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay
1316
+ ? "none"
1317
+ : "flex";
1318
+ chatBoxFilter.style.display = chatBoxListContainerDisplay
1319
+ ? "none"
1320
+ : "flex";
1242
1321
 
1243
1322
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1244
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1245
-
1246
- userNameInput.classList.remove('error');
1247
- hsblInput.classList.remove('error');
1248
- errorInfoInput.textContent = '';
1249
- hsblInput.value = '';
1323
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1324
+ ? "none"
1325
+ : "flex";
1326
+
1327
+ userNameInput.classList.remove("error");
1328
+ hsblInput.classList.remove("error");
1329
+ errorInfoInput.textContent = "";
1330
+ hsblInput.value = "";
1250
1331
  }
1251
1332
  });
1252
1333
 
1253
1334
  // Continue button
1254
- continueButton.addEventListener('click', async function () {
1335
+ continueButton.addEventListener("click", async function () {
1255
1336
  if (chatDetails) {
1256
1337
  if (!validateInfoInputs()) {
1257
1338
  return;
@@ -1262,59 +1343,74 @@ class ChatWidget extends HTMLElement {
1262
1343
  selectedChat.userName = userNameInput.value.trim();
1263
1344
  }
1264
1345
 
1265
- errorInfoInput.textContent = '';
1346
+ errorInfoInput.textContent = "";
1266
1347
 
1267
1348
  // Join group
1268
- connection.invoke("JoinGroupAsync", selectedChat.hsbl).then(() => {
1269
- console.log(`Đã join nhóm: ${selectedChat.hsbl}`);
1270
- }).catch(err => console.error(err.toString()));
1349
+ connection
1350
+ .invoke("JoinGroupAsync", selectedChat.hsbl)
1351
+ .then(() => {
1352
+ console.log(`Đã join nhóm: ${selectedChat.hsbl}`);
1353
+ })
1354
+ .catch((err) => console.error(err.toString()));
1271
1355
 
1272
1356
  var messages = await loadMessageHistory(selectedChat.hsbl, 1);
1273
1357
  await appendMessageHistory(messages);
1274
1358
 
1275
1359
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1276
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1360
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1361
+ ? "none"
1362
+ : "flex";
1277
1363
 
1278
1364
  chatDetailsDisplay = !chatDetailsDisplay;
1279
- chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1365
+ chatDetails.style.display = chatDetailsDisplay ? "none" : "flex";
1280
1366
 
1281
1367
  setTimeout(scrollToBottom, 0);
1282
1368
  }
1283
1369
  });
1284
1370
 
1285
1371
  // Details back button
1286
- detailsBackButton.addEventListener('click', function () {
1372
+ detailsBackButton.addEventListener("click", function () {
1287
1373
  if (chatDetails) {
1288
1374
  // Leave group
1289
- connection.invoke("RemoveFromGroupAsync", selectedChat.hsbl).then(() => {
1290
- console.log(`Đã rời nhóm: ${selectedChat.hsbl}`);
1291
- }).catch(err => console.error(err.toString()));
1375
+ connection
1376
+ .invoke("RemoveFromGroupAsync", selectedChat.hsbl)
1377
+ .then(() => {
1378
+ console.log(`Đã rời nhóm: ${selectedChat.hsbl}`);
1379
+ })
1380
+ .catch((err) => console.error(err.toString()));
1292
1381
 
1293
1382
  // Reset state before go back
1294
1383
  currentPage = 1;
1295
1384
  hasMoreMessages = true;
1296
1385
  isLoading = false;
1297
- messageContainer.innerHTML = '';
1386
+ messageContainer.innerHTML = "";
1298
1387
 
1299
1388
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1300
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1389
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1390
+ ? "none"
1391
+ : "flex";
1301
1392
 
1302
1393
  chatDetailsDisplay = !chatDetailsDisplay;
1303
- chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1394
+ chatDetails.style.display = chatDetailsDisplay ? "none" : "flex";
1304
1395
  }
1305
1396
  });
1306
1397
 
1307
1398
  // Send message button
1308
- btnSendMessage.addEventListener('click', async function () {
1309
- const message = shadowRoot.getElementById('message-input').value;
1310
- console.log('Gửi tin nhắn:', message);
1399
+ btnSendMessage.addEventListener("click", async function () {
1400
+ const message = shadowRoot.getElementById("message-input").value;
1401
+ console.log("Gửi tin nhắn:", message);
1311
1402
  if (message) {
1312
1403
  try {
1313
- await connection.invoke("SendMessageToGroupAsync", selectedChat.hsbl, message, userNameInput.value.trim());
1404
+ await connection.invoke(
1405
+ "SendMessageToGroupAsync",
1406
+ selectedChat.hsbl,
1407
+ message,
1408
+ userNameInput.value.trim()
1409
+ );
1314
1410
  shadowRoot.getElementById("message-input").value = "";
1315
1411
 
1316
- const textarea = shadowRoot.getElementById('message-input');
1317
- textarea.style.height = 'auto';
1412
+ const textarea = shadowRoot.getElementById("message-input");
1413
+ textarea.style.height = "auto";
1318
1414
  } catch (err) {
1319
1415
  console.error("Lỗi gửi tin nhắn:", err);
1320
1416
  }
@@ -1362,34 +1458,37 @@ class ChatWidget extends HTMLElement {
1362
1458
 
1363
1459
  // ================== SIGNALR CONNECTION ==================
1364
1460
  const connection = new signalR.HubConnectionBuilder()
1365
- .withUrl(`${BASE_URL}chatHub?token=${token}&ePartnerCode=${ePartnerCode}`, {
1366
- withCredentials: false,
1367
- })
1461
+ .withUrl(
1462
+ `${BASE_URL}chatHub?token=${token}&ePartnerCode=${ePartnerCode}`,
1463
+ {
1464
+ withCredentials: false,
1465
+ }
1466
+ )
1368
1467
  .configureLogging(signalR.LogLevel.Information)
1369
1468
  .withAutomaticReconnect()
1370
1469
  .build();
1371
1470
 
1372
1471
  // Nhận tin nhắn từ server
1373
1472
  connection.on("ReceiveGroupMessage", (data) => {
1374
- console.log('Tin nhắn nhận được:', data);
1473
+ console.log("Tin nhắn nhận được:", data);
1375
1474
  const isMe = userId === data.ma_doi_tac_nsd;
1376
1475
  const msg = document.createElement("div");
1377
- msg.classList.add(isMe ? 'me' : 'partner');
1476
+ msg.classList.add(isMe ? "me" : "partner");
1378
1477
  msg.innerHTML = `
1379
1478
  <div class="info">
1380
- ${isMe ?
1381
- `<div class="message-title">
1479
+ ${
1480
+ isMe
1481
+ ? `<div class="message-title">
1382
1482
  <h5>${data.ten}</h5>
1383
1483
  <p class="message-time">${data.ngay_gui}</p>
1384
1484
  </div>
1385
1485
  <img class="logo-item" src="${data.link_logo}" alt="Logo" />`
1386
- :
1387
- `<img class="logo-item" src="${data.link_logo}" alt="Logo" />
1486
+ : `<img class="logo-item" src="${data.link_logo}" alt="Logo" />
1388
1487
  <div class="message-title">
1389
1488
  <h5>${data.ten}</h5>
1390
1489
  <p class="message-time">${data.ngay_gui}</p>
1391
1490
  </div>`
1392
- }
1491
+ }
1393
1492
  </div>
1394
1493
  <div class="message">
1395
1494
  <p>${data.noi_dung_tin_nhan}</p>
@@ -1410,7 +1509,7 @@ class ChatWidget extends HTMLElement {
1410
1509
  }
1411
1510
 
1412
1511
  async initSignalR() {
1413
- const signalR = window.signalR || await this.loadSignalR();
1512
+ const signalR = window.signalR || (await this.loadSignalR());
1414
1513
 
1415
1514
  this.connection = new signalR.HubConnectionBuilder()
1416
1515
  .withUrl(`${BASE_URL}chatHub`, { withCredentials: false })
@@ -1432,8 +1531,9 @@ class ChatWidget extends HTMLElement {
1432
1531
  return new Promise((resolve, reject) => {
1433
1532
  if (typeof signalR !== "undefined") return resolve();
1434
1533
 
1435
- const script = document.createElement('script');
1436
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js";
1534
+ const script = document.createElement("script");
1535
+ script.src =
1536
+ "https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js";
1437
1537
  script.onload = () => {
1438
1538
  console.log("SignalR đã tải xong.");
1439
1539
  resolve();
@@ -1445,45 +1545,49 @@ class ChatWidget extends HTMLElement {
1445
1545
 
1446
1546
  setupEmojiPickerAfterLoad() {
1447
1547
  const shadow = this.shadowRoot;
1448
- const messageSending = shadow.querySelector('.message-sending');
1449
- const emojiButton = shadow.querySelector('#emoji-button');
1450
- const messageInput = shadow.querySelector('#message-input');
1548
+ const messageSending = shadow.querySelector(".message-sending");
1549
+ const emojiButton = shadow.querySelector("#emoji-button");
1550
+ const messageInput = shadow.querySelector("#message-input");
1451
1551
 
1452
1552
  if (!messageSending || !emojiButton || !messageInput) return;
1453
1553
 
1454
1554
  // Tạo emoji-picker element
1455
- let picker = messageSending.querySelector('emoji-picker');
1555
+ let picker = messageSending.querySelector("emoji-picker");
1456
1556
  if (!picker) {
1457
- picker = document.createElement('emoji-picker');
1557
+ picker = document.createElement("emoji-picker");
1458
1558
  messageSending.appendChild(picker);
1459
1559
  }
1460
1560
 
1461
1561
  // Xử lý chọn emoji
1462
- picker.addEventListener('emoji-click', (event) => {
1562
+ picker.addEventListener("emoji-click", (event) => {
1463
1563
  const emoji = event.detail.unicode;
1464
1564
  const start = messageInput.selectionStart;
1465
1565
  const end = messageInput.selectionEnd;
1466
- messageInput.value = messageInput.value.substring(0, start) + emoji + messageInput.value.substring(end);
1566
+ messageInput.value =
1567
+ messageInput.value.substring(0, start) +
1568
+ emoji +
1569
+ messageInput.value.substring(end);
1467
1570
  const newPos = start + emoji.length;
1468
1571
  messageInput.setSelectionRange(newPos, newPos);
1469
1572
  messageInput.focus();
1470
- picker.style.display = 'none';
1471
- messageInput.dispatchEvent(new Event('input')); // resize textarea
1573
+ picker.style.display = "none";
1574
+ messageInput.dispatchEvent(new Event("input")); // resize textarea
1472
1575
  });
1473
1576
 
1474
1577
  // Toggle picker
1475
- emojiButton.addEventListener('click', (e) => {
1578
+ emojiButton.addEventListener("click", (e) => {
1476
1579
  e.stopPropagation();
1477
- picker.style.display = picker.style.display === 'block' ? 'none' : 'block';
1580
+ picker.style.display =
1581
+ picker.style.display === "block" ? "none" : "block";
1478
1582
  });
1479
1583
 
1480
1584
  // Đóng khi click ngoài
1481
- document.addEventListener('click', (e) => {
1585
+ document.addEventListener("click", (e) => {
1482
1586
  if (!picker.contains(e.target) && e.target !== emojiButton) {
1483
- picker.style.display = 'none';
1587
+ picker.style.display = "none";
1484
1588
  }
1485
1589
  });
1486
1590
  }
1487
1591
  }
1488
1592
 
1489
- customElements.define('chat-widget', ChatWidget);
1593
+ customElements.define("chat-widget", ChatWidget);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhnd1010/chat-widget",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "Chat widget hỗ trợ SignalR real-time cho website",
5
5
  "main": "index.js",
6
6
  "type": "module",