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
This commit is contained in:
barisusakli
2016-08-24 14:47:01 +03:00
parent 768c1b37d4
commit 84f88a6f15
12 changed files with 181 additions and 236 deletions

View File

@@ -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;
});

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
if (!username) {
return loadPage(page);
}
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);
});
var query = {
section: activeSection,
page: page
};
if (!username) {
return loadPage(query);
}
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() {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
});

View File

@@ -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);
});
};

View File

@@ -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<hotswapIds.length;x++) {

View File

@@ -265,51 +265,6 @@ SocketUser.getUnreadCounts = function(socket, data, callback) {
}, callback);
};
SocketUser.loadMore = function(socket, data, callback) {
if (!data || !data.set || parseInt(data.after, 10) < 0) {
return callback(new Error('[[error:invalid-data]]'));
}
if (!socket.uid && !!parseInt(meta.config.privateUserInfo, 10)) {
return callback(new Error('[[error:no-privileges]]'));
}
var start = parseInt(data.after, 10);
var stop = start + 19;
async.parallel({
isAdmin: function(next) {
user.isAdministrator(socket.uid, next);
},
isGlobalMod: function(next) {
user.isGlobalModerator(socket.uid, next);
},
users: function(next) {
user.getUsersFromSet(data.set, socket.uid, start, stop, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
if (data.set === 'users:banned' && !results.isAdmin && !results.isGlobalMod) {
return callback(new Error('[[error:no-privileges]]'));
}
if (!results.isAdmin && data.set === 'users:online') {
results.users = results.users.filter(function(user) {
return user.status !== 'offline';
});
}
var result = {
users: results.users,
nextStart: stop + 1,
};
result['route_' + data.set] = true;
callback(null, result);
});
};
SocketUser.invite = function(socket, email, callback) {
if (!email || !socket.uid) {
return callback(new Error('[[error:invalid-data]]'));

View File

@@ -84,7 +84,16 @@ module.exports = function(User) {
function filterAndSortUids(uids, data, callback) {
var sortBy = data.sortBy || 'joindate';
var fields = ['uid', 'status', 'lastonline', 'banned', 'flags', sortBy];
var fields = ['uid', sortBy];
if (data.onlineOnly) {
fields = fields.concat(['status', 'lastonline']);
}
if (data.bannedOnly) {
fields.push('banned');
}
if (data.flaggedOnly) {
fields.push('flags');
}
User.getUsersFields(uids, fields, function(err, userData) {
if (err) {