diff --git a/public/src/app.js b/public/src/app.js index 2f6e759609..c0c5fb6332 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -1,5 +1,5 @@ "use strict"; -/*global io, templates, ajaxify, utils, bootbox, overrides, socket, config, Visibility*/ +/*global templates, translator, ajaxify, utils, bootbox, overrides, socket, config, Visibility*/ var app = app || {}; @@ -159,7 +159,7 @@ app.cacheBuster = null; } app.currentRoom = ''; }); - } + }; function highlightNavigationLink() { var path = window.location.pathname; @@ -251,12 +251,21 @@ app.cacheBuster = null; chat.focusInput(chatModal); } - if (!chat.modalExists(roomId)) { - chat.createModal({ - roomId: roomId - }, loadAndCenter); - } else { + if (chat.modalExists(roomId)) { loadAndCenter(chat.getModal(roomId)); + } else { + socket.emit('modules.chats.getUsersInRoom', {roomId: roomId}, function(err, users) { + if (err) { + return app.alertError(err.message); + } + users = users.filter(function(user) { + return user && parseInt(user.uid, 10) !== parseInt(app.user.uid, 10); + }); + chat.createModal({ + roomId: roomId, + users: users + }, loadAndCenter); + }); } }); }; @@ -266,6 +275,12 @@ app.cacheBuster = null; return app.alertError('[[error:not-logged-in]]'); } + socket.emit('modules.chats.newRoom', {touid: touid}, function(err, roomId) { + if (err) { + return app.alertError(err.message); + } + app.openChat(roomId); + }); }; var titleObj = { @@ -410,7 +425,7 @@ app.cacheBuster = null; function handleStatusChange() { $('[component="header/usercontrol"] [data-status]').off('click').on('click', function(e) { var status = $(this).attr('data-status'); - socket.emit('user.setStatus', status, function(err, data) { + socket.emit('user.setStatus', status, function(err) { if(err) { return app.alertError(err.message); } diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index b67f9dd4a9..9c04d4a84d 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -30,7 +30,7 @@ define('forum/account/header', [ }); components.get('account/chat').on('click', function() { - app.openChat($('.account-username').html(), theirid); + app.newChat(theirid); }); components.get('account/ban').on('click', banAccount); diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 4c1986be9f..9064b2ef17 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -18,6 +18,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll', } Chats.addEventListeners(); + Chats.createTagsInput(ajaxify.data.roomId, ajaxify.data.users); if (env === 'md' || env === 'lg') { Chats.resizeMainWindow(); @@ -191,6 +192,42 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll', }); }; + Chats.createTagsInput = function(roomId, users) { + var tagEl = $('.users-tag-input'); + + tagEl.tagsinput({ + confirmKeys: [13, 44], + trimValue: true + }); + + if (users && users.length) { + users.forEach(function(user) { + tagEl.tagsinput('add', user.username); + }); + } + + tagEl.on('itemAdded', function(event) { + if (event.item === app.user.username) { + return; + } + socket.emit('modules.chats.addUserToRoom', {roomId: roomId, username: event.item}, function(err) { + if (err && err.message === '[[error:no-user]]') { + tagEl.tagsinput('remove', event.item); + } + }); + }); + + tagEl.on('itemRemoved', function(event) { + socket.emit('modules.chats.removeUserFromRoom', {roomId: roomId, username: event.item}); + }); + + var input = $('.users-tag-container').find('.bootstrap-tagsinput input'); + + require(['autocomplete'], function(autocomplete) { + autocomplete.user(input); + }); + }; + Chats.switchChat = function(roomid) { ajaxify.go('chats/' + roomid); }; diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index c971a7e5fc..0ae05ec7a5 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -405,12 +405,10 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator }); } - - function openChat(button) { var post = button.parents('[data-pid]'); - app.openChat(post.attr('data-username'), post.attr('data-uid')); + app.newChat(post.attr('data-uid')); button.parents('.btn-group').find('.dropdown-toggle').click(); return false; } diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js index a66fff7264..ecf15c2ead 100644 --- a/public/src/modules/autocomplete.js +++ b/public/src/modules/autocomplete.js @@ -10,6 +10,9 @@ define('autocomplete', function() { app.loadJQueryUI(function() { input.autocomplete({ delay: 100, + open: function() { + $(this).autocomplete('widget').css('z-index', 20000); + }, select: onselect, source: function(request, response) { socket.emit('user.search', {query: request.term}, function(err, result) { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 8ccc475741..a419583ced 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -229,7 +229,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra components.get('chat/input').val(text); }); - ajaxify.go('chats/' + utils.slugify(data.username)); + ajaxify.go('chats/' + chatModal.attr('roomId')); module.close(chatModal); } @@ -240,7 +240,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra module.bringModalToTop(chatModal); if (!dragged) { - chatModal.find('#chat-message-input').focus(); + //chatModal.find('#chat-message-input').focus(); } else { dragged = false; } @@ -274,6 +274,8 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn')); + Chats.createTagsInput(data.roomId, data.users); + Chats.loadChatSince(chatModal.attr('roomId'), chatModal.find('.chat-content'), 'recent'); checkStatus(chatModal); diff --git a/src/controllers/accounts/chats.js b/src/controllers/accounts/chats.js index 30c73fcce1..63e324d1f5 100644 --- a/src/controllers/accounts/chats.js +++ b/src/controllers/accounts/chats.js @@ -14,7 +14,7 @@ chatsController.get = function(req, res, callback) { return callback(); } - messaging.getRecentChats(req.user.uid, 0, 19, function(err, recentChats) { + messaging.getRecentChats(req.uid, 0, 19, function(err, recentChats) { if (err) { return callback(err); } @@ -41,12 +41,12 @@ chatsController.get = function(req, res, callback) { async.parallel({ users: async.apply(messaging.getUsersInRoom, req.params.roomid, 0, -1), messages: async.apply(messaging.getMessages, { - uid: req.user.uid, + uid: req.uid, roomId: req.params.roomid, since: 'recent', isNew: false }), - allowed: async.apply(messaging.canMessageRoom, req.user.uid, req.params.roomid) + allowed: async.apply(messaging.canMessageRoom, req.uid, req.params.roomid) }, next); } ], function(err, data) { @@ -54,6 +54,10 @@ chatsController.get = function(req, res, callback) { return callback(err); } + data.users = data.users.filter(function(user) { + return user && parseInt(user.uid, 10) !== req.uid; + }); + var usernames = data.users.map(function(user) { return user && user.username; }).join(', '); diff --git a/src/messaging.js b/src/messaging.js index a71000854f..70e535a0d9 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -274,7 +274,9 @@ var async = require('async'), markRead: false }, function(err, teaser) { teaser = teaser[0]; - teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; + if (teaser && teaser.content) { + teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; + } next(err, teaser); }); }, next); diff --git a/src/messaging/create.js b/src/messaging/create.js index 360f800e81..da9be535a8 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -9,29 +9,6 @@ var db = require('../database'); module.exports = function(Messaging) { - - Messaging.newMessage = function(uid, toUids, content, timestamp, callback) { - var roomId; - async.waterfall([ - function (next) { - Messaging.checkContent(content, next); - }, - function (next) { - db.incrObjectField('global', 'nextChatRoomId', next); - }, - function (_roomId, next) { - roomId = _roomId; - db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamp, uid, next); - }, - function (next) { - Messaging.addUsersToRoom(uid, toUids, roomId, next); - }, - function (next) { - Messaging.sendMessage(uid, roomId, content, timestamp, next); - } - ], callback); - }; - Messaging.sendMessage = function(uid, roomId, content, timestamp, callback) { async.waterfall([ function (next) { @@ -87,7 +64,7 @@ module.exports = function(Messaging) { }, function (uids, next) { async.parallel([ - async.apply(Messaging.updateChatTime, roomId, uids, timestamp), + async.apply(Messaging.addRoomToUsers, roomId, uids, timestamp), async.apply(Messaging.addMessageToUsers, roomId, uids, mid, timestamp), async.apply(Messaging.markRead, fromuid, roomId), async.apply(Messaging.markUnread, uids, roomId) @@ -112,7 +89,7 @@ module.exports = function(Messaging) { ], callback); }; - Messaging.updateChatTime = function(roomId, uids, timestamp, callback) { + Messaging.addRoomToUsers = function(roomId, uids, timestamp, callback) { if (!uids.length) { return callback(); } diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index c9612a9e75..434a789508 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -7,6 +7,29 @@ var user = require('../user'); module.exports = function(Messaging) { + Messaging.newRoom = function(uid, toUids, callback) { + var roomId; + var now = Date.now(); + async.waterfall([ + function (next) { + db.incrObjectField('global', 'nextChatRoomId', next); + }, + function (_roomId, next) { + roomId = _roomId; + db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid, next); + }, + function (next) { + Messaging.addUsersToRoom(uid, toUids, roomId, next); + }, + function (next) { + Messaging.addRoomToUsers(roomId, [uid].concat(toUids), now, next); + }, + function (next) { + next(null, roomId); + } + ], callback); + }; + Messaging.isUserInRoom = function(uid, roomId, callback) { db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, callback); }; @@ -27,31 +50,40 @@ module.exports = function(Messaging) { }); }; - Messaging.addUsersToRoom = function(fromuid, toUids, roomId, callback) { + Messaging.addUsersToRoom = function(uid, uids, roomId, callback) { async.waterfall([ function (next) { - Messaging.isRoomOwner(fromuid, roomId, next); + Messaging.isRoomOwner(uid, roomId, next); }, function (isOwner, next) { if (!isOwner) { return next(new Error('[[error:cant-add-users-to-chat-room]]')); } var now = Date.now(); - var timestamps = toUids.map(function() { + var timestamps = uids.map(function() { return now; }); - db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, toUids, next); + db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids, next); } ], callback); }; - Messaging.leaveRoom = function(uid, roomId, callback) { + Messaging.removeUsersFromRoom = function(uid, uids, roomId, callback) { async.waterfall([ function (next) { - db.sortedSetRemove('chat:room:' + roomId + ':uids', uid, next); + Messaging.isRoomOwner(uid, roomId, next); + }, + function (isOwner, next) { + if (!isOwner) { + return next(new Error('[[error:cant-add-users-to-chat-room]]')); + } + db.sortedSetRemove('chat:room:' + roomId + ':uids', uids, next); }, function (next) { - db.sortedSetRemove('uid:' + uid + ':chat:rooms', roomId, next); + var keys = uids.map(function(uid) { + return 'uid:' + uid + ':chat:rooms'; + }); + db.sortedSetsRemove(keys, roomId, next); } ], callback); }; diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index c22f48fef0..575012955e 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -1,14 +1,13 @@ "use strict"; -var meta = require('../meta'), - Messaging = require('../messaging'), - utils = require('../../public/src/utils'), - - async = require('async'), - - server = require('./'), - - SocketModules = { +var async = require('async'); +var meta = require('../meta'); +var Messaging = require('../messaging'); +var utils = require('../../public/src/utils'); +var server = require('./'); +var user = require('../user'); + +var SocketModules = { chats: {}, sounds: {}, settings: {} @@ -39,7 +38,7 @@ SocketModules.chats.getRaw = function(socket, data, callback) { Messaging.getMessageField(data.mid, 'content', callback); }; -SocketModules.chats.newMessage = function(socket, data, callback) { +SocketModules.chats.newRoom = function(socket, data, callback) { if (!data) { return callback(new Error('[[error:invalid-data]]')); } @@ -57,15 +56,7 @@ SocketModules.chats.newMessage = function(socket, data, callback) { return callback(err || new Error('[[error:chat-restricted]]')); } - Messaging.newMessage(socket.uid, [data.touid], data.content, now, function(err, message) { - if (err) { - return callback(err); - } - - Messaging.notifyUsersInRoom(socket.uid, message.roomId, message); - - callback(); - }); + Messaging.newRoom(socket.uid, [data.touid], callback); }); }; @@ -101,6 +92,59 @@ SocketModules.chats.send = function(socket, data, callback) { }); }; +SocketModules.chats.getUsersInRoom = function(socket, data, callback) { + if (!data || !data.roomId) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.waterfall([ + function (next) { + Messaging.isUserInRoom(socket.uid, data.roomId, next); + }, + function (inRoom, next) { + if (!inRoom) { + return next(new Error('[[error:not-allowerd]]')); + } + Messaging.getUsersInRoom(data.roomId, 0, -1, next); + } + ], callback); +}; + +SocketModules.chats.addUserToRoom = function(socket, data, callback) { + if (!data || !data.roomId || !data.username) { + return callback(new Error('[[error:invalid-data]]')); + } + async.waterfall([ + function (next) { + user.getUidByUsername(data.username, next); + }, + function (uid, next) { + if (!uid) { + return next(new Error('[[error:no-user]]')); + } + Messaging.addUsersToRoom(socket.uid, [uid], data.roomId, next); + } + ], callback); +}; + +SocketModules.chats.removeUserFromRoom = function(socket, data, callback) { + if (!data || !data.roomId) { + return callback(new Error('[[error:invalid-data]]')); + } + async.waterfall([ + function (next) { + user.getUidByUsername(data.username, next); + }, + function (uid, next) { + if (!uid) { + return next(new Error('[[error:no-user]]')); + } + + Messaging.removeUsersFromRoom(socket.uid, [uid], data.roomId, next); + } + ], callback); +}; + SocketModules.chats.edit = function(socket, data, callback) { if (!data || !data.roomId) { return callback(new Error('[[error:invalid-data]]'));