diff --git a/README.md b/README.md index ec84b1303e..d06bc0362a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Workflow](https://github.com/NodeBB/NodeBB/actions/workflows/test.yaml/badge.svg)](https://github.com/NodeBB/NodeBB/actions/workflows/test.yaml) [![Coverage Status](https://coveralls.io/repos/github/NodeBB/NodeBB/badge.svg?branch=master)](https://coveralls.io/github/NodeBB/NodeBB?branch=master) [![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB) +[![](https://dcbadge.vercel.app/api/server/p6YKPXu7er?style=flat)](https://discord.gg/p6YKPXu7er) [**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging. diff --git a/install/package.json b/install/package.json index 5cddfbaee5..e5962ed205 100644 --- a/install/package.json +++ b/install/package.json @@ -99,9 +99,9 @@ "nodebb-plugin-spam-be-gone": "1.0.2", "nodebb-rewards-essentials": "0.2.1", "nodebb-theme-lavender": "6.0.0", - "nodebb-theme-persona": "12.1.9", + "nodebb-theme-persona": "12.1.10", "nodebb-theme-slick": "2.0.2", - "nodebb-theme-vanilla": "12.1.18", + "nodebb-theme-vanilla": "12.1.19", "nodebb-widget-essentials": "6.0.0", "nodemailer": "6.8.0", "nprogress": "0.2.0", diff --git a/public/src/client/groups/memberlist.js b/public/src/client/groups/memberlist.js index 44b593c63c..7a3376eb51 100644 --- a/public/src/client/groups/memberlist.js +++ b/public/src/client/groups/memberlist.js @@ -2,7 +2,6 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, bootbox, alerts) { const MemberList = {}; - let searchInterval; let groupName; let templateName; @@ -89,25 +88,22 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, b } function handleMemberSearch() { - $('[component="groups/members/search"]').on('keyup', function () { - const query = $(this).val(); - if (searchInterval) { - clearInterval(searchInterval); - searchInterval = 0; - } - - searchInterval = setTimeout(function () { - socket.emit('groups.searchMembers', { groupName: groupName, query: query }, function (err, results) { - if (err) { - return alerts.error(err); - } - parseAndTranslate(results.users, function (html) { - $('[component="groups/members"] tbody').html(html); - $('[component="groups/members"]').attr('data-nextstart', 20); - }); + const searchEl = $('[component="groups/members/search"]'); + searchEl.on('keyup', utils.debounce(function () { + const query = searchEl.val(); + socket.emit('groups.searchMembers', { + groupName: groupName, + query: query, + }, function (err, results) { + if (err) { + return alerts.error(err); + } + parseAndTranslate(results.users, function (html) { + $('[component="groups/members"] tbody').html(html); + $('[component="groups/members"]').attr('data-nextstart', 20); }); - }, 250); - }); + }); + }, 250)); } function handleMemberInfiniteScroll() { diff --git a/src/controllers/search.js b/src/controllers/search.js index 82aed2b308..238c167709 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -29,11 +29,15 @@ searchController.search = async function (req, res, next) { 'search:tags': privileges.global.can('search:tags', req.uid), }); req.query.in = req.query.in || meta.config.searchDefaultIn || 'titlesposts'; - const allowed = (req.query.in === 'users' && userPrivileges['search:users']) || + let allowed = (req.query.in === 'users' && userPrivileges['search:users']) || (req.query.in === 'tags' && userPrivileges['search:tags']) || (req.query.in === 'categories') || (['titles', 'titlesposts', 'posts'].includes(req.query.in) && userPrivileges['search:content']); - + ({ allowed } = await plugins.hooks.fire('filter:search.isAllowed', { + uid: req.uid, + query: req.query, + allowed, + })); if (!allowed) { return helpers.notAllowed(req, res); } diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 3c6ea76f0b..9febe062a8 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -171,7 +171,7 @@ Auth.reloadRoutes = async function (params) { router.post('/register', middlewares, controllers.authentication.register); router.post('/register/complete', middlewares, controllers.authentication.registerComplete); - router.post('/register/abort', controllers.authentication.registerAbort); + router.post('/register/abort', Auth.middleware.applyCSRF, controllers.authentication.registerAbort); router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login); router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout); }; diff --git a/src/search.js b/src/search.js index a46934ff2f..cbd4e4f40c 100644 --- a/src/search.js +++ b/src/search.js @@ -15,7 +15,6 @@ const search = module.exports; search.search = async function (data) { const start = process.hrtime(); - data.searchIn = data.searchIn || 'titlesposts'; data.sortBy = data.sortBy || 'relevance'; let result; @@ -27,6 +26,10 @@ search.search = async function (data) { result = await categories.search(data); } else if (data.searchIn === 'tags') { result = await topics.searchAndLoadTags(data); + } else if (data.searchIn) { + result = await plugins.hooks.fire('filter:search.searchIn', { + data, + }); } else { throw new Error('[[error:unknown-search-filter]]'); } @@ -108,6 +111,7 @@ async function searchInContent(data) { returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, {}); await plugins.hooks.fire('filter:search.contentGetResult', { result: returnData, data: data }); delete metadata.pids; + delete metadata.data; return Object.assign(returnData, metadata); } diff --git a/src/upgrades/1.11.0/navigation_visibility_groups.js b/src/upgrades/1.11.0/navigation_visibility_groups.js index 3567b3c9c1..4e3730b9cb 100644 --- a/src/upgrades/1.11.0/navigation_visibility_groups.js +++ b/src/upgrades/1.11.0/navigation_visibility_groups.js @@ -4,9 +4,7 @@ module.exports = { name: 'Navigation item visibility groups', timestamp: Date.UTC(2018, 10, 10), method: async function () { - const navigationAdmin = require('../../navigation/admin'); - - const data = await navigationAdmin.get(); + const data = await navigationAdminGet(); data.forEach((navItem) => { if (navItem && navItem.properties) { navItem.groups = []; @@ -23,6 +21,38 @@ module.exports = { } } }); - await navigationAdmin.save(data); + await navigationAdminSave(data); }, }; +// use navigation.get/save as it was in 1.11.0 so upgrade script doesn't crash on latest nbb +// see https://github.com/NodeBB/NodeBB/pull/11013 +async function navigationAdminGet() { + const db = require('../../database'); + const data = await db.getSortedSetRange('navigation:enabled', 0, -1); + return data.filter(Boolean).map((item) => { + item = JSON.parse(item); + item.groups = item.groups || []; + if (item.groups && !Array.isArray(item.groups)) { + item.groups = [item.groups]; + } + return item; + }); +} + +async function navigationAdminSave(data) { + const db = require('../../database'); + const translator = require('../../translator'); + const order = Object.keys(data); + const items = data.map((item, index) => { + Object.keys(item).forEach((key) => { + if (item.hasOwnProperty(key) && typeof item[key] === 'string' && (key === 'title' || key === 'text')) { + item[key] = translator.escape(item[key]); + } + }); + item.order = order[index]; + return JSON.stringify(item); + }); + + await db.delete('navigation:enabled'); + await db.sortedSetAdd('navigation:enabled', order, items); +} diff --git a/test/search.js b/test/search.js index b58702bb38..14179ad5cd 100644 --- a/test/search.js +++ b/test/search.js @@ -183,7 +183,7 @@ describe('Search', () => { it('should fail if searchIn is wrong', (done) => { search.search({ query: 'plug', - searchIn: 'invalidfilter', + searchIn: '', }, (err) => { assert.equal(err.message, '[[error:unknown-search-filter]]'); done(); @@ -216,6 +216,7 @@ describe('Search', () => { it('should not find anything', (done) => { search.search({ query: 'xxxxxxxxxxxxxx', + searchIn: 'titles', }, (err, data) => { assert.ifError(err); assert(Array.isArray(data.posts));