diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index 7a91ef40df..619dbc9ec0 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -1,17 +1,15 @@
 'use strict';
 
-/* globals define, app, ajaxify, utils, socket, templates, bootbox */
+/* globals define, app, ajaxify, utils, socket, templates */
 
 define('forum/chats', [
 	'components',
-	'string',
-	'sounds',
-	'forum/infinitescroll',
 	'translator',
 	'mousetrap',
 	'forum/chats/recent',
-	'forum/chats/search'
-], function(components, S, sounds, infinitescroll, translator, mousetrap, recentChats, search) {
+	'forum/chats/search',
+	'forum/chats/messages'
+], function(components, translator, mousetrap, recentChats, search, messages) {
 	var Chats = {
 		initialised: false
 	};
@@ -35,7 +33,7 @@ define('forum/chats', [
 			Chats.addHotkeys();
 		}
 
-		Chats.scrollToBottom($('.expanded-chat ul'));
+		messages.scrollToBottom($('.expanded-chat ul'));
 
 		Chats.initialised = true;
 
@@ -77,16 +75,7 @@ define('forum/chats', [
 			});
 		});
 
-		components.get('chat/messages')
-			.on('click', '[data-action="edit"]', function() {
-				var messageId = $(this).parents('[data-mid]').attr('data-mid');
-				var inputEl = components.get('chat/input');
-				Chats.prepEdit(inputEl, messageId, ajaxify.data.roomId);
-			})
-			.on('click', '[data-action="delete"]', function() {
-				var messageId = $(this).parents('[data-mid]').attr('data-mid');
-				Chats.delete(messageId, ajaxify.data.roomId);
-			});
+		Chats.addEditDeleteHandler(components.get('chat/messages'), ajaxify.data.roomId);
 
 		recentChats.init();
 
@@ -94,6 +83,17 @@ define('forum/chats', [
 		Chats.addRenameHandler(ajaxify.data.roomId, $('[component="chat/room/name"]'));
 	};
 
+	Chats.addEditDeleteHandler = function(element, roomId) {
+		element.on('click', '[data-action="edit"]', function() {
+			var messageId = $(this).parents('[data-mid]').attr('data-mid');
+			var inputEl = components.get('chat/input');
+			messages.prepEdit(inputEl, messageId, roomId);
+		}).on('click', '[data-action="delete"]', function() {
+			var messageId = $(this).parents('[data-mid]').attr('data-mid');
+			messages.delete(messageId, roomId);
+		});
+	};
+
 	Chats.addHotkeys = function() {
 		mousetrap.bind('ctrl+up', function() {
 			var activeContact = $('.chats-list .bg-primary'),
@@ -118,49 +118,11 @@ define('forum/chats', [
 				var lastMid = message.attr('data-mid');
 				var inputEl = components.get('chat/input');
 
-				Chats.prepEdit(inputEl, lastMid, ajaxify.data.roomId);
-			}
-		});
-	};
-
-	Chats.prepEdit = function(inputEl, messageId, roomId) {
-		socket.emit('modules.chats.getRaw', { mid: messageId, roomId: roomId }, function(err, raw) {
-			if (err) {
-				return app.alertError(err.message);
-			}
-			// 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);
+				messages.prepEdit(inputEl, lastMid, ajaxify.data.roomId);
 			}
 		});
 	};
 
-	Chats.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 app.alertError(err.message);
-					}
-
-					components.get('chat/message', messageId).slideUp('slow', function() {
-						$(this).remove();
-					});
-				});
-			});
-		});
-	};
-
 	Chats.addSinceHandler = function(roomId, chatContentEl, sinceEl) {
 		sinceEl.on('click', function() {
 			var since = $(this).attr('data-since');
@@ -193,25 +155,15 @@ define('forum/chats', [
 	};
 
 	Chats.addSendHandlers = function(roomId, inputEl, sendEl) {
-
 		inputEl.off('keypress').on('keypress', function(e) {
 			if (e.which === 13 && !e.shiftKey) {
-				Chats.sendMessage(roomId, inputEl);
+				messages.sendMessage(roomId, inputEl);
 				return false;
 			}
 		});
 
-		inputEl.off('keyup').on('keyup', function() {
-			var val = !!$(this).val();
-			if ((val && $(this).attr('data-typing') === 'true') || (!val && $(this).attr('data-typing') === 'false')) {
-				return;
-			}
-
-			$(this).attr('data-typing', val);
-		});
-
 		sendEl.off('click').on('click', function() {
-			Chats.sendMessage(roomId, inputEl);
+			messages.sendMessage(roomId, inputEl);
 			inputEl.focus();
 			return false;
 		});
@@ -320,14 +272,14 @@ define('forum/chats', [
 		if (!roomId) {
 			return;
 		}
-		socket.emit('modules.chats.get', {roomId: roomId, since: since}, function(err, messages) {
+		socket.emit('modules.chats.get', {roomId: roomId, since: since}, function(err, messageData) {
 			if (err) {
 				return app.alertError(err.message);
 			}
 
 			chatContentEl.find('[component="chat/message"]').remove();
 
-			Chats.appendChatMessage(chatContentEl, messages);
+			messages.appendChatMessage(chatContentEl, messageData);
 		});
 	};
 
@@ -341,34 +293,13 @@ define('forum/chats', [
 		});
 	};
 
-	Chats.appendChatMessage = function(chatContentEl, data) {
-
-		var lastSpeaker = parseInt(chatContentEl.find('.chat-message').last().attr('data-uid'), 10);
-		if (!Array.isArray(data)) {
-			data.newSet = lastSpeaker !== data.fromuid;
-		}
-
-		Chats.parseMessage(data, function(html) {
-			onMessagesParsed(chatContentEl, html);
-		});
-	};
-
-	function onMessagesParsed(chatContentEl, html) {
-		var newMessage = $(html);
-
-		newMessage.appendTo(chatContentEl);
-		newMessage.find('.timeago').timeago();
-		newMessage.find('img:not(.not-responsive)').addClass('img-responsive');
-		Chats.scrollToBottom(chatContentEl);
-	}
-
 	Chats.addSocketListeners = function() {
 		socket.on('event:chats.receive', function(data) {
 			if (parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10)) {
 				newMessage = data.self === 0;
 				data.message.self = data.self;
 
-				Chats.appendChatMessage($('.expanded-chat .chat-content'), data.message);
+				messages.appendChatMessage($('.expanded-chat .chat-content'), data.message);
 			} else {
 				if (ajaxify.currentPage.startsWith("chats")) {
 					var roomEl = $('[data-roomid=' + data.roomId + ']');
@@ -393,29 +324,13 @@ define('forum/chats', [
 			app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status);
 		});
 
-		Chats.onChatEdit();
+		messages.onChatMessageEdit();
 
 		socket.on('event:chats.roomRename', function(data) {
 			$('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
 		});
 	};
 
-	Chats.onChatEdit = function() {
-		socket.on('event:chats.edit', function(data) {
-			data.messages.forEach(function(message) {
-				var self = parseInt(message.fromuid, 10) === parseInt(app.user.uid);
-				message.self = self ? 1 : 0;
-				Chats.parseMessage(message, function(html) {
-				    var body = components.get('chat/message', message.messageId);
-					if (body.length) {
-						body.replaceWith(html);
-						components.get('chat/message', message.messageId).find('.timeago').timeago();
-					}
-				});
-			});
-		});
-	};
-
 	Chats.resizeMainWindow = function() {
 		var	messagesList = $('.expanded-chat .chat-content');
 
@@ -434,56 +349,6 @@ define('forum/chats', [
 		Chats.setActive();
 	};
 
-	Chats.sendMessage = function(roomId, inputEl) {
-		var msg = inputEl.val();
-		var mid = inputEl.attr('data-mid');
-
-		if (msg.length > ajaxify.data.maximumChatMessageLength) {
-			return app.alertError('[[error:chat-message-too-long]]');
-		}
-
-		if (!msg.length) {
-			return;
-		}
-
-		inputEl.val('');
-		inputEl.removeAttr('data-mid');
-
-		if (!mid) {
-			socket.emit('modules.chats.send', {
-				roomId: roomId,
-				message: msg
-			}, function(err) {
-				if (err) {
-					if (err.message === '[[error:email-not-confirmed-chat]]') {
-						return app.showEmailConfirmWarning(err);
-					}
-					return app.alertError(err.message);
-				}
-
-				sounds.play('chat-outgoing');
-			});
-		} else {
-			socket.emit('modules.chats.edit', {
-				roomId: roomId,
-				mid: mid,
-				message: msg
-			}, function(err) {
-				if (err) {
-					return app.alertError(err.message);
-				}
-			});
-		}
-	};
-
-	Chats.scrollToBottom = function(containerEl) {
-		if (containerEl.length) {
-			containerEl.scrollTop(
-				containerEl[0].scrollHeight - containerEl.height()
-			);
-		}
-	};
-
 	Chats.setActive = function() {
 		if (ajaxify.data.roomId) {
 			socket.emit('modules.chats.markRead', ajaxify.data.roomId);
@@ -493,13 +358,6 @@ define('forum/chats', [
 		$('.chats-list li[data-roomid="' + ajaxify.data.roomId + '"]').addClass('bg-primary');
 	};
 
-	Chats.parseMessage = function(data, callback) {
-		templates.parse('partials/chat_message' + (Array.isArray(data) ? 's' : ''), {
-			messages: data
-		}, function(html) {
-			translator.translate(html, callback);
-		});
-	};
 
 	return Chats;
 });
diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js
new file mode 100644
index 0000000000..ab444e36a4
--- /dev/null
+++ b/public/src/client/chats/messages.js
@@ -0,0 +1,145 @@
+'use strict';
+
+/* globals define, socket, app, ajaxify, templates, bootbox */
+
+define('forum/chats/messages', ['components', 'sounds', 'translator'], function(components, sounds, translator) {
+
+	var messages = {};
+
+	messages.sendMessage = function(roomId, inputEl) {
+		var msg = inputEl.val();
+		var mid = inputEl.attr('data-mid');
+
+		if (msg.length > ajaxify.data.maximumChatMessageLength) {
+			return app.alertError('[[error:chat-message-too-long]]');
+		}
+
+		if (!msg.length) {
+			return;
+		}
+
+		inputEl.val('');
+		inputEl.removeAttr('data-mid');
+
+		if (!mid) {
+			socket.emit('modules.chats.send', {
+				roomId: roomId,
+				message: msg
+			}, function(err) {
+				if (err) {
+					if (err.message === '[[error:email-not-confirmed-chat]]') {
+						return app.showEmailConfirmWarning(err);
+					}
+					return app.alertError(err.message);
+				}
+
+				sounds.play('chat-outgoing');
+			});
+		} else {
+			socket.emit('modules.chats.edit', {
+				roomId: roomId,
+				mid: mid,
+				message: msg
+			}, function(err) {
+				if (err) {
+					return app.alertError(err.message);
+				}
+			});
+		}
+	};
+
+	messages.appendChatMessage = function(chatContentEl, data) {
+
+		var lastSpeaker = parseInt(chatContentEl.find('.chat-message').last().attr('data-uid'), 10);
+		if (!Array.isArray(data)) {
+			data.newSet = lastSpeaker !== data.fromuid;
+		}
+
+		messages.parseMessage(data, function(html) {
+			onMessagesParsed(chatContentEl, html);
+		});
+	};
+
+	function onMessagesParsed(chatContentEl, html) {
+		var newMessage = $(html);
+
+		newMessage.appendTo(chatContentEl);
+		newMessage.find('.timeago').timeago();
+		newMessage.find('img:not(.not-responsive)').addClass('img-responsive');
+		messages.scrollToBottom(chatContentEl);
+	}
+
+
+	messages.parseMessage = function(data, callback) {
+		templates.parse('partials/chat_message' + (Array.isArray(data) ? 's' : ''), {
+			messages: data
+		}, function(html) {
+			translator.translate(html, callback);
+		});
+	};
+
+
+	messages.scrollToBottom = function(containerEl) {
+		if (containerEl.length) {
+			containerEl.scrollTop(
+				containerEl[0].scrollHeight - containerEl.height()
+			);
+		}
+	};
+
+	messages.prepEdit = function(inputEl, messageId, roomId) {
+		socket.emit('modules.chats.getRaw', { mid: messageId, roomId: roomId }, function(err, raw) {
+			if (err) {
+				return app.alertError(err.message);
+			}
+			// 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);
+			}
+		});
+	};
+
+	messages.onChatMessageEdit = function() {
+		socket.on('event:chats.edit', function(data) {
+			data.messages.forEach(function(message) {
+				var self = parseInt(message.fromuid, 10) === parseInt(app.user.uid);
+				message.self = self ? 1 : 0;
+				messages.parseMessage(message, function(html) {
+				    var body = components.get('chat/message', message.messageId);
+					if (body.length) {
+						body.replaceWith(html);
+						components.get('chat/message', message.messageId).find('.timeago').timeago();
+					}
+				});
+			});
+		});
+	};
+
+	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 app.alertError(err.message);
+					}
+
+					components.get('chat/message', messageId).slideUp('slow', function() {
+						$(this).remove();
+					});
+				});
+			});
+		});
+	};
+
+	return messages;
+});
\ No newline at end of file
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index 8c3e8c7962..3bc391cfd1 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -1,7 +1,15 @@
 "use strict";
 /* globals app, define, socket, templates, utils, ajaxify */
 
-define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'translator'], function(components, taskbar, S, sounds, Chats, translator) {
+define('chat', [
+	'components',
+	'taskbar',
+	'string',
+	'sounds',
+	'forum/chats',
+	'forum/chats/messages',
+	'translator'
+], function(components, taskbar, S, sounds, Chats, ChatsMessages, translator) {
 
 	var module = {};
 	var newMessage = false;
@@ -35,11 +43,11 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 			if (module.modalExists(data.roomId)) {
 				var modal = module.getModal(data.roomId);
 
-				Chats.appendChatMessage(modal.find('.chat-content'), data.message);
+				ChatsMessages.appendChatMessage(modal.find('.chat-content'), data.message);
 
 				if (modal.is(':visible')) {
 					taskbar.updateActive(modal.attr('UUID'));
-					Chats.scrollToBottom(modal.find('.chat-content'));
+					ChatsMessages.scrollToBottom(modal.find('.chat-content'));
 				} else {
 					module.toggleNew(modal.attr('UUID'), true, true);
 				}
@@ -83,7 +91,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 			module.getModal(data.roomId).find('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
 		});
 
-		Chats.onChatEdit();
+		ChatsMessages.onChatMessageEdit();
 	};
 
 	module.loadChatsDropdown = function(chatsListEl) {
@@ -258,16 +266,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 					}
 				});
 
-				chatModal.find('[component="chat/messages"]')
-					.on('click', '[data-action="edit"]', function() {
-						var messageId = $(this).parents('[data-mid]').attr('data-mid');
-						var inputEl = chatModal.find('[component="chat/input"]');
-						Chats.prepEdit(inputEl, messageId, data.roomId);
-					})
-					.on('click', '[data-action="delete"]', function() {
-						var messageId = $(this).parents('[data-mid]').attr('data-mid');
-						Chats.delete(messageId, data.roomId);
-					});
+				Chats.addEditDeleteHandler(chatModal.find('[component="chat/messages"]'), data.roomId);
 
 				chatModal.find('[component="chat/controlsToggle"]').on('click', function() {
 					var messagesEl = chatModal.find('[component="chat/messages"]');
@@ -308,7 +307,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 		chatModal.find('#chat-message-input').focus();
 	};
 
-	module.close = function(chatModal, silent) {
+	module.close = function(chatModal) {
 		clearInterval(chatModal.attr('intervalId'));
 		chatModal.attr('intervalId', 0);
 		chatModal.remove();
@@ -340,7 +339,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 		chatModal.removeClass('hide');
 		checkStatus(chatModal);
 		taskbar.updateActive(uuid);
-		Chats.scrollToBottom(chatModal.find('.chat-content'));
+		ChatsMessages.scrollToBottom(chatModal.find('.chat-content'));
 		module.bringModalToTop(chatModal);
 		module.focusInput(chatModal);
 		socket.emit('modules.chats.markRead', chatModal.attr('roomId'));
@@ -367,11 +366,11 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 	};
 
 	module.calculateChatListHeight = function(modalEl) {
-		var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight(),
-			padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10),
-			contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10),
-			sinceHeight = modalEl.find('.since-bar').outerHeight(true),
-			inputGroupHeight = modalEl.find('.input-group').outerHeight();
+		var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight();
+		var padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10);
+		var contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10);
+		var sinceHeight = modalEl.find('.since-bar').outerHeight(true);
+		var inputGroupHeight = modalEl.find('.input-group').outerHeight();
 
 		return totalHeight - padding - contentMargin - inputGroupHeight;
 	};