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.

Chat.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /* global APP, $ */
  2. import {processReplacements, linkify} from './Replacement';
  3. import CommandsProcessor from './Commands';
  4. import VideoLayout from "../../videolayout/VideoLayout";
  5. import UIUtil from '../../util/UIUtil';
  6. import UIEvents from '../../../../service/UI/UIEvents';
  7. import { smileys } from './smileys';
  8. import { dockToolbar, setSubject } from '../../../../react/features/toolbar';
  9. let unreadMessages = 0;
  10. const sidePanelsContainerId = 'sideToolbarContainer';
  11. const htmlStr = `
  12. <div id="chat_container" class="sideToolbarContainer__inner">
  13. <div id="nickname">
  14. <span data-i18n="chat.nickname.title"></span>
  15. <form>
  16. <input type='text'
  17. class="input-control" id="nickinput" autofocus
  18. data-i18n="[placeholder]chat.nickname.popover">
  19. </form>
  20. </div>
  21. <div id="chatconversation"></div>
  22. <audio id="chatNotification" src="sounds/incomingMessage.wav"
  23. preload="auto"></audio>
  24. <textarea id="usermsg" autofocus
  25. data-i18n="[placeholder]chat.messagebox"></textarea>
  26. <div id="smileysarea">
  27. <div id="smileys" id="toggle_smileys">
  28. <img src="images/smile.svg"/>
  29. </div>
  30. </div>
  31. </div>`;
  32. function initHTML() {
  33. $(`#${sidePanelsContainerId}`)
  34. .append(htmlStr);
  35. }
  36. /**
  37. * The container id, which is and the element id.
  38. */
  39. var CHAT_CONTAINER_ID = "chat_container";
  40. /**
  41. * Updates visual notification, indicating that a message has arrived.
  42. */
  43. function updateVisualNotification() {
  44. // XXX The rewrite of the toolbar in React delayed the availability of the
  45. // element unreadMessages. In order to work around the delay, I introduced
  46. // and utilized unreadMsgSelector in addition to unreadMsgElement.
  47. const unreadMsgSelector = $('#unreadMessages');
  48. const unreadMsgElement
  49. = unreadMsgSelector.length > 0 ? unreadMsgSelector[0] : undefined;
  50. if (unreadMessages) {
  51. unreadMsgElement.innerHTML = unreadMessages.toString();
  52. APP.store.dispatch(dockToolbar(true));
  53. const chatButtonElement
  54. = document.getElementById('toolbar_button_chat');
  55. const leftIndent
  56. = (UIUtil.getTextWidth(chatButtonElement)
  57. - UIUtil.getTextWidth(unreadMsgElement))
  58. / 2;
  59. const topIndent
  60. = (UIUtil.getTextHeight(chatButtonElement)
  61. - UIUtil.getTextHeight(unreadMsgElement))
  62. / 2
  63. - 5;
  64. unreadMsgElement.setAttribute(
  65. 'style',
  66. 'top:' + topIndent + '; left:' + leftIndent + ';');
  67. }
  68. else {
  69. unreadMsgSelector.html('');
  70. }
  71. if (unreadMsgElement) {
  72. unreadMsgSelector.parent()[unreadMessages > 0 ? 'show' : 'hide']();
  73. }
  74. }
  75. /**
  76. * Returns the current time in the format it is shown to the user
  77. * @returns {string}
  78. */
  79. function getCurrentTime(stamp) {
  80. var now = (stamp? new Date(stamp): new Date());
  81. var hour = now.getHours();
  82. var minute = now.getMinutes();
  83. var second = now.getSeconds();
  84. if(hour.toString().length === 1) {
  85. hour = '0'+hour;
  86. }
  87. if(minute.toString().length === 1) {
  88. minute = '0'+minute;
  89. }
  90. if(second.toString().length === 1) {
  91. second = '0'+second;
  92. }
  93. return hour+':'+minute+':'+second;
  94. }
  95. function toggleSmileys() {
  96. var smileys = $('#smileysContainer');
  97. if(!smileys.is(':visible')) {
  98. smileys.show("slide", { direction: "down", duration: 300});
  99. } else {
  100. smileys.hide("slide", { direction: "down", duration: 300});
  101. }
  102. $('#usermsg').focus();
  103. }
  104. function addClickFunction(smiley, number) {
  105. smiley.onclick = function addSmileyToMessage() {
  106. var usermsg = $('#usermsg');
  107. var message = usermsg.val();
  108. message += smileys['smiley' + number];
  109. usermsg.val(message);
  110. usermsg.get(0).setSelectionRange(message.length, message.length);
  111. toggleSmileys();
  112. usermsg.focus();
  113. };
  114. }
  115. /**
  116. * Adds the smileys container to the chat
  117. */
  118. function addSmileys() {
  119. var smileysContainer = document.createElement('div');
  120. smileysContainer.id = 'smileysContainer';
  121. for(var i = 1; i <= 21; i++) {
  122. var smileyContainer = document.createElement('div');
  123. smileyContainer.id = 'smiley' + i;
  124. smileyContainer.className = 'smileyContainer';
  125. var smiley = document.createElement('img');
  126. smiley.src = 'images/smileys/smiley' + i + '.svg';
  127. smiley.className = 'smiley';
  128. addClickFunction(smiley, i);
  129. smileyContainer.appendChild(smiley);
  130. smileysContainer.appendChild(smileyContainer);
  131. }
  132. $("#chat_container").append(smileysContainer);
  133. }
  134. /**
  135. * Resizes the chat conversation.
  136. */
  137. function resizeChatConversation() {
  138. var msgareaHeight = $('#usermsg').outerHeight();
  139. var chatspace = $('#' + CHAT_CONTAINER_ID);
  140. var width = chatspace.width();
  141. var chat = $('#chatconversation');
  142. var smileys = $('#smileysarea');
  143. smileys.height(msgareaHeight);
  144. $("#smileys").css('bottom', (msgareaHeight - 26) / 2);
  145. $('#smileysContainer').css('bottom', msgareaHeight);
  146. chat.width(width - 10);
  147. chat.height(window.innerHeight - 15 - msgareaHeight);
  148. }
  149. /**
  150. * Focus input after 400 ms
  151. * Found input by id
  152. *
  153. * @param id {string} input id
  154. */
  155. function deferredFocus(id){
  156. setTimeout(() => $(`#${id}`).focus(), 400);
  157. }
  158. /**
  159. * Chat related user interface.
  160. */
  161. var Chat = {
  162. /**
  163. * Initializes chat related interface.
  164. */
  165. init (eventEmitter) {
  166. initHTML();
  167. if (APP.settings.getDisplayName()) {
  168. Chat.setChatConversationMode(true);
  169. }
  170. $("#toggle_smileys").click(function() {
  171. Chat.toggleSmileys();
  172. });
  173. $('#nickinput').keydown(function (event) {
  174. if (event.keyCode === 13) {
  175. event.preventDefault();
  176. let val = this.value;
  177. this.value = '';
  178. eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
  179. deferredFocus('usermsg');
  180. }
  181. });
  182. var usermsg = $('#usermsg');
  183. usermsg.keydown(function (event) {
  184. if (event.keyCode === 13) {
  185. event.preventDefault();
  186. var value = this.value;
  187. usermsg.val('').trigger('autosize.resize');
  188. this.focus();
  189. var command = new CommandsProcessor(value, eventEmitter);
  190. if (command.isCommand()) {
  191. command.processCommand();
  192. } else {
  193. var message = UIUtil.escapeHtml(value);
  194. eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
  195. }
  196. }
  197. });
  198. var onTextAreaResize = function () {
  199. resizeChatConversation();
  200. Chat.scrollChatToBottom();
  201. };
  202. usermsg.autosize({callback: onTextAreaResize});
  203. eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
  204. function(containerId, isVisible) {
  205. if (containerId !== CHAT_CONTAINER_ID || !isVisible)
  206. return;
  207. unreadMessages = 0;
  208. updateVisualNotification();
  209. // Undock the toolbar when the chat is shown and if we're in a
  210. // video mode.
  211. if (VideoLayout.isLargeVideoVisible()) {
  212. APP.store.dispatch(dockToolbar(false));
  213. }
  214. // if we are in conversation mode focus on the text input
  215. // if we are not, focus on the display name input
  216. if (APP.settings.getDisplayName())
  217. deferredFocus('usermsg');
  218. else
  219. deferredFocus('nickinput');
  220. });
  221. addSmileys();
  222. updateVisualNotification();
  223. },
  224. /**
  225. * Appends the given message to the chat conversation.
  226. */
  227. updateChatConversation (id, displayName, message, stamp) {
  228. var divClassName = '';
  229. if (APP.conference.isLocalId(id)) {
  230. divClassName = "localuser";
  231. } else {
  232. divClassName = "remoteuser";
  233. if (!Chat.isVisible()) {
  234. unreadMessages++;
  235. UIUtil.playSoundNotification('chatNotification');
  236. updateVisualNotification();
  237. }
  238. }
  239. // replace links and smileys
  240. // Strophe already escapes special symbols on sending,
  241. // so we escape here only tags to avoid double &amp;
  242. var escMessage = message.replace(/</g, '&lt;').
  243. replace(/>/g, '&gt;').replace(/\n/g, '<br/>');
  244. var escDisplayName = UIUtil.escapeHtml(displayName);
  245. message = processReplacements(escMessage);
  246. var messageContainer =
  247. '<div class="chatmessage">'+
  248. '<img src="images/chatArrow.svg" class="chatArrow">' +
  249. '<div class="username ' + divClassName +'">' + escDisplayName +
  250. '</div>' + '<div class="timestamp">' + getCurrentTime(stamp) +
  251. '</div>' + '<div class="usermessage">' + message + '</div>' +
  252. '</div>';
  253. $('#chatconversation').append(messageContainer);
  254. $('#chatconversation').animate(
  255. { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
  256. },
  257. /**
  258. * Appends error message to the conversation
  259. * @param errorMessage the received error message.
  260. * @param originalText the original message.
  261. */
  262. chatAddError (errorMessage, originalText) {
  263. errorMessage = UIUtil.escapeHtml(errorMessage);
  264. originalText = UIUtil.escapeHtml(originalText);
  265. $('#chatconversation').append(
  266. '<div class="errorMessage"><b>Error: </b>' + 'Your message' +
  267. (originalText? (' \"'+ originalText + '\"') : "") +
  268. ' was not sent.' +
  269. (errorMessage? (' Reason: ' + errorMessage) : '') + '</div>');
  270. $('#chatconversation').animate(
  271. { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
  272. },
  273. /**
  274. * Sets the subject to the UI
  275. * @param subject the subject
  276. */
  277. setSubject (subject) {
  278. if (subject) {
  279. subject = subject.trim();
  280. }
  281. const html = linkify(UIUtil.escapeHtml(subject));
  282. APP.store.dispatch(setSubject(html));
  283. },
  284. /**
  285. * Sets the chat conversation mode.
  286. * Conversation mode is the normal chat mode, non conversation mode is
  287. * where we ask user to input its display name.
  288. * @param {boolean} isConversationMode if chat should be in
  289. * conversation mode or not.
  290. */
  291. setChatConversationMode (isConversationMode) {
  292. $('#' + CHAT_CONTAINER_ID)
  293. .toggleClass('is-conversation-mode', isConversationMode);
  294. },
  295. /**
  296. * Resizes the chat area.
  297. */
  298. resizeChat (width, height) {
  299. $('#' + CHAT_CONTAINER_ID).width(width).height(height);
  300. resizeChatConversation();
  301. },
  302. /**
  303. * Indicates if the chat is currently visible.
  304. */
  305. isVisible () {
  306. return UIUtil.isVisible(
  307. document.getElementById(CHAT_CONTAINER_ID));
  308. },
  309. /**
  310. * Shows and hides the window with the smileys
  311. */
  312. toggleSmileys,
  313. /**
  314. * Scrolls chat to the bottom.
  315. */
  316. scrollChatToBottom () {
  317. setTimeout(
  318. () => {
  319. const chatconversation = $('#chatconversation');
  320. // XXX Prevent TypeError: undefined is not an object when the
  321. // Web browser does not support WebRTC (yet).
  322. chatconversation.length > 0
  323. && chatconversation.scrollTop(
  324. chatconversation[0].scrollHeight);
  325. },
  326. 5);
  327. }
  328. };
  329. export default Chat;