'use strict'; define('forum/chats/messages', [ 'components', 'translator', 'benchpress', 'hooks', 'bootbox', 'alerts', 'messages', ], function (components, translator, Benchpress, hooks, bootbox, alerts, messagesModule) { const messages = {}; messages.sendMessage = function (roomId, inputEl) { const msg = inputEl.val(); const mid = inputEl.attr('data-mid'); if (!msg.trim().length) { return; } inputEl.val(''); inputEl.removeAttr('data-mid'); messages.updateRemainingLength(inputEl.parent()); hooks.fire('action:chat.sent', { roomId: roomId, message: msg, mid: mid, }); if (!mid) { socket.emit('modules.chats.send', { roomId: roomId, message: msg, }, function (err) { if (err) { inputEl.val(msg); messages.updateRemainingLength(inputEl.parent()); if (err.message === '[[error:email-not-confirmed-chat]]') { return messagesModule.showEmailConfirmWarning(err.message); } return alerts.alert({ alert_id: 'chat_spam_error', title: '[[global:alert.error]]', message: err.message, type: 'danger', timeout: 10000, }); } }); } else { socket.emit('modules.chats.edit', { roomId: roomId, mid: mid, message: msg, }, function (err) { if (err) { inputEl.val(msg); inputEl.attr('data-mid', mid); messages.updateRemainingLength(inputEl.parent()); return alerts.error(err); } }); } }; messages.updateRemainingLength = function (parent) { const element = parent.find('[component="chat/input"]'); parent.find('[component="chat/message/length"]').text(element.val().length); parent.find('[component="chat/message/remaining"]').text(config.maximumChatMessageLength - element.val().length); hooks.fire('action:chat.updateRemainingLength', { parent: parent, }); }; messages.appendChatMessage = function (chatContentEl, data) { const lastSpeaker = parseInt(chatContentEl.find('.chat-message').last().attr('data-uid'), 10); const lasttimestamp = parseInt(chatContentEl.find('.chat-message').last().attr('data-timestamp'), 10); if (!Array.isArray(data)) { data.newSet = lastSpeaker !== parseInt(data.fromuid, 10) || parseInt(data.timestamp, 10) > parseInt(lasttimestamp, 10) + (1000 * 60 * 3); } messages.parseMessage(data, function (html) { onMessagesParsed(chatContentEl, html); }); }; function onMessagesParsed(chatContentEl, html) { const newMessage = $(html); const isAtBottom = messages.isAtBottom(chatContentEl); newMessage.appendTo(chatContentEl); newMessage.find('.timeago').timeago(); newMessage.find('img:not(.not-responsive)').addClass('img-responsive'); if (isAtBottom) { messages.scrollToBottom(chatContentEl); } hooks.fire('action:chat.received', { messageEl: newMessage, }); } messages.parseMessage = function (data, callback) { function done(html) { translator.translate(html, callback); } if (Array.isArray(data)) { Benchpress.render('partials/chats/message' + (Array.isArray(data) ? 's' : ''), { messages: data, }).then(done); } else { Benchpress.render('partials/chats/' + (data.system ? 'system-message' : 'message'), { messages: data, }).then(done); } }; messages.isAtBottom = function (containerEl, threshold) { if (containerEl.length) { const distanceToBottom = containerEl[0].scrollHeight - ( containerEl.outerHeight() + containerEl.scrollTop() ); return distanceToBottom < (threshold || 100); } }; messages.scrollToBottom = function (containerEl) { if (containerEl && containerEl.length) { containerEl.scrollTop(containerEl[0].scrollHeight - containerEl.height()); containerEl.parent() .find('[component="chat/messages/scroll-up-alert"]') .addClass('hidden'); } }; messages.toggleScrollUpAlert = function (containerEl) { const isAtBottom = messages.isAtBottom(containerEl, 300); containerEl.parent() .find('[component="chat/messages/scroll-up-alert"]') .toggleClass('hidden', isAtBottom); }; messages.prepEdit = function (inputEl, messageId, roomId) { socket.emit('modules.chats.getRaw', { mid: messageId, roomId: roomId }, function (err, raw) { if (err) { return alerts.error(err); } // Populate the input field with the raw message content if (inputEl.val().length === 0) { // By setting the `data-mid` attribute, I tell the chat code that I am editing a // message, instead of posting a new one. inputEl.attr('data-mid', messageId).addClass('editing'); inputEl.val(raw).focus(); hooks.fire('action:chat.prepEdit', { inputEl: inputEl, messageId: messageId, roomId: roomId, }); } }); }; messages.addSocketListeners = function () { socket.removeListener('event:chats.edit', onChatMessageEdited); socket.on('event:chats.edit', onChatMessageEdited); socket.removeListener('event:chats.delete', onChatMessageDeleted); socket.on('event:chats.delete', onChatMessageDeleted); socket.removeListener('event:chats.restore', onChatMessageRestored); socket.on('event:chats.restore', onChatMessageRestored); }; function onChatMessageEdited(data) { data.messages.forEach(function (message) { const self = parseInt(message.fromuid, 10) === parseInt(app.user.uid, 10); message.self = self ? 1 : 0; messages.parseMessage(message, function (html) { const body = components.get('chat/message', message.messageId); if (body.length) { body.replaceWith(html); components.get('chat/message', message.messageId).find('.timeago').timeago(); } }); }); } function onChatMessageDeleted(messageId) { components.get('chat/message', messageId) .toggleClass('deleted', true) .find('[component="chat/message/body"]').translateHtml('[[modules:chat.message-deleted]]'); } function onChatMessageRestored(message) { components.get('chat/message', message.messageId) .toggleClass('deleted', false) .find('[component="chat/message/body"]').html(message.content); } messages.delete = function (messageId, roomId) { translator.translate('[[modules:chat.delete_message_confirm]]', function (translated) { bootbox.confirm(translated, function (ok) { if (!ok) { return; } socket.emit('modules.chats.delete', { messageId: messageId, roomId: roomId, }, function (err) { if (err) { return alerts.error(err); } components.get('chat/message', messageId).toggleClass('deleted', true); }); }); }); }; messages.restore = function (messageId, roomId) { socket.emit('modules.chats.restore', { messageId: messageId, roomId: roomId, }, function (err) { if (err) { return alerts.error(err); } components.get('chat/message', messageId).toggleClass('deleted', false); }); }; return messages; });