From 84f88a6f15126250bdfcf666c20be28de4a0d45a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 24 Aug 2016 14:47:01 +0300 Subject: [PATCH] refactor user search use pagination on results removed infinite scroll changed the term and section to use the query param as well pagination urls respect search --- public/src/client/account/followers.js | 34 +----- public/src/client/account/following.js | 6 +- public/src/client/search.js | 17 ++- public/src/client/users.js | 143 +++++++++---------------- public/src/modules/search.js | 15 +-- src/controllers/accounts/follow.js | 20 ++-- src/controllers/accounts/helpers.js | 14 ++- src/controllers/search.js | 4 +- src/controllers/users.js | 79 +++++++++++--- src/routes/index.js | 27 ++--- src/socket.io/user.js | 45 -------- src/user/search.js | 11 +- 12 files changed, 180 insertions(+), 235 deletions(-) diff --git a/public/src/client/account/followers.js b/public/src/client/account/followers.js index 91d3f4daaa..8a2d15f0b0 100644 --- a/public/src/client/account/followers.js +++ b/public/src/client/account/followers.js @@ -1,43 +1,13 @@ 'use strict'; -/* globals define, socket, utils */ +/* globals define */ -define('forum/account/followers', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) { +define('forum/account/followers', ['forum/account/header'], function(header) { var Followers = {}; Followers.init = function() { header.init(); - - infinitescroll.init(function(direction) { - Followers.loadMore(direction, 'account/followers', 'followers:' + ajaxify.data.uid); - }); }; - Followers.loadMore = function(direction, tpl, set) { - if (direction < 0) { - return; - } - - infinitescroll.loadMore('user.loadMore', { - set: set, - after: $('#users-container').attr('data-nextstart') - }, function(data, done) { - if (data.users && data.users.length) { - onUsersLoaded(tpl, data.users, done); - $('#users-container').attr('data-nextstart', data.nextStart); - } else { - done(); - } - }); - }; - - function onUsersLoaded(tpl, users, callback) { - app.parseAndTranslate(tpl, 'users', {users: users}, function(html) { - $('#users-container').append(html); - utils.addCommasToNumbers(html.find('.formatted-number')); - callback(); - }); - } - return Followers; }); diff --git a/public/src/client/account/following.js b/public/src/client/account/following.js index 8e421b1892..04230fb979 100644 --- a/public/src/client/account/following.js +++ b/public/src/client/account/following.js @@ -2,15 +2,11 @@ /* globals define */ -define('forum/account/following', ['forum/account/header', 'forum/infinitescroll', 'forum/account/followers'], function(header, infinitescroll, followers) { +define('forum/account/following', ['forum/account/header'], function(header) { var Following = {}; Following.init = function() { header.init(); - - infinitescroll.init(function(direction) { - followers.loadMore(direction, 'account/following', 'following:' + ajaxify.data.uid); - }); }; return Following; diff --git a/public/src/client/search.js b/public/src/client/search.js index bf07ad70b6..b9632513bc 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -22,15 +22,10 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco $('#advanced-search').off('submit').on('submit', function(e) { e.preventDefault(); - - var input = $('#search-input'); - - var searchData = getSearchData(); - searchData.term = input.val(); - - searchModule.query(searchData, function() { - input.val(''); + searchModule.query(getSearchData(), function() { + $('#search-input').val(''); }); + return false; }); handleSavePreferences(); @@ -43,7 +38,7 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco var searchData = { in: $('#search-in').val() }; - + searchData.term = $('#search-input').val(); if (searchData.in === 'posts' || searchData.in === 'titlesposts' || searchData.in === 'titles') { searchData.by = form.find('#posted-by-user').val(); searchData.categories = form.find('#posted-in-categories').val(); @@ -71,6 +66,10 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco params = utils.merge(searchData, params); if (params) { + if (params.term) { + $('#search-input').val(params.term); + } + if (params.in) { $('#search-in').val(params.in); updateFormItemVisiblity(params.in); diff --git a/public/src/client/users.js b/public/src/client/users.js index 40f221e33f..17016fc7b0 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -1,16 +1,24 @@ 'use strict'; -/* globals define, socket, app, templates, bootbox, ajaxify */ +/* globals define, socket, app, templates, bootbox, utils */ define('forum/users', ['translator'], function(translator) { var Users = {}; - var loadingMoreUsers = false; + var searchTimeoutID = 0; + + $(window).on('action:ajaxify.start', function() { + if (searchTimeoutID) { + clearTimeout(searchTimeoutID); + searchTimeoutID = 0; + } + }); Users.init = function() { app.enterRoom('user_list'); - $('.nav-pills li').removeClass('active').find('a[href="' + window.location.pathname + '"]').parent().addClass('active'); + var section = utils.params().section ? ('?section=' + utils.params().section) : ''; + $('.nav-pills li').removeClass('active').find('a[href="' + window.location.pathname + section + '"]').parent().addClass('active'); handleSearch(); @@ -18,110 +26,55 @@ define('forum/users', ['translator'], function(translator) { socket.removeListener('event:user_status_change', onUserStatusChange); socket.on('event:user_status_change', onUserStatusChange); - - $('#load-more-users-btn').on('click', loadMoreUsers); - - $(window).off('scroll').on('scroll', function() { - var bottom = ($(document).height() - $(window).height()) * 0.9; - - if ($(window).scrollTop() > bottom && !loadingMoreUsers) { - loadMoreUsers(); - } - }); }; - function loadMoreUsers() { - if ($('#search-user').val()) { - return; - } - - if (ajaxify.data.setName) { - startLoading(ajaxify.data.setName, $('#users-container').children('.registered-user').length); - } - } - - function startLoading(set, after) { - loadingMoreUsers = true; - - socket.emit('user.loadMore', { - set: set, - after: after - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - - if (data && data.users.length) { - onUsersLoaded(data); - $('#load-more-users-btn').removeClass('disabled'); - } else { - $('#load-more-users-btn').addClass('disabled'); - } - loadingMoreUsers = false; - }); - } - - function onUsersLoaded(data) { - data.users = data.users.filter(function(user) { - return !$('.users-box[data-uid="' + user.uid + '"]').length; - }); - - templates.parse('users', 'users', data, function(html) { - translator.translate(html, function(translated) { - translated = $(translated); - $('#users-container').append(translated); - translated.find('span.timeago').timeago(); - utils.addCommasToNumbers(translated.find('.formatted-number')); - $('#users-container .anon-user').appendTo($('#users-container')); - }); - }); - } - function handleSearch() { - var timeoutId = 0; + searchTimeoutID = 0; $('#search-user').on('keyup', function() { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = 0; + if (searchTimeoutID) { + clearTimeout(searchTimeoutID); + searchTimeoutID = 0; } - timeoutId = setTimeout(doSearch, 250); + searchTimeoutID = setTimeout(doSearch, 150); }); $('.search select, .search input[type="checkbox"]').on('change', function() { doSearch(); }); - - $('.users').on('click', '.pagination a', function() { - doSearch($(this).attr('data-page')); - return false; - }); } function doSearch(page) { + $('[component="user/search/icon"]').removeClass('fa-search').addClass('fa-spinner fa-spin'); var username = $('#search-user').val(); page = page || 1; + var activeSection = getActiveSection(); + + var query = { + section: activeSection, + page: page + }; + if (!username) { - return loadPage(page); + return loadPage(query); } - var activeSection = getActiveSection(); - socket.emit('user.search', { - query: username, - page: page, - searchBy: 'username', - sortBy: $('.search select').val() || getSortBy(), - onlineOnly: $('.search .online-only').is(':checked') || (activeSection === 'online'), - bannedOnly: activeSection === 'banned', - flaggedOnly: activeSection === 'flagged' - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - renderSearchResults(data); - }); + query.term = username; + query.sortBy = getSortBy(); + + if ($('.search .online-only').is(':checked') || (activeSection === 'online')) { + query.onlineOnly = true; + } + if (activeSection === 'banned') { + query.bannedOnly = true; + } + if (activeSection === 'flagged') { + query.flaggedOnly = true; + } + + loadPage(query); } function getSortBy() { @@ -137,16 +90,17 @@ define('forum/users', ['translator'], function(translator) { return sortBy; } - function loadPage(page) { - var section = getActiveSection(); - section = section !== 'users' ? section : ''; - $.get('/api/users/' + section + '?page=' + page, function(data) { - renderSearchResults(data); + + function loadPage(query) { + var qs = decodeURIComponent($.param(query)); + $.get('/api/users?' + qs, renderSearchResults).fail(function(xhrErr) { + if (xhrErr && xhrErr.responseJSON && xhrErr.responseJSON.error) { + app.alertError(xhrErr.responseJSON.error); + } }); } function renderSearchResults(data) { - $('#load-more-users-btn').addClass('hide'); templates.parse('partials/paginator', {pagination: data.pagination}, function(html) { $('.pagination-container').replaceWith(html); }); @@ -156,6 +110,7 @@ define('forum/users', ['translator'], function(translator) { translated = $(translated); $('#users-container').html(translated); translated.find('span.timeago').timeago(); + $('[component="user/search/icon"]').addClass('fa-search').removeClass('fa-spinner fa-spin'); }); }); } @@ -173,9 +128,7 @@ define('forum/users', ['translator'], function(translator) { } function getActiveSection() { - var url = window.location.href; - var parts = url.split('/'); - return parts[parts.length - 1]; + return utils.params().section || ''; } function handleInvite() { diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 147683c550..304235c7fa 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -4,8 +4,8 @@ define('search', ['navigator', 'translator'], function(nav, translator) { var Search = { - current: {} - }; + current: {} + }; Search.query = function(data, callback) { var term = data.term; @@ -22,11 +22,11 @@ define('search', ['navigator', 'translator'], function(nav, translator) { return app.alertError('[[error:invalid-search-term]]'); } - ajaxify.go('search/' + term + '?' + createQueryString(data)); + ajaxify.go('search?' + createQueryString(data)); callback(); } else { - var cleanedTerm = term.replace(topicSearch[0], ''), - tid = topicSearch[1]; + var cleanedTerm = term.replace(topicSearch[0], ''); + var tid = topicSearch[1]; if (cleanedTerm.length > 0) { Search.queryTopic(tid, cleanedTerm, callback); @@ -38,8 +38,9 @@ define('search', ['navigator', 'translator'], function(nav, translator) { var searchIn = data['in'] || 'titlesposts'; var postedBy = data.by || ''; var query = { - 'in': searchIn - }; + term: data.term, + 'in': searchIn + }; if (postedBy && (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts')) { query.by = postedBy; diff --git a/src/controllers/accounts/follow.js b/src/controllers/accounts/follow.js index f9dc72c6f3..5897347a12 100644 --- a/src/controllers/accounts/follow.js +++ b/src/controllers/accounts/follow.js @@ -1,10 +1,11 @@ 'use strict'; -var async = require('async'), +var async = require('async'); - user = require('../../user'), - helpers = require('../helpers'), - accountHelpers = require('./helpers'); +var user = require('../../user'); +var helpers = require('../helpers'); +var accountHelpers = require('./helpers'); +var pagination = require('../../pagination'); var followController = {}; @@ -19,6 +20,11 @@ followController.getFollowers = function(req, res, next) { function getFollow(tpl, name, req, res, callback) { var userData; + var page = parseInt(req.query.page, 10) || 1; + var resultsPerPage = 50; + var start = Math.max(0, page - 1) * resultsPerPage; + var stop = start + resultsPerPage - 1; + async.waterfall([ function(next) { accountHelpers.getBaseUser(req.params.userslug, req.uid, next); @@ -29,7 +35,7 @@ function getFollow(tpl, name, req, res, callback) { return callback(); } var method = name === 'following' ? 'getFollowing' : 'getFollowers'; - user[method](userData.uid, 0, 49, next); + user[method](userData.uid, start, stop, next); } ], function(err, users) { if (err) { @@ -37,8 +43,10 @@ function getFollow(tpl, name, req, res, callback) { } userData.users = users; - userData.nextStart = 50; userData.title = '[[pages:' + tpl + ', ' + userData.username + ']]'; + var count = name === 'following' ? userData.followingCount : userData.followerCount; + var pageCount = Math.ceil(count / resultsPerPage); + userData.pagination = pagination.create(page, pageCount); userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:' + name + ']]'}]); res.render(tpl, userData); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 12a891a673..2b1cac23c3 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -133,7 +133,19 @@ helpers.getBaseUser = function(userslug, callerUID, callback) { async.parallel({ user: function(next) { - user.getUserFields(uid, ['uid', 'username', 'userslug', 'picture', 'cover:url', 'cover:position', 'status', 'lastonline', 'groupTitle'], next); + user.getUserFields(uid, [ + 'uid', + 'username', + 'userslug', + 'picture', + 'cover:url', + 'cover:position', + 'status', + 'lastonline', + 'groupTitle', + 'followingCount', + 'followerCount' + ], next); }, isAdmin: function(next) { user.isAdministrator(callerUID, next); diff --git a/src/controllers/search.js b/src/controllers/search.js index 2a7f6b145c..2466f17670 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -28,7 +28,7 @@ searchController.search = function(req, res, next) { } var data = { - query: req.params.term, + query: req.query.term, searchIn: req.query.in || 'posts', postedBy: req.query.by, categories: req.query.categories, @@ -59,7 +59,7 @@ searchController.search = function(req, res, next) { searchData.showAsTopics = req.query.showAs === 'topics'; searchData.title = '[[global:header.search]]'; searchData.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]); - searchData.expandSearch = !req.params.term; + searchData.expandSearch = !req.query.term; res.render('search', searchData); }); diff --git a/src/controllers/users.js b/src/controllers/users.js index 20ea62ca40..4f503bb6a3 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -11,10 +11,61 @@ var helpers = require('./helpers'); var usersController = {}; + +usersController.index = function(req, res, next) { + var section = req.query.section || 'joindate'; + var sectionToController = { + joindate: usersController.getUsersSortedByJoinDate, + online: usersController.getOnlineUsers, + 'sort-posts': usersController.getUsersSortedByPosts, + 'sort-reputation': usersController.getUsersSortedByReputation, + banned: usersController.getBannedUsers, + flagged: usersController.getFlaggedUsers + }; + + if (req.query.term) { + usersController.search(req, res, next); + } else if (sectionToController[section]) { + sectionToController[section](req, res, next); + } else { + usersController.getUsersSortedByJoinDate(req, res, next); + } +}; + +usersController.search = function(req, res, next) { + async.parallel({ + search: function(next) { + user.search({ + query: req.query.term, + searchBy: req.query.searchBy || 'username', + page: req.query.page || 1, + sortBy: req.query.sortBy, + onlineOnly: req.query.onlineOnly === 'true', + bannedOnly: req.query.bannedOnly === 'true', + flaggedOnly: req.query.flaggedOnly === 'true' + }, next); + }, + isAdminOrGlobalMod: function(next) { + user.isAdminOrGlobalMod(req.uid, next); + } + }, function(err, results) { + if (err) { + return next(err); + } + + var section = req.query.section || 'joindate'; + + results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; + results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); + results.search['section_' + section] = true; + render(req, res, results.search, next); + }); +}; + usersController.getOnlineUsers = function(req, res, next) { async.parallel({ users: function(next) { - usersController.getUsers('users:online', req.uid, req.query.page, next); + usersController.getUsers('users:online', req.uid, req.query, next); }, guests: function(next) { require('../socket.io/admin/rooms').getTotalGuestCount(next); @@ -56,7 +107,7 @@ usersController.getUsersSortedByJoinDate = function(req, res, next) { }; usersController.getBannedUsers = function(req, res, next) { - usersController.getUsers('users:banned', req.uid, req.query.page, function(err, userData) { + usersController.getUsers('users:banned', req.uid, req.query, function(err, userData) { if (err) { return next(err); } @@ -70,7 +121,7 @@ usersController.getBannedUsers = function(req, res, next) { }; usersController.getFlaggedUsers = function(req, res, next) { - usersController.getUsers('users:flags', req.uid, req.query.page, function(err, userData) { + usersController.getUsers('users:flags', req.uid, req.query, function(err, userData) { if (err) { return next(err); } @@ -84,15 +135,16 @@ usersController.getFlaggedUsers = function(req, res, next) { }; usersController.renderUsersPage = function(set, req, res, next) { - usersController.getUsers(set, req.uid, req.query.page, function(err, userData) { + usersController.getUsers(set, req.uid, req.query, function(err, userData) { if (err) { return next(err); } + render(req, res, userData, next); }); }; -usersController.getUsers = function(set, uid, page, callback) { +usersController.getUsers = function(set, uid, query, callback) { var setToData = { 'users:postcount': {title: '[[pages:users/sort-posts]]', crumb: '[[users:top_posters]]'}, 'users:reputation': {title: '[[pages:users/sort-reputation]]', crumb: '[[users:most_reputation]]'}, @@ -112,17 +164,14 @@ usersController.getUsers = function(set, uid, page, callback) { breadcrumbs.unshift({text: '[[global:users]]', url: '/users'}); } - page = parseInt(page, 10) || 1; + var page = parseInt(query.page, 10) || 1; var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 50; var start = Math.max(0, page - 1) * resultsPerPage; var stop = start + resultsPerPage - 1; async.parallel({ - isAdministrator: function(next) { - user.isAdministrator(uid, next); - }, - isGlobalMod: function(next) { - user.isGlobalModerator(uid, next); + isAdminOrGlobalMod: function(next) { + user.isAdminOrGlobalMod(uid, next); }, usersData: function(next) { usersController.getUsersAndCount(set, uid, start, stop, next); @@ -134,16 +183,14 @@ usersController.getUsers = function(set, uid, page, callback) { var pageCount = Math.ceil(results.usersData.count / resultsPerPage); var userData = { - loadmore_display: results.usersData.count > (stop - start + 1) ? 'block' : 'hide', users: results.usersData.users, - pagination: pagination.create(page, pageCount), + pagination: pagination.create(page, pageCount, query), userCount: results.usersData.count, title: setToData[set].title || '[[pages:users/latest]]', breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), - setName: set, - isAdminOrGlobalMod: results.isAdministrator || results.isGlobalMod + isAdminOrGlobalMod: results.isAdminOrGlobalMod }; - userData['route_' + set] = true; + userData['section_' + (query.section || 'joindate')] = true; callback(null, userData); }); }; diff --git a/src/routes/index.js b/src/routes/index.js index 006e5b8c18..00e0ea0e77 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -33,7 +33,7 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/compose', middleware, [], controllers.compose); setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); - setupPageRoute(app, '/search/:term?', middleware, [], controllers.search.search); + setupPageRoute(app, '/search', middleware, [], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); @@ -73,12 +73,7 @@ function categoryRoutes(app, middleware, controllers) { function userRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; - setupPageRoute(app, '/users', middleware, middlewares, controllers.users.getUsersSortedByJoinDate); - setupPageRoute(app, '/users/online', middleware, middlewares, controllers.users.getOnlineUsers); - setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); - setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); - setupPageRoute(app, '/users/banned', middleware, middlewares, controllers.users.getBannedUsers); - setupPageRoute(app, '/users/flagged', middleware, middlewares, controllers.users.getFlaggedUsers); + setupPageRoute(app, '/users', middleware, middlewares, controllers.users.index); } function groupRoutes(app, middleware, controllers) { @@ -91,15 +86,15 @@ function groupRoutes(app, middleware, controllers) { module.exports = function(app, middleware, hotswapIds) { var routers = [ - express.Router(), // plugin router - express.Router(), // main app router - express.Router() // auth router - ], - router = routers[1], - pluginRouter = routers[0], - authRouter = routers[2], - relativePath = nconf.get('relative_path'), - ensureLoggedIn = require('connect-ensure-login'); + express.Router(), // plugin router + express.Router(), // main app router + express.Router() // auth router + ]; + var router = routers[1]; + var pluginRouter = routers[0]; + var authRouter = routers[2]; + var relativePath = nconf.get('relative_path'); + var ensureLoggedIn = require('connect-ensure-login'); if (Array.isArray(hotswapIds) && hotswapIds.length) { for(var idx,x=0;x