暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * App Chat
  3. */
  4. 'use strict';
  5. document.addEventListener('DOMContentLoaded', () => {
  6. // DOM Elements
  7. const elements = {
  8. chatContactsBody: document.querySelector('.app-chat-contacts .sidebar-body'),
  9. chatHistoryBody: document.querySelector('.chat-history-body'),
  10. chatSidebarLeftBody: document.querySelector('.app-chat-sidebar-left .sidebar-body'),
  11. chatSidebarRightBody: document.querySelector('.app-chat-sidebar-right .sidebar-body'),
  12. chatUserStatus: [...document.querySelectorAll(".form-check-input[name='chat-user-status']")],
  13. chatSidebarLeftUserAbout: document.getElementById('chat-sidebar-left-user-about'),
  14. formSendMessage: document.querySelector('.form-send-message'),
  15. messageInput: document.querySelector('.message-input'),
  16. searchInput: document.querySelector('.chat-search-input'),
  17. chatContactListItems: [...document.querySelectorAll('.chat-contact-list-item:not(.chat-contact-list-item-title)')],
  18. textareaInfo: document.getElementById('textarea-maxlength-info'),
  19. conversationButton: document.getElementById('app-chat-conversation-btn'),
  20. chatHistoryHeader: document.querySelector(".chat-history-header [data-target='#app-chat-contacts']"),
  21. speechToText: $('.speech-to-text'),
  22. appChatConversation: document.getElementById('app-chat-conversation'),
  23. appChatHistory: document.getElementById('app-chat-history')
  24. };
  25. const userStatusClasses = {
  26. active: 'avatar-online',
  27. offline: 'avatar-offline',
  28. away: 'avatar-away',
  29. busy: 'avatar-busy'
  30. };
  31. /**
  32. * Initialize PerfectScrollbar on provided elements.
  33. * @param {HTMLElement[]} elements - List of elements to initialize.
  34. */
  35. const initPerfectScrollbar = elements => {
  36. elements.forEach(el => {
  37. if (el) {
  38. new PerfectScrollbar(el, {
  39. wheelPropagation: false,
  40. suppressScrollX: true
  41. });
  42. }
  43. });
  44. };
  45. /**
  46. * Scroll chat history to the bottom.
  47. */
  48. const scrollToBottom = () => elements.chatHistoryBody?.scrollTo(0, elements.chatHistoryBody.scrollHeight);
  49. /**
  50. * Update user status avatar classes.
  51. * @param {string} status - Status key from userStatusClasses.
  52. */
  53. const updateUserStatus = status => {
  54. const leftSidebarAvatar = document.querySelector('.chat-sidebar-left-user .avatar');
  55. const contactsAvatar = document.querySelector('.app-chat-contacts .avatar');
  56. [leftSidebarAvatar, contactsAvatar].forEach(avatar => {
  57. if (avatar) avatar.className = avatar.className.replace(/avatar-\w+/, userStatusClasses[status]);
  58. });
  59. };
  60. // Handle textarea max length count.
  61. function handleMaxLengthCount(inputElement, infoElement, maxLength) {
  62. const currentLength = inputElement.value.length;
  63. const remaining = maxLength - currentLength;
  64. infoElement.className = 'maxLength label-success';
  65. if (remaining >= 0) {
  66. infoElement.textContent = `${currentLength}/${maxLength}`;
  67. }
  68. if (remaining <= 0) {
  69. infoElement.textContent = `${currentLength}/${maxLength}`;
  70. infoElement.classList.remove('label-success');
  71. infoElement.classList.add('label-danger');
  72. }
  73. }
  74. /**
  75. * Switch to chat conversation view.
  76. */
  77. const switchToChatConversation = () => {
  78. elements.appChatConversation.classList.replace('d-flex', 'd-none');
  79. elements.appChatHistory.classList.replace('d-none', 'd-block');
  80. };
  81. /**
  82. * Filter chat contacts by search input.
  83. * @param {string} selector - CSS selector for chat/contact list items.
  84. * @param {string} searchValue - Search input value.
  85. * @param {string} placeholderSelector - Selector for placeholder element.
  86. */
  87. const filterChatContacts = (selector, searchValue, placeholderSelector) => {
  88. const items = document.querySelectorAll(`${selector}:not(.chat-contact-list-item-title)`);
  89. let visibleCount = 0;
  90. items.forEach(item => {
  91. const isVisible = item.textContent.toLowerCase().includes(searchValue);
  92. item.classList.toggle('d-flex', isVisible);
  93. item.classList.toggle('d-none', !isVisible);
  94. if (isVisible) visibleCount++;
  95. });
  96. document.querySelector(placeholderSelector)?.classList.toggle('d-none', visibleCount > 0);
  97. };
  98. /**
  99. * Initialize speech-to-text functionality.
  100. */
  101. const initSpeechToText = () => {
  102. const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  103. if (!SpeechRecognition || elements.speechToText.length === 0) return;
  104. const recognition = new SpeechRecognition();
  105. let listening = false;
  106. elements.speechToText.on('click', function () {
  107. if (!listening) recognition.start();
  108. recognition.onspeechstart = () => (listening = true);
  109. recognition.onresult = event => {
  110. $(this).closest('.form-send-message').find('.message-input').val(event.results[0][0].transcript);
  111. };
  112. recognition.onspeechend = () => (listening = false);
  113. recognition.onerror = () => (listening = false);
  114. });
  115. };
  116. // Initialize PerfectScrollbar
  117. initPerfectScrollbar([
  118. elements.chatContactsBody,
  119. elements.chatHistoryBody,
  120. elements.chatSidebarLeftBody,
  121. elements.chatSidebarRightBody
  122. ]);
  123. // Scroll to the bottom of the chat history
  124. scrollToBottom();
  125. // Attach user status change event
  126. elements.chatUserStatus.forEach(statusInput => {
  127. statusInput.addEventListener('click', () => updateUserStatus(statusInput.value));
  128. });
  129. // Handle max length for textarea
  130. const maxLength = parseInt(elements.chatSidebarLeftUserAbout.getAttribute('maxlength'), 10);
  131. handleMaxLengthCount(elements.chatSidebarLeftUserAbout, elements.textareaInfo, maxLength);
  132. elements.chatSidebarLeftUserAbout.addEventListener('input', () => {
  133. handleMaxLengthCount(elements.chatSidebarLeftUserAbout, elements.textareaInfo, maxLength);
  134. });
  135. // Attach chat conversation switch event
  136. elements.conversationButton?.addEventListener('click', switchToChatConversation);
  137. // Attach chat contact selection event
  138. elements.chatContactListItems.forEach(item => {
  139. item.addEventListener('click', () => {
  140. elements.chatContactListItems.forEach(contact => contact.classList.remove('active'));
  141. item.classList.add('active');
  142. switchToChatConversation();
  143. });
  144. });
  145. // Attach chat search filter event
  146. elements.searchInput?.addEventListener(
  147. 'keyup',
  148. debounce(e => {
  149. const searchValue = e.target.value.toLowerCase();
  150. filterChatContacts('#chat-list li', searchValue, '.chat-list-item-0');
  151. filterChatContacts('#contact-list li', searchValue, '.contact-list-item-0');
  152. }, 300)
  153. );
  154. // Attach message send event
  155. elements.formSendMessage?.addEventListener('submit', e => {
  156. e.preventDefault();
  157. const message = elements.messageInput.value.trim();
  158. if (message) {
  159. const messageDiv = document.createElement('div');
  160. messageDiv.className = 'chat-message-text mt-2';
  161. messageDiv.innerHTML = `<p class="mb-0 text-break">${message}</p>`;
  162. document.querySelector('li:last-child .chat-message-wrapper')?.appendChild(messageDiv);
  163. elements.messageInput.value = '';
  164. scrollToBottom();
  165. }
  166. });
  167. // ! Fix overlay issue for chat sidebar
  168. elements.chatHistoryHeader?.addEventListener('click', () => {
  169. document.querySelector('.app-chat-sidebar-left .close-sidebar')?.removeAttribute('data-overlay');
  170. });
  171. // Initialize speech-to-text
  172. initSpeechToText();
  173. });
  174. /**
  175. * Debounce utility function.
  176. * @param {Function} func - Function to debounce.
  177. * @param {number} wait - Delay in milliseconds.
  178. */
  179. function debounce(func, wait) {
  180. let timeout;
  181. return (...args) => {
  182. clearTimeout(timeout);
  183. timeout = setTimeout(() => func.apply(this, args), wait);
  184. };
  185. }