From 491d376fb42aa547e4373160d066d3c7c3f3151c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Jul 2015 16:42:48 -0400 Subject: [PATCH] closes #2605 --- public/src/client/groups/details.js | 85 ++++++++++++++++++++++++++++- src/controllers/groups.js | 4 +- src/groups.js | 66 +++++++++++++--------- src/groups/ownership.js | 8 +++ src/groups/search.js | 43 ++++++++++++++- src/socket.io/groups.js | 14 +++++ 6 files changed, 192 insertions(+), 28 deletions(-) diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 1f7bd3f2df..118dbcddda 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -1,11 +1,13 @@ "use strict"; /* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */ -define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components) { +define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescroll', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components, infinitescroll) { var Details = { cover: {} }; + var searchInterval; + Details.init = function() { var detailsPage = components.get('groups/container'), settingsFormEl = detailsPage.find('form'); @@ -15,6 +17,9 @@ define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/ Details.initialiseCover(); } + handleMemberSearch(); + handleMemberInfiniteScroll(); + components.get('groups/activity').find('.content img').addClass('img-responsive'); detailsPage.on('click', '[data-action]', function() { @@ -280,5 +285,83 @@ define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/ }); }; + function handleMemberSearch() { + $('[component="groups/members/search"]').on('keyup', function() { + var query = $(this).val(); + if (searchInterval) { + clearInterval(searchInterval); + searchInterval = 0; + } + + searchInterval = setTimeout(function() { + socket.emit('groups.searchMembers', {groupName: ajaxify.variables.get('group_name'), query: query}, function(err, results) { + if (err) { + return app.alertError(err.message); + } + + infinitescroll.parseAndTranslate('groups/details', 'members', { + group: { + members: results.users, + isOwner: ajaxify.variables.get('is_owner') === 'true' + } + }, function(html) { + $('[component="groups/members"] tbody').html(html); + }); + }); + }, 250); + }) + } + + function handleMemberInfiniteScroll() { + $('[component="groups/members"] tbody').on('scroll', function() { + var $this = $(this); + var bottom = ($this[0].scrollHeight - $this.height()) * 0.9; + if ($this.scrollTop() > bottom) { + loadMoreMembers(); + } + }); + } + + function loadMoreMembers() { + var members = $('[component="groups/members"]'); + if (members.attr('loading')) { + return; + } + members.attr('loading', 1); + socket.emit('groups.loadMoreMembers', { + groupName: ajaxify.variables.get('group_name'), + after: members.attr('data-nextstart') + }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + + if (data && data.users.length) { + onMembersLoaded(data.users, function() { + members.removeAttr('loading'); + members.attr('data-nextstart', data.nextStart); + }); + } else { + members.removeAttr('loading'); + } + }); + } + + function onMembersLoaded(users, callback) { + users = users.filter(function(user) { + return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length; + }); + + infinitescroll.parseAndTranslate('groups/details', 'members', { + group: { + members: users, + isOwner: ajaxify.variables.get('is_owner') === 'true' + } + }, function(html) { + $('[component="groups/members"] tbody').append(html); + callback(); + }); + } + return Details; }); \ No newline at end of file diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 8d7ddac3e2..84a3e25e2d 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -79,7 +79,9 @@ groupsController.details = function(req, res, next) { async.parallel({ group: function(next) { groups.get(res.locals.groupName, { - uid: req.uid + uid: req.uid, + truncateUserList: true, + userListCount: 20 }, next); }, posts: function(next) { diff --git a/src/groups.js b/src/groups.js index 8db9afe80b..b08c6eed3c 100644 --- a/src/groups.js +++ b/src/groups.js @@ -115,27 +115,18 @@ var async = require('async'), } options.escape = options.hasOwnProperty('escape') ? options.escape : true; + var stop = -1; async.parallel({ base: function (next) { db.getObject('group:' + groupName, next); }, - owners: function (next) { - async.waterfall([ - function(next) { - db.getSetMembers('group:' + groupName + ':owners', next); - }, - function(uids, next) { - user.getUsers(uids, options.uid, next); - } - ], next); - }, members: function (next) { - var stop = -1; if (options.truncateUserList) { stop = (parseInt(options.userListCount, 10) || 4) - 1; } - user.getUsersFromSet('group:' + groupName + ':members', options.uid, 0, stop, next); + + Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next); }, pending: function (next) { async.waterfall([ @@ -164,19 +155,6 @@ var async = require('async'), results.base['cover:position'] = '50% 50%'; } - var ownerUids = []; - results.owners.forEach(function(user) { - if (user) { - user.isOwner = true; - ownerUids.push(user.uid.toString()); - } - }); - - results.members = results.members.filter(function(user, index, array) { - return user && user.uid && ownerUids.indexOf(user.uid.toString()) === -1; - }); - results.members = results.owners.concat(results.members); - plugins.fireHook('filter:parse.raw', results.base.description, function(err, descriptionParsed) { if (err) { return callback(err); @@ -190,6 +168,7 @@ var async = require('async'), results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true; results.base.createtimeISO = utils.toISOString(results.base.createtime); results.base.members = results.members; + results.base.membersNextStart = stop + 1; results.base.pending = results.pending.filter(Boolean); results.base.deleted = !!parseInt(results.base.deleted, 10); results.base.hidden = !!parseInt(results.base.hidden, 10); @@ -207,6 +186,43 @@ var async = require('async'), }); }; + Groups.getOwnersAndMembers = function(groupName, uid, start, stop, callback) { + async.parallel({ + owners: function (next) { + async.waterfall([ + function(next) { + db.getSetMembers('group:' + groupName + ':owners', next); + }, + function(uids, next) { + user.getUsers(uids, uid, next); + } + ], next); + }, + members: function (next) { + user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + var ownerUids = []; + results.owners.forEach(function(user) { + if (user) { + user.isOwner = true; + ownerUids.push(user.uid.toString()); + } + }); + + results.members = results.members.filter(function(user, index, array) { + return user && user.uid && ownerUids.indexOf(user.uid.toString()) === -1; + }); + results.members = results.owners.concat(results.members); + + callback(null, results.members); + }); + }; + Groups.escapeGroupData = function(group) { if (group) { group.nameEncoded = encodeURIComponent(group.name); diff --git a/src/groups/ownership.js b/src/groups/ownership.js index fa4beb70ba..4af6126aef 100644 --- a/src/groups/ownership.js +++ b/src/groups/ownership.js @@ -13,6 +13,14 @@ module.exports = function(Groups) { db.isSetMember('group:' + groupName + ':owners', uid, callback); }; + Groups.ownership.isOwners = function(uids, groupName, callback) { + if (!Array.isArray(uids)) { + return callback(null, []); + } + + db.isSetMembers('group:' + groupName + ':owners', uids, callback); + }; + Groups.ownership.grant = function(toUid, groupName, callback) { // Note: No ownership checking is done here on purpose! db.setAdd('group:' + groupName + ':owners', toUid, callback); diff --git a/src/groups/search.js b/src/groups/search.js index d674b29dba..1491129f30 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -87,7 +87,48 @@ module.exports = function(Groups) { ], callback); } + if (!data.query) { + Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19, function(err, users) { + if (err) { + return callback(err); + } + callback(null, {users: users}); + }); + return; + } + data.findUids = findUids; - user.search(data, callback); + var results; + async.waterfall([ + function(next) { + user.search(data, next); + }, + function(_results, next) { + results = _results; + var uids = results.users.map(function(user) { + return user && user.uid; + }); + Groups.ownership.isOwners(uids, data.groupName, next); + }, + function(isOwners, next) { + + results.users.forEach(function(user, index) { + if (user) { + user.isOwner = isOwners[index]; + } + }); + + results.users.sort(function(a,b) { + if (a.isOwner && !b.isOwner) { + return -1; + } else if (!a.isOwner && b.isOwner) { + return 1; + } else { + return 0; + } + }) + next(null, results); + } + ], callback); }; }; diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 69ce6a367c..671777ea95 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -244,6 +244,20 @@ SocketGroups.searchMembers = function(socket, data, callback) { groups.searchMembers(data, callback); }; +SocketGroups.loadMoreMembers = function(socket, data, callback) { + if (!data || !data.groupName || !parseInt(data.after, 10)) { + return callback(new Error('[[error:invalid-data]]')); + } + data.after = parseInt(data.after, 10); + user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function(err, users) { + if (err) { + return callback(err); + } + + callback(null, {users: users, nextStart: data.after + 10}); + }); +}; + SocketGroups.kick = function(socket, data, callback) { if (!data) { return callback(new Error('[[error:invalid-data]]'));