From 38dd3d428192e63ebb3202bef7c83fce0b2d8741 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 6 Feb 2015 19:47:27 -0500 Subject: [PATCH] search newer/older than and better pagination --- public/language/en_GB/search.json | 13 ++- public/src/client/search.js | 59 ++++++---- public/src/modules/search.js | 5 + src/controllers/categories.js | 3 +- src/controllers/search.js | 12 +- src/controllers/topics.js | 7 +- src/pagination.js | 13 +-- src/search.js | 175 +++++++++++++++++++++++++++--- src/user/search.js | 2 +- 9 files changed, 229 insertions(+), 60 deletions(-) diff --git a/public/language/en_GB/search.json b/public/language/en_GB/search.json index c68ead290b..4f099ef554 100644 --- a/public/language/en_GB/search.json +++ b/public/language/en_GB/search.json @@ -8,5 +8,16 @@ "search-child-categories": "Search child categories", "reply-count": "Reply Count", "at-least": "At least", - "at-most": "At most" + "at-most": "At most", + "post-time": "Post time", + "newer-than": "Newer than", + "older-than": "Older than", + "any-date": "Any date", + "yesterday": "Yesterday", + "one-week": "One week", + "two-weeks": "Two weeks", + "one-month": "One month", + "three-months": "Three months", + "six-months": "Six months", + "one-year": "One year" } diff --git a/public/src/client/search.js b/public/src/client/search.js index 7350d0b068..029f7b811a 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -9,29 +9,10 @@ define('forum/search', ['search'], function(searchModule) { var searchQuery = $('#results').attr('data-search-query'); $('#advanced-search #search-input').val(searchQuery); - var params = utils.params(); + var searchIn = $('#advanced-search #search-in'); - if (params && params.in) { - searchIn.val(params.in); - $('.post-search-item').toggleClass('hide', params.in !== 'posts'); - } - if (params && params.by) { - $('#posted-by-user').val(params.by); - } - - if (params && (params['categories[]'] || params.categories)) { - $('#posted-in-categories').val(params['categories[]'] || params.categories); - } - - if (params && params.searchChildren) { - $('#search-children').prop('checked', true); - } - - if (params && params.replies) { - $('#reply-count').val(params.replies); - $('#reply-count-filter').val(params.repliesFilter); - } + fillOutFormFromQueryParams(); searchIn.on('change', function() { $('.post-search-item').toggleClass('hide', searchIn.val() !== 'posts'); @@ -50,7 +31,9 @@ define('forum/search', ['search'], function(searchModule) { categories: $(this).find('#posted-in-categories').val(), searchChildren: $(this).find('#search-children').is(':checked'), replies: $(this).find('#reply-count').val(), - repliesFilter: $(this).find('#reply-count-filter').val() + repliesFilter: $(this).find('#reply-count-filter').val(), + timeFilter: $(this).find('#post-time-filter').val(), + timeRange: $(this).find('#post-time-range').val() }, function() { input.val(''); }); @@ -59,6 +42,38 @@ define('forum/search', ['search'], function(searchModule) { enableAutoComplete(); }; + function fillOutFormFromQueryParams() { + var params = utils.params(); + if (params) { + if (params.in) { + $('#search-in').val(params.in); + $('.post-search-item').toggleClass('hide', params.in !== 'posts'); + } + + if (params.by) { + $('#posted-by-user').val(params.by); + } + + if ((params['categories[]'] || params.categories)) { + $('#posted-in-categories').val(params['categories[]'] || params.categories); + } + + if (params.searchChildren) { + $('#search-children').prop('checked', true); + } + + if (params.replies) { + $('#reply-count').val(params.replies); + $('#reply-count-filter').val(params.repliesFilter); + } + + if (params.timeRange) { + $('#post-time-range').val(params.timeRange); + $('#post-time-filter').val(params.timeFilter); + } + } + } + function highlightMatches(searchQuery) { var searchTerms = searchQuery.split(' '); var regexes = []; diff --git a/public/src/modules/search.js b/public/src/modules/search.js index b8f4eb6607..2b90ab290a 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -40,6 +40,11 @@ define('search', ['navigator'], function(nav) { query.repliesFilter = data.repliesFilter || 'atleast'; } + if (data.timeRange) { + query.timeRange = data.timeRange; + query.timeFilter = data.timeFilter || 'newer'; + } + ajaxify.go('search/' + term + '?' + decodeURIComponent($.param(query))); callback(); } else { diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 1783fd69b3..25690a6810 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -260,8 +260,7 @@ categoriesController.get = function(req, res, next) { data.currentPage = page; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data['rssFeedUrl'] = nconf.get('relative_path') + '/category/' + data.cid + '.rss'; - - pagination.create(data.currentPage, data.pageCount, data); + data.pagination = pagination.create(data.currentPage, data.pageCount); data.pagination.rel.forEach(function(rel) { res.locals.linkTags.push(rel); diff --git a/src/controllers/search.js b/src/controllers/search.js index d21977b69d..2c0222a6b3 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -37,6 +37,7 @@ searchController.search = function(req, res, next) { } req.params.term = validator.escape(req.params.term); + var page = Math.max(1, parseInt(req.query.page, 10)) || 1; search.search({ query: req.params.term, @@ -46,18 +47,17 @@ searchController.search = function(req, res, next) { searchChildren: req.query.searchChildren, replies: req.query.replies, repliesFilter: req.query.repliesFilter, + timeRange: req.query.timeRange, + timeFilter: req.query.timeFilter, + page: page, uid: uid }, function(err, results) { if (err) { return next(err); } - var currentPage = Math.max(1, parseInt(req.query.page, 10)) || 1; - var pageCount = Math.max(1, Math.ceil(results.matchCount / 10)); - var searchIn = req.query.in || 'posts'; - var start = Math.max(0, (currentPage - 1)) * 10; - results[searchIn] = results[searchIn].slice(start, start + 10); - pagination.create(currentPage, pageCount, results, req.query); + var pageCount = Math.max(1, Math.ceil(results.matchCount / 10)); + results.pagination = pagination.create(page, pageCount, req.query); results.breadcrumbs = breadcrumbs; results.categories = categories; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index ffcca5ab46..f470cfc6d1 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -256,15 +256,12 @@ topicsController.get = function(req, res, next) { data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data['rssFeedUrl'] = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; - - topics.increaseViewCount(tid); - - pagination.create(data.currentPage, data.pageCount, data); - + data.pagination = pagination.create(data.currentPage, data.pageCount); data.pagination.rel.forEach(function(rel) { res.locals.linkTags.push(rel); }); + topics.increaseViewCount(tid); res.render('topic', data); }); }; diff --git a/src/pagination.js b/src/pagination.js index 9fadfceab4..d3a3ca9dd9 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -4,15 +4,14 @@ var qs = require('querystring'); var pagination = {}; -pagination.create = function(currentPage, pageCount, data, queryObj) { +pagination.create = function(currentPage, pageCount, queryObj) { if (pageCount <= 1) { - data.pagination = { + return { prev: {page: 1, active: currentPage > 1}, next: {page: 1, active: currentPage < pageCount}, rel: [], pages: [] }; - return; } var pagesToShow = [1]; @@ -43,7 +42,7 @@ pagination.create = function(currentPage, pageCount, data, queryObj) { return {page: page, active: page === currentPage, qs: qs.stringify(queryObj)}; }); - data.pagination = { + var data = { prev: {page: previous, active: currentPage > 1}, next: {page: next, active: currentPage < pageCount}, rel: [], @@ -51,19 +50,19 @@ pagination.create = function(currentPage, pageCount, data, queryObj) { }; if (currentPage < pageCount) { - data.pagination.rel.push({ + data.rel.push({ rel: 'next', href: '?page=' + next }); } if (currentPage > 1) { - data.pagination.rel.push({ + data.rel.push({ rel: 'prev', href: '?page=' + previous }); } - + return data; }; diff --git a/src/search.js b/src/search.js index 9f260cd7cc..86f39f6b52 100644 --- a/src/search.js +++ b/src/search.js @@ -1,6 +1,8 @@ 'use strict'; var async = require('async'), + + db = require('./database'), posts = require('./posts'), topics = require('./topics'), categories = require('./categories'), @@ -19,8 +21,8 @@ search.search = function(data, callback) { } result.search_query = query; - result[searchIn] = data; - result.matchCount = data.length; + result[searchIn] = data.matches; + result.matchCount = data.matchCount; result.hidePostedBy = searchIn !== 'posts'; result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2); callback(null, result); @@ -65,8 +67,9 @@ function searchInPosts(query, data, callback) { return callback(err); } + var matchCount = 0; if (!results || (!results.pids.length && !results.tids.length)) { - return callback(null, []); + return callback(null, {matches: [], matchCount: matchCount}); } async.waterfall([ @@ -82,31 +85,162 @@ function searchInPosts(query, data, callback) { privileges.posts.filter('read', mainPids, data.uid, next); }, function(pids, next) { + filterAndSort(pids, data, results.searchCategories, next); + }, + function(pids, next) { + matchCount = pids.length; + if (data.page) { + var start = Math.max(0, (data.page - 1)) * 10; + pids = pids.slice(start, start + 10); + } + posts.getPostSummaryByPids(pids, data.uid, {stripTags: true, parse: false}, next); }, function(posts, next) { - posts = filterPosts(data, results.searchCategories, posts); - next(null, posts); + next(null, {matches: posts, matchCount: matchCount}); } ], callback); }); } -function filterPosts(data, searchCategories, posts) { - var postedBy = data.postedBy; - var isAtLeast = data.repliesFilter === 'atleast'; - data.replies = parseInt(data.replies, 10); - if (postedBy || searchCategories.length || data.replies) { +function filterAndSort(pids, data, searchCategories, callback) { + var postFields = ['pid', 'tid', 'timestamp']; + var topicFields = []; + + if (data.postedBy) { + postFields.push('uid'); + } + + if (searchCategories.length) { + topicFields.push('cid'); + } + + if (data.replies) { + topicFields.push('postcount'); + } + + async.parallel({ + posts: function(next) { + getMatchedPosts(pids, postFields, topicFields, next); + }, + postedByUid: function(next) { + if (data.postedBy) { + user.getUidByUsername(data.postedBy, next); + } else { + next(); + } + } + }, function(err, results) { + if (err) { + return callback(err); + } + if (!results.posts) { + return callback(null, pids); + } + var posts = results.posts.filter(Boolean); + + posts = filterByUser(posts, results.postedByUid); + posts = filterByCategories(posts, searchCategories); + posts = filterByPostcount(posts, data.replies, data.repliesFilter); + posts = filterByTimerange(posts, data.timeRange, data.timeFilter); + + sortPosts(posts, data); + + pids = posts.map(function(post) { + return post && post.pid; + }); + + callback(null, pids); + }); +} + +function getMatchedPosts(pids, postFields, topicFields, callback) { + var keys = pids.map(function(pid) { + return 'post:' + pid; + }); + var posts; + async.waterfall([ + function(next) { + db.getObjectsFields(keys, postFields, next); + }, + function(_posts, next) { + posts = _posts; + if (!topicFields.length) { + return callback(null, posts); + } + var topicKeys = posts.map(function(post) { + return 'topic:' + post.tid; + }); + db.getObjectsFields(topicKeys, topicFields, next); + }, + function(topics, next) { + posts.forEach(function(post, index) { + post.topic = topics[index]; + }); + + next(null, posts); + } + ], callback); +} + +function filterByUser(posts, postedByUid) { + if (postedByUid) { + postedByUid = parseInt(postedByUid, 10); posts = posts.filter(function(post) { - return post && - (postedBy ? post.user && (post.user.username === postedBy) : true) && - (searchCategories.length ? (post.category && searchCategories.indexOf(post.category.cid) !== -1) : true) && - (data.replies ? (isAtLeast ? post.topic.postcount >= data.replies : post.topic.postcount <= data.replies) : true); + return parseInt(post.uid, 10) === postedByUid; }); } return posts; } +function filterByCategories(posts, searchCategories) { + if (searchCategories.length) { + posts = posts.filter(function(post) { + return post.topic && searchCategories.indexOf(post.topic.cid) !== -1; + }); + } + return posts; +} + +function filterByPostcount(posts, postCount, repliesFilter) { + postCount = parseInt(postCount, 10); + if (postCount) { + if (repliesFilter === 'atleast') { + posts = posts.filter(function(post) { + return post.topic && post.topic.postcount >= postCount; + }); + } else { + posts = posts.filter(function(post) { + return post.topic && post.topic.postcount <= postCount; + }); + } + } + return posts; +} + +function filterByTimerange(posts, timeRange, timeFilter) { + timeRange = parseInt(timeRange) * 1000; + if (timeRange) { + var time = Date.now() - timeRange; + if (timeFilter === 'newer') { + posts = posts.filter(function(post) { + return post.timestamp >= time; + }); + } else { + posts = posts.filter(function(post) { + return post.timestamp <= time; + }); + } + } + return posts; +} + +function sortPosts(posts, data) { + posts.sort(function(p1, p2) { + return p2.timestamp - p1.timestamp; + }); +} + function getSearchCategories(data, callback) { if (!Array.isArray(data.categories) || !data.categories.length || data.categories.indexOf('all') !== -1) { return callback(null, []); @@ -159,12 +293,21 @@ function getChildrenCids(cids, uid, callback) { function searchInUsers(query, callback) { user.search({query: query}, function(err, results) { - callback(err, results ? results.users : null); + if (err) { + return callback(err); + } + callback(null, {matches: results.users, matchCount: results.matchCount}); }); } function searchInTags(query, callback) { - topics.searchAndLoadTags({query: query}, callback); + topics.searchAndLoadTags({query: query}, function(err, tags) { + if (err) { + return callback(err); + } + + callback(null, {matches: tags, matchCount: tags.length}); + }); } function getMainPids(tids, callback) { diff --git a/src/user/search.js b/src/user/search.js index 95130bf68f..ebfb08551f 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -60,7 +60,7 @@ module.exports = function(User) { var currentPage = Math.max(1, Math.ceil((start + 1) / resultsPerPage)); pageCount = Math.ceil(matchCount / resultsPerPage); - pagination.create(currentPage, pageCount, data); + data.pagination = pagination.create(currentPage, pageCount); next(null, data); }