@minhnd1010/chat-widget 1.0.2 → 1.0.5

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 +281 -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
  * {
@@ -462,7 +467,7 @@ class ChatWidget extends HTMLElement {
462
467
  align-items: center;
463
468
  background-color: #e0e0e0ff;
464
469
  border-radius: 50px;
465
- padding: 10px 20px;
470
+ padding: 8px 20px;
466
471
  overflow-y: hidden;
467
472
  }
468
473
 
@@ -504,15 +509,38 @@ class ChatWidget extends HTMLElement {
504
509
  background-color: #4b4bda;
505
510
  }
506
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
+
507
536
  /* ================================================================================
508
537
  EMOJI PICKER STYLING
509
538
  ================================================================================ */
510
539
  #emoji-button {
511
540
  background: none;
512
541
  border: none;
513
- font-size: 24px;
542
+ font-size: 18px;
514
543
  cursor: pointer;
515
- padding: 8px;
516
544
  border-radius: 50%;
517
545
  display: flex;
518
546
  align-items: center;
@@ -523,6 +551,12 @@ class ChatWidget extends HTMLElement {
523
551
  background-color: rgba(0, 0, 0, 0.1);
524
552
  }
525
553
 
554
+ #emoji-button svg {
555
+ width: 25px;
556
+ height: 25px;
557
+ fill: #5b5bdfff;
558
+ }
559
+
526
560
  emoji-picker {
527
561
  position: absolute;
528
562
  bottom: 70px;
@@ -755,6 +789,13 @@ class ChatWidget extends HTMLElement {
755
789
 
756
790
  <!-- Message input -->
757
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>
758
799
  <div class="message-content">
759
800
  <textarea
760
801
  id="message-input"
@@ -762,7 +803,13 @@ class ChatWidget extends HTMLElement {
762
803
  placeholder="Nhập tin nhắn..."
763
804
  ></textarea>
764
805
  </div>
765
- <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>
766
813
  <button id="btn-send-message">
767
814
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
768
815
  <path
@@ -784,24 +831,20 @@ class ChatWidget extends HTMLElement {
784
831
  const shadowRoot = this.shadowRoot;
785
832
 
786
833
  // ================== CONFIGURATION ==================
787
- const BASE_URL = this.getAttribute('base-url');
834
+ const BASE_URL = this.getAttribute("base-url");
788
835
  const PAGE_SIZE = 10;
789
836
  const SCROLL_THRESHOLD = 20;
790
837
  const SCROLL_LOAD_MORE_THRESHOLD = 100;
791
838
  const DEBOUNCE_DELAY = 200;
792
839
 
793
840
  // ================== USER INPUT ==================
794
- let token = prompt("Nhập access token của bạn:", "");
795
- if (!token) token = "Anonymous";
841
+ let token = this.getAttribute("token");
796
842
 
797
- let userId = prompt("Nhập mã của bạn:", "");
798
- if (!userId) userId = "Anonymous";
843
+ let userId = this.getAttribute("ma-doi-tac-nsd");
799
844
 
800
- let fullName = prompt("Nhập tên đầy đủ của bạn:");
801
- if (!fullName) fullName = "Khách hàng";
845
+ let fullName = this.getAttribute("ten");
802
846
 
803
- let ePartnerCode = prompt("Nhập ePartnerCode của bạn:", "BENH_VIEN");
804
- if (!ePartnerCode) ePartnerCode = "PORTAL";
847
+ let ePartnerCode = this.getAttribute("e-partner-code");
805
848
 
806
849
  // ================== STATE MANAGEMENT ==================
807
850
  let selectedChat = null;
@@ -821,35 +864,35 @@ class ChatWidget extends HTMLElement {
821
864
 
822
865
  // ================== DOM ELEMENTS ==================
823
866
  const listEl = shadowRoot.querySelector("#list-items ul");
824
- const infoTitle = shadowRoot.querySelector('#info-input .info-header h4');
825
- const chatBoxIcon = shadowRoot.querySelector('#chat-icon');
826
- const chatBoxContainer = shadowRoot.querySelector('#chat-box');
827
- const chatBoxFilter = shadowRoot.querySelector('#chat-box-header');
828
- const searchChatInput = shadowRoot.querySelector('#search-chat-input');
829
- const searchChatIcon = shadowRoot.querySelector('#search-chat-icon');
830
- const chatBoxListContainer = shadowRoot.querySelector('#list-items');
831
- const chatBoxInputInfo = shadowRoot.querySelector('#info-input');
832
- const turnBackButton = shadowRoot.querySelector('#turn-back-button');
833
- const continueButton = shadowRoot.querySelector('#continue-button');
834
- const chatDetails = shadowRoot.querySelector('#chat-details');
835
- const btnX = shadowRoot.querySelector('#btn-x');
836
- const detailsBackButton = shadowRoot.querySelector('#btn-details-back');
837
- const messageContainer = shadowRoot.querySelector('#message-container');
838
- const hsblInput = shadowRoot.querySelector('#input-shsbl');
839
- const userNameInput = shadowRoot.querySelector('#input-username');
840
- const errorInfoInput = shadowRoot.querySelector('#error-info-input');
841
- const btnSendMessage = shadowRoot.querySelector('#btn-send-message');
842
- const textarea = shadowRoot.getElementById('message-input');
843
- const emojiButton = shadowRoot.getElementById('emoji-button');
844
- 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");
845
888
 
846
889
  userNameInput.value = fullName;
847
890
 
848
891
  // ================== UTILITY FUNCTIONS ==================
849
892
  function parseCustomDateString(str) {
850
893
  // Expected format: dd/MM/yyyy HH:mm:ss
851
- const [date, time] = str.split(' ');
852
- const [day, month, year] = date.split('/');
894
+ const [date, time] = str.split(" ");
895
+ const [day, month, year] = date.split("/");
853
896
  return new Date(`${year}-${month}-${day}T${time}`);
854
897
  }
855
898
 
@@ -866,8 +909,8 @@ class ChatWidget extends HTMLElement {
866
909
  }
867
910
 
868
911
  function autoResize() {
869
- this.style.height = 'auto';
870
- this.style.height = this.scrollHeight + 'px';
912
+ this.style.height = "auto";
913
+ this.style.height = this.scrollHeight + "px";
871
914
  }
872
915
 
873
916
  function scrollToBottom() {
@@ -879,12 +922,14 @@ class ChatWidget extends HTMLElement {
879
922
  // ================== CHAT LIST FUNCTIONS ==================
880
923
  function renderChatList(items, append = false) {
881
924
  const htmlContent = items
882
- .map(item => `
925
+ .map(
926
+ (item) => `
883
927
  <li data-id="${item.MA}">
884
928
  <img class="logo-item" src="${item.LINK_LOGO}" alt="Logo" />
885
929
  <h4 class="title-item">${item.TEN}</h4>
886
930
  </li>
887
- `)
931
+ `
932
+ )
888
933
  .join("");
889
934
 
890
935
  if (append) {
@@ -898,8 +943,12 @@ class ChatWidget extends HTMLElement {
898
943
  const scrollHeight = chatBoxListContainer.scrollHeight;
899
944
  const clientHeight = chatBoxListContainer.clientHeight;
900
945
 
901
- if (scrollHeight <= clientHeight && chatListHasMore && !chatListIsLoading) {
902
- 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...");
903
952
  await loadMoreChatList();
904
953
  setTimeout(() => ensureChatListFilled(), 100);
905
954
  }
@@ -910,7 +959,7 @@ class ChatWidget extends HTMLElement {
910
959
  const body = {
911
960
  trang: page,
912
961
  so_dong: PAGE_SIZE,
913
- nd_tim: searchTerm || ""
962
+ nd_tim: searchTerm || "",
914
963
  };
915
964
 
916
965
  const header = {
@@ -919,17 +968,20 @@ class ChatWidget extends HTMLElement {
919
968
  };
920
969
 
921
970
  const response = await fetch(`${BASE_URL}api/chat/execute`, {
922
- method: 'POST',
971
+ method: "POST",
923
972
  headers: header,
924
- body: JSON.stringify(body)
973
+ body: JSON.stringify(body),
925
974
  });
926
975
 
927
- if (!response.ok) throw new Error('Failed to fetch chat list');
976
+ if (!response.ok) throw new Error("Failed to fetch chat list");
928
977
 
929
978
  const data = await response.json();
930
979
 
931
980
  // Check if there are more items
932
- 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
+ ) {
933
985
  chatListHasMore = false;
934
986
  } else {
935
987
  chatListHasMore = true;
@@ -945,9 +997,10 @@ class ChatWidget extends HTMLElement {
945
997
 
946
998
  return data.data.length;
947
999
  } catch (error) {
948
- console.error('Error loading chat list:', error);
1000
+ console.error("Error loading chat list:", error);
949
1001
  if (!append) {
950
- 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>';
951
1004
  }
952
1005
  return 0;
953
1006
  }
@@ -962,14 +1015,18 @@ class ChatWidget extends HTMLElement {
962
1015
 
963
1016
  try {
964
1017
  chatListCurrentPage++;
965
- const count = await loadChatList(currentSearchTerm, chatListCurrentPage, true);
1018
+ const count = await loadChatList(
1019
+ currentSearchTerm,
1020
+ chatListCurrentPage,
1021
+ true
1022
+ );
966
1023
 
967
1024
  if (count === 0) {
968
1025
  chatListHasMore = false;
969
- console.log('Đã tải hết danh sách chat');
1026
+ console.log("Đã tải hết danh sách chat");
970
1027
  }
971
1028
  } catch (error) {
972
- console.error('Lỗi khi load thêm chat list:', error);
1029
+ console.error("Lỗi khi load thêm chat list:", error);
973
1030
  chatListCurrentPage--;
974
1031
  } finally {
975
1032
  chatListIsLoading = false;
@@ -977,22 +1034,29 @@ class ChatWidget extends HTMLElement {
977
1034
  }
978
1035
 
979
1036
  function wireChatItemClicks() {
980
- const chatBoxListItem = shadowRoot.querySelectorAll('#list-items li');
1037
+ const chatBoxListItem = shadowRoot.querySelectorAll("#list-items li");
981
1038
  chatBoxListItem.forEach((item) => {
982
- item.addEventListener('click', function () {
1039
+ item.addEventListener("click", function () {
983
1040
  if (chatBoxInputInfo) {
984
1041
  chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
985
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
986
- chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1042
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay
1043
+ ? "none"
1044
+ : "flex";
1045
+ chatBoxFilter.style.display = chatBoxListContainerDisplay
1046
+ ? "none"
1047
+ : "flex";
987
1048
 
988
1049
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
989
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1050
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1051
+ ? "none"
1052
+ : "flex";
990
1053
 
991
1054
  const { id } = item.dataset;
992
- const name = item.querySelector('.title-item')?.textContent?.trim() || '';
1055
+ const name =
1056
+ item.querySelector(".title-item")?.textContent?.trim() || "";
993
1057
 
994
1058
  selectedChat = { id, name };
995
- 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!";
996
1060
  userNameInput.value = fullName;
997
1061
  }
998
1062
  });
@@ -1005,7 +1069,7 @@ class ChatWidget extends HTMLElement {
1005
1069
  const body = {
1006
1070
  trang: page,
1007
1071
  so_dong: PAGE_SIZE,
1008
- nhom: groupName || ""
1072
+ nhom: groupName || "",
1009
1073
  };
1010
1074
 
1011
1075
  const header = {
@@ -1014,23 +1078,23 @@ class ChatWidget extends HTMLElement {
1014
1078
  };
1015
1079
 
1016
1080
  const response = await fetch(`${BASE_URL}api/chat/execute`, {
1017
- method: 'POST',
1081
+ method: "POST",
1018
1082
  headers: header,
1019
- body: JSON.stringify(body)
1083
+ body: JSON.stringify(body),
1020
1084
  });
1021
1085
 
1022
- if (!response.ok) throw new Error('Failed to fetch chat list');
1086
+ if (!response.ok) throw new Error("Failed to fetch chat list");
1023
1087
 
1024
1088
  const data = await response.json();
1025
1089
 
1026
1090
  // Sort messages by NGAY_GUI ascending
1027
- console.log('Loaded messages:', data.data);
1091
+ console.log("Loaded messages:", data.data);
1028
1092
  const sortedMessages = data.data.sort((a, b) => {
1029
1093
  const dateA = parseCustomDateString(a.NGAY_GUI);
1030
1094
  const dateB = parseCustomDateString(b.NGAY_GUI);
1031
1095
  return dateA - dateB;
1032
1096
  });
1033
- console.log('Sorted messages:', sortedMessages);
1097
+ console.log("Sorted messages:", sortedMessages);
1034
1098
 
1035
1099
  return sortedMessages;
1036
1100
  } catch {
@@ -1044,22 +1108,22 @@ class ChatWidget extends HTMLElement {
1044
1108
  for (const msg of messages) {
1045
1109
  const isMe = userId === msg.MA_DOI_TAC_NSD;
1046
1110
  const msgDiv = document.createElement("div");
1047
- msgDiv.classList.add(isMe ? 'me' : 'partner');
1111
+ msgDiv.classList.add(isMe ? "me" : "partner");
1048
1112
  msgDiv.innerHTML = `
1049
1113
  <div class="info">
1050
- ${isMe ?
1051
- `<div class="message-title">
1114
+ ${
1115
+ isMe
1116
+ ? `<div class="message-title">
1052
1117
  <h5>${msg.TEN}</h5>
1053
1118
  <p class="message-time">${msg.NGAY_GUI}</p>
1054
1119
  </div>
1055
1120
  <img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />`
1056
- :
1057
- `<img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />
1121
+ : `<img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />
1058
1122
  <div class="message-title">
1059
1123
  <h5>${msg.TEN}</h5>
1060
1124
  <p class="message-time">${msg.NGAY_GUI}</p>
1061
1125
  </div>`
1062
- }
1126
+ }
1063
1127
  </div>
1064
1128
  <div class="message">
1065
1129
  <p>${msg.NOI_DUNG_TIN_NHAN}</p>
@@ -1087,21 +1151,24 @@ class ChatWidget extends HTMLElement {
1087
1151
 
1088
1152
  try {
1089
1153
  currentPage++;
1090
- const messages = await loadMessageHistory(selectedChat.hsbl, currentPage);
1154
+ const messages = await loadMessageHistory(
1155
+ selectedChat.hsbl,
1156
+ currentPage
1157
+ );
1091
1158
 
1092
1159
  if (messages.length === 0) {
1093
1160
  hasMoreMessages = false;
1094
- console.log('Đã tải hết tin nhắn');
1161
+ console.log("Đã tải hết tin nhắn");
1095
1162
  return;
1096
1163
  }
1097
1164
 
1098
1165
  await appendMessageHistory(messages, true);
1099
1166
 
1100
1167
  const newScrollHeight = messageContainer.scrollHeight;
1101
- messageContainer.scrollTop = newScrollHeight - oldScrollHeight + oldScrollTop;
1102
-
1168
+ messageContainer.scrollTop =
1169
+ newScrollHeight - oldScrollHeight + oldScrollTop;
1103
1170
  } catch (error) {
1104
- 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);
1105
1172
  currentPage--;
1106
1173
  } finally {
1107
1174
  isLoading = false;
@@ -1113,17 +1180,17 @@ class ChatWidget extends HTMLElement {
1113
1180
  let isValid = true;
1114
1181
 
1115
1182
  if (!hsblInput.value.trim()) {
1116
- hsblInput.classList.add('error');
1183
+ hsblInput.classList.add("error");
1117
1184
  isValid = false;
1118
1185
  } else {
1119
- hsblInput.classList.remove('error');
1186
+ hsblInput.classList.remove("error");
1120
1187
  }
1121
1188
 
1122
1189
  if (!userNameInput.value.trim()) {
1123
- userNameInput.classList.add('error');
1190
+ userNameInput.classList.add("error");
1124
1191
  isValid = false;
1125
1192
  } else {
1126
- userNameInput.classList.remove('error');
1193
+ userNameInput.classList.remove("error");
1127
1194
  }
1128
1195
 
1129
1196
  return isValid;
@@ -1131,7 +1198,7 @@ class ChatWidget extends HTMLElement {
1131
1198
 
1132
1199
  function validateContractId(contractId) {
1133
1200
  return fetch(`${BASE_URL}api/chat/execute`)
1134
- .then(response => {
1201
+ .then((response) => {
1135
1202
  if (!response.ok) {
1136
1203
  errorInfoInput.textContent = `Mã hợp đồng không tồn tại.`;
1137
1204
  return false;
@@ -1146,7 +1213,7 @@ class ChatWidget extends HTMLElement {
1146
1213
 
1147
1214
  // ================== EVENT LISTENERS ==================
1148
1215
  // Auto resize textarea
1149
- textarea.addEventListener('input', autoResize, false);
1216
+ textarea.addEventListener("input", autoResize, false);
1150
1217
 
1151
1218
  // Scroll handlers
1152
1219
  const handleScroll = debounce(function () {
@@ -1155,64 +1222,71 @@ class ChatWidget extends HTMLElement {
1155
1222
  }
1156
1223
  }, DEBOUNCE_DELAY);
1157
1224
 
1158
- messageContainer.addEventListener('scroll', handleScroll);
1225
+ messageContainer.addEventListener("scroll", handleScroll);
1159
1226
 
1160
1227
  const handleChatListScroll = debounce(function () {
1161
- console.log('Scroll chat list:', chatBoxListContainer.scrollTop);
1228
+ console.log("Scroll chat list:", chatBoxListContainer.scrollTop);
1162
1229
  const scrollTop = chatBoxListContainer.scrollTop;
1163
1230
  const scrollHeight = chatBoxListContainer.scrollHeight;
1164
1231
  const clientHeight = chatBoxListContainer.clientHeight;
1165
1232
 
1166
- if (scrollTop + clientHeight >= scrollHeight - SCROLL_LOAD_MORE_THRESHOLD) {
1233
+ if (
1234
+ scrollTop + clientHeight >=
1235
+ scrollHeight - SCROLL_LOAD_MORE_THRESHOLD
1236
+ ) {
1167
1237
  loadMoreChatList();
1168
1238
  }
1169
1239
  }, DEBOUNCE_DELAY);
1170
1240
 
1171
- chatBoxListContainer.addEventListener('scroll', handleChatListScroll);
1241
+ chatBoxListContainer.addEventListener("scroll", handleChatListScroll);
1172
1242
 
1173
1243
  // Input validation listeners
1174
- hsblInput.addEventListener('input', function () {
1244
+ hsblInput.addEventListener("input", function () {
1175
1245
  if (this.value.trim()) {
1176
- this.classList.remove('error');
1246
+ this.classList.remove("error");
1177
1247
  }
1178
1248
  });
1179
1249
 
1180
- userNameInput.addEventListener('input', function () {
1250
+ userNameInput.addEventListener("input", function () {
1181
1251
  if (this.value.trim()) {
1182
- this.classList.remove('error');
1252
+ this.classList.remove("error");
1183
1253
  }
1184
1254
  });
1185
1255
 
1186
1256
  // Display initialization
1187
- chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1188
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1189
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1190
- 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";
1191
1263
 
1192
1264
  // Chat box icon click
1193
- chatBoxIcon.addEventListener('click', function () {
1265
+ chatBoxIcon.addEventListener("click", function () {
1194
1266
  if (chatBoxContainer) {
1195
1267
  chatListCurrentPage = 1;
1196
1268
  chatListIsLoading = false;
1197
1269
  currentSearchTerm = "";
1198
- searchChatInput.value = '';
1270
+ searchChatInput.value = "";
1199
1271
 
1200
1272
  loadChatList();
1201
1273
  chatBoxContainerDisplay = !chatBoxContainerDisplay;
1202
- chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1274
+ chatBoxContainer.style.display = chatBoxContainerDisplay
1275
+ ? "none"
1276
+ : "flex";
1203
1277
  }
1204
1278
  });
1205
1279
 
1206
1280
  // Close button
1207
- btnX.addEventListener('click', function () {
1281
+ btnX.addEventListener("click", function () {
1208
1282
  if (chatBoxContainer) {
1209
1283
  chatBoxContainerDisplay = true;
1210
- chatBoxContainer.style.display = 'none';
1284
+ chatBoxContainer.style.display = "none";
1211
1285
  }
1212
1286
  });
1213
1287
 
1214
1288
  // Search handlers
1215
- searchChatIcon.addEventListener('click', function () {
1289
+ searchChatIcon.addEventListener("click", function () {
1216
1290
  const searchTerm = searchChatInput.value.trim();
1217
1291
  currentSearchTerm = searchTerm;
1218
1292
 
@@ -1222,8 +1296,8 @@ class ChatWidget extends HTMLElement {
1222
1296
  loadChatList(currentSearchTerm);
1223
1297
  });
1224
1298
 
1225
- searchChatInput.addEventListener('keypress', function (event) {
1226
- if (event.key === 'Enter') {
1299
+ searchChatInput.addEventListener("keypress", function (event) {
1300
+ if (event.key === "Enter") {
1227
1301
  const searchTerm = searchChatInput.value.trim();
1228
1302
  currentSearchTerm = searchTerm;
1229
1303
 
@@ -1235,24 +1309,30 @@ class ChatWidget extends HTMLElement {
1235
1309
  });
1236
1310
 
1237
1311
  // Turn back button
1238
- turnBackButton.addEventListener('click', function () {
1312
+ turnBackButton.addEventListener("click", function () {
1239
1313
  if (chatBoxInputInfo) {
1240
1314
  chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
1241
- chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1242
- chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1315
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay
1316
+ ? "none"
1317
+ : "flex";
1318
+ chatBoxFilter.style.display = chatBoxListContainerDisplay
1319
+ ? "none"
1320
+ : "flex";
1243
1321
 
1244
1322
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1245
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1246
-
1247
- userNameInput.classList.remove('error');
1248
- hsblInput.classList.remove('error');
1249
- errorInfoInput.textContent = '';
1250
- 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 = "";
1251
1331
  }
1252
1332
  });
1253
1333
 
1254
1334
  // Continue button
1255
- continueButton.addEventListener('click', async function () {
1335
+ continueButton.addEventListener("click", async function () {
1256
1336
  if (chatDetails) {
1257
1337
  if (!validateInfoInputs()) {
1258
1338
  return;
@@ -1263,59 +1343,74 @@ class ChatWidget extends HTMLElement {
1263
1343
  selectedChat.userName = userNameInput.value.trim();
1264
1344
  }
1265
1345
 
1266
- errorInfoInput.textContent = '';
1346
+ errorInfoInput.textContent = "";
1267
1347
 
1268
1348
  // Join group
1269
- connection.invoke("JoinGroupAsync", selectedChat.hsbl).then(() => {
1270
- console.log(`Đã join nhóm: ${selectedChat.hsbl}`);
1271
- }).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()));
1272
1355
 
1273
1356
  var messages = await loadMessageHistory(selectedChat.hsbl, 1);
1274
1357
  await appendMessageHistory(messages);
1275
1358
 
1276
1359
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1277
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1360
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1361
+ ? "none"
1362
+ : "flex";
1278
1363
 
1279
1364
  chatDetailsDisplay = !chatDetailsDisplay;
1280
- chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1365
+ chatDetails.style.display = chatDetailsDisplay ? "none" : "flex";
1281
1366
 
1282
1367
  setTimeout(scrollToBottom, 0);
1283
1368
  }
1284
1369
  });
1285
1370
 
1286
1371
  // Details back button
1287
- detailsBackButton.addEventListener('click', function () {
1372
+ detailsBackButton.addEventListener("click", function () {
1288
1373
  if (chatDetails) {
1289
1374
  // Leave group
1290
- connection.invoke("RemoveFromGroupAsync", selectedChat.hsbl).then(() => {
1291
- console.log(`Đã rời nhóm: ${selectedChat.hsbl}`);
1292
- }).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()));
1293
1381
 
1294
1382
  // Reset state before go back
1295
1383
  currentPage = 1;
1296
1384
  hasMoreMessages = true;
1297
1385
  isLoading = false;
1298
- messageContainer.innerHTML = '';
1386
+ messageContainer.innerHTML = "";
1299
1387
 
1300
1388
  chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1301
- chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1389
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay
1390
+ ? "none"
1391
+ : "flex";
1302
1392
 
1303
1393
  chatDetailsDisplay = !chatDetailsDisplay;
1304
- chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1394
+ chatDetails.style.display = chatDetailsDisplay ? "none" : "flex";
1305
1395
  }
1306
1396
  });
1307
1397
 
1308
1398
  // Send message button
1309
- btnSendMessage.addEventListener('click', async function () {
1310
- const message = shadowRoot.getElementById('message-input').value;
1311
- 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);
1312
1402
  if (message) {
1313
1403
  try {
1314
- 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
+ );
1315
1410
  shadowRoot.getElementById("message-input").value = "";
1316
1411
 
1317
- const textarea = shadowRoot.getElementById('message-input');
1318
- textarea.style.height = 'auto';
1412
+ const textarea = shadowRoot.getElementById("message-input");
1413
+ textarea.style.height = "auto";
1319
1414
  } catch (err) {
1320
1415
  console.error("Lỗi gửi tin nhắn:", err);
1321
1416
  }
@@ -1363,34 +1458,37 @@ class ChatWidget extends HTMLElement {
1363
1458
 
1364
1459
  // ================== SIGNALR CONNECTION ==================
1365
1460
  const connection = new signalR.HubConnectionBuilder()
1366
- .withUrl(`${BASE_URL}chatHub?token=${token}&ePartnerCode=${ePartnerCode}`, {
1367
- withCredentials: false,
1368
- })
1461
+ .withUrl(
1462
+ `${BASE_URL}chatHub?token=${token}&ePartnerCode=${ePartnerCode}`,
1463
+ {
1464
+ withCredentials: false,
1465
+ }
1466
+ )
1369
1467
  .configureLogging(signalR.LogLevel.Information)
1370
1468
  .withAutomaticReconnect()
1371
1469
  .build();
1372
1470
 
1373
1471
  // Nhận tin nhắn từ server
1374
1472
  connection.on("ReceiveGroupMessage", (data) => {
1375
- console.log('Tin nhắn nhận được:', data);
1473
+ console.log("Tin nhắn nhận được:", data);
1376
1474
  const isMe = userId === data.ma_doi_tac_nsd;
1377
1475
  const msg = document.createElement("div");
1378
- msg.classList.add(isMe ? 'me' : 'partner');
1476
+ msg.classList.add(isMe ? "me" : "partner");
1379
1477
  msg.innerHTML = `
1380
1478
  <div class="info">
1381
- ${isMe ?
1382
- `<div class="message-title">
1479
+ ${
1480
+ isMe
1481
+ ? `<div class="message-title">
1383
1482
  <h5>${data.ten}</h5>
1384
1483
  <p class="message-time">${data.ngay_gui}</p>
1385
1484
  </div>
1386
1485
  <img class="logo-item" src="${data.link_logo}" alt="Logo" />`
1387
- :
1388
- `<img class="logo-item" src="${data.link_logo}" alt="Logo" />
1486
+ : `<img class="logo-item" src="${data.link_logo}" alt="Logo" />
1389
1487
  <div class="message-title">
1390
1488
  <h5>${data.ten}</h5>
1391
1489
  <p class="message-time">${data.ngay_gui}</p>
1392
1490
  </div>`
1393
- }
1491
+ }
1394
1492
  </div>
1395
1493
  <div class="message">
1396
1494
  <p>${data.noi_dung_tin_nhan}</p>
@@ -1411,7 +1509,7 @@ class ChatWidget extends HTMLElement {
1411
1509
  }
1412
1510
 
1413
1511
  async initSignalR() {
1414
- const signalR = window.signalR || await this.loadSignalR();
1512
+ const signalR = window.signalR || (await this.loadSignalR());
1415
1513
 
1416
1514
  this.connection = new signalR.HubConnectionBuilder()
1417
1515
  .withUrl(`${BASE_URL}chatHub`, { withCredentials: false })
@@ -1433,8 +1531,9 @@ class ChatWidget extends HTMLElement {
1433
1531
  return new Promise((resolve, reject) => {
1434
1532
  if (typeof signalR !== "undefined") return resolve();
1435
1533
 
1436
- const script = document.createElement('script');
1437
- 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";
1438
1537
  script.onload = () => {
1439
1538
  console.log("SignalR đã tải xong.");
1440
1539
  resolve();
@@ -1446,45 +1545,49 @@ class ChatWidget extends HTMLElement {
1446
1545
 
1447
1546
  setupEmojiPickerAfterLoad() {
1448
1547
  const shadow = this.shadowRoot;
1449
- const messageSending = shadow.querySelector('.message-sending');
1450
- const emojiButton = shadow.querySelector('#emoji-button');
1451
- 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");
1452
1551
 
1453
1552
  if (!messageSending || !emojiButton || !messageInput) return;
1454
1553
 
1455
1554
  // Tạo emoji-picker element
1456
- let picker = messageSending.querySelector('emoji-picker');
1555
+ let picker = messageSending.querySelector("emoji-picker");
1457
1556
  if (!picker) {
1458
- picker = document.createElement('emoji-picker');
1557
+ picker = document.createElement("emoji-picker");
1459
1558
  messageSending.appendChild(picker);
1460
1559
  }
1461
1560
 
1462
1561
  // Xử lý chọn emoji
1463
- picker.addEventListener('emoji-click', (event) => {
1562
+ picker.addEventListener("emoji-click", (event) => {
1464
1563
  const emoji = event.detail.unicode;
1465
1564
  const start = messageInput.selectionStart;
1466
1565
  const end = messageInput.selectionEnd;
1467
- 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);
1468
1570
  const newPos = start + emoji.length;
1469
1571
  messageInput.setSelectionRange(newPos, newPos);
1470
1572
  messageInput.focus();
1471
- picker.style.display = 'none';
1472
- messageInput.dispatchEvent(new Event('input')); // resize textarea
1573
+ picker.style.display = "none";
1574
+ messageInput.dispatchEvent(new Event("input")); // resize textarea
1473
1575
  });
1474
1576
 
1475
1577
  // Toggle picker
1476
- emojiButton.addEventListener('click', (e) => {
1578
+ emojiButton.addEventListener("click", (e) => {
1477
1579
  e.stopPropagation();
1478
- picker.style.display = picker.style.display === 'block' ? 'none' : 'block';
1580
+ picker.style.display =
1581
+ picker.style.display === "block" ? "none" : "block";
1479
1582
  });
1480
1583
 
1481
1584
  // Đóng khi click ngoài
1482
- document.addEventListener('click', (e) => {
1585
+ document.addEventListener("click", (e) => {
1483
1586
  if (!picker.contains(e.target) && e.target !== emojiButton) {
1484
- picker.style.display = 'none';
1587
+ picker.style.display = "none";
1485
1588
  }
1486
1589
  });
1487
1590
  }
1488
1591
  }
1489
1592
 
1490
- 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.2",
3
+ "version": "1.0.5",
4
4
  "description": "Chat widget hỗ trợ SignalR real-time cho website",
5
5
  "main": "index.js",
6
6
  "type": "module",