diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index eaf15bff36..211d6907ab 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -2,6 +2,8 @@ "banned": "Banned", "offline": "Offline", "username": "User Name", + "joindate": "Join Date", + "postcount": "Post Count", "email": "Email", "confirm_email": "Confirm Email", diff --git a/public/language/en_GB/users.json b/public/language/en_GB/users.json index fa46c52cdd..683ad7e155 100644 --- a/public/language/en_GB/users.json +++ b/public/language/en_GB/users.json @@ -5,5 +5,8 @@ "search": "Search", "enter_username": "Enter a username to search", "load_more": "Load More", - "users-found-search-took": "%1 user(s) found! Search took %2 ms." + "users-found-search-took": "%1 user(s) found! Search took %2 ms.", + "filter-by": "Filter By", + "online-only": "Online only", + "picture-only": "Picture only" } \ No newline at end of file diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index 61e1e3b0f8..4ab72c8570 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -150,7 +150,7 @@ define('admin/manage/groups', [ var searchText = groupDetailsSearch.val(), foundUser; - socket.emit('admin.user.search', {type: 'username', query:searchText}, function(err, results) { + socket.emit('admin.user.search', {query: searchText}, function(err, results) { if (!err && results && results.users.length > 0) { var numResults = results.users.length, x; if (numResults > 4) { diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 75ab7e224c..e2b5a8f408 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -237,7 +237,7 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) timeoutId = setTimeout(function() { $('.fa-spinner').removeClass('hidden'); - socket.emit('admin.user.search', {type: type, query: $this.val()}, function(err, data) { + socket.emit('admin.user.search', {searchBy: [type], query: $this.val()}, function(err, data) { if (err) { return app.alertError(err.message); } diff --git a/public/src/client/users.js b/public/src/client/users.js index 67d51dc052..c19c69afe6 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -86,65 +86,93 @@ define('forum/users', function() { function handleSearch() { var timeoutId = 0; - var lastSearch = null; $('#search-user').on('keyup', function() { - if (timeoutId !== 0) { + if (timeoutId) { clearTimeout(timeoutId); timeoutId = 0; } - timeoutId = setTimeout(function() { - function reset() { - notify.html(''); - notify.parent().removeClass('btn-warning label-warning btn-success label-success'); - } - var username = $('#search-user').val(); - var notify = $('#user-notfound-notify'); - - if (username === '') { - notify.html(''); - notify.parent().removeClass('btn-warning label-warning btn-success label-success'); - return; - } - - if (lastSearch === username) { - return; - } - lastSearch = username; - - notify.html(''); - - socket.emit('user.search', {query: username, by: $('.search select').val()}, function(err, data) { - if (err) { - reset(); - return app.alertError(err.message); - } + timeoutId = setTimeout(doSearch, 250); + }); - if (!data) { - return reset(); - } + $('.search select, .search .checkbox input').on('change', function() { + console.log('doing search'); + doSearch(); + }); - templates.parse('users', 'users', data, function(html) { - translator.translate(html, function(translated) { - $('#users-container').html(translated); - - if (!data.users.length) { - translator.translate('[[error:no-user]]', function(translated) { - notify.html(translated); - notify.parent().addClass('btn-warning label-warning'); - }); - } else { - translator.translate('[[users:users-found-search-took, ' + data.users.length + ', ' + data.timing + ']]', function(translated) { - notify.html(translated); - notify.parent().addClass('btn-success label-success'); - }); - } - }); - }); + $('.pagination').on('click', 'a', function() { + console.log('loading page', $(this).attr('data-page')); + doSearch($(this).attr('data-page')); + return false; + }) + } + + function doSearch(page) { + function reset() { + notify.html(''); + notify.parent().removeClass('btn-warning label-warning btn-success label-success'); + } + + var username = $('#search-user').val(); + var notify = $('#user-notfound-notify'); + page = page || 1; + if (!username) { + notify.html(''); + notify.parent().removeClass('btn-warning label-warning btn-success label-success'); + return; + } + + notify.html(''); + var filters = []; + $('.user-filter').each(function() { + var $this = $(this); + if($this.is(':checked')) { + filters.push({ + field:$this.attr('data-filter-field'), + type: $this.attr('data-filter-type'), + value: $this.attr('data-filter-value') }); + } + }); + + socket.emit('user.search', { + query: username, + page: page, + searchBy: ['username', 'fullname'], + sortBy: $('.search select').val(), + filterBy: filters + }, function(err, data) { + if (err) { + reset(); + return app.alertError(err.message); + } + + if (!data) { + return reset(); + } + + templates.parse('users', 'pages', data, function(html) { + $('.pagination').html(html); + }); + + templates.parse('users', 'users', data, function(html) { + translator.translate(html, function(translated) { + $('#users-container').html(translated); - }, 250); + if (!data.users.length) { + translator.translate('[[error:no-user]]', function(translated) { + notify.html(translated); + notify.parent().removeClass('btn-success label-success').addClass('btn-warning label-warning'); + }); + } else { + translator.translate('[[users:users-found-search-took, ' + data.matchCount + ', ' + data.timing + ']]', function(translated) { + notify.html(translated); + notify.parent().removeClass('btn-warning label-warning').addClass('btn-success label-success'); + }); + } + }); + }); }); } diff --git a/src/controllers/users.js b/src/controllers/users.js index 626d69da4a..107c64a166 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -4,6 +4,7 @@ var usersController = {}; var async = require('async'), user = require('../user'), + meta = require('../meta'), db = require('../database'); usersController.getOnlineUsers = function(req, res, next) { @@ -59,41 +60,57 @@ usersController.getUsersSortedByJoinDate = function(req, res, next) { }; function getUsers(set, res, next) { + getUsersAndCount(set, 50, function(err, data) { + if (err) { + return next(err); + } + var userData = { + search_display: 'hidden', + loadmore_display: data.count > 50 ? 'block' : 'hide', + users: data.users, + show_anon: 'hide' + }; + + res.render('users', userData); + }); +} + +function getUsersAndCount(set, count, callback) { async.parallel({ users: function(next) { - user.getUsersFromSet(set, 0, 49, next); + user.getUsersFromSet(set, 0, count - 1, next); }, count: function(next) { db.getObjectField('global', 'userCount', next); } }, function(err, results) { if (err) { - return next(err); + return callback(err); } results.users = results.users.filter(function(user) { return user && parseInt(user.uid, 10); }); - var userData = { - search_display: 'hidden', - loadmore_display: results.count > 50 ? 'block' : 'hide', - users: results.users, - show_anon: 'hide' - }; - - res.render('users', userData); + callback(null, {users: results.users, count: results.count}); }); } usersController.getUsersForSearch = function(req, res, next) { - var data = { - search_display: 'block', - loadmore_display: 'hidden', - users: [], - show_anon: 'hide' - }; - - res.render('users', data); + var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20; + getUsersAndCount('users:joindate', resultsPerPage, function(err, data) { + if (err) { + return next(err); + } + + var result = { + search_display: 'block', + loadmore_display: 'hidden', + users: data.users, + show_anon: 'hide' + }; + + res.render('users', result); + }); }; diff --git a/src/posts/user.js b/src/posts/user.js index fcfcd8643b..555b60e46d 100644 --- a/src/posts/user.js +++ b/src/posts/user.js @@ -32,7 +32,7 @@ module.exports = function(Posts) { var userData = results.userData; for(var i=0; i resultsPerPage) { + pageCount = Math.ceil(matchCount / resultsPerPage); + var currentPage = Math.max(1, Math.ceil((start + 1) / resultsPerPage)); + for(var i=1; i<=pageCount; ++i) { + pages.push({page: i, active: i === currentPage}); + } + } + + var diff = process.hrtime(startTime); + var timing = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(1); + next(null, { + timing: timing, + users: userData, + matchCount: matchCount, + pages: pages + }); + } + ], callback); + }; - db.getObject(key, function(err, hash) { - if (err || !hash) { - return callback(null, {timing: 0, users:[]}); + function findUids(query, keys, startsWith, callback) { + db.getObjects(keys, function(err, hashes) { + if (err || !hashes) { + return callback(err, []); } + hashes = hashes.filter(Boolean); + query = query.toLowerCase(); - var values = Object.keys(hash); var uids = []; + var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20; + var hardCap = resultsPerPage * 10; - for(var i=0; i= hardCap) { + break; + } } - } else if (values[i].toLowerCase().indexOf(query) !== -1) { - uids.push(values[i]); + } + + if (uids.length >= hardCap) { + break; } } - uids = uids.slice(0, 20) - .sort(function(a, b) { - return a > b; - }) - .map(function(username) { - return hash[username]; + if (hashes.length > 1) { + uids = uids.filter(function(uid, index, array) { + return array.indexOf(uid) === index; }); + } + + callback(null, uids); + }); + } + + function filterAndSortUids(uids, filterBy, sortBy, callback) { + sortBy = sortBy || 'joindate'; - User.getUsers(uids, function(err, userdata) { - if (err) { - return callback(err); + var fields = filterBy.map(function(filter) { + return filter.field; + }).concat(['uid', sortBy]).filter(function(field, index, array) { + return array.indexOf(field) === index; + }); + + async.parallel({ + userData: function(next) { + user.getMultipleUserFields(uids, fields, next); + }, + isOnline: function(next) { + if (fields.indexOf('status') !== -1) { + require('../socket.io').isUsersOnline(uids, next); + } else { + next(); } + } + }, function(err, results) { + if (err) { + return callback(err); + } + var userData = results.userData; - var diff = process.hrtime(start); - var timing = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(1); - callback(null, {timing: timing, users: userdata}); + if (results.isOnline) { + userData.forEach(function(userData, index) { + userData.status = user.getStatus(userData.status, results.isOnline[index]); + }); + } + + userData = filterUsers(userData, filterBy); + + sortUsers(userData, sortBy); + + uids = userData.map(function(user) { + return user && user.uid; }); + callback(null, uids); }); - }; + } + + function filterUsers(userData, filterBy) { + function passesFilter(user, filter) { + if (!user || !filter) { + return false; + } + var userValue = user[filter.field]; + if (filter.type === '=') { + return userValue === filter.value; + } else if (filter.type === '!=') { + return userValue !== filter.value; + } + return false; + } + + if (!filterBy.length) { + return userData; + } + + return userData.filter(function(user) { + for(var i=0; i user2[sortBy]) { + return 1; + } + return 0; + } + }); + } function searchByIP(ip, callback) { var start = process.hrtime(); diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl index b76f04476e..5f1bfd11f9 100644 --- a/src/views/admin/settings/user.tpl +++ b/src/views/admin/settings/user.tpl @@ -141,4 +141,17 @@ + + User Search + + + + Number of results to display + + + + + + + \ No newline at end of file