main.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. const emojiPatterns = [
  2. { pattern: ':)', replacement: '🙂' },
  3. { pattern: ':-)', replacement: '🙂' },
  4. { pattern: ':D', replacement: '😀' },
  5. { pattern: ':-D', replacement: '😀' },
  6. { pattern: ';)', replacement: '😉' },
  7. { pattern: ';-)', replacement: '😉' },
  8. { pattern: ':(', replacement: '😞' },
  9. { pattern: ':-(', replacement: '😞' },
  10. { pattern: ':\'(', replacement: '😭' },
  11. { pattern: ':\'-(', replacement: '😭' },
  12. { pattern: ':\\', replacement: '😕' },
  13. { pattern: ':-\\', replacement: '😕' },
  14. { pattern: ':/', replacement: '😕' },
  15. { pattern: ':-/', replacement: '😕' },
  16. { pattern: ':|', replacement: '😐' },
  17. { pattern: ':-|', replacement: '😐' },
  18. { pattern: ':*', replacement: '😘' },
  19. { pattern: ':-*', replacement: '😘' },
  20. { pattern: '<3', replacement: '❤️' },
  21. { pattern: '(y)', replacement: '👍' },
  22. ];
  23. const socket = io();
  24. $(() => {
  25. socket.on('serverHandshake', onServerHandshake);
  26. socket.on('serverLogin', onServerLogin);
  27. socket.on('serverKick', onServerKick);
  28. });
  29. function onServerHandshake() {
  30. resetChatListeners();
  31. resetLoginListeners();
  32. var template = $('#chat-login-template').html();
  33. $('#chat-host').empty();
  34. $('#chat-host').append(template);
  35. $('#chat-login-form').submit((e) => {
  36. e.preventDefault();
  37. socket.emit('login', $('#chat-login-form .username').val(), $('#chat-login-form .password').val());
  38. return false;
  39. });
  40. socket.on('usernameInvalid', () => {
  41. $('#chat-login-form .username').focus();
  42. $('#chat-host .loginError').text('Username invalid.');
  43. });
  44. socket.on('usernameTaken', () => {
  45. $('#chat-login-form .username').focus();
  46. $('#chat-host .loginError').text('Username already in use.');
  47. });
  48. socket.on('passwordRequired', () => {
  49. $('#chat-login-form .passwordWrapper').css('display', '');
  50. $('#chat-login-form .password').focus();
  51. $('#chat-host .loginError').text('Für diesen reservierten Benutzer ist ein Passwort erforderlich.');
  52. });
  53. socket.on('passwordWrong', () => {
  54. $('#chat-login-form .passwordWrapper').css('display', '');
  55. $('#chat-login-form .password').focus();
  56. $('#chat-host .loginError').text('Das eingegebene Passwort ist ungültig.');
  57. });
  58. }
  59. function onServerLogin(user, history) {
  60. resetChatListeners();
  61. resetLoginListeners();
  62. var template;
  63. if (user.admin) {
  64. template = $('#chat-admin-template').html()
  65. } else {
  66. template = $('#chat-template').html()
  67. }
  68. $('#chat-host').empty();
  69. $('#chat-host').append(template);
  70. $('#chat-host').append($('#chat-message-form-template').html());
  71. $('#chat-host .messageField').focus();
  72. for (const historyMsg of history) {
  73. appendUserMessage(historyMsg.msg, historyMsg.user, historyMsg.timestamp, user);
  74. }
  75. socket.on('usersUpdated', (userList) => {
  76. $('#chat-host .main .userlist').empty();
  77. userList.sort((userA, userB) => userA.name.localeCompare(userB.name));
  78. userList.sort((userA, userB) => {
  79. if (userA.admin && !userB.admin) {
  80. return Number.MIN_SAFE_INTEGER;
  81. } else if (!userA.admin && userB.admin) {
  82. return Number.MAX_SAFE_INTEGER;
  83. }
  84. return 0;
  85. });
  86. for (let listUser of userList) {
  87. let userString = listUser.name;
  88. if (listUser.admin) {
  89. userString = '👑 ' + userString;
  90. }
  91. const userListEl = $('<li>');
  92. const usernameEl = $('<span>');
  93. usernameEl.text(userString);
  94. usernameEl.css('color', listUser.color);
  95. usernameEl.click(() => mentionUser(listUser.name));
  96. userListEl.append(usernameEl);
  97. if (user.admin) {
  98. const kickBtn = $('<button class="kickBtn">🥾</button>');
  99. kickBtn.click(() => onKickUser(listUser));
  100. userListEl.append(kickBtn);
  101. }
  102. $('#chat-host .main .userlist').append(userListEl);
  103. }
  104. });
  105. if (user.admin) {
  106. socket.on('bannedUpdated', (bannedList) => {
  107. $('#chat-host .main .bannedlist').empty();
  108. for (let bannedItem of bannedList) {
  109. const bannedListEl = $('<li>');
  110. bannedListEl.text(bannedItem.ip + ' (' + bannedItem.user.name + ')');
  111. const removeBtn = $('<button class="removeBtn">✖</button>');
  112. removeBtn.click(() => onLiftBan(bannedItem.ip));
  113. bannedListEl.append(removeBtn);
  114. $('#chat-host .main .bannedlist').append(bannedListEl);
  115. }
  116. });
  117. }
  118. socket.on('message', (msg, msgUser, timestamp) => appendUserMessage(msg, msgUser, timestamp, user));
  119. socket.on('userJoined', (msg) => {
  120. appendMessage($('<li class="systemMsg user joined">').text(msg + ' hat den Chat betreten.'));
  121. });
  122. socket.on('userLeft', (msg) => {
  123. appendMessage($('<li class="systemMsg user left">').text(msg + ' hat den Chat verlassen.'));
  124. });
  125. $('#chat-host .messageForm').submit((e) => {
  126. e.preventDefault();
  127. socket.emit('message', $('#chat-host .messageForm .messageField').val());
  128. $('#chat-host .messageForm .messageField').val('');
  129. $('#chat-host .messageForm .messageField').focus();
  130. return false;
  131. });
  132. $("#chat-host .messageField").keypress((e) => {
  133. const field = $('#chat-host .messageForm .messageField');
  134. if (field.prop('selectionStart') == field.prop('selectionEnd')) {
  135. const val = field.val();
  136. const cursorPos = field.prop('selectionStart');
  137. const valBefore = val.substring(0, cursorPos);
  138. const valAfter = val.substring(cursorPos);
  139. if (!valBefore.endsWith('http:/') && !valBefore.endsWith('https:/')) {
  140. for (const emojiPatternItem of emojiPatterns) {
  141. if (valBefore.endsWith(emojiPatternItem.pattern)) {
  142. field.val(
  143. valBefore.substring(0, valBefore.length - emojiPatternItem.pattern.length) + emojiPatternItem.replacement + valAfter
  144. );
  145. field.prop('selectionStart', cursorPos);
  146. field.prop('selectionEnd', cursorPos);
  147. }
  148. }
  149. }
  150. }
  151. if(e.key == 'Enter' && !e.shiftKey) {
  152. e.preventDefault();
  153. $('#chat-host .messageForm').submit();
  154. return false;
  155. }
  156. });
  157. }
  158. function onServerKick() {
  159. resetChatListeners();
  160. resetLoginListeners();
  161. var template = $('#chat-kick-template').html();
  162. $('#chat-host').empty();
  163. $('#chat-host').append(template);
  164. }
  165. function resetChatListeners() {
  166. socket.removeAllListeners('usersUpdated');
  167. socket.removeAllListeners('message');
  168. socket.removeAllListeners('userJoined');
  169. socket.removeAllListeners('userLeft');
  170. }
  171. function resetLoginListeners() {
  172. socket.removeAllListeners('usernameInvalid');
  173. socket.removeAllListeners('usernameTaken');
  174. socket.removeAllListeners('passwordRequired');
  175. socket.removeAllListeners('passwordWrong');
  176. }
  177. function appendMessage(element) {
  178. const messagesListEl = $('#chat-host .main .messages')[0];
  179. const scrollMax = messagesListEl.scrollHeight - messagesListEl.clientHeight;
  180. const scrolled = messagesListEl.scrollTop != scrollMax;
  181. $('#chat-host .main .messages').append(element);
  182. // TODO: Scrolling
  183. //if (!scrolled) {
  184. messagesListEl.scrollTop = messagesListEl.scrollHeight - messagesListEl.clientHeight;
  185. //}
  186. }
  187. function appendUserMessage(msg, msgUser, timestamp, user) {
  188. const messageWrapper = $('<li class="userMsg">');
  189. if(user.name == msgUser.name) {
  190. messageWrapper.addClass('self');
  191. }
  192. messageWrapper.css('borderColor', msgUser.color);
  193. const avaEl = $('<img class="avatar" />');
  194. avaEl.prop('src', '/img/ava/' + msgUser.name);
  195. avaEl.css('backgroundColor', msgUser.color);
  196. avaEl.click(() => mentionUser(msgUser.name));
  197. messageWrapper.append(avaEl);
  198. const timeEl = $('<span class="time">');
  199. timeEl.text(new Date(timestamp).toLocaleTimeString());
  200. timeEl.prop('title', new Date(timestamp).toLocaleString());
  201. messageWrapper.append(timeEl);
  202. const userEl = $('<span class="user">');
  203. let userString = msgUser.name;
  204. if (msgUser.admin) {
  205. userString = '👑 ' + userString;
  206. }
  207. userEl.text(userString);
  208. userEl.css('color', msgUser.color);
  209. userEl.click(() => mentionUser(msgUser.name));
  210. messageWrapper.append(userEl);
  211. const messageEl = $('<span class="message">');
  212. messageEl.html(msg)
  213. messageWrapper.append(messageEl);
  214. appendMessage(messageWrapper);
  215. }
  216. function onKickUser(user) {
  217. if (confirm('Möchten Sie den Benutzer "' + user.name + '" wirklich bannen?\nDies wird sich auf alle Benutzer mit der gleichen IP-Adresse auswirken.')) {
  218. socket.emit('requestKick', user);
  219. }
  220. }
  221. function onLiftBan(ip) {
  222. socket.emit('requestLiftBan', ip);
  223. }
  224. function mentionUser(username) {
  225. const field = $("#chat-host .messageField");
  226. if (field) {
  227. const mention = '@' + username + ' ';
  228. const val = field.val();
  229. var cursorPos = +field.prop('selectionStart');
  230. const valBefore = val.substring(0, cursorPos);
  231. const valAfter = val.substring(cursorPos);
  232. field.val(valBefore + mention + valAfter);
  233. cursorPos += mention.length;
  234. field.prop('selectionStart', cursorPos);
  235. field.prop('selectionEnd', cursorPos);
  236. field.focus();
  237. }
  238. }