diff --git a/public/language/en_GB/search.json b/public/language/en_GB/search.json index 81c404a591..945e58c852 100644 --- a/public/language/en_GB/search.json +++ b/public/language/en_GB/search.json @@ -1,4 +1,5 @@ { "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", - "no-matches": "No posts found" -} \ No newline at end of file + "no-matches": "No matches found", + "in": "In" +} diff --git a/public/src/app.js b/public/src/app.js index bf5ec32941..1664a251d5 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -435,11 +435,9 @@ app.uid = null; require(['search', 'mousetrap'], function(search, Mousetrap) { $('#search-form').on('submit', function (e) { e.preventDefault(); - var input = $(this).find('input'), - term = input.val(); + var input = $(this).find('input'); - - search.query(term, function() { + search.query(input.val(), 'posts', function() { input.val(''); }); }); diff --git a/public/src/client/search.js b/public/src/client/search.js index 861d8dd441..e08a5dc410 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -2,9 +2,17 @@ define('forum/search', ['search'], function(searchModule) { var Search = {}; Search.init = function() { - var searchQuery = $('#post-results').attr('data-search-query'); + var searchQuery = $('#results').attr('data-search-query'); var regexes = []; var searchTerms = searchQuery.split(' '); + + $('#advanced-search input').val(searchQuery); + var params = utils.params(); + if (params && params.in) { + $('#advanced-search select').val(params.in); + } + + for (var i=0; i<searchTerms.length; ++i) { var regex = new RegExp(searchTerms[i], 'gi'); regexes.push({regex: regex, term: searchTerms[i]}); @@ -19,13 +27,13 @@ define('forum/search', ['search'], function(searchModule) { result.html(text).find('img').addClass('img-responsive'); }); - $('#search-form input').val(searchQuery); - $('#mobile-search-form').off('submit').on('submit', function(e) { + $('#advanced-search').off('submit').on('submit', function(e) { e.preventDefault(); var input = $(this).find('input'); + var searchIn = $(this).find('select'); - searchModule.query(input.val(), function() { + searchModule.query(input.val(), searchIn.val(), function() { input.val(''); }); }); diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 0efb754eb2..7dd7abddff 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -1,12 +1,13 @@ +"use strict"; +/* globals socket, ajaxify, translator, app, define */ + define('search', ['navigator'], function(nav) { - "use strict"; - /* globals socket, ajaxify */ var Search = { current: {} }; - Search.query = function(term, callback) { + Search.query = function(term, searchIn, callback) { // Detect if a tid was specified var topicSearch = term.match(/in:topic-([\d]+)/); @@ -19,7 +20,7 @@ define('search', ['navigator'], function(nav) { return app.alertError('[[error:invalid-search-term]]'); } - ajaxify.go('search/' + term); + ajaxify.go('search/' + term + (searchIn ? '?in=' + searchIn : '')); callback(); } else { var cleanedTerm = term.replace(topicSearch[0], ''), @@ -105,7 +106,7 @@ define('search', ['navigator'], function(nav) { Mousetrap.bind('esc', Search.topicDOM.end); }); } - } + }; Search.topicDOM.end = function() { $('.topic-search').addClass('hidden').find('.prev, .next').attr('disabled', 'disabled'); diff --git a/src/controllers/index.js b/src/controllers/index.js index 0e982c59fa..49bf28f35c 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -3,6 +3,7 @@ var topicsController = require('./topics'), categoriesController = require('./categories'), tagsController = require('./tags'), + searchController = require('./search'), usersController = require('./users'), groupsController = require('./groups'), accountsController = require('./accounts'), @@ -20,7 +21,6 @@ var topicsController = require('./topics'), user = require('../user'), posts = require('../posts'), topics = require('../topics'), - search = require('../search'), plugins = require('../plugins'), categories = require('../categories'), privileges = require('../privileges'); @@ -29,6 +29,7 @@ var Controllers = { topics: topicsController, categories: categoriesController, tags: tagsController, + search: searchController, users: usersController, groups: groupsController, accounts: accountsController, @@ -103,33 +104,6 @@ Controllers.home = function(req, res, next) { }); }; -Controllers.search = function(req, res, next) { - if (!plugins.hasListeners('filter:search.query')) { - return helpers.notFound(req, res); - } - - if (!req.params.term) { - return res.render('search', { - time: 0, - search_query: '', - posts: [], - topics: [] - }); - } - - var uid = req.user ? req.user.uid : 0; - - req.params.term = validator.escape(req.params.term); - - search.search(req.params.term, uid, function(err, results) { - if (err) { - return next(err); - } - - return res.render('search', results); - }); -}; - Controllers.reset = function(req, res, next) { res.render(req.params.code ? 'reset_code' : 'reset', { reset_code: req.params.code ? req.params.code : null diff --git a/src/controllers/search.js b/src/controllers/search.js new file mode 100644 index 0000000000..1d4aff306c --- /dev/null +++ b/src/controllers/search.js @@ -0,0 +1,41 @@ + +'use strict'; + +var searchController = {}, + validator = require('validator'), + plugins = require('../plugins'), + search = require('../search'), + helpers = require('./helpers'); + + +searchController.search = function(req, res, next) { + if (!plugins.hasListeners('filter:search.query')) { + return helpers.notFound(req, res); + } + + if (!req.params.term) { + return res.render('search', { + time: 0, + search_query: '', + posts: [], + topics: [], + users: [], + tags: [] + }); + } + + var uid = req.user ? req.user.uid : 0; + + req.params.term = validator.escape(req.params.term); + + search.search(req.params.term, req.query.in, uid, function(err, results) { + if (err) { + return next(err); + } + + res.render('search', results); + }); +}; + + +module.exports = searchController; diff --git a/src/routes/index.js b/src/routes/index.js index 98d75c637f..ffee5b8272 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -26,7 +26,7 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/register', middleware, loginRegisterMiddleware, controllers.register); setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); - setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search); + setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); } diff --git a/src/search.js b/src/search.js index 0f55c6aafb..869455b3de 100644 --- a/src/search.js +++ b/src/search.js @@ -3,6 +3,7 @@ var async = require('async'), posts = require('./posts'), topics = require('./topics'), + user = require('./user'), plugins = require('./plugins'), privileges = require('./privileges'); @@ -10,15 +11,46 @@ var search = {}; module.exports = search; -search.search = function(term, uid, callback) { +search.search = function(query, searchIn, uid, callback) { + function done(err, data) { + if (err) { + return callback(err); + } + + result.search_query = query; + result[searchIn] = data; + result.matchCount = data.length; + result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2); + callback(null, result); + } + var start = process.hrtime(); + searchIn = searchIn || 'posts'; + var result = { + posts: [], + users: [], + tags: [] + }; + + if (searchIn === 'posts') { + searchInPosts(query, uid, done); + } else if (searchIn === 'users') { + searchInUsers(query, done); + } else if (searchIn === 'tags') { + searchInTags(query, done); + } else { + callback(new Error('[[error:unknown-search-filter]]')); + } +}; + +function searchInPosts(query, uid, callback) { async.parallel({ pids: function(next) { - searchTerm('post', term, next); + searchQuery('post', query, next); }, tids: function(next) { - searchTerm('topic', term, next); + searchQuery('topic', query, next); } }, function (err, results) { if (err) { @@ -26,47 +58,37 @@ search.search = function(term, uid, callback) { } if (!results || (!results.pids.length && !results.tids.length)) { - return callback(null, { - time: (process.elapsedTimeSince(start) / 1000).toFixed(2), - search_query: term, - results: [], - matchCount: 0 - }); + return callback(null, []); } - getMainPids(results.tids, function(err, mainPids) { - if (err) { - return callback(err); - } - - results.pids.forEach(function(pid) { - if (mainPids.indexOf(pid) === -1) { - mainPids.push(pid); - } - }); - - privileges.posts.filter('read', mainPids, uid, function(err, pids) { - if (err) { - return callback(err); - } - - posts.getPostSummaryByPids(pids, uid, {stripTags: true, parse: false}, function(err, posts) { - if (err) { - return callback(err); + async.waterfall([ + function(next) { + getMainPids(results.tids, next); + }, + function(mainPids, next) { + results.pids.forEach(function(pid) { + if (mainPids.indexOf(pid) === -1) { + mainPids.push(pid); } - - callback(null, { - time: (process.elapsedTimeSince(start) / 1000).toFixed(2), - search_query: term, - results: posts, - matchCount: posts.length - }); }); - }); - }); + privileges.posts.filter('read', mainPids, uid, next); + }, + function(pids, next) { + posts.getPostSummaryByPids(pids, uid, {stripTags: true, parse: false}, next); + } + ], callback); }); -}; +} +function searchInUsers(query, callback) { + user.search({query: query}, function(err, results) { + callback(err, results ? results.users : null); + }); +} + +function searchInTags(query, callback) { + topics.searchAndLoadTags({query: query}, callback); +} function getMainPids(tids, callback) { topics.getTopicsFields(tids, ['mainPid'], function(err, topics) { @@ -80,10 +102,10 @@ function getMainPids(tids, callback) { }); } -function searchTerm(index, term, callback) { +function searchQuery(index, query, callback) { plugins.fireHook('filter:search.query', { index: index, - query: term + query: query }, callback); } diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index fe2c313452..ccd063444f 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -522,38 +522,7 @@ SocketTopics.searchAndLoadTags = function(socket, data, callback) { if (!data) { return callback(new Error('[[error:invalid-data]]')); } - if (!data.query || !data.query.length) { - return callback(null, []); - } - topics.searchTags(data, function(err, tags) { - if (err) { - return callback(err); - } - async.parallel({ - counts: function(next) { - db.sortedSetScores('tags:topic:count', tags, next); - }, - tagData: function(next) { - tags = tags.map(function(tag) { - return {value: tag}; - }); - - topics.getTagData(tags, next); - } - }, function(err, results) { - if (err) { - return callback(err); - } - results.tagData.forEach(function(tag, index) { - tag.score = results.counts[index]; - }); - results.tagData.sort(function(a, b) { - return b.score - a.score; - }); - - callback(null, results.tagData); - }); - }); + topics.searchAndLoadTags(data, callback); }; SocketTopics.loadMoreTags = function(socket, data, callback) { diff --git a/src/topics/tags.js b/src/topics/tags.js index c9e341d926..be01380ef5 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -276,4 +276,39 @@ module.exports = function(Topics) { }); }; + Topics.searchAndLoadTags = function(data, callback) { + if (!data.query || !data.query.length) { + return callback(null, []); + } + Topics.searchTags(data, function(err, tags) { + if (err) { + return callback(err); + } + async.parallel({ + counts: function(next) { + db.sortedSetScores('tags:topic:count', tags, next); + }, + tagData: function(next) { + tags = tags.map(function(tag) { + return {value: tag}; + }); + + Topics.getTagData(tags, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + results.tagData.forEach(function(tag, index) { + tag.score = results.counts[index]; + }); + results.tagData.sort(function(a, b) { + return b.score - a.score; + }); + + callback(null, results.tagData); + }); + }); + }; + }; \ No newline at end of file