From b6d71710a0ce6a97370fc4fe7859c2f683eedc62 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 6 Jan 2021 19:17:56 +0000 Subject: [PATCH 01/69] chore: update changelog for v1.16.1 --- CHANGELOG.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd9ca2d29..a1eba6cfb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,115 @@ +#### v1.16.1 (2021-01-06) + +##### Chores + +* increase test timeout (0d7dfeeb) +* incrementing version number - v1.16.1-beta.0 (5fcf3ea6) +* add deprecation notice to topic thumb tpl value (05d8b3c3) +* minor reordering of lines (8e5a413e) +* incrementing version number - v1.16.0 (6d01fd50) +* update changelog for v1.16.0 (1437c62f) +* **deps:** + * update dependency eslint to v7.17.0 (18ae7cf7) + * update dependency eslint to v7.16.0 (2610dfcf) + * update actions/setup-node action to v2 (#9115) (55a55ea2) +* **api:** add deprecation notices re: #9123 (cdff8d28) + +##### New Features + +* #9173, show installed plugin versions in ./nodebb plugins (8c31afae) +* added note that you can now upload videos (4d6ddf6d) +* automatically attempt socket.io reconnection on ajaxify (e5edbc6f) +* #9135, don't try to reconnect forever (c1ecfd1e) +* add confirmation modal when assigning admin:admins-mods privilege (d90aa958) +* allow dashes in privilege group names (5b8558e9) +* allow multiple privileges to be defined for a given admin socket call (3aa5beb8) +* rename admin middleware header hook (fcc1e24a) +* explicitly add filter:admin/header.build hook (75b1bbd0) +* fix more tests, add more routes, update api test suite (cb32e32a) +* add registration/complete route, fix some other tests (14c51e3c) +* add missing schemas for various ACP settings routes (9de35ec5) +* add missing schema for category update and deletion (d6de9253) +* add schema for api ping routes (d85181e0) +* normalize paths before comparison (df8d62ba) +* additional test to ensure any new routes added to express have a corresponding schema doc (dbe85630) +* update html-to-text closes https://github.com/NodeBB/NodeBB/pull/8810 (a2152dd1) +* **api:** + * closes #9123 category and topic routes migrated to Write API (edb8da1e) + * #9123, migrate rest of the getObject controllers to Write API (9ecfac9b) + * #9123, migrate /api/post/pid/:pid to Write API (e267f295) + * group ownership API route, switch client-side to use API route (32e36f7b) + * add schema for groups update route (98550d61) + * added schema for email unsubscribe token (4fc13377) +* **acp:** + * admin tags privilege (223f0a55) + * admins-mods privilege (fb46a8d9) + * added new admin privilege for groups management (da191341) + +##### Bug Fixes + +* #9130, remove timestamp prefix from thumbnail names in API response (171017c3) +* #9166 missing relative path in topic thumbs modal and topic list (b9ba44ed) +* #9163, fix total connection count on ACP (1968bf50) +* genericise .necro-post, bump persona to latest (041d45c3) +* #9126, skip base64 and long values (33290850) +* #9127, use assets path (3121215e) +* inability for admins with setting privilege to save plugin settings (a555f024) +* #9149, server-side handling of disableChatMessageEditing (895e3d93) +* #9149, incorrect client-side `disableChatMessageEditing` value for admins/gmods (d27815a8) +* #9151, dont use service worker for posts requests (20c1b684) +* #9150, fix selector so it doesn't add img-responsive to profile pics (183cabe9) +* tests (28740360) +* dont show deleted posts in navigator (931105e6) +* bug in api path existence test (501a7b77) +* #9136, fix move topic/post timeout errors (2ef72a94) +* bad assignment logic in middleware.renderHeader (34ccabe3) +* #9113, wrong path separator used in thumbs.get (da4f9118) +* email testing and settings change from ACP (2be396ff) +* removing ability to specify deprecated topic 'thumb' on topic creation (713f029d) +* #9129, event is fired on socket.io (b369dc88) +* subfolder handling in tests (bbd97ccb) +* .flat() not defined in v10, added debug router to exclusion list (6062039d) +* all tests, wrap up work (f416dc17) +* two more routes (9c2de86a) +* api tests (b9a61d2d) +* don't return deleted: 0 for ephemeral groups (600807fb) +* send fewer items to client-side for ACP settings/email page (438fa5c8) +* errors in write-api schema (c079051b) +* broken tests from last round of fixes (990f1077) +* bad error message for request body api test (a9629357) +* modify backreference test to not check router.all() calls (7fc329de) +* add missing token generation route to write api spec (eef052c1) +* trigger action:posts.edited (b7b588f5) +* **deps:** + * update dependency autoprefixer to v10.2.0 (e445ae5a) + * update socket.io packages to v3.0.5 (fd045c67) + * update dependency nodebb-theme-persona to v10.3.16 (87e333b4) + * update dependency benchpressjs to v2.4.0 (4524f825) + * update dependency nodebb-theme-persona to v10.3.15 (189be9e0) + * update dependency nodebb-widget-essentials to v5.0.2 (1dd1d3b0) + * update dependency nodebb-widget-essentials to v5.0.1 (#9144) (f55dddb2) + * update dependency nodebb-plugin-composer-default to v6.5.5 (6d980d26) + * update dependency sharp to v0.27.0 (4919e596) + * update dependency nodebb-theme-persona to v10.3.12 (37b35f7d) + * update dependency nodebb-theme-persona to v10.3.11 (db4c6863) +* **tests:** handle nested allOf blocks (77a5adb6) +* **api:** + * failing test due to missing file (3959a7bd) + * tests (80ee3dfb) +* **pwa:** #9127 service-worker.js missing on subfolder installs (b8d4709e) + +##### Refactors + +* **openapi:** update TopicObject component to reference TopicObjectSlim in its schema (fb3f3f72) +* **api:** + * deprecated groups update socket in favour of API lib (1cd2689c) + * update group deletion calls to use write API (e640a41a) +* schema backreference test to use map instead of reduce, properly check write-api routes (878ee067) + +##### Tests + +* changed test a bit to see what is going on (5f038dff) + #### v1.16.0 (2020-12-17) ##### Breaking Changes From e3cd7a2343d61a7b0f5989b5d596b47651f03ae1 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 6 Jan 2021 19:17:55 +0000 Subject: [PATCH 02/69] chore: incrementing version number - v1.16.1 (cherry picked from commit 7fb032b396261149419df693fd68002cb3607923) Signed-off-by: Misty (Bot) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 52ced9f460..eaf379ea38 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.16.1-beta.0", + "version": "1.16.1", "homepage": "http://www.nodebb.org", "repository": { "type": "git", From 072a0e32d80d6270fd05817883ea0318d7388d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 6 Jan 2021 17:04:18 -0500 Subject: [PATCH 03/69] feat: #8813, faster category search dropdown when you have 4k+ categories manipulating DOM becomes very slow clone the list and manipulate it outside of DOM, replace list on DOM when search is done add utils.debounce so list is updated slower --- public/src/modules/categorySearch.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/public/src/modules/categorySearch.js b/public/src/modules/categorySearch.js index 5e8f2fcd26..ed78a5ebe0 100644 --- a/public/src/modules/categorySearch.js +++ b/public/src/modules/categorySearch.js @@ -14,18 +14,23 @@ define('categorySearch', function () { var toggleVisibility = searchEl.parent('[component="category/dropdown"]').length > 0 || searchEl.parent('[component="category-selector"]').length > 0; - var categoryEls = el.find('[component="category/list"] [data-cid]'); + var listEl = el.find('[component="category/list"]'); + var clonedList = listEl.clone(); + var categoryEls = clonedList.find('[data-cid]'); + el.on('show.bs.dropdown', function () { + var cidToParentCid = {}; + function revealParents(cid) { - var parentCid = el.find('[component="category/list"] [data-cid="' + cid + '"]').attr('data-parent-cid'); + var parentCid = cidToParentCid[cid]; if (parentCid) { - el.find('[component="category/list"] [data-cid="' + parentCid + '"]').removeClass('hidden'); + clonedList.find('[data-cid="' + parentCid + '"]').removeClass('hidden'); revealParents(parentCid); } } function revealChildren(cid) { - var els = el.find('[component="category/list"] [data-parent-cid="' + cid + '"]'); + var els = clonedList.find('[data-parent-cid="' + cid + '"]'); els.each(function (index, el) { var $el = $(el); $el.removeClass('hidden'); @@ -39,12 +44,14 @@ define('categorySearch', function () { var cids = []; categoryEls.each(function () { var liEl = $(this); - var isMatch = liEl.attr('data-name').toLowerCase().indexOf(val) !== -1; + var isMatch = cids.length < 100 && (!val || (val.length > 1 && liEl.attr('data-name').toLowerCase().indexOf(val) !== -1)); if (noMatch && isMatch) { noMatch = false; } if (isMatch && val) { - cids.push(liEl.attr('data-cid')); + var cid = liEl.attr('data-cid'); + cids.push(cid); + cidToParentCid[cid] = parseInt(liEl.attr('data-parent-cid'), 10); } liEl.toggleClass('hidden', !isMatch).find('[component="category-markup"]').css({ 'font-weight': val && isMatch ? 'bold' : 'normal' }); }); @@ -54,6 +61,7 @@ define('categorySearch', function () { revealChildren(cid); }); + listEl.html(clonedList.html()); el.find('[component="category/list"] [component="category/no-matches"]').toggleClass('hidden', !noMatch); } if (toggleVisibility) { @@ -65,7 +73,7 @@ define('categorySearch', function () { ev.preventDefault(); ev.stopPropagation(); }); - searchEl.find('input').val('').on('keyup', updateList); + searchEl.find('input').val('').on('keyup', utils.debounce(updateList, 200)); updateList(); }); el.on('shown.bs.dropdown', function () { From a51c5698c704a39efdbd87b9c77ee66e0208b3f1 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Wed, 6 Jan 2021 18:36:50 -0700 Subject: [PATCH 04/69] fix: `--help` usage info yargs (via nconf) would exit when detecting a help flag also improves the speed of `./nodebb help build` --- install/package.json | 1 + src/cli/index.js | 48 +++++++++++++++++++++++++------------------- src/cli/manage.js | 23 --------------------- src/meta/aliases.js | 44 ++++++++++++++++++++++++++++++++++++++++ src/meta/build.js | 26 ++++-------------------- 5 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 src/meta/aliases.js diff --git a/install/package.json b/install/package.json index eaf379ea38..0a448564e9 100644 --- a/install/package.json +++ b/install/package.json @@ -146,6 +146,7 @@ "winston": "3.3.3", "xml": "^1.0.1", "xregexp": "^4.3.0", + "yargs": "16.2.0", "zxcvbn": "^4.4.2" }, "devDependencies": { diff --git a/src/cli/index.js b/src/cli/index.js index 8d53f069f7..88683d24ba 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,3 +1,5 @@ +/* eslint-disable import/order */ + 'use strict'; const fs = require('fs'); @@ -35,13 +37,13 @@ try { try { fs.accessSync(path.join(paths.nodeModules, 'semver/package.json'), fs.constants.R_OK); - var semver = require('semver'); - var defaultPackage = require('../../install/package.json'); + const semver = require('semver'); + const defaultPackage = require('../../install/package.json'); - var checkVersion = function (packageName) { - var version = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, packageName, 'package.json'), 'utf8')).version; + const checkVersion = function (packageName) { + const version = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, packageName, 'package.json'), 'utf8')).version; if (!semver.satisfies(version, defaultPackage.dependencies[packageName])) { - var e = new TypeError('Incorrect dependency version: ' + packageName); + const e = new TypeError('Incorrect dependency version: ' + packageName); e.code = 'DEP_WRONG_VERSION'; throw e; } @@ -67,14 +69,13 @@ try { } require('colors'); -// eslint-disable-next-line -var nconf = require('nconf'); -// eslint-disable-next-line -var program = require('commander'); +const nconf = require('nconf'); +const { program } = require('commander'); +const yargs = require('yargs'); -var pkg = require('../../package.json'); -var file = require('../file'); -var prestart = require('../prestart'); +const pkg = require('../../package.json'); +const file = require('../file'); +const prestart = require('../prestart'); program .name('./nodebb') @@ -86,19 +87,23 @@ program .option('-d, --dev', 'Development mode, including verbose logging', false) .option('-l, --log', 'Log subprocess output to console', false); -nconf.argv().env({ +// provide a yargs object ourselves +// otherwise yargs will consume `--help` or `help` +// and `nconf` will exit with useless usage info +const opts = yargs(process.argv.slice(2)).help(false).exitProcess(false); +nconf.argv(opts).env({ separator: '__', }); -var env = program.dev ? 'development' : (process.env.NODE_ENV || 'production'); +const env = program.dev ? 'development' : (process.env.NODE_ENV || 'production'); process.env.NODE_ENV = env; global.env = env; prestart.setupWinston(); // Alternate configuration file support -var configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json'); -var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); +const configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json'); +const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); prestart.loadConfig(configFile); prestart.versionCheck(); @@ -195,7 +200,7 @@ program require('./manage').build(targets.length ? targets : true, options); }) .on('--help', function () { - require('./manage').buildTargets(); + require('../meta/aliases').buildTargets(); }); program .command('activate [plugin]') @@ -223,7 +228,7 @@ program }); // reset -var resetCommand = program.command('reset'); +const resetCommand = program.command('reset'); resetCommand .description('Reset plugins, themes, settings, etc') @@ -233,7 +238,7 @@ resetCommand .option('-s, --settings', 'Reset settings to their default values') .option('-a, --all', 'All of the above') .action(function (options) { - var valid = ['theme', 'plugin', 'widgets', 'settings', 'all'].some(function (x) { + const valid = ['theme', 'plugin', 'widgets', 'settings', 'all'].some(function (x) { return options[x]; }); if (!valid) { @@ -295,10 +300,11 @@ program return program.help(); } - var command = program.commands.find(function (command) { return command._name === name; }); + const command = program.commands.find(function (command) { return command._name === name; }); if (command) { command.help(); } else { + console.log(`error: unknown command '${command}'.`); program.help(); } }); @@ -311,4 +317,4 @@ if (process.argv.length === 2) { program.executables = false; -program.parse(process.argv); +program.parse(); diff --git a/src/cli/manage.js b/src/cli/manage.js index c986a2725f..d586484bf6 100644 --- a/src/cli/manage.js +++ b/src/cli/manage.js @@ -2,7 +2,6 @@ const winston = require('winston'); const childProcess = require('child_process'); -const _ = require('lodash'); const CliGraph = require('cli-graph'); const build = require('../meta/build'); @@ -13,27 +12,6 @@ const analytics = require('../analytics'); const reset = require('./reset'); const { pluginNamePattern, themeNamePattern } = require('../constants'); -function buildTargets() { - var aliases = build.aliases; - var length = 0; - var output = Object.keys(aliases).map(function (name) { - var arr = aliases[name]; - if (name.length > length) { - length = name.length; - } - - return [name, arr.join(', ')]; - }).map(function (tuple) { - return ' ' + _.padEnd('"' + tuple[0] + '"', length + 2).magenta + ' | ' + tuple[1]; - }).join('\n'); - console.log( - '\n\n Build targets:\n' + - ('\n ' + _.padEnd('Target', length + 2) + ' | Aliases').green + - '\n ------------------------------------------------------\n'.blue + - output + '\n' - ); -} - async function activate(plugin) { if (themeNamePattern.test(plugin)) { await reset.reset({ @@ -176,7 +154,6 @@ async function buildWrapper(targets, options) { } exports.build = buildWrapper; -exports.buildTargets = buildTargets; exports.activate = activate; exports.listPlugins = listPlugins; exports.listEvents = listEvents; diff --git a/src/meta/aliases.js b/src/meta/aliases.js new file mode 100644 index 0000000000..d35df972f1 --- /dev/null +++ b/src/meta/aliases.js @@ -0,0 +1,44 @@ +'use strict'; + +const _ = require('lodash'); + +const aliases = { + 'plugin static dirs': ['staticdirs'], + 'requirejs modules': ['rjs', 'modules'], + 'client js bundle': ['clientjs', 'clientscript', 'clientscripts'], + 'admin js bundle': ['adminjs', 'adminscript', 'adminscripts'], + javascript: ['js'], + 'client side styles': [ + 'clientcss', 'clientless', 'clientstyles', 'clientstyle', + ], + 'admin control panel styles': [ + 'admincss', 'adminless', 'adminstyles', 'adminstyle', 'acpcss', 'acpless', 'acpstyles', 'acpstyle', + ], + styles: ['css', 'less', 'style'], + templates: ['tpl'], + languages: ['lang', 'i18n'], +}; + +exports.aliases = aliases; + +function buildTargets() { + var length = 0; + var output = Object.keys(aliases).map(function (name) { + var arr = aliases[name]; + if (name.length > length) { + length = name.length; + } + + return [name, arr.join(', ')]; + }).map(function (tuple) { + return ' ' + _.padEnd('"' + tuple[0] + '"', length + 2).magenta + ' | ' + tuple[1]; + }).join('\n'); + console.log( + '\n\n Build targets:\n' + + ('\n ' + _.padEnd('Target', length + 2) + ' | Aliases').green + + '\n ------------------------------------------------------\n'.blue + + output + '\n' + ); +} + +exports.buildTargets = buildTargets; diff --git a/src/meta/build.js b/src/meta/build.js index 9c63902d3a..dbf69296e1 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -8,6 +8,7 @@ const path = require('path'); const mkdirp = require('mkdirp'); const cacheBuster = require('./cacheBuster'); +const { aliases } = require('./aliases'); let meta; const targetHandlers = { @@ -47,26 +48,7 @@ const targetHandlers = { }, }; -let aliases = { - 'plugin static dirs': ['staticdirs'], - 'requirejs modules': ['rjs', 'modules'], - 'client js bundle': ['clientjs', 'clientscript', 'clientscripts'], - 'admin js bundle': ['adminjs', 'adminscript', 'adminscripts'], - javascript: ['js'], - 'client side styles': [ - 'clientcss', 'clientless', 'clientstyles', 'clientstyle', - ], - 'admin control panel styles': [ - 'admincss', 'adminless', 'adminstyles', 'adminstyle', 'acpcss', 'acpless', 'acpstyles', 'acpstyle', - ], - styles: ['css', 'less', 'style'], - templates: ['tpl'], - languages: ['lang', 'i18n'], -}; - -exports.aliases = aliases; - -aliases = Object.keys(aliases).reduce(function (prev, key) { +const aliasMap = Object.keys(aliases).reduce(function (prev, key) { var arr = aliases[key]; arr.forEach(function (alias) { prev[alias] = key; @@ -151,7 +133,7 @@ exports.build = async function (targets, options) { // get full target name .map(function (target) { target = target.toLowerCase().replace(/-/g, ''); - if (!aliases[target]) { + if (!aliasMap[target]) { winston.warn('[build] Unknown target: ' + target); if (target.includes(',')) { winston.warn('[build] Are you specifying multiple targets? Separate them with spaces:'); @@ -161,7 +143,7 @@ exports.build = async function (targets, options) { return false; } - return aliases[target]; + return aliasMap[target]; }) // filter nonexistent targets .filter(Boolean); From f0dd302c77ea4002b351e78648adcafd67c6f637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 6 Jan 2021 21:25:32 -0500 Subject: [PATCH 05/69] perf: use only required calls --- src/privileges/categories.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 10d5c7c785..188fb10ac6 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -106,8 +106,14 @@ module.exports = function (privileges) { } cids = _.uniq(cids); - const results = await privileges.categories.getBase(privilege, cids, uid); - return cids.filter((cid, index) => !!cid && !results.categories[index].disabled && (results.allowedTo[index] || results.isAdmin)); + const [categoryData, allowedTo, isAdmin] = await Promise.all([ + categories.getCategoriesFields(cids, ['disabled']), + helpers.isAllowedTo(privilege, uid, cids), + user.isAdministrator(uid), + ]); + return cids.filter( + (cid, index) => !!cid && !categoryData[index].disabled && (allowedTo[index] || isAdmin) + ); }; privileges.categories.getBase = async function (privilege, cids, uid) { From 6cbb77afda6860205ca8726069bc9bb0808aceb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 6 Jan 2021 22:00:56 -0500 Subject: [PATCH 06/69] fix: add missing breadcrumb on /user//categories --- src/controllers/accounts/categories.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/accounts/categories.js b/src/controllers/accounts/categories.js index a1253ef57e..1f0f434f09 100644 --- a/src/controllers/accounts/categories.js +++ b/src/controllers/accounts/categories.js @@ -3,6 +3,7 @@ const user = require('../../user'); const categories = require('../../categories'); const accountHelpers = require('./helpers'); +const helpers = require('../helpers'); const categoriesController = module.exports; @@ -25,5 +26,9 @@ categoriesController.get = async function (req, res, next) { }); userData.categories = categoriesData; userData.title = '[[pages:account/watched_categories, ' + userData.username + ']]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([ + { text: userData.username, url: '/user/' + userData.userslug }, + { text: '[[pages:categories]]' }, + ]); res.render('account/categories', userData); }; From d3e041e2921e105a6502ba6d514f494d836a85d4 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 7 Jan 2021 02:33:39 +0000 Subject: [PATCH 07/69] chore(deps): update dependency husky to v4.3.7 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0a448564e9..d35197d4ec 100644 --- a/install/package.json +++ b/install/package.json @@ -159,7 +159,7 @@ "eslint-plugin-import": "2.22.1", "grunt": "1.3.0", "grunt-contrib-watch": "1.1.0", - "husky": "4.3.6", + "husky": "4.3.7", "jsdom": "16.4.0", "lint-staged": "10.5.3", "mocha": "8.2.1", From 1cf0032d9fb74396d0778a2e30ada9f03fdb9223 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 12:16:35 -0500 Subject: [PATCH 08/69] feat: allow override of local fns in login controller, 400 instead of 500 for wrong login type [breaking] --- src/controllers/authentication.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index eec386bb8f..48ed0393e6 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -226,7 +226,7 @@ authenticationController.login = function (req, res, next) { plugins.hooks.fire('filter:login.check', { req: req, res: res, userData: req.body }, (err) => { if (err) { - return helpers.noScriptErrors(req, res, err.message, 403); + return (res.locals.noScriptErrors || helpers.noScriptErrors)(req, res, err.message, 403); } if (req.body.username && utils.isEmailValid(req.body.username) && loginWith.includes('email')) { async.waterfall([ @@ -235,14 +235,14 @@ authenticationController.login = function (req, res, next) { }, function (username, next) { req.body.username = username || req.body.username; - continueLogin(req, res, next); + (res.locals.continueLogin || continueLogin)(req, res, next); }, ], next); } else if (loginWith.includes('username') && !validator.isEmail(req.body.username)) { - continueLogin(req, res, next); + (res.locals.continueLogin || continueLogin)(req, res, next); } else { err = '[[error:wrong-login-type-' + loginWith + ']]'; - helpers.noScriptErrors(req, res, err, 500); + (res.locals.noScriptErrors || helpers.noScriptErrors)(req, res, err, 400); } }); }; From 56f929ed4f0bea0efcb4edd1bbe1dfb35e6b73d3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:05:15 -0500 Subject: [PATCH 09/69] feat: add write API route for checking login credentials --- src/controllers/write/index.js | 1 + src/controllers/write/utilities.js | 44 ++++++++++++++++++++++++++++++ src/routes/write/index.js | 16 +++-------- src/routes/write/utilities.js | 16 +++++++++++ src/user/posts.js | 1 + 5 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 src/controllers/write/utilities.js create mode 100644 src/routes/write/utilities.js diff --git a/src/controllers/write/index.js b/src/controllers/write/index.js index 66d6598780..e781467c18 100644 --- a/src/controllers/write/index.js +++ b/src/controllers/write/index.js @@ -9,3 +9,4 @@ Write.topics = require('./topics'); Write.posts = require('./posts'); Write.admin = require('./admin'); Write.files = require('./files'); +Write.utilities = require('./utilities'); diff --git a/src/controllers/write/utilities.js b/src/controllers/write/utilities.js new file mode 100644 index 0000000000..fe6dbf78a3 --- /dev/null +++ b/src/controllers/write/utilities.js @@ -0,0 +1,44 @@ +'use strict'; + +const user = require('../../user'); +const authenticationController = require('../authentication'); +const slugify = require('../../slugify'); +const helpers = require('../helpers'); + +const Utilities = module.exports; + +Utilities.ping = {}; +Utilities.ping.get = (req, res) => { + helpers.formatApiResponse(200, res, { + pong: true, + }); +}; + +Utilities.ping.post = (req, res) => { + helpers.formatApiResponse(200, res, { + uid: req.user.uid, + received: req.body, + }); +}; + +Utilities.login = (req, res) => { + res.locals.continueLogin = async (req, res) => { + const { username, password } = req.body; + + const userslug = slugify(username); + const uid = await user.getUidByUserslug(userslug); + const ok = await user.isPasswordCorrect(uid, password, req.ip); + + if (ok) { + const userData = await user.getUsers([uid], uid); + helpers.formatApiResponse(200, res, userData); + } else { + helpers.formatApiResponse(403, res); + } + }; + res.locals.noScriptErrors = (req, res, err, statusCode) => { + helpers.formatApiResponse(statusCode, res, new Error(err)); + }; + + authenticationController.login(req, res); +}; diff --git a/src/routes/write/index.js b/src/routes/write/index.js index 6cf266acdb..b2689cc586 100644 --- a/src/routes/write/index.js +++ b/src/routes/write/index.js @@ -4,6 +4,7 @@ const winston = require('winston'); const meta = require('../../meta'); const plugins = require('../../plugins'); const middleware = require('../../middleware'); +const writeControllers = require('../../controllers/write'); const helpers = require('../../controllers/helpers'); const Write = module.exports; @@ -38,19 +39,10 @@ Write.reload = async (params) => { router.use('/api/v3/posts', require('./posts')()); router.use('/api/v3/admin', require('./admin')()); router.use('/api/v3/files', require('./files')()); + router.use('/api/v3/utilities', require('./utilities')()); - router.get('/api/v3/ping', function (req, res) { - helpers.formatApiResponse(200, res, { - pong: true, - }); - }); - - router.post('/api/v3/ping', middleware.authenticate, function (req, res) { - helpers.formatApiResponse(200, res, { - uid: req.user.uid, - received: req.body, - }); - }); + router.get('/api/v3/ping', writeControllers.utilities.ping.get); + router.post('/api/v3/ping', middleware.authenticate, writeControllers.utilities.ping.post); /** * Plugins can add routes to the Write API by attaching a listener to the diff --git a/src/routes/write/utilities.js b/src/routes/write/utilities.js new file mode 100644 index 0000000000..085536766c --- /dev/null +++ b/src/routes/write/utilities.js @@ -0,0 +1,16 @@ +'use strict'; + +const router = require('express').Router(); +const middleware = require('../../middleware'); +const controllers = require('../../controllers'); +const routeHelpers = require('../helpers'); + +const setupApiRoute = routeHelpers.setupApiRoute; + +module.exports = function () { + // The "ping" routes are mounted at root level, but for organizational purposes, the controllers are in `utilities.js` + + setupApiRoute(router, 'post', '/login', [middleware.checkRequired.bind(null, ['username', 'password'])], controllers.write.utilities.login); + + return router; +}; diff --git a/src/user/posts.js b/src/user/posts.js index 9f85b3456a..a3ee52a854 100644 --- a/src/user/posts.js +++ b/src/user/posts.js @@ -1,5 +1,6 @@ 'use strict'; +const { loadFiles } = require('nconf'); const db = require('../database'); const meta = require('../meta'); const privileges = require('../privileges'); From 8bbb3208673a481950e997870f649e03860cefc8 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:05:23 -0500 Subject: [PATCH 10/69] feat: handle HTTP 429 as a response code --- src/controllers/helpers.js | 5 +++++ src/controllers/write/utilities.js | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 517035f0f8..5cd77f4487 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -461,6 +461,11 @@ helpers.generateError = (statusCode, message) => { payload.status.message = message || 'HTTPS is required for requests to the write api, please re-send your request via HTTPS'; break; + case 429: + payload.status.code = 'too-many-requests'; + payload.status.message = message || 'You have made too many requests, please try again later'; + break; + case 500: payload.status.code = 'internal-server-error'; payload.status.message = message || payload.status.message; diff --git a/src/controllers/write/utilities.js b/src/controllers/write/utilities.js index fe6dbf78a3..35a7b74275 100644 --- a/src/controllers/write/utilities.js +++ b/src/controllers/write/utilities.js @@ -27,7 +27,16 @@ Utilities.login = (req, res) => { const userslug = slugify(username); const uid = await user.getUidByUserslug(userslug); - const ok = await user.isPasswordCorrect(uid, password, req.ip); + let ok = false; + try { + ok = await user.isPasswordCorrect(uid, password, req.ip); + } catch (err) { + if (err.message === '[[error:account-locked]]') { + helpers.formatApiResponse(429, res, err); + } else { + helpers.formatApiResponse(500, res, err); + } + } if (ok) { const userData = await user.getUsers([uid], uid); From 534224133b5699cf2b54483c805ee94eff6a4561 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:06:48 -0500 Subject: [PATCH 11/69] fix: random loadFiles added by errant vscode autocompletion --- src/user/posts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/user/posts.js b/src/user/posts.js index a3ee52a854..9f85b3456a 100644 --- a/src/user/posts.js +++ b/src/user/posts.js @@ -1,6 +1,5 @@ 'use strict'; -const { loadFiles } = require('nconf'); const db = require('../database'); const meta = require('../meta'); const privileges = require('../privileges'); From 87a7d85e895878c2680169cf4852d3f4e1c2268c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:15:12 -0500 Subject: [PATCH 12/69] fix: missing breadcrumbs in schema --- public/openapi/read/user/userslug/categories.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/public/openapi/read/user/userslug/categories.yaml b/public/openapi/read/user/userslug/categories.yaml index b3d7b5feb5..3860dad988 100644 --- a/public/openapi/read/user/userslug/categories.yaml +++ b/public/openapi/read/user/userslug/categories.yaml @@ -58,4 +58,5 @@ get: type: string title: type: string + - $ref: ../../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file From 8c86f5bceca2ee7c57c5585174bd63c69f75836d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:36:02 -0500 Subject: [PATCH 13/69] fix: bad execution flow in utilities.login --- src/controllers/write/utilities.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/controllers/write/utilities.js b/src/controllers/write/utilities.js index 35a7b74275..790bb45976 100644 --- a/src/controllers/write/utilities.js +++ b/src/controllers/write/utilities.js @@ -32,9 +32,7 @@ Utilities.login = (req, res) => { ok = await user.isPasswordCorrect(uid, password, req.ip); } catch (err) { if (err.message === '[[error:account-locked]]') { - helpers.formatApiResponse(429, res, err); - } else { - helpers.formatApiResponse(500, res, err); + return helpers.formatApiResponse(429, res, err); } } From 97d678fd2a1439870085381da9f476241805d553 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:44:13 -0500 Subject: [PATCH 14/69] fix: return a user object, not an array of user objects (in v3 login check route) --- src/controllers/write/utilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/write/utilities.js b/src/controllers/write/utilities.js index 790bb45976..1d81fa0698 100644 --- a/src/controllers/write/utilities.js +++ b/src/controllers/write/utilities.js @@ -38,7 +38,7 @@ Utilities.login = (req, res) => { if (ok) { const userData = await user.getUsers([uid], uid); - helpers.formatApiResponse(200, res, userData); + helpers.formatApiResponse(200, res, userData.pop()); } else { helpers.formatApiResponse(403, res); } From 0da2843281fe9a4a5ca998c985e4169ff4126635 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:44:51 -0500 Subject: [PATCH 15/69] feat(api): schema definition for new credential checking route --- public/openapi/write.yaml | 2 ++ public/openapi/write/login.yaml | 30 ++++++++++++++++++++++++++++++ test/api.js | 4 ++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 public/openapi/write/login.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 2b6266d64c..1c691bc5ac 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -42,6 +42,8 @@ tags: paths: /ping: $ref: 'write/ping.yaml' + /utilities/login: + $ref: 'write/login.yaml' /users/: $ref: 'write/users.yaml' /users/{uid}: diff --git a/public/openapi/write/login.yaml b/public/openapi/write/login.yaml new file mode 100644 index 0000000000..43ba2f8cb9 --- /dev/null +++ b/public/openapi/write/login.yaml @@ -0,0 +1,30 @@ +post: + tags: + - utilities + summary: verify login credentials + description: | + This route accepts a username/password or email/password pair (dependent on forum settings), returning a standard user object if credentials are validated successfully. + requestBody: + content: + application/json: + schema: + type: object + properties: + username: + type: string + example: admin + password: + type: string + example: '123456' + responses: + '200': + description: credentials successfully validated + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../components/schemas/Status.yaml#/Status + response: + $ref: ../components/schemas/UserObject.yaml#/UserObjectSlim \ No newline at end of file diff --git a/test/api.js b/test/api.js index e4c28cfcef..730682a7e9 100644 --- a/test/api.js +++ b/test/api.js @@ -332,7 +332,7 @@ describe('API', async () => { } }); - it('should resolve with a 200 when called', async () => { + it('should not error out when called', async () => { await setupData(); if (csrfToken) { @@ -372,7 +372,7 @@ describe('API', async () => { }); } } catch (e) { - assert(!e, `${method.toUpperCase()} ${path} resolved with ${e.message}`); + assert(!e, `${method.toUpperCase()} ${path} errored with: ${e.message}`); } }); From 9534d9561977da6ff420c560e972ca6c77ac835c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 7 Jan 2021 14:58:19 -0500 Subject: [PATCH 16/69] fix: broken test due to change in response code --- test/authentication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/authentication.js b/test/authentication.js index 6a1d255bac..35be6ad0c2 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -422,7 +422,7 @@ describe('authentication', function () { loginUser('ginger@nodebb.org', '123456', function (err, response, body) { meta.config.allowLoginWith = 'username-email'; assert.ifError(err); - assert.equal(response.statusCode, 500); + assert.equal(response.statusCode, 400); assert.equal(body, '[[error:wrong-login-type-username]]'); done(); }); From da5469707584f37bd6f86b636520db4ed382a410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 7 Jan 2021 20:58:45 -0500 Subject: [PATCH 17/69] fix: #9176, limit description size --- src/upgrades/1.15.0/consolidate_flags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrades/1.15.0/consolidate_flags.js b/src/upgrades/1.15.0/consolidate_flags.js index 49da790840..481895e9d3 100644 --- a/src/upgrades/1.15.0/consolidate_flags.js +++ b/src/upgrades/1.15.0/consolidate_flags.js @@ -32,7 +32,7 @@ module.exports = { } methods.push( - db.sortedSetAdd.bind(db, `flag:${flagObj.flagId}:reports`, flagObj.datetime, flagObj.description), + db.sortedSetAdd.bind(db, `flag:${flagObj.flagId}:reports`, flagObj.datetime, String(flagObj.description).substr(0, 250)), db.sortedSetAdd.bind(db, `flag:${flagObj.flagId}:reporters`, flagObj.datetime, flagObj.uid) ); From 8ece64ab829f157898b3b52816a4ffc6df6fd26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 7 Jan 2021 21:42:19 -0500 Subject: [PATCH 18/69] fix: test for https://github.com/NodeBB/NodeBB/pull/9180 --- test/controllers.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/controllers.js b/test/controllers.js index 670ab3be34..382e1ccd62 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -95,17 +95,17 @@ describe('Controllers', function () { assert(hookData.res); assert(hookData.next); - hookData.res.render('custom', { + hookData.res.render('mycustompage', { works: true, }); } var message = utils.generateUUID(); - var name = 'custom.tpl'; + var name = 'mycustompage.tpl'; var tplPath = path.join(nconf.get('views_dir'), name); before(async () => { plugins.registerHook('myTestPlugin', { - hook: 'action:homepage.get:custom', + hook: 'action:homepage.get:mycustompage', method: hookMethod, }); @@ -224,14 +224,14 @@ describe('Controllers', function () { }); it('api should work with hook', function (done) { - meta.configs.set('homePageRoute', 'custom', function (err) { + meta.configs.set('homePageRoute', 'mycustompage', function (err) { assert.ifError(err); request(nconf.get('url') + '/api', { json: true }, function (err, res, body) { assert.ifError(err); assert.equal(res.statusCode, 200); assert.equal(body.works, true); - assert.equal(body.template.custom, true); + assert.equal(body.template.mycustompage, true); done(); }); @@ -239,7 +239,7 @@ describe('Controllers', function () { }); it('should render with hook', function (done) { - meta.configs.set('homePageRoute', 'custom', function (err) { + meta.configs.set('homePageRoute', 'mycustompage', function (err) { assert.ifError(err); request(nconf.get('url'), function (err, res, body) { From e14b67786c94d6c59fa6c984b0b08093dc6de160 Mon Sep 17 00:00:00 2001 From: Manuel Valle Date: Thu, 7 Jan 2021 20:44:02 -0600 Subject: [PATCH 19/69] Solve Custom home page route error bug (#9180) When you select Custom Route as home you get a 404 error "/custom not found" error. This because 'homePageRoute' property was used instead of 'homePageCustom' --- src/controllers/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/home.js b/src/controllers/home.js index 4b69e719c7..d8fa7a1d83 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -7,7 +7,7 @@ const meta = require('../meta'); const user = require('../user'); function adminHomePageRoute() { - return (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; + return ((meta.config.homePageRoute === 'custom' ? meta.config.homePageCustom : meta.config.homePageRoute) || 'categories').replace(/^\//, ''); } async function getUserHomeRoute(uid) { From 5b3c48fd025b5cdb952ee88458d668b137b3401b Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 8 Jan 2021 02:46:23 +0000 Subject: [PATCH 20/69] fix(deps): update dependency autoprefixer to v10.2.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d35197d4ec..0c3d4986c3 100644 --- a/install/package.json +++ b/install/package.json @@ -39,7 +39,7 @@ "ace-builds": "^1.4.9", "archiver": "^5.0.0", "async": "^3.2.0", - "autoprefixer": "10.2.0", + "autoprefixer": "10.2.1", "bcryptjs": "2.4.3", "benchpressjs": "2.4.0", "body-parser": "^1.19.0", From 36069da2f8b326f838ffa677def3e68e45a003a1 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 8 Jan 2021 02:46:31 +0000 Subject: [PATCH 21/69] fix(deps): update dependency sortablejs to v1.13.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0c3d4986c3..865e1a0a19 100644 --- a/install/package.json +++ b/install/package.json @@ -132,7 +132,7 @@ "socket.io-adapter-cluster": "^1.0.1", "socket.io-client": "3.0.5", "socket.io-redis": "6.0.1", - "sortablejs": "1.10.2", + "sortablejs": "1.13.0", "spdx-license-list": "^6.1.0", "spider-detector": "2.0.0", "textcomplete": "^0.17.1", From d83d40cf9bfa5869f62cfd7266478b38053a5183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 8 Jan 2021 12:01:44 -0500 Subject: [PATCH 22/69] fix: missing error message --- public/language/en-GB/error.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 7a08489cb5..2c507278f3 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -158,6 +158,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", From 03a0e72fae84f6fd0fe6d9b46e5334e0a1d21280 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 10:14:55 -0500 Subject: [PATCH 23/69] refactor: split out logic dedicated to calculating unread counts, to a separate local method --- src/middleware/header.js | 92 +++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index 2174f7c461..e363dee7cc 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -79,9 +79,6 @@ middleware.renderHeader = async function renderHeader(req, res, data) { timeagoCode: languages.userTimeagoCode(res.locals.config.userLang), browserTitle: translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title))), navigation: navigation.get(req.uid), - unreadData: topics.getUnreadData({ uid: req.uid }), - unreadChatCount: messaging.getUnreadCount(req.uid), - unreadNotificationCount: user.notifications.getUnreadCount(req.uid), }); const unreadData = { @@ -105,44 +102,15 @@ middleware.renderHeader = async function renderHeader(req, res, data) { templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+ - - const unreadCounts = results.unreadData.counts; - const unreadCount = { - topic: unreadCounts[''] || 0, - newTopic: unreadCounts.new || 0, - watchedTopic: unreadCounts.watched || 0, - unrepliedTopic: unreadCounts.unreplied || 0, - chat: results.unreadChatCount || 0, - notification: results.unreadNotificationCount || 0, - }; - - Object.keys(unreadCount).forEach(function (key) { - if (unreadCount[key] > 99) { - unreadCount[key] = '99+'; - } - }); - - const tidsByFilter = results.unreadData.tidsByFilter; - results.navigation = results.navigation.map(function (item) { - function modifyNavItem(item, route, filter, content) { - if (item && validator.unescape(item.originalRoute) === route) { - unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); - item.content = content; - if (unreadCounts[filter] > 0) { - item.iconClass += ' unread-count'; - } - } - } - modifyNavItem(item, '/unread', '', unreadCount.topic); - modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); - modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); - modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); - return item; - }); - templateValues.browserTitle = results.browserTitle; - templateValues.navigation = results.navigation; - templateValues.unreadCount = unreadCount; + ({ + navigation: templateValues.navigation, + unreadCount: templateValues.unreadCount, + } = await appendUnreadCounts({ + uid: req.uid, + navigation: results.navigation, + unreadData, + })); templateValues.isAdmin = results.user.isAdmin; templateValues.isGlobalMod = results.user.isGlobalMod; templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; @@ -183,6 +151,50 @@ middleware.renderHeader = async function renderHeader(req, res, data) { return await req.app.renderAsync('header', hookReturn.templateValues); }; +async function appendUnreadCounts({ uid, navigation, unreadData }) { + const results = await utils.promiseParallel({ + unreadData: topics.getUnreadData({ uid: uid }), + unreadChatCount: messaging.getUnreadCount(uid), + unreadNotificationCount: user.notifications.getUnreadCount(uid), + }); + + const unreadCounts = results.unreadData.counts; + const unreadCount = { + topic: unreadCounts[''] || 0, + newTopic: unreadCounts.new || 0, + watchedTopic: unreadCounts.watched || 0, + unrepliedTopic: unreadCounts.unreplied || 0, + chat: results.unreadChatCount || 0, + notification: results.unreadNotificationCount || 0, + }; + + Object.keys(unreadCount).forEach(function (key) { + if (unreadCount[key] > 99) { + unreadCount[key] = '99+'; + } + }); + + const tidsByFilter = results.unreadData.tidsByFilter; + navigation = navigation.map(function (item) { + function modifyNavItem(item, route, filter, content) { + if (item && validator.unescape(item.originalRoute) === route) { + unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); + item.content = content; + if (unreadCounts[filter] > 0) { + item.iconClass += ' unread-count'; + } + } + } + modifyNavItem(item, '/unread', '', unreadCount.topic); + modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); + modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); + modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + return item; + }); + + return { navigation, unreadCount }; +} + middleware.renderFooter = async function renderFooter(req, res, templateValues) { const data = await plugins.hooks.fire('filter:middleware.renderFooter', { req: req, From 6cb5888c133355f6b0903356b4dddd2dcfd4de6b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 10:33:48 -0500 Subject: [PATCH 24/69] fix: unescape header navigation originalRoute [breaking] --- src/middleware/header.js | 2 +- src/navigation/index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index e363dee7cc..049d8a43a0 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -177,7 +177,7 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { const tidsByFilter = results.unreadData.tidsByFilter; navigation = navigation.map(function (item) { function modifyNavItem(item, route, filter, content) { - if (item && validator.unescape(item.originalRoute) === route) { + if (item && item.originalRoute === route) { unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); item.content = content; if (unreadCounts[filter] > 0) { diff --git a/src/navigation/index.js b/src/navigation/index.js index ed639afbfa..a581508367 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -1,6 +1,7 @@ 'use strict'; const nconf = require('nconf'); +const validator = require('validator'); const admin = require('./admin'); const groups = require('../groups'); @@ -12,7 +13,7 @@ navigation.get = async function (uid) { let data = await admin.get(); data = data.filter(item => item && item.enabled).map(function (item) { - item.originalRoute = item.route; + item.originalRoute = validator.unescape(item.route); if (!item.route.startsWith('http')) { item.route = relative_path + item.route; From 6a1311b4bca99a35aa39858f0ff7782e4ee2cd42 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 12:17:42 -0500 Subject: [PATCH 25/69] refactor: flags lib to have a separate getFlagIdsWithFilters method added quick filter for unresolved flags --- src/flags.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/flags.js b/src/flags.js index 8bf57257fd..e2c3085c6f 100644 --- a/src/flags.js +++ b/src/flags.js @@ -36,7 +36,11 @@ Flags.init = async function () { if (!Array.isArray(value)) { sets.push(prefix + value); } else if (value.length) { - value.forEach(x => orSets.push(prefix + x)); + if (value.length === 1) { + sets.push(prefix + value.pop()); + } else { + value.forEach(x => orSets.push(prefix + x)); + } } } @@ -67,6 +71,10 @@ Flags.init = async function () { case 'mine': sets.push('flags:byAssignee:' + uid); break; + + case 'unresolved': + prepareSets(sets, orSets, 'flags:byState:', ['open', 'wip']); + break; } }, }, @@ -113,9 +121,13 @@ Flags.get = async function (flagId) { return data.flag; }; -Flags.list = async function (data) { - const filters = data.filters || {}; +Flags.getCount = async function ({ uid, filters }) { + filters = filters || {}; + const flagIds = await Flags.getFlagIdsWithFilters({ filters, uid }); + return flagIds.length; +}; +Flags.getFlagIdsWithFilters = async function ({ filters, uid }) { let sets = []; const orSets = []; @@ -126,7 +138,7 @@ Flags.list = async function (data) { for (var type in filters) { if (filters.hasOwnProperty(type)) { if (Flags._filters.hasOwnProperty(type)) { - Flags._filters[type](sets, orSets, filters[type], data.uid); + Flags._filters[type](sets, orSets, filters[type], uid); } else { winston.warn('[flags/list] No flag filter type found: ' + type); } @@ -152,6 +164,15 @@ Flags.list = async function (data) { } } + return flagIds; +}; + +Flags.list = async function (data) { + const filters = data.filters || {}; + let flagIds = Flags.getFlagIdsWithFilters({ + filters, + uid: data.uid, + }); flagIds = await Flags.sort(flagIds, data.sort); // Create subset for parsing based on page number (n=20) From c07e1e16afac834c37c30a53e207e635804aaa51 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 12:18:07 -0500 Subject: [PATCH 26/69] feat: add unread-count badge if navigator contains /flags route --- src/middleware/header.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index 049d8a43a0..b6c7651c79 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -10,6 +10,7 @@ var db = require('../database'); var user = require('../user'); var topics = require('../topics'); var messaging = require('../messaging'); +var flags = require('../flags'); var meta = require('../meta'); var plugins = require('../plugins'); var navigation = require('../navigation'); @@ -152,11 +153,25 @@ middleware.renderHeader = async function renderHeader(req, res, data) { }; async function appendUnreadCounts({ uid, navigation, unreadData }) { - const results = await utils.promiseParallel({ + const originalRoutes = navigation.map(nav => nav.originalRoute); + const calls = { unreadData: topics.getUnreadData({ uid: uid }), unreadChatCount: messaging.getUnreadCount(uid), unreadNotificationCount: user.notifications.getUnreadCount(uid), - }); + unreadFlagCount: (async function () { + if (originalRoutes.includes('/flags') && await user.isPrivileged(uid)) { + return flags.getCount({ + uid, + filters: { + quick: 'unresolved', + cid: (await user.isAdminOrGlobalMod(uid)) ? [] : (await user.getModeratedCids(uid)), + }, + }); + } + return 0; + }()), + }; + const results = await utils.promiseParallel(calls); const unreadCounts = results.unreadData.counts; const unreadCount = { @@ -166,6 +181,7 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { unrepliedTopic: unreadCounts.unreplied || 0, chat: results.unreadChatCount || 0, notification: results.unreadNotificationCount || 0, + flags: results.unreadFlagCount || 0, }; Object.keys(unreadCount).forEach(function (key) { @@ -189,6 +205,14 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + + ['flags'].forEach((prop) => { + if (item && item.originalRoute === `/${prop}` && unreadCount[prop] > 0) { + item.iconClass += ' unread-count'; + item.content = unreadCount.flags; + } + }); + return item; }); From 4ede18ce5f7dd1da9276a6a8530fcc3e14c0aa7f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 13:57:20 -0500 Subject: [PATCH 27/69] fix: broken test caused by errant .pop(), missing await --- src/flags.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flags.js b/src/flags.js index e2c3085c6f..c6a2a90edd 100644 --- a/src/flags.js +++ b/src/flags.js @@ -37,7 +37,7 @@ Flags.init = async function () { sets.push(prefix + value); } else if (value.length) { if (value.length === 1) { - sets.push(prefix + value.pop()); + sets.push(prefix + value[0]); } else { value.forEach(x => orSets.push(prefix + x)); } @@ -169,7 +169,7 @@ Flags.getFlagIdsWithFilters = async function ({ filters, uid }) { Flags.list = async function (data) { const filters = data.filters || {}; - let flagIds = Flags.getFlagIdsWithFilters({ + let flagIds = await Flags.getFlagIdsWithFilters({ filters, uid: data.uid, }); From eaf62d39fd464a86644ea3f1250d742d6a4aa61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 8 Jan 2021 14:19:25 -0500 Subject: [PATCH 28/69] fix: #9177, handled multiple deleted users properly --- src/user/data.js | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/user/data.js b/src/user/data.js index 1fd101a8a9..6bc1f9055b 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -37,12 +37,14 @@ module.exports = function (User) { User.guestData = { uid: 0, username: '[[global:guest]]', + displayname: '[[global:guest]]', userslug: '', fullname: '[[global:guest]]', email: '', 'icon:text': '?', 'icon:bgColor': '#aaa', groupTitle: '', + groupTitleArray: [], status: 'offline', reputation: 0, 'email:confirmed': 0, @@ -68,14 +70,19 @@ module.exports = function (User) { fields = fields.filter(value => value !== 'password'); } - let users = await db.getObjectsFields(uniqueUids.map(uid => 'user:' + uid), fields); + const users = await db.getObjectsFields(uniqueUids.map(uid => 'user:' + uid), fields); const result = await plugins.hooks.fire('filter:user.getFields', { uids: uniqueUids, users: users, fields: fields, }); - users = uidsToUsers(uids, uniqueUids, result.users); - return await modifyUserData(users, fields, fieldsToRemove); + result.users.forEach((user, index) => { + if (uniqueUids[index] > 0 && !user.uid) { + user.oldUid = uniqueUids[index]; + } + }); + await modifyUserData(result.users, fields, fieldsToRemove); + return uidsToUsers(uids, uniqueUids, result.users); }; function ensureRequiredFields(fields, fieldsToRemove) { @@ -110,12 +117,13 @@ module.exports = function (User) { function uidsToUsers(uids, uniqueUids, usersData) { const uidToUser = _.zipObject(uniqueUids, usersData); const users = uids.map(function (uid) { - const returnPayload = uidToUser[uid] || { ...User.guestData }; - if (uid > 0 && !returnPayload.uid) { - returnPayload.oldUid = parseInt(uid, 10); + const user = uidToUser[uid] || { ...User.guestData }; + if (!parseInt(user.uid, 10)) { + user.username = (user.hasOwnProperty('oldUid') && parseInt(user.oldUid, 10)) ? '[[global:former_user]]' : '[[global:guest]]'; + user.displayname = user.username; } - return returnPayload; + return user; }); return users; } @@ -142,19 +150,14 @@ module.exports = function (User) { async function modifyUserData(users, requestedFields, fieldsToRemove) { let uidToSettings = {}; if (meta.config.showFullnameAsDisplayName) { - const uids = _.uniq(users.map(user => user.uid)); + const uids = users.map(user => user.uid); uidToSettings = _.zipObject(uids, await db.getObjectsFields( uids.map(uid => 'user:' + uid + ':settings'), ['showfullname'] )); } - const uidToUser = {}; - users.forEach(function (user) { - uidToUser[user.uid] = user; - }); - await Promise.all(Object.keys(uidToUser).map(async function (uid) { - const user = uidToUser[uid]; + await Promise.all(users.map(async function (user) { if (!user) { return; } @@ -171,14 +174,10 @@ module.exports = function (User) { } if (!parseInt(user.uid, 10)) { - user.uid = 0; - user.username = (user.hasOwnProperty('oldUid') && parseInt(user.oldUid, 10)) ? '[[global:former_user]]' : '[[global:guest]]'; - user.displayname = user.username; - user.userslug = ''; + for (const [key, value] of Object.entries(User.guestData)) { + user[key] = value; + } user.picture = User.getDefaultAvatar(); - user['icon:text'] = '?'; - user['icon:bgColor'] = '#aaa'; - user.groupTitle = ''; } if (user.hasOwnProperty('groupTitle')) { From b742229e598b247725c80e91482c4d766f1bbb3e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 6 Jan 2021 10:49:48 -0500 Subject: [PATCH 29/69] fix: #9169, re-adding v2-style behaviour so as to not break the API... yet --- src/controllers/uploads.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index bbaeb5ab31..ab5fd7dc1a 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -16,11 +16,19 @@ const helpers = require('./helpers'); const uploadsController = module.exports; uploadsController.upload = async function (req, res, filesIterator) { - let files = req.files.files; + let files; + try { + files = req.files.files; + } catch (e) { + return helpers.formatApiResponse(400, res); + } + + // TODO: Remove this (and the usages of isV2 below) in v1.17.0 + const isV2 = req.originalUrl === `${nconf.get('relative_path')}/api/v2/util/upload`; // These checks added because of odd behaviour by request: https://github.com/request/request/issues/2445 if (!Array.isArray(files)) { - return helpers.formatApiResponse(500, res, new Error('[[error:invalid-file]]')); + return isV2 ? res.status(500).json('invalid files') : helpers.formatApiResponse(500, res, new Error('[[error:invalid-file]]')); } if (Array.isArray(files[0])) { files = files[0]; @@ -32,10 +40,20 @@ uploadsController.upload = async function (req, res, filesIterator) { /* eslint-disable no-await-in-loop */ images.push(await filesIterator(fileObj)); } - helpers.formatApiResponse(200, res, { images }); + + if (isV2) { + res.status(200).json(images); + } else { + helpers.formatApiResponse(200, res, { images }); + } + return images; } catch (err) { - return helpers.formatApiResponse(500, res, err); + if (isV2) { + res.status(500).json({ path: req.path, error: err.message }); + } else { + return helpers.formatApiResponse(500, res, err); + } } finally { deleteTempFiles(files); } From 96280d76af9b9a4ef74ecf8ebde560c9d902352c Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sat, 9 Jan 2021 09:07:47 +0000 Subject: [PATCH 30/69] Latest translations and fallbacks --- public/language/ar/error.json | 1 + public/language/bg/error.json | 1 + public/language/bn/error.json | 1 + public/language/cs/error.json | 1 + public/language/da/error.json | 1 + public/language/de/error.json | 1 + public/language/el/error.json | 1 + public/language/en-US/error.json | 1 + public/language/en-x-pirate/error.json | 1 + public/language/es/error.json | 1 + public/language/et/error.json | 1 + public/language/fa-IR/error.json | 1 + public/language/fi/error.json | 1 + public/language/fr/error.json | 1 + public/language/gl/error.json | 1 + public/language/he/error.json | 1 + public/language/hr/error.json | 1 + public/language/hu/error.json | 1 + public/language/id/error.json | 1 + public/language/it/error.json | 1 + public/language/ja/error.json | 1 + public/language/ko/error.json | 1 + public/language/lt/error.json | 1 + public/language/lv/error.json | 1 + public/language/ms/error.json | 1 + public/language/nb/error.json | 1 + public/language/nl/error.json | 1 + public/language/pl/error.json | 1 + public/language/pt-BR/error.json | 1 + public/language/pt-PT/error.json | 1 + public/language/ro/error.json | 1 + public/language/ru/error.json | 1 + public/language/rw/error.json | 1 + public/language/sc/error.json | 1 + public/language/sk/error.json | 1 + public/language/sl/error.json | 1 + public/language/sr/error.json | 1 + public/language/sv/error.json | 1 + public/language/th/error.json | 1 + public/language/tr/error.json | 1 + public/language/uk/error.json | 1 + public/language/vi/error.json | 1 + public/language/zh-CN/error.json | 1 + public/language/zh-TW/error.json | 1 + 44 files changed, 44 insertions(+) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 078a2d95c9..730b6e0fa3 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "لقد شاركت بالتصويت ، ألا تذكر؟", "reputation-system-disabled": "نظام السمعة معطل", "downvoting-disabled": "التصويتات السلبية معطلة", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 0a6461682d..991e63efb8 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Можете да изтривате съобщенията си в разговорите до %1 секунда/и след пускането им", "chat-deleted-already": "Това съобщение вече е изтрито.", "chat-restored-already": "Това съобщение вече е възстановено.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Вече сте дали глас за тази публикация.", "reputation-system-disabled": "Системата за репутация е изключена.", "downvoting-disabled": "Отрицателното гласуване е изключено", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 6fd47fffaf..0937cab25f 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "সম্মাননা ব্যাবস্থা নিস্ক্রীয় রাখা হয়েছে", "downvoting-disabled": "ঋণাত্মক ভোট নিস্ক্রীয় রাখা হয়েছে।", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index fbedf86258..13659387da 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Je vám umožněno odstranit konverzační zprávy pod dobu %1 sekund/y po jejich odeslání", "chat-deleted-already": "Tato konverzační zpráva již byla odstraněna.", "chat-restored-already": "Tato konverzační zpráva již byla obnovena.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Již jste v tomto příspěvku hlasoval.", "reputation-system-disabled": "Systém reputací je zakázán.", "downvoting-disabled": "Systém nesouhlasu je zakázán", diff --git a/public/language/da/error.json b/public/language/da/error.json index 9f2b9c8eff..95cf4f8595 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Vurderingssystem er slået fra.", "downvoting-disabled": "Nedvurdering er slået fra", diff --git a/public/language/de/error.json b/public/language/de/error.json index e20858a89d..e8672cd322 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Du darfst Chat-Nachrichten nur bis zu %1 Sekunde(n) nach der erstellung löschen", "chat-deleted-already": "Diese Chatnachricht wurde bereits gelöscht.", "chat-restored-already": "Diese Chatnachricht wurde bereits wiederhergestellt.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Du hast diesen Beitrag bereits bewertet.", "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.", "downvoting-disabled": "Downvotes sind deaktiviert.", diff --git a/public/language/el/error.json b/public/language/el/error.json index 695f0f3d1e..5ab9cc626c 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Το σύστημα φήμης έχει απενεργοποιηθεί.", "downvoting-disabled": "Η καταψήφιση έχει απενεργοποιηθεί", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index ebf6de4ec7..2f070ca81b 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index ebf6de4ec7..2f070ca81b 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", diff --git a/public/language/es/error.json b/public/language/es/error.json index 91c3a067e4..9efa34bfbb 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Sólo se te permite borrar mensajes de chat durante %1 segundo(s) después de enviar el mensaje", "chat-deleted-already": "Este mensaje de chat ya ha sido borrado.", "chat-restored-already": "Este mensaje de chat ya ha sido restaurado.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Ya has votado a este mensaje.", "reputation-system-disabled": "El sistema de reputación está deshabilitado.", "downvoting-disabled": "La votación negativa está deshabilitada.", diff --git a/public/language/et/error.json b/public/language/et/error.json index 081c2623d0..7b33fd341e 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Sa oled juba hääletanud sellel postitusel.", "reputation-system-disabled": "Reputatsiooni süsteem ei ole aktiveeritud", "downvoting-disabled": "Negatiivsete häälte andmine ei ole võimaldatud", diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index 5b695d287f..adf72bfc3c 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "شما قادر هستید پیام های چت را فقط بعد از %1 ثانیه پاک کنید", "chat-deleted-already": "این پیام قبلا حذف شده است", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "شما قبلا به این پست رای داده اید.", "reputation-system-disabled": "سیستم اعتبار غیر فعال شده است", "downvoting-disabled": "رأی منفی غیر فعال شده است", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 7828030e84..04aa2a363d 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 9c712fd0a7..5392655243 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Vous n'êtes autorisé à supprimer des messages que pendant %1 seconde(s) après les avoir postés", "chat-deleted-already": "Ce message a déjà été supprimé.", "chat-restored-already": "Ce message de discussion a déjà été restauré.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Vous avez déjà voté pour ce message.", "reputation-system-disabled": "Le système de réputation est désactivé", "downvoting-disabled": "Les votes négatifs ne sont pas autorisés", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 41356ebc76..3b6f53ad2c 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Xa votache esta mensaxe.", "reputation-system-disabled": "O sistema de reputación está deshabilitado", "downvoting-disabled": "Os votos negativos están deshabilitados", diff --git a/public/language/he/error.json b/public/language/he/error.json index 3f07d4829d..9a2def1d5f 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "הנך רשאי למחוק הודעת צ'אט עד %1 דק(ות) מרגע פרסום התגובה.", "chat-deleted-already": "הודעות הצ'אט הזו כבר נמחקה.", "chat-restored-already": "הודעות הצ'אט הזו כבר שוחזרה.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "הצבעת כבר בנושא זה", "reputation-system-disabled": "מערכת המוניטין לא פעילה.", "downvoting-disabled": "היכולת להצביע נגד לא פעילה", diff --git a/public/language/hr/error.json b/public/language/hr/error.json index 3daf75a236..454b558e2b 100644 --- a/public/language/hr/error.json +++ b/public/language/hr/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Već ste glasali za ovu objavu", "reputation-system-disabled": "Sistem reputacije onemogućen.", "downvoting-disabled": "Oduzimanje glasova je onemogućeno", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 708f3c775d..8314e3bfbb 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Hírnév funkció kikapcsolva.", "downvoting-disabled": "Leszavazás funkció kikapcsolva", diff --git a/public/language/id/error.json b/public/language/id/error.json index 0a0ac11943..234e745cf4 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Sistem reputasi ditiadakan.", "downvoting-disabled": "Downvoting ditiadakan", diff --git a/public/language/it/error.json b/public/language/it/error.json index 1218441858..632af35768 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Sei l'unico che ha il permesso di eliminare i messaggi per %1 secondi(o) dopo il loro invio", "chat-deleted-already": "Il messaggio è già stato eliminato.", "chat-restored-already": "Questo messaggio della chat è già stato ripristinato.", + "chat-room-does-not-exist": "La stanza chat non esiste.", "already-voting-for-this-post": "Hai già votato per questo post", "reputation-system-disabled": "Il sistema di reputazione è disabilitato.", "downvoting-disabled": "Votata negativamente è disabilitato", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index c4eac6aeca..76be962e2d 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "投稿後、あなたは %1秒間(s)だけチャットメッセージを削除することを許可されています", "chat-deleted-already": "このチャットメッセージは既に削除されています", "chat-restored-already": "このチャットメッセージは既に削除されています", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "あなたはすでにこの投稿を評価しました。", "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index f879589785..ec8d5dbde4 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "채팅 메시지를 게시한 뒤 %1초 뒤부터 삭제가 가능합니다.", "chat-deleted-already": "이미 삭제된 대화 메시지입니다.", "chat-restored-already": "이 채팅 메시지는 이미 복원되었습니다.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "이미 이 포스트에 투표하셨습니다.", "reputation-system-disabled": "평판 시스템이 비활성화되어있습니다.", "downvoting-disabled": "비추천 기능이 비활성 상태입니다.", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 3598ac9193..0e7956288d 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Trinti žinutes galima tik %1 sekundę(-es/-ių) po paskelbimo", "chat-deleted-already": "Ši žinutė buvo pašalinta", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Jūs jau balsavote už šį pranešimą.", "reputation-system-disabled": "Reputacijos sistema išjungta.", "downvoting-disabled": "Downvoting yra išjungtas", diff --git a/public/language/lv/error.json b/public/language/lv/error.json index ce80b1aee3..532521c87b 100644 --- a/public/language/lv/error.json +++ b/public/language/lv/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Pēc publicēšanas ir atļauts tikai %1 sekundes laika izdzēst sarunu", "chat-deleted-already": "Saruna jau ir izdzēsta.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Tu jau balsoji par šo rakstu.", "reputation-system-disabled": "Ranga punktu sistēma ir atspējota.", "downvoting-disabled": "Balsošana \"pret\" ir atspējota", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 5106040b82..9380e709da 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Sistem reputasi dilumpuhkan.", "downvoting-disabled": "Undi turun dilumpuhkan", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 755ebfd9b2..1c793058ab 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Ryktesystemet er deaktivert.", "downvoting-disabled": "Nedstemming er deaktivert", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index ea797e2ea1..ad6434ef7d 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Het is slechts toegestaan om binnen %1 seconde(n) na plaatsen van het chat bericht, deze te verwijderen.", "chat-deleted-already": "Dit chat bericht is al verwijderd.", "chat-restored-already": "Dit chat bericht is al hersteld.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Je hebt al gestemd voor deze post.", "reputation-system-disabled": "Reputatie systeem is uitgeschakeld.", "downvoting-disabled": "Negatief stemmen is uitgeschakeld", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index b0c6f69db0..94094454a5 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Możesz skasować komunikat czatu tylko przez %1 sekund(y) po napisaniu.", "chat-deleted-already": "Ten komunikat czatu jest już skasowany", "chat-restored-already": "Ta wiadomość została już przywrócona", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Już zagłosowałeś na ten post", "reputation-system-disabled": "System reputacji jest wyłączony.", "downvoting-disabled": "Negatywna ocena postów jest wyłączona", diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 4f65019307..64eca8de40 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Você só pode deletar mensagens de chat %1 segundo(s) após postar", "chat-deleted-already": "Essa mensagem de chat já foi deletada", "chat-restored-already": "Essa mensagem de chat já foi restaurada.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Você já votou neste post.", "reputation-system-disabled": "O sistema de reputação está desabilitado.", "downvoting-disabled": "Negativação está desabilitada", diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index ddc52e0b16..2e02d2b49a 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Só tens permissão para apagar mensagens do chat %1 segundo(s) depois de publicares", "chat-deleted-already": "Esta mensagem já foi apagada.", "chat-restored-already": "Esta mensagem já foi restaurada.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Já votaste nesta publicação.", "reputation-system-disabled": "O sistema de reputação está desativado.", "downvoting-disabled": "Os votos negativos estão desativados", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index 4b494f2870..2fc5f32255 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Sistemul de reputație este dezactivat.", "downvoting-disabled": "Votarea negativă este dezactivată", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 6cfcdda27e..7dadc271ec 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Вам разрешено удалять сообщения чата за% 1 секунду после публикации", "chat-deleted-already": "Это сообщение чата уже удалено.", "chat-restored-already": "Это сообщение чата уже было восстановлено.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Вы уже проголосовали за это сообщение.", "reputation-system-disabled": "Система репутации отключена.", "downvoting-disabled": "Понижение рейтинга отключено", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 56efaaab99..9edf183390 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Ibijyanye n'itangwa ry'amanota ntibyemerewe. ", "downvoting-disabled": "Kwambura amanota ntibyemerewe", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index ebf6de4ec7..2f070ca81b 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index b4dc7108c1..6549f41019 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Je Vám umožnené odstrániť správy konverzácie po dobu %1 sekúnd po ich odoslaní", "chat-deleted-already": "Táto správa konverzácie už bola odstránená.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Za tento príspevok ste už hlasovali.", "reputation-system-disabled": "Systém reputácie je zablokovaný.", "downvoting-disabled": "Hlasovanie proti je zablokované", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index a186c812ba..3ad8de943d 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Za to objavo ste že glasovali.", "reputation-system-disabled": "Sistem za ugled je onemogočen.", "downvoting-disabled": "Negativno glasovanje je onemogočeno.", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index d2500b60d0..ca0bf5e0a4 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Време у којем вам је дозвољено брисање порука ћаскања након објављивања: %1 сек.", "chat-deleted-already": "Ова порука ћаскања је већ избрисана.", "chat-restored-already": "Ова порука ћаскања је већ обновљена.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Већ сте гласали за ову поруку.", "reputation-system-disabled": "Угледи су онемогућени.", "downvoting-disabled": "Негативно гласање је онемогућено", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 2a245b4fdf..1eed2fdf5f 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Du kan endast radera chattmeddelanden %1 sekunder efter att du skrivit dem", "chat-deleted-already": "Detta chattmeddelande har redan raderats.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Du har redan röstat på det här inlägget.", "reputation-system-disabled": "Ryktessystemet är inaktiverat.", "downvoting-disabled": "Nedröstning är inaktiverat", diff --git a/public/language/th/error.json b/public/language/th/error.json index f21336e4ed..e130c86aea 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "คุณได้โหวตโพสต์นี้แล้ว", "reputation-system-disabled": "ระบบชื่อเสียงถูกปิดใช้งาน", "downvoting-disabled": "\"การโหวตลง\" ถูกปิดใช้งาน", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index bff70f0089..49ee07e936 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Gönderildikten sonra yalnızca %1 saniye mesajı(ları) silmene izin verilir", "chat-deleted-already": "Bu sohbet mesajı zaten silinmiş.", "chat-restored-already": "Bu sohbet mesajı zaten geri yüklendi.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Bu gönderi için zaten oy verdin.", "reputation-system-disabled": "İtibar sistemi devre dışı.", "downvoting-disabled": "Aşagı oylama kapatılmış", diff --git a/public/language/uk/error.json b/public/language/uk/error.json index 8229219d14..5763195b48 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Ви можете видаляти повідомлення чату лише через %1 секунд після публікації", "chat-deleted-already": "Це повідомлення чату вже було видалено.", "chat-restored-already": "Це чат повідомлення вже було відновлене", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Ви вже проголосували за цей пост.", "reputation-system-disabled": "Система репутацій вимкнена.", "downvoting-disabled": "Голосування проти вимкнено", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index e0f6e0c346..6d915c09d6 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "Bạn chỉ được phép xóa cuộc trò chuyện này sau %1 giây(s) sau khi viết bài.", "chat-deleted-already": "Cuộc trò chuyện này đã được xóa.", "chat-restored-already": "This chat message has already been restored.", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "Bạn đã bỏ phiếu cho bài viết này", "reputation-system-disabled": "Hệ thống tín nhiệm đã bị vô hiệu hóa.", "downvoting-disabled": "Downvote đã bị tắt", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index 9d586203bf..036c5581f1 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "您只能在发布 %1 秒后删除聊天信息", "chat-deleted-already": "聊天消息已经被删除", "chat-restored-already": "此聊天消息已经恢复。\n", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "您已为此帖回复投过票了。", "reputation-system-disabled": "声望系统已禁用。", "downvoting-disabled": "踩已被禁用", diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index 509624f544..3ec3687bb7 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -133,6 +133,7 @@ "chat-delete-duration-expired": "您只能在發佈 %1 秒後刪除聊天訊息", "chat-deleted-already": "聊天訊息已經被刪除", "chat-restored-already": "此聊天訊息已經恢復。", + "chat-room-does-not-exist": "Chat room does not exist.", "already-voting-for-this-post": "您已讚過此貼文回覆了。", "reputation-system-disabled": "聲望系統已停用。", "downvoting-disabled": "倒讚已被停用", From 6c855bf48ee4d41166ff94bcb669645de4b9ab39 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sun, 10 Jan 2021 09:08:11 +0000 Subject: [PATCH 31/69] Latest translations and fallbacks --- public/language/de/global.json | 2 +- public/language/fr/error.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/de/global.json b/public/language/de/global.json index d4292a1566..76edc31104 100644 --- a/public/language/de/global.json +++ b/public/language/de/global.json @@ -62,7 +62,7 @@ "downvoters": "Downvoter", "downvoted": "Negativ bewertet", "views": "Aufrufe", - "posters": "Posters", + "posters": "Kommentatoren", "reputation": "Ansehen", "lastpost": "Letzter Beitrag", "firstpost": "Erster Beitrag", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 5392655243..e852a346f9 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -133,7 +133,7 @@ "chat-delete-duration-expired": "Vous n'êtes autorisé à supprimer des messages que pendant %1 seconde(s) après les avoir postés", "chat-deleted-already": "Ce message a déjà été supprimé.", "chat-restored-already": "Ce message de discussion a déjà été restauré.", - "chat-room-does-not-exist": "Chat room does not exist.", + "chat-room-does-not-exist": "Le salon de discussion n'existe pas.", "already-voting-for-this-post": "Vous avez déjà voté pour ce message.", "reputation-system-disabled": "Le système de réputation est désactivé", "downvoting-disabled": "Les votes négatifs ne sont pas autorisés", From d90c7ff43b6324856c389b89879f5300d8aee98e Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Mon, 11 Jan 2021 09:08:27 +0000 Subject: [PATCH 32/69] Latest translations and fallbacks --- public/language/bg/error.json | 2 +- public/language/ru/admin/manage/groups.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 991e63efb8..b15f9f6ba4 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -133,7 +133,7 @@ "chat-delete-duration-expired": "Можете да изтривате съобщенията си в разговорите до %1 секунда/и след пускането им", "chat-deleted-already": "Това съобщение вече е изтрито.", "chat-restored-already": "Това съобщение вече е възстановено.", - "chat-room-does-not-exist": "Chat room does not exist.", + "chat-room-does-not-exist": "Стаята за разговори не съществува.", "already-voting-for-this-post": "Вече сте дали глас за тази публикация.", "reputation-system-disabled": "Системата за репутация е изключена.", "downvoting-disabled": "Отрицателното гласуване е изключено", diff --git a/public/language/ru/admin/manage/groups.json b/public/language/ru/admin/manage/groups.json index f645e2443a..420c13e5e5 100644 --- a/public/language/ru/admin/manage/groups.json +++ b/public/language/ru/admin/manage/groups.json @@ -9,7 +9,7 @@ "private": "Закрытая", "edit": "Редактировать", "delete": "Удалить", - "privileges": "Privileges", + "privileges": "Права доступа", "download-csv": "CSV", "search-placeholder": "Поиск", "create": "Создать группу", From 66da6bcd1cda252a562c41f1614997425bc019cd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 11 Jan 2021 13:28:04 -0500 Subject: [PATCH 33/69] fix: #9184 proper relative_path usage in topic thumbs.get --- install/package.json | 2 +- src/topics/thumbs.js | 2 +- src/views/modals/topic-thumbs.tpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index 865e1a0a19..c7ff1cbc04 100644 --- a/install/package.json +++ b/install/package.json @@ -101,7 +101,7 @@ "nodebb-plugin-spam-be-gone": "0.7.7", "nodebb-rewards-essentials": "0.1.4", "nodebb-theme-lavender": "5.0.17", - "nodebb-theme-persona": "10.3.17", + "nodebb-theme-persona": "10.3.18", "nodebb-theme-slick": "1.3.7", "nodebb-theme-vanilla": "11.3.10", "nodebb-widget-essentials": "5.0.2", diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js index d65b99b3c4..169bc8d60e 100644 --- a/src/topics/thumbs.js +++ b/src/topics/thumbs.js @@ -32,7 +32,7 @@ Thumbs.get = async function (tids) { } const hasTimestampPrefix = /^\d+-/; - const upload_url = nconf.get('upload_url'); + const upload_url = nconf.get('relative_path') + nconf.get('upload_url'); const sets = tids.map(tid => `${validator.isUUID(String(tid)) ? 'draft' : 'topic'}:${tid}:thumbs`); const thumbs = await Promise.all(sets.map(set => getThumbs(set))); let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({ diff --git a/src/views/modals/topic-thumbs.tpl b/src/views/modals/topic-thumbs.tpl index 814ea86718..7ee9773836 100644 --- a/src/views/modals/topic-thumbs.tpl +++ b/src/views/modals/topic-thumbs.tpl @@ -5,7 +5,7 @@ {{{ each thumbs }}}
- +

From e817d5beda618415b331eef7e5b6288b12b7180f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 11 Jan 2021 14:31:47 -0500 Subject: [PATCH 34/69] fix: test for topicThumbs --- test/topicThumbs.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/topicThumbs.js b/test/topicThumbs.js index 7be9e56f89..bc952139d3 100644 --- a/test/topicThumbs.js +++ b/test/topicThumbs.js @@ -94,7 +94,7 @@ describe('Topic thumbs', () => { assert.deepStrictEqual(thumbs, [{ id: 1, name: 'test.png', - url: `${nconf.get('upload_url')}${relativeThumbPaths[0]}`, + url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, }]); }); @@ -104,7 +104,7 @@ describe('Topic thumbs', () => { [{ id: 1, name: 'test.png', - url: `${nconf.get('upload_url')}${relativeThumbPaths[0]}`, + url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, }], [], ]); @@ -153,7 +153,7 @@ describe('Topic thumbs', () => { { id: 2, name: 'test.png', - url: `${nconf.get('upload_url')}${relativeThumbPaths[0]}`, + url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`, }, { id: 2, @@ -163,7 +163,7 @@ describe('Topic thumbs', () => { { id: 2, name: 'test2.png', - url: `${nconf.get('upload_url')}${relativeThumbPaths[1]}`, + url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[1]}`, }, ]); }); From 89e6c75d5839cc2cbb4c1b9501725ada537459fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 11 Jan 2021 14:35:23 -0500 Subject: [PATCH 35/69] fix: #9185, fix string boolean values --- src/groups/update.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/groups/update.js b/src/groups/update.js index 0e34169660..bced8783df 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -24,10 +24,11 @@ module.exports = function (Groups) { values: values, })); - // Case some values as bool (if not boolean already) + // Cast some values as bool (if not boolean already) + // 'true' and '1' = true, everything else false ['userTitleEnabled', 'private', 'hidden', 'disableJoinRequests', 'disableLeave'].forEach((prop) => { if (values.hasOwnProperty(prop) && typeof values[prop] !== 'boolean') { - values[prop] = !!parseInt(values[prop], 10); + values[prop] = values[prop] === 'true' || parseInt(values[prop], 10) === 1; } }); From a407a51da6c344da34eb5c843767dacdf4ed9c51 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 11 Jan 2021 14:47:03 -0500 Subject: [PATCH 36/69] fix: #9178 missing language strings (and fallbacks) for post-queue --- public/language/ar/post-queue.json | 9 ++++++++- public/language/bg/post-queue.json | 9 ++++++++- public/language/bn/post-queue.json | 9 ++++++++- public/language/cs/post-queue.json | 9 ++++++++- public/language/da/post-queue.json | 9 ++++++++- public/language/de/post-queue.json | 9 ++++++++- public/language/el/post-queue.json | 9 ++++++++- public/language/en-US/post-queue.json | 9 ++++++++- public/language/en-x-pirate/post-queue.json | 9 ++++++++- public/language/es/post-queue.json | 9 ++++++++- public/language/et/post-queue.json | 9 ++++++++- public/language/fa-IR/post-queue.json | 9 ++++++++- public/language/fi/post-queue.json | 9 ++++++++- public/language/fr/post-queue.json | 9 ++++++++- public/language/gl/post-queue.json | 9 ++++++++- public/language/he/post-queue.json | 9 ++++++++- public/language/hr/post-queue.json | 9 ++++++++- public/language/hu/post-queue.json | 9 ++++++++- public/language/id/post-queue.json | 9 ++++++++- public/language/it/post-queue.json | 9 ++++++++- public/language/ja/post-queue.json | 9 ++++++++- public/language/ko/post-queue.json | 9 ++++++++- public/language/lt/post-queue.json | 9 ++++++++- public/language/lv/post-queue.json | 9 ++++++++- public/language/ms/post-queue.json | 9 ++++++++- public/language/nb/post-queue.json | 9 ++++++++- public/language/nl/post-queue.json | 9 ++++++++- public/language/pl/post-queue.json | 9 ++++++++- public/language/pt-BR/post-queue.json | 9 ++++++++- public/language/pt-PT/post-queue.json | 9 ++++++++- public/language/ro/post-queue.json | 9 ++++++++- public/language/ru/post-queue.json | 9 ++++++++- public/language/rw/post-queue.json | 9 ++++++++- public/language/sc/post-queue.json | 9 ++++++++- public/language/sk/post-queue.json | 9 ++++++++- public/language/sl/post-queue.json | 9 ++++++++- public/language/sr/post-queue.json | 9 ++++++++- public/language/sv/post-queue.json | 9 ++++++++- public/language/th/post-queue.json | 9 ++++++++- public/language/tr/post-queue.json | 9 ++++++++- public/language/uk/post-queue.json | 9 ++++++++- public/language/vi/post-queue.json | 9 ++++++++- public/language/zh-CN/post-queue.json | 9 ++++++++- public/language/zh-TW/post-queue.json | 9 ++++++++- 44 files changed, 352 insertions(+), 44 deletions(-) diff --git a/public/language/ar/post-queue.json b/public/language/ar/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/ar/post-queue.json +++ b/public/language/ar/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/bg/post-queue.json b/public/language/bg/post-queue.json index c5ae725e4d..3122e2e90e 100644 --- a/public/language/bg/post-queue.json +++ b/public/language/bg/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Опашка за публикации", "description": "Няма публикации в опашката.
За да включите тази функционалност, идете в Настройки → Публикуване → Опашка за публикации и включете Опашката за публикации.", @@ -7,5 +8,11 @@ "content": "Съдържание", "posted": "Публикувано", "reply-to": "Отговор на „%1“", - "content-editable": "Можете да щракнете върху всеки от текстовете, за да ги редактирате преди публикуване." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/bn/post-queue.json b/public/language/bn/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/bn/post-queue.json +++ b/public/language/bn/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/cs/post-queue.json b/public/language/cs/post-queue.json index e707996b17..301dc9d3a4 100644 --- a/public/language/cs/post-queue.json +++ b/public/language/cs/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Fronta příspěvků", "description": "Nejsou žádné příspěvky ve frontě. Pro povolení této funkčnosti, přejděte do Nastavení – Příspěvky – Fronta příspěvků a povolte Fronta příspěvků.", @@ -7,5 +8,11 @@ "content": "Obsah", "posted": "Přidáno", "reply-to": "Odpovědět na \"%1\"", - "content-editable": "Kvůli úpravám a před odesláním příspěvku můžete klikat na obsah." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/da/post-queue.json b/public/language/da/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/da/post-queue.json +++ b/public/language/da/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/de/post-queue.json b/public/language/de/post-queue.json index 1e32f5e785..2184fbd04b 100644 --- a/public/language/de/post-queue.json +++ b/public/language/de/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Beitragswarteschlange", "description": "Es gibt keine Beiträge in der Warteschlange.
Um dieses Feature zu aktivieren, gehe auf Einstellungen → Posts → Beitragswarteschlange und aktiviere Beitragswarteschlange.", @@ -7,5 +8,11 @@ "content": "Inhalt", "posted": "Gepostet", "reply-to": "Auf \"%1\" antworten", - "content-editable": "Du kannst auf den einzelnen Inhalt klicken um ihn zu ändern bevor du ihn postest." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/el/post-queue.json b/public/language/el/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/el/post-queue.json +++ b/public/language/el/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/en-US/post-queue.json b/public/language/en-US/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/en-US/post-queue.json +++ b/public/language/en-US/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/en-x-pirate/post-queue.json b/public/language/en-x-pirate/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/en-x-pirate/post-queue.json +++ b/public/language/en-x-pirate/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/es/post-queue.json b/public/language/es/post-queue.json index 533fb96f44..78e2ea1f91 100644 --- a/public/language/es/post-queue.json +++ b/public/language/es/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Cola de Mensajes", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Contenido", "posted": "Publicado", "reply-to": "Responder a %1", - "content-editable": "Puedes hacer click en contenido individual para editar antes de enviarlo." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/et/post-queue.json b/public/language/et/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/et/post-queue.json +++ b/public/language/et/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/fa-IR/post-queue.json b/public/language/fa-IR/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/fa-IR/post-queue.json +++ b/public/language/fa-IR/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/fi/post-queue.json b/public/language/fi/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/fi/post-queue.json +++ b/public/language/fi/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/fr/post-queue.json b/public/language/fr/post-queue.json index d350c6918c..b8b8364840 100644 --- a/public/language/fr/post-queue.json +++ b/public/language/fr/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "File d’attente des messages", "description": "Aucun messages dans la file d'attente.
Pour activer cette fonctionnalité, accédez aux Paramètres → Messages → File d'attente et activez la file d'attente.", @@ -7,5 +8,11 @@ "content": "Contenu", "posted": "Posté", "reply-to": "Répondre à \"%1\"", - "content-editable": "Vous pouvez cliquer sur le contenu pour le modifier avant de le poster." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/gl/post-queue.json b/public/language/gl/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/gl/post-queue.json +++ b/public/language/gl/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/he/post-queue.json b/public/language/he/post-queue.json index 1aef0f967d..eeef23a806 100644 --- a/public/language/he/post-queue.json +++ b/public/language/he/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "פוסטים ממתינים", "description": "אין פוסטים בתור.
כדי לאפשר את תור ההרשמה, גשו להגדרות → פוסט → תור פוסטים ואפשרו את תור פוסט.", @@ -7,5 +8,11 @@ "content": "תוכן", "posted": "נשלח", "reply-to": "תגובה ל %1", - "content-editable": "אתה יכול ללחוץ על התוכן כדי לערוך אותו." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/hr/post-queue.json b/public/language/hr/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/hr/post-queue.json +++ b/public/language/hr/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/hu/post-queue.json b/public/language/hu/post-queue.json index b533498eb0..cd12778bfb 100644 --- a/public/language/hu/post-queue.json +++ b/public/language/hu/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Hozzászólási várósor", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Tartalom", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/id/post-queue.json b/public/language/id/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/id/post-queue.json +++ b/public/language/id/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/it/post-queue.json b/public/language/it/post-queue.json index 09552abd32..08109f58e5 100644 --- a/public/language/it/post-queue.json +++ b/public/language/it/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post in attesa", "description": "Non ci sono post nella coda della post.
Per abilitare questa funzione, vai in Impostazioni → Post → Codice post e abilita Post in attesa.", @@ -7,5 +8,11 @@ "content": "Contenuto", "posted": "Pubblicato", "reply-to": "Rispondi a \"%1\"", - "content-editable": "Puoi cliccare su singoli contenuti per modificarli prima della pubblicazione." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/ja/post-queue.json b/public/language/ja/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/ja/post-queue.json +++ b/public/language/ja/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/ko/post-queue.json b/public/language/ko/post-queue.json index e5ec934ddf..247cf71dc7 100644 --- a/public/language/ko/post-queue.json +++ b/public/language/ko/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "게시 대기열", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "컨텐츠", "posted": "작성됨", "reply-to": "'%1'에 대한 답글", - "content-editable": "게시하기 전에 콘텐츠를 클릭하여 편집 할 수 있습니다." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/lt/post-queue.json b/public/language/lt/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/lt/post-queue.json +++ b/public/language/lt/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/lv/post-queue.json b/public/language/lv/post-queue.json index 4f68aea318..176a68626f 100644 --- a/public/language/lv/post-queue.json +++ b/public/language/lv/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Rakstu apstiprināšanas rinda", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Saturs", "posted": "Datums", "reply-to": "Atbildēt \"%1\"", - "content-editable": "Pirms publicēšanas var noklikšķināt uz saturu, lai to rediģētu." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/ms/post-queue.json b/public/language/ms/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/ms/post-queue.json +++ b/public/language/ms/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/nb/post-queue.json b/public/language/nb/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/nb/post-queue.json +++ b/public/language/nb/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/nl/post-queue.json b/public/language/nl/post-queue.json index 7a9877a785..e9642a2e2c 100644 --- a/public/language/nl/post-queue.json +++ b/public/language/nl/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Berichtenwachtrij", "description": "Er zijn geen berichten in de wachtrij.
Om deze functionaliteit in te schakelen, ga naar Instellingen → Bericht → Berichtenwachtrij en schakel Berichtenwachtrij in.", @@ -7,5 +8,11 @@ "content": "Inhoud", "posted": "Geplaatst", "reply-to": "Antwoord naar \"%1\"", - "content-editable": "Je kunt op individuele inhoud klikken om dit aan te passen voor het plaatsen." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/pl/post-queue.json b/public/language/pl/post-queue.json index 270290e155..5661a4b8fb 100644 --- a/public/language/pl/post-queue.json +++ b/public/language/pl/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Kolejka postów", "description": "Nie ma żadnych postów w kolejce.
Aby włączyć tą funkcję, idź do: Ustawienia → Post → Kolejka postów i włącz kolejkę postów.", @@ -7,5 +8,11 @@ "content": "Zawartość", "posted": "Napisano", "reply-to": "Odpowiedz do \"%1\"", - "content-editable": "Możesz kliknąć na danej treści, by edytować ją przed publikacją." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/pt-BR/post-queue.json b/public/language/pt-BR/post-queue.json index 3dd2f9342b..dd6fd18d3d 100644 --- a/public/language/pt-BR/post-queue.json +++ b/public/language/pt-BR/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Fila de Posts", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Conteúdo", "posted": "Postado", "reply-to": "Resposta para \"%1\"", - "content-editable": "Você pode clicar em um post individual para editar antes de postar." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/pt-PT/post-queue.json b/public/language/pt-PT/post-queue.json index c592cc1e4f..06526a5a65 100644 --- a/public/language/pt-PT/post-queue.json +++ b/public/language/pt-PT/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Publicações por Aprovar", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Conteúdo", "posted": "Publicada", "reply-to": "Responder a \"%1\"", - "content-editable": "Podes clicar no conteúdo da publicação para o editares antes de a publicares." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/ro/post-queue.json b/public/language/ro/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/ro/post-queue.json +++ b/public/language/ro/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/ru/post-queue.json b/public/language/ru/post-queue.json index 48d59429e6..5669d90214 100644 --- a/public/language/ru/post-queue.json +++ b/public/language/ru/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Очередь на публикацию", "description": "В очереди на публикацию нет сообщений.
Чтобы включить эту функцию, перейдите в Настройки → Сообщения → Очередь на публикацию и включите Очередь на публикацию.", @@ -7,5 +8,11 @@ "content": "Содержимое", "posted": "Время", "reply-to": "Ответ \"%1\"", - "content-editable": "Вы можете отредактировать содержимое сообщения перед публикацией" + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/rw/post-queue.json b/public/language/rw/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/rw/post-queue.json +++ b/public/language/rw/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/sc/post-queue.json b/public/language/sc/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/sc/post-queue.json +++ b/public/language/sc/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/sk/post-queue.json b/public/language/sk/post-queue.json index ec4966fd70..03869036fd 100644 --- a/public/language/sk/post-queue.json +++ b/public/language/sk/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Príspevky vo fronte", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Obsah", "posted": "Pridané", "reply-to": "Odpovedať na \"%1\"", - "content-editable": "Kvôli úpravám a pred odoslaním príspevku môžete klikať na obsah." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/sl/post-queue.json b/public/language/sl/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/sl/post-queue.json +++ b/public/language/sl/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/sr/post-queue.json b/public/language/sr/post-queue.json index 78c828fee3..1b4646fd58 100644 --- a/public/language/sr/post-queue.json +++ b/public/language/sr/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Садржај", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/sv/post-queue.json b/public/language/sv/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/sv/post-queue.json +++ b/public/language/sv/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/th/post-queue.json b/public/language/th/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/th/post-queue.json +++ b/public/language/th/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/tr/post-queue.json b/public/language/tr/post-queue.json index 1e8054f526..1ebd1b5579 100644 --- a/public/language/tr/post-queue.json +++ b/public/language/tr/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "İleti Kuyruğu", "description": "İleti kuyruğunda hiçbir ileti bulunmamaktadır.
Bu özelliği aktifleştirmek için, şuraya gidin Ayarlar → İleti → İleti Kuyruğu ve şu özelliği aktifleştirin: İleti Kuyruğu.", @@ -7,5 +8,11 @@ "content": "İçerik", "posted": "Gönderildi", "reply-to": "\"%1\"'e Cevap Ver", - "content-editable": "Göndermeden önce her bir içeriğe tıklayıp düzenleyebilirsiniz." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/uk/post-queue.json b/public/language/uk/post-queue.json index 298a366ecf..0a0d13c6c7 100644 --- a/public/language/uk/post-queue.json +++ b/public/language/uk/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Черга Постів", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Зміст", "posted": "Опубліковано", "reply-to": "Відповідь для \"%1\"", - "content-editable": "Ви можете натиснути на окремий вміст, щоб редагувати його перед публікацією." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/vi/post-queue.json b/public/language/vi/post-queue.json index 2742e3a7af..bfaa367870 100644 --- a/public/language/vi/post-queue.json +++ b/public/language/vi/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "Post Queue", "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", @@ -7,5 +8,11 @@ "content": "Content", "posted": "Posted", "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/zh-CN/post-queue.json b/public/language/zh-CN/post-queue.json index 53f7208248..a63312d82b 100644 --- a/public/language/zh-CN/post-queue.json +++ b/public/language/zh-CN/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "发布队列", "description": "发布队列中暂无新帖。启用此特性,请前往设置→发布→发布队列,然后启用发布队列。", @@ -7,5 +8,11 @@ "content": "内容", "posted": "发布", "reply-to": "回复\"%1\"", - "content-editable": "发帖前,您可以点击内容进行编辑。" + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file diff --git a/public/language/zh-TW/post-queue.json b/public/language/zh-TW/post-queue.json index 9907d7d81a..43491ab280 100644 --- a/public/language/zh-TW/post-queue.json +++ b/public/language/zh-TW/post-queue.json @@ -1,3 +1,4 @@ + { "post-queue": "貼文隊列", "description": "貼文隊列中暫無新貼文。啟用此功能,請前往設定→貼文→貼文隊列,然後啟用貼文隊列。", @@ -7,5 +8,11 @@ "content": "內容", "posted": "發佈", "reply-to": "回覆\"%1\"", - "content-editable": "發文前,您可以點擊內容進行編輯。" + "content-editable": "Click on content to edit", + "category-editable": "Click on category to edit", + "title-editable": "Click on title to edit", + "reply": "Reply", + "topic": "Topic", + "accept": "Accept", + "reject": "Reject" } \ No newline at end of file From 69419571a933caca496bce56f8ebee1efd287d78 Mon Sep 17 00:00:00 2001 From: gasoved Date: Tue, 12 Jan 2021 00:02:40 +0300 Subject: [PATCH 37/69] fix: make sure inviter user exists --- src/user/invite.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/user/invite.js b/src/user/invite.js index 8085ae4ece..b939f6cd68 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -38,6 +38,10 @@ module.exports = function (User) { }; User.sendInvitationEmail = async function (uid, email, groupsToJoin) { + if (!uid) { + throw new Error('[[error:invalid-uid]]'); + } + const email_exists = await User.getUidByEmail(email); if (email_exists) { throw new Error('[[error:email-taken]]'); @@ -104,6 +108,11 @@ module.exports = function (User) { } async function prepareInvitation(uid, email, groupsToJoin) { + const inviterExists = await User.exists(uid); + if (!inviterExists) { + throw new Error('[[error:invalid-uid]]'); + } + const token = utils.generateUUID(); const registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + encodeURIComponent(email); From 1901ecb24411ca1e1ba29d42038c0233f9ff191f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 12 Jan 2021 03:37:24 +0000 Subject: [PATCH 38/69] fix(deps): update dependency nodebb-theme-slick to v1.3.8 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c7ff1cbc04..496f129696 100644 --- a/install/package.json +++ b/install/package.json @@ -102,7 +102,7 @@ "nodebb-rewards-essentials": "0.1.4", "nodebb-theme-lavender": "5.0.17", "nodebb-theme-persona": "10.3.18", - "nodebb-theme-slick": "1.3.7", + "nodebb-theme-slick": "1.3.8", "nodebb-theme-vanilla": "11.3.10", "nodebb-widget-essentials": "5.0.2", "nodemailer": "^6.4.6", From a26bfb3fdaf9ede1a4dbecd3e8138a6684bf671a Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Tue, 12 Jan 2021 09:08:47 +0000 Subject: [PATCH 39/69] Latest translations and fallbacks --- public/language/bg/post-queue.json | 14 +++++++------- public/language/fr/post-queue.json | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/public/language/bg/post-queue.json b/public/language/bg/post-queue.json index 3122e2e90e..73647030b1 100644 --- a/public/language/bg/post-queue.json +++ b/public/language/bg/post-queue.json @@ -8,11 +8,11 @@ "content": "Съдържание", "posted": "Публикувано", "reply-to": "Отговор на „%1“", - "content-editable": "Click on content to edit", - "category-editable": "Click on category to edit", - "title-editable": "Click on title to edit", - "reply": "Reply", - "topic": "Topic", - "accept": "Accept", - "reject": "Reject" + "content-editable": "Щракнете върху съдържание, за да го редактирате", + "category-editable": "Щракнете върху категория, за да я редактирате", + "title-editable": "Щракнете върху заглавие, за да го редактирате", + "reply": "Отговор", + "topic": "Тема", + "accept": "Приемане", + "reject": "Отказване" } \ No newline at end of file diff --git a/public/language/fr/post-queue.json b/public/language/fr/post-queue.json index b8b8364840..0ffcfa9c26 100644 --- a/public/language/fr/post-queue.json +++ b/public/language/fr/post-queue.json @@ -8,11 +8,11 @@ "content": "Contenu", "posted": "Posté", "reply-to": "Répondre à \"%1\"", - "content-editable": "Click on content to edit", - "category-editable": "Click on category to edit", - "title-editable": "Click on title to edit", - "reply": "Reply", - "topic": "Topic", - "accept": "Accept", - "reject": "Reject" + "content-editable": "Cliquez sur le contenu pour modifier", + "category-editable": "Cliquez sur la catégorie pour modifier", + "title-editable": "Cliquez sur le titre pour modifier", + "reply": "Répondre", + "topic": "Sujet", + "accept": "Accepter", + "reject": "Refuser" } \ No newline at end of file From ef16cd2e6f43c1d11e69a4c2007104822b813efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 12 Jan 2021 10:46:55 -0500 Subject: [PATCH 40/69] fix: #9192, exit after logging error --- src/cli/manage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/manage.js b/src/cli/manage.js index d586484bf6..acce6810c9 100644 --- a/src/cli/manage.js +++ b/src/cli/manage.js @@ -41,11 +41,10 @@ async function activate(plugin) { type: 'plugin-activate', text: plugin, }); - process.exit(0); } catch (err) { winston.error('An error occurred during plugin activation\n' + err.stack); - throw err; } + process.exit(0); } async function listPlugins() { From c0fb1cb59cd76f46919a171862bc4015d1ff2d2a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 12 Jan 2021 14:02:12 -0500 Subject: [PATCH 41/69] fix: #9194 global mods unable to pin or unpin topics --- src/topics/tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/tools.js b/src/topics/tools.js index bc23e55ccb..8d04f4603e 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -146,7 +146,7 @@ module.exports = function (Topics) { throw new Error('[[error:no-topic]]'); } - if (uid !== 'system' && !await privileges.topics.can('moderate', tid, uid)) { + if (uid !== 'system' && !await privileges.topics.isAdminOrMod(tid, uid)) { throw new Error('[[error:no-privileges]]'); } From 77ab46686db62871f149419a368c35628453884e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 12 Jan 2021 17:38:35 -0500 Subject: [PATCH 42/69] fix: access checks for tags and thumbs get route --- src/controllers/write/topics.js | 12 ++++++++++++ src/routes/write/topics.js | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index c20cc6a269..5c1ae175cf 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -86,16 +86,28 @@ Topics.unfollow = async (req, res) => { }; Topics.addTags = async (req, res) => { + if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) { + return helpers.formatApiResponse(403, res); + } + await topics.createTags(req.body.tags, req.params.tid, Date.now()); helpers.formatApiResponse(200, res); }; Topics.deleteTags = async (req, res) => { + if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) { + return helpers.formatApiResponse(403, res); + } + await topics.deleteTopicTags(req.params.tid); helpers.formatApiResponse(200, res); }; Topics.getThumbs = async (req, res) => { + if (!await privileges.topics.can('topics:read', req.params.tid, req.uid)) { + return helpers.formatApiResponse(403, res); + } + helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); }; diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index d2c05a38bd..465ecdea45 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -35,7 +35,7 @@ module.exports = function () { setupApiRoute(router, 'put', '/:tid/tags', [...middlewares, middleware.checkRequired.bind(null, ['tags']), middleware.assert.topic], controllers.write.topics.addTags); setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags); - setupApiRoute(router, 'get', '/:tid/thumbs', [], controllers.write.topics.getThumbs); + setupApiRoute(router, 'get', '/:tid/thumbs', middleware.authenticateOrGuest, controllers.write.topics.getThumbs); setupApiRoute(router, 'post', '/:tid/thumbs', [multipartMiddleware, middleware.validateFiles, ...middlewares], controllers.write.topics.addThumb); setupApiRoute(router, 'put', '/:tid/thumbs', [], controllers.write.topics.migrateThumbs); setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['path'])], controllers.write.topics.deleteThumb); From fc3a0bd4279e9ee3fd2bc4c41a6ae737bd910192 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 13 Jan 2021 09:08:53 +0000 Subject: [PATCH 43/69] Latest translations and fallbacks --- public/language/he/admin/extend/rewards.json | 4 ++-- public/language/he/admin/menu.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/he/admin/extend/rewards.json b/public/language/he/admin/extend/rewards.json index aebf2d1986..efffa7057f 100644 --- a/public/language/he/admin/extend/rewards.json +++ b/public/language/he/admin/extend/rewards.json @@ -1,10 +1,10 @@ { "rewards": "תגמולים", "condition-if-users": "אם משתמש", - "condition-is": "Is:", + "condition-is": "לערך:", "condition-then": "תגמל ב:", "max-claims": "מספר פעמים בה ניתן לדרוש תגמול", - "zero-infinite": "הזן 0 לאינסוף", + "zero-infinite": "הזן 0 ללא הגבלה", "delete": "מחק", "enable": "הפעל", "disable": "השבת", diff --git a/public/language/he/admin/menu.json b/public/language/he/admin/menu.json index ed2932db94..55365b28a9 100644 --- a/public/language/he/admin/menu.json +++ b/public/language/he/admin/menu.json @@ -19,7 +19,7 @@ "settings/general": "כללי", "settings/homepage": "דף הבית", "settings/navigation": "ניווט", - "settings/reputation": "מוניטין ודיווחים", + "settings/reputation": "דיווחים ומוניטין", "settings/email": "דוא\"ל", "settings/user": "משתמשים", "settings/group": "קבוצות", @@ -27,7 +27,7 @@ "settings/uploads": "העלאות", "settings/languages": "שפות", "settings/post": "פוסטים", - "settings/chat": "צ'אטים", + "settings/chat": "צ'אט", "settings/pagination": "עמודים", "settings/tags": "תגיות", "settings/notifications": "התראות", From fdf034724000a88147aa861294741d5ef37b2c0c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Jan 2021 10:26:36 -0500 Subject: [PATCH 44/69] style: update codeclimate config to be less sensitive to duplicate code blocks After conversing with Dave from CodeClimate, he suggested these changes to adjust CodeClimate's detection so that it does not alert as frequently for code blocks that are similar, but functionally different. I also added a line to enforce the rule of threes, since CC often alerted only when two blocks were identical. --- .codeclimate.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 33d591df48..63c7d5cab1 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -17,6 +17,14 @@ checks: similar-code: config: threshold: 65 +plugins: + duplication: + enabled: true + config: + languages: + javascript: + mass_threshold: 110 + count_threshold: 3 exclude_paths: - "public/vendor/*" - "test/*" \ No newline at end of file From c30b40abe791e379cb7aca92470f334f331f4323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 13 Jan 2021 11:35:18 -0500 Subject: [PATCH 45/69] fix: keep 60 topics minimum on topic list --- public/src/modules/topicList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index 8ddf566d48..2458666498 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -247,7 +247,7 @@ define('topicList', [ } if (!topicSelect.getSelectedTids().length) { - infinitescroll.removeExtra(topicListEl.find('[component="category/topic"]'), direction, config.topicsPerPage * 3); + infinitescroll.removeExtra(topicListEl.find('[component="category/topic"]'), direction, 60); } html.find('.timeago').timeago(); From 0a471b76b58a3fa871190c65fc5be86425692764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 13 Jan 2021 11:37:15 -0500 Subject: [PATCH 46/69] fix: use max --- public/src/modules/topicList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index 2458666498..ff3d67e8b0 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -247,7 +247,7 @@ define('topicList', [ } if (!topicSelect.getSelectedTids().length) { - infinitescroll.removeExtra(topicListEl.find('[component="category/topic"]'), direction, 60); + infinitescroll.removeExtra(topicListEl.find('[component="category/topic"]'), direction, Math.max(60, config.topicsPerPage * 3)); } html.find('.timeago').timeago(); From 9b289eca9d63a2be7c1831c7384e92a436a42964 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Jan 2021 16:25:19 -0500 Subject: [PATCH 47/69] fix: ssoState passed to strategies even if not called for --- src/routes/authentication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 8de1915c21..eba27b038f 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -103,7 +103,7 @@ Auth.reloadRoutes = async function (params) { passport.authenticate(strategy.name, { scope: strategy.scope, prompt: strategy.prompt || undefined, - state: req.session.ssoState, + state: strategy.checkState ? req.session.ssoState : undefined, })(req, res, next); }); } From ab11435ed5d949866edc42843a222ca930758815 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Jan 2021 16:57:48 -0500 Subject: [PATCH 48/69] feat: grant plugins the ability to specify options to the SSO handler ... to be handled in the plugin itself (overriding the passport prototype's authorizationParams method) + new hook filter:auth.options --- src/routes/authentication.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index eba27b038f..9831d72677 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -98,13 +98,21 @@ Auth.reloadRoutes = async function (params) { loginStrategies = loginStrategies || []; loginStrategies.forEach(function (strategy) { if (strategy.url) { - router.get(strategy.url, Auth.middleware.applyCSRF, function (req, res, next) { - req.session.ssoState = req.csrfToken && req.csrfToken(); - passport.authenticate(strategy.name, { + router.get(strategy.url, Auth.middleware.applyCSRF, async function (req, res, next) { + let opts = { scope: strategy.scope, prompt: strategy.prompt || undefined, - state: strategy.checkState ? req.session.ssoState : undefined, - })(req, res, next); + }; + + if (strategy.checkState) { + req.session.ssoState = req.csrfToken && req.csrfToken(); + opts.state = req.session.ssoState; + } + + // Allow SSO plugins to override/append options (for use in passport prototype authorizationParams) + ({ opts } = await plugins.hooks.fire('filter:auth.options', { req, res, opts })); + + passport.authenticate(strategy.name, opts)(req, res, next); }); } From 3d1224e18da193cd215c93e582d89c19535d0cf2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 15 Jan 2021 02:16:04 +0000 Subject: [PATCH 49/69] fix(deps): update socket.io packages to v3.1.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 496f129696..f2187e9a3e 100644 --- a/install/package.json +++ b/install/package.json @@ -128,9 +128,9 @@ "sharp": "0.27.0", "sitemap": "^6.1.0", "slideout": "1.0.1", - "socket.io": "3.0.5", + "socket.io": "3.1.0", "socket.io-adapter-cluster": "^1.0.1", - "socket.io-client": "3.0.5", + "socket.io-client": "3.1.0", "socket.io-redis": "6.0.1", "sortablejs": "1.13.0", "spdx-license-list": "^6.1.0", From a972b5a77a2047fa2f5b381b61f7c976e6343810 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 15 Jan 2021 09:08:26 +0000 Subject: [PATCH 50/69] Latest translations and fallbacks --- public/language/it/post-queue.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/public/language/it/post-queue.json b/public/language/it/post-queue.json index 08109f58e5..b052203795 100644 --- a/public/language/it/post-queue.json +++ b/public/language/it/post-queue.json @@ -8,11 +8,11 @@ "content": "Contenuto", "posted": "Pubblicato", "reply-to": "Rispondi a \"%1\"", - "content-editable": "Click on content to edit", - "category-editable": "Click on category to edit", - "title-editable": "Click on title to edit", - "reply": "Reply", - "topic": "Topic", - "accept": "Accept", - "reject": "Reject" + "content-editable": "Clicca sul contenuto da modificare", + "category-editable": "Clicca sulla categoria da modificare", + "title-editable": "Clicca sul titolo da modificare", + "reply": "Rispondi", + "topic": "Discussione", + "accept": "Accetta", + "reject": "Rifiuta" } \ No newline at end of file From 43ff8e41978f71bcaa6ddb1f92f72432bb13b27a Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 15 Jan 2021 15:31:56 +0000 Subject: [PATCH 51/69] chore: incrementing version number - v1.16.2-beta.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index f2187e9a3e..ce1a65f2af 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.16.1", + "version": "1.16.2-beta.0", "homepage": "http://www.nodebb.org", "repository": { "type": "git", @@ -190,4 +190,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From 25ab99b91d0ce1afb3cb5dd29f0655d91d1015ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 15 Jan 2021 14:41:45 -0500 Subject: [PATCH 52/69] refactor: single remove call --- src/notifications.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index b98a987cb8..1befdc8c24 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -327,10 +327,9 @@ Notifications.prune = async function () { ]); await batch.processSortedSet('users:joindate', async function (uids) { - await Promise.all([ - db.sortedSetsRemoveRangeByScore(uids.map(uid => 'uid:' + uid + ':notifications:unread'), '-inf', cutoffTime), - db.sortedSetsRemoveRangeByScore(uids.map(uid => 'uid:' + uid + ':notifications:read'), '-inf', cutoffTime), - ]); + const unread = uids.map(uid => 'uid:' + uid + ':notifications:unread'); + const read = uids.map(uid => 'uid:' + uid + ':notifications:read'); + db.sortedSetsRemoveRangeByScore(unread.concat(read), '-inf', cutoffTime); }, { batch: 500, interval: 100 }); } catch (err) { if (err) { From 9938a139fe486bf3852192218940d9641db9470e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 15 Jan 2021 14:42:19 -0500 Subject: [PATCH 53/69] fix: add missing await --- src/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications.js b/src/notifications.js index 1befdc8c24..1b12d27e39 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -329,7 +329,7 @@ Notifications.prune = async function () { await batch.processSortedSet('users:joindate', async function (uids) { const unread = uids.map(uid => 'uid:' + uid + ':notifications:unread'); const read = uids.map(uid => 'uid:' + uid + ':notifications:read'); - db.sortedSetsRemoveRangeByScore(unread.concat(read), '-inf', cutoffTime); + await db.sortedSetsRemoveRangeByScore(unread.concat(read), '-inf', cutoffTime); }, { batch: 500, interval: 100 }); } catch (err) { if (err) { From afbef95f7d1a1e3ac4d5a71fbfecff172f3556a4 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 16 Jan 2021 01:10:34 +0000 Subject: [PATCH 54/69] chore(deps): update dependency eslint to v7.18.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index ce1a65f2af..6e2cd4e812 100644 --- a/install/package.json +++ b/install/package.json @@ -154,7 +154,7 @@ "@commitlint/cli": "11.0.0", "@commitlint/config-angular": "11.0.0", "coveralls": "3.1.0", - "eslint": "7.17.0", + "eslint": "7.18.0", "eslint-config-airbnb-base": "14.2.1", "eslint-plugin-import": "2.22.1", "grunt": "1.3.0", From a6f5de86906b4df44469d04514f6011a9fb78488 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 16 Jan 2021 01:10:43 +0000 Subject: [PATCH 55/69] chore(deps): update dependency husky to v4.3.8 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6e2cd4e812..1703e93757 100644 --- a/install/package.json +++ b/install/package.json @@ -159,7 +159,7 @@ "eslint-plugin-import": "2.22.1", "grunt": "1.3.0", "grunt-contrib-watch": "1.1.0", - "husky": "4.3.7", + "husky": "4.3.8", "jsdom": "16.4.0", "lint-staged": "10.5.3", "mocha": "8.2.1", From f16cdc9f0a044fa4635f3e3ac711714db2991517 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 16 Jan 2021 01:10:50 +0000 Subject: [PATCH 56/69] fix(deps): update dependency nodebb-theme-persona to v10.3.19 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 1703e93757..4d3979a83f 100644 --- a/install/package.json +++ b/install/package.json @@ -101,7 +101,7 @@ "nodebb-plugin-spam-be-gone": "0.7.7", "nodebb-rewards-essentials": "0.1.4", "nodebb-theme-lavender": "5.0.17", - "nodebb-theme-persona": "10.3.18", + "nodebb-theme-persona": "10.3.19", "nodebb-theme-slick": "1.3.8", "nodebb-theme-vanilla": "11.3.10", "nodebb-widget-essentials": "5.0.2", From 00ba89b6d635d1db92789068a79a655d20314cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 16 Jan 2021 09:37:32 -0500 Subject: [PATCH 57/69] fix: #9204, bypass groupname length check for system group in upgrade script --- src/upgrades/1.15.0/verified_users_group.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/upgrades/1.15.0/verified_users_group.js b/src/upgrades/1.15.0/verified_users_group.js index d09b5fa9e5..9d940f1f84 100644 --- a/src/upgrades/1.15.0/verified_users_group.js +++ b/src/upgrades/1.15.0/verified_users_group.js @@ -13,6 +13,9 @@ module.exports = { timestamp: Date.UTC(2020, 9, 13), method: async function () { const progress = this.progress; + + const maxGroupLength = meta.config.maximumGroupNameLength; + meta.config.maximumGroupNameLength = 30; const timestamp = await db.getObjectField('group:administrators', 'timestamp'); const verifiedExists = await groups.exists('verified-users'); if (!verifiedExists) { @@ -38,7 +41,8 @@ module.exports = { timestamp: timestamp + 1, }); } - + // restore setting + meta.config.maximumGroupNameLength = maxGroupLength; await batch.processSortedSet('users:joindate', async function (uids) { progress.incr(uids.length); const userData = await user.getUsersFields(uids, ['uid', 'email:confirmed']); From 1374e0eeba2846c5cdaf268baa9c07df8ce1848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 16 Jan 2021 12:54:11 -0500 Subject: [PATCH 58/69] refactor: change var to const --- src/middleware/header.js | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index b6c7651c79..c2f6594e80 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -1,26 +1,26 @@ 'use strict'; -var nconf = require('nconf'); -var jsesc = require('jsesc'); -var _ = require('lodash'); +const nconf = require('nconf'); +const jsesc = require('jsesc'); +const _ = require('lodash'); const validator = require('validator'); -var util = require('util'); +const util = require('util'); -var db = require('../database'); -var user = require('../user'); -var topics = require('../topics'); -var messaging = require('../messaging'); -var flags = require('../flags'); -var meta = require('../meta'); -var plugins = require('../plugins'); -var navigation = require('../navigation'); -var translator = require('../translator'); -var privileges = require('../privileges'); -var languages = require('../languages'); -var utils = require('../utils'); -var helpers = require('./helpers'); +const db = require('../database'); +const user = require('../user'); +const topics = require('../topics'); +const messaging = require('../messaging'); +const flags = require('../flags'); +const meta = require('../meta'); +const plugins = require('../plugins'); +const navigation = require('../navigation'); +const translator = require('../translator'); +const privileges = require('../privileges'); +const languages = require('../languages'); +const utils = require('../utils'); +const helpers = require('./helpers'); -var controllers = { +const controllers = { api: require('../controllers/api'), helpers: require('../controllers/helpers'), }; @@ -49,9 +49,9 @@ middleware.buildHeader = helpers.try(async function buildHeader(req, res, next) middleware.buildHeaderAsync = util.promisify(middleware.buildHeader); middleware.renderHeader = async function renderHeader(req, res, data) { - var registrationType = meta.config.registrationType || 'normal'; + const registrationType = meta.config.registrationType || 'normal'; res.locals.config = res.locals.config || {}; - var templateValues = { + const templateValues = { title: meta.config.title || '', 'title:url': meta.config['title:url'] || '', description: meta.config.description || '', @@ -240,7 +240,7 @@ middleware.renderFooter = async function renderFooter(req, res, templateValues) }; function modifyTitle(obj) { - var title = controllers.helpers.buildTitle(meta.config.homePageTitle || '[[pages:home]]'); + const title = controllers.helpers.buildTitle(meta.config.homePageTitle || '[[pages:home]]'); obj.browserTitle = title; if (obj.metaTags) { From 4fb907875e31501a28427c7ec1e41b4862649e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 17 Jan 2021 13:10:28 -0500 Subject: [PATCH 59/69] fix: don't crash if fullname is not a string https://community.nodebb.org/topic/15291/upgrade-error-fullname-substr-is-not-a-function --- src/upgrades/1.15.0/fullname_search_set.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrades/1.15.0/fullname_search_set.js b/src/upgrades/1.15.0/fullname_search_set.js index 9f479af145..c3955226bd 100644 --- a/src/upgrades/1.15.0/fullname_search_set.js +++ b/src/upgrades/1.15.0/fullname_search_set.js @@ -16,7 +16,7 @@ module.exports = { const userData = await user.getUsersFields(uids, ['uid', 'fullname']); const bulkAdd = userData .filter(u => u.uid && u.fullname) - .map(u => ['fullname:sorted', 0, u.fullname.substr(0, 255).toLowerCase() + ':' + u.uid]); + .map(u => ['fullname:sorted', 0, String(u.fullname).substr(0, 255).toLowerCase() + ':' + u.uid]); await db.sortedSetAddBulk(bulkAdd); }, { batch: 500, From f5fcd232f6cfba2684ef0aa512d0f3f045459c8c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 17 Jan 2021 15:43:21 -0500 Subject: [PATCH 60/69] fix: regression caused by 77ab46686db62871f149419a368c35628453884e Access checks were added for topic GET route, but occasionally a post_uuid is passed in, which is available to everyone, and so checks should be skipped --- src/controllers/write/topics.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index 5c1ae175cf..5696f51a07 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -104,8 +104,14 @@ Topics.deleteTags = async (req, res) => { }; Topics.getThumbs = async (req, res) => { - if (!await privileges.topics.can('topics:read', req.params.tid, req.uid)) { - return helpers.formatApiResponse(403, res); + if (isFinite(req.params.tid)) { // post_uuids can be passed in occasionally, in that case no checks are necessary + const [exists, canRead] = await Promise.all([ + topics.exists(req.params.tid), + privileges.topics.can('topics:read', req.params.tid, req.uid), + ]); + if (!exists || !canRead) { + return helpers.formatApiResponse(403, res); + } } helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); From 1590cdf19f54de13704ebcb18b8e0870a86c6e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 17 Jan 2021 20:22:46 -0500 Subject: [PATCH 61/69] fix: #9206, fix double escaped arguments --- public/src/modules/translator.js | 3 +++ test/translator.js | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 2fd50d5703..30ec2e1979 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -286,6 +286,9 @@ var out = translated; translatedArgs.forEach(function (arg, i) { var escaped = arg.replace(/%(?=\d)/g, '%').replace(/\\,/g, ','); + // fix double escaped translation keys, see https://github.com/NodeBB/NodeBB/issues/9206 + escaped = escaped.replace(/&lsqb;/g, '[') + .replace(/&rsqb;/g, ']'); out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped); }); return out; diff --git a/test/translator.js b/test/translator.js index 1776c06b9b..bd6d0f0fbb 100644 --- a/test/translator.js +++ b/test/translator.js @@ -135,6 +135,16 @@ describe('new Translator(language)', function () { }); }); + it('should translate escaped translation arguments properly', function () { + // https://github.com/NodeBB/NodeBB/issues/9206 + var translator = Translator.create('en-GB'); + + var key = '[[notifications:upvoted_your_post_in, test1, error: Error: [[error:group-name-too-long]] on NodeBB Upgrade]]'; + return translator.translate(key).then(function (translated) { + assert.strictEqual(translated, 'test1 has upvoted your post in error: Error: [[error:group-name-too-long]] on NodeBB Upgrade.'); + }); + }); + it('should properly escape and ignore % and \\, in arguments', function () { var translator = Translator.create('en-GB'); From c2e2370655ab1014893ecca8732575f95f794efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 18 Jan 2021 13:34:28 -0500 Subject: [PATCH 62/69] feat: add filter:email.cancel to allow plugins to cancel sending emails --- src/emailer.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/emailer.js b/src/emailer.js index 7a2fcdc46d..f1717ccb26 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -230,6 +230,16 @@ Emailer.send = async (template, uid, params) => { params.uid = uid; params.username = userData.username; params.rtl = await translator.translate('[[language:dir]]', userSettings.userLang) === 'rtl'; + + const result = await Plugins.hooks.fire('filter:email.cancel', { + cancel: false, // set to true in plugin to cancel sending email + template: template, + params: params, + }); + + if (result.cancel) { + return; + } await Emailer.sendToEmail(template, userData.email, userSettings.userLang, params); }; From e118e59ce0c32d7ec8890214a15b2f80fce22d9e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 18 Jan 2021 13:32:55 -0500 Subject: [PATCH 63/69] refactor(api): post diffs to use write API --- public/openapi/write.yaml | 4 ++ public/openapi/write/posts/pid/diffs.yaml | 41 ++++++++++++ .../openapi/write/posts/pid/diffs/since.yaml | 65 +++++++++++++++++++ public/src/client/topic/diffs.js | 26 ++------ src/api/posts.js | 59 +++++++++++++++++ src/controllers/write/posts.js | 13 ++++ src/routes/write/posts.js | 4 ++ src/socket.io/posts/diffs.js | 60 ++--------------- 8 files changed, 200 insertions(+), 72 deletions(-) create mode 100644 public/openapi/write/posts/pid/diffs.yaml create mode 100644 public/openapi/write/posts/pid/diffs/since.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 1c691bc5ac..628f21836c 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -108,6 +108,10 @@ paths: $ref: 'write/posts/pid/vote.yaml' /posts/{pid}/bookmark: $ref: 'write/posts/pid/bookmark.yaml' + /posts/{pid}/diffs: + $ref: 'write/posts/pid/diffs.yaml' + /posts/{pid}/diffs/{since}: + $ref: 'write/posts/pid/diffs/since.yaml' /admin/settings/{setting}: $ref: 'write/admin/settings/setting.yaml' /files/: diff --git a/public/openapi/write/posts/pid/diffs.yaml b/public/openapi/write/posts/pid/diffs.yaml new file mode 100644 index 0000000000..40d4201185 --- /dev/null +++ b/public/openapi/write/posts/pid/diffs.yaml @@ -0,0 +1,41 @@ +get: + tags: + - posts + summary: get post edit history + description: This operation retrieves a post's edit history + parameters: + - in: path + name: pid + schema: + type: string + required: true + description: a valid post id + example: 2 + responses: + '200': + description: Post history successfully retrieved. + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + timestamps: + type: array + items: + type: number + revisions: + type: array + items: + type: object + properties: + timestamp: + type: number + username: + type: string + editable: + type: boolean \ No newline at end of file diff --git a/public/openapi/write/posts/pid/diffs/since.yaml b/public/openapi/write/posts/pid/diffs/since.yaml new file mode 100644 index 0000000000..8db8c6f4ac --- /dev/null +++ b/public/openapi/write/posts/pid/diffs/since.yaml @@ -0,0 +1,65 @@ +get: + tags: + - posts + summary: get single post edit history + description: This operation retrieves a post's edit history + parameters: + - in: path + name: pid + schema: + type: string + required: true + description: a valid post id + example: 2 + - in: path + name: since + schema: + type: number + required: true + description: a valid UNIX timestamp + example: 0 + responses: + '200': + description: Post history successfully retrieved. + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + $ref: ../../../../components/schemas/PostObject.yaml#/PostObject +put: + tags: + - posts + summary: revert a post + description: This operation reverts a post to an earlier version. The revert process will append a new history item to the post's edit history. + parameters: + - in: path + name: pid + schema: + type: string + required: true + description: a valid post id + example: 2 + - in: path + name: since + schema: + type: number + required: true + description: a valid UNIX timestamp + example: 0 + responses: + '200': + description: Post successfully reverted + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} \ No newline at end of file diff --git a/public/src/client/topic/diffs.js b/public/src/client/topic/diffs.js index 007182ae8e..3b310f6d85 100644 --- a/public/src/client/topic/diffs.js +++ b/public/src/client/topic/diffs.js @@ -1,6 +1,6 @@ 'use strict'; -define('forum/topic/diffs', ['forum/topic/images'], function () { +define('forum/topic/diffs', ['api', 'forum/topic/images'], function (api) { var Diffs = {}; Diffs.open = function (pid) { @@ -10,11 +10,7 @@ define('forum/topic/diffs', ['forum/topic/images'], function () { var localeStringOpts = { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' }; - socket.emit('posts.getDiffs', { pid: pid }, function (err, data) { - if (err) { - return app.alertError(err.message); - } - + api.get(`/posts/${pid}/diffs`, {}).then((data) => { app.parseAndTranslate('partials/modals/post_history', { diffs: data.revisions.map(function (revision) { var timestamp = parseInt(revision.timestamp, 10); @@ -56,7 +52,7 @@ define('forum/topic/diffs', ['forum/topic/images'], function () { revertEl.prop('disabled', true); }); }); - }); + }).catch(app.alertError); }; Diffs.load = function (pid, since, postContainer) { @@ -64,11 +60,7 @@ define('forum/topic/diffs', ['forum/topic/images'], function () { return; } - socket.emit('posts.showPostAt', { pid: pid, since: since }, function (err, data) { - if (err) { - return app.alertError(err.message); - } - + api.get(`/posts/${pid}/diffs/${since}`, {}).then((data) => { data.deleted = !!parseInt(data.deleted, 10); app.parseAndTranslate('partials/posts_list', 'posts', { @@ -76,7 +68,7 @@ define('forum/topic/diffs', ['forum/topic/images'], function () { }, function (html) { postContainer.empty().append(html); }); - }); + }).catch(app.alertError); }; Diffs.restore = function (pid, since, modal) { @@ -84,14 +76,10 @@ define('forum/topic/diffs', ['forum/topic/images'], function () { return; } - socket.emit('posts.restoreDiff', { pid: pid, since: since }, function (err) { - if (err) { - return app.alertError(err); - } - + api.put(`/posts/${pid}/diffs/${since}`, {}).then(() => { modal.modal('hide'); app.alertSuccess('[[topic:diffs.post-restored]]'); - }); + }).catch(app.alertError); }; return Diffs; diff --git a/src/api/posts.js b/src/api/posts.js index c01b377340..c1582765e2 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -4,6 +4,7 @@ const validator = require('validator'); const _ = require('lodash'); const utils = require('../utils'); +const user = require('../user'); const posts = require('../posts'); const topics = require('../topics'); const groups = require('../groups'); @@ -213,3 +214,61 @@ postsAPI.bookmark = async function (caller, data) { postsAPI.unbookmark = async function (caller, data) { return await apiHelpers.postCommand(caller, 'unbookmark', 'bookmarked', '', data); }; + +async function diffsPrivilegeCheck(pid, uid) { + const [deleted, privilegesData] = await Promise.all([ + posts.getPostField(pid, 'deleted'), + privileges.posts.get([pid], uid), + ]); + + const allowed = privilegesData[0]['posts:history'] && (deleted ? privilegesData[0]['posts:view_deleted'] : true); + if (!allowed) { + throw new Error('[[error:no-privileges]]'); + } +} + +postsAPI.getDiffs = async (caller, data) => { + await diffsPrivilegeCheck(data.pid, caller.uid); + const timestamps = await posts.diffs.list(data.pid); + const post = await posts.getPostFields(data.pid, ['timestamp', 'uid']); + + const diffs = await posts.diffs.get(data.pid); + const uids = diffs.map(diff => diff.uid || null); + uids.push(post.uid); + let usernames = await user.getUsersFields(uids, ['username']); + usernames = usernames.map(userObj => (userObj.uid ? userObj.username : null)); + + let canEdit = true; + try { + await user.isPrivilegedOrSelf(caller.uid, post.uid); + } catch (e) { + canEdit = false; + } + + timestamps.push(post.timestamp); + + return { + timestamps: timestamps, + revisions: timestamps.map((timestamp, idx) => ({ + timestamp: timestamp, + username: usernames[idx], + })), + editable: canEdit, + }; +}; + +postsAPI.loadDiff = async (caller, data) => { + await diffsPrivilegeCheck(data.pid, caller.uid); + return await posts.diffs.load(data.pid, data.since, caller.uid); +}; + +postsAPI.restoreDiff = async (caller, data) => { + const cid = await posts.getCidByPid(data.pid); + const canEdit = await privileges.categories.can('edit', cid, caller.uid); + if (!canEdit) { + throw new Error('[[error:no-privileges]]'); + } + + const edit = await posts.diffs.restore(data.pid, data.since, caller.uid, apiHelpers.buildReqObject(caller)); + websockets.in('topic_' + edit.topic.tid).emit('event:post_edited', edit); +}; diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index 7988439a1f..197e699c31 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -73,3 +73,16 @@ Posts.unbookmark = async (req, res) => { await api.posts.unbookmark(req, data); helpers.formatApiResponse(200, res); }; + +Posts.getDiffs = async (req, res) => { + helpers.formatApiResponse(200, res, await api.posts.getDiffs(req, { ...req.params })); +}; + +Posts.loadDiff = async (req, res) => { + helpers.formatApiResponse(200, res, await api.posts.loadDiff(req, { ...req.params })); +}; + +Posts.restoreDiff = async (req, res) => { + helpers.formatApiResponse(200, res, await api.posts.restoreDiff(req, { ...req.params })); +}; + diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index 837f8def8c..c7a68e94c2 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -24,5 +24,9 @@ module.exports = function () { setupApiRoute(router, 'put', '/:pid/bookmark', [...middlewares, middleware.assert.post], controllers.write.posts.bookmark); setupApiRoute(router, 'delete', '/:pid/bookmark', [...middlewares, middleware.assert.post], controllers.write.posts.unbookmark); + setupApiRoute(router, 'get', '/:pid/diffs', [middleware.authenticateOrGuest, middleware.assert.post], controllers.write.posts.getDiffs); + setupApiRoute(router, 'get', '/:pid/diffs/:since', [middleware.authenticateOrGuest, middleware.assert.post], controllers.write.posts.loadDiff); + setupApiRoute(router, 'put', '/:pid/diffs/:since', [...middlewares, middleware.assert.post], controllers.write.posts.restoreDiff); + return router; }; diff --git a/src/socket.io/posts/diffs.js b/src/socket.io/posts/diffs.js index fb74f6a9ef..a1e2043243 100644 --- a/src/socket.io/posts/diffs.js +++ b/src/socket.io/posts/diffs.js @@ -1,67 +1,21 @@ 'use strict'; -const posts = require('../../posts'); -const user = require('../../user'); -const privileges = require('../../privileges'); -const apiHelpers = require('../../api/helpers'); +const api = require('../../api'); const websockets = require('..'); module.exports = function (SocketPosts) { SocketPosts.getDiffs = async function (socket, data) { - await privilegeCheck(data.pid, socket.uid); - const timestamps = await posts.diffs.list(data.pid); - const post = await posts.getPostFields(data.pid, ['timestamp', 'uid']); - - const diffs = await posts.diffs.get(data.pid); - const uids = diffs.map(diff => diff.uid || null); - uids.push(post.uid); - let usernames = await user.getUsersFields(uids, ['username']); - usernames = usernames.map(userObj => (userObj.uid ? userObj.username : null)); - - let canEdit = true; - try { - await user.isPrivilegedOrSelf(socket.uid, post.uid); - } catch (e) { - canEdit = false; - } - - timestamps.push(post.timestamp); - - return { - timestamps: timestamps, - revisions: timestamps.map((timestamp, idx) => ({ - timestamp: timestamp, - username: usernames[idx], - })), - editable: canEdit, - }; + websockets.warnDeprecated(socket, 'GET /api/v3/posts/:pid/diffs'); + return await api.posts.getDiffs(socket, data); }; SocketPosts.showPostAt = async function (socket, data) { - await privilegeCheck(data.pid, socket.uid); - return await posts.diffs.load(data.pid, data.since, socket.uid); + websockets.warnDeprecated(socket, 'GET /api/v3/posts/:pid/diffs/:since'); + return await api.posts.loadDiff(socket, data); }; - async function privilegeCheck(pid, uid) { - const [deleted, privilegesData] = await Promise.all([ - posts.getPostField(pid, 'deleted'), - privileges.posts.get([pid], uid), - ]); - - const allowed = privilegesData[0]['posts:history'] && (deleted ? privilegesData[0]['posts:view_deleted'] : true); - if (!allowed) { - throw new Error('[[error:no-privileges]]'); - } - } - SocketPosts.restoreDiff = async function (socket, data) { - const cid = await posts.getCidByPid(data.pid); - const canEdit = await privileges.categories.can('edit', cid, socket.uid); - if (!canEdit) { - throw new Error('[[error:no-privileges]]'); - } - - const edit = await posts.diffs.restore(data.pid, data.since, socket.uid, apiHelpers.buildReqObject(socket)); - websockets.in('topic_' + edit.topic.tid).emit('event:post_edited', edit); + websockets.warnDeprecated(socket, 'PUT /api/v3/posts/:pid/diffs/:since'); + return await api.posts.restoreDiff(socket, data); }; }; From 0fa4c11ea6fd0d73756554bf5bbcc262ccaac41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 18 Jan 2021 14:52:52 -0500 Subject: [PATCH 64/69] fix: don't chagne scrollTop if at the top of page --- public/src/client/topic/posts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 8e14941524..e0dd634049 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -210,7 +210,9 @@ define('forum/topic/posts', [ html.insertBefore(before); // Now restore the relative position the user was on prior to new post insertion - $(window).scrollTop(scrollTop + ($(document).height() - height)); + if (scrollTop > 0) { + $(window).scrollTop(scrollTop + ($(document).height() - height)); + } } else { components.get('topic').append(html); } From 966c4117ecac1dae2c8bca43c761939409ce3259 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 18 Jan 2021 15:31:14 -0500 Subject: [PATCH 65/69] refactor(api): post move to write API --- public/openapi/write.yaml | 2 ++ public/openapi/write/posts/pid/move.yaml | 36 +++++++++++++++++++ public/src/client/topic/move-post.js | 14 ++++---- src/api/posts.js | 22 ++++++++++++ src/controllers/write/posts.js | 8 +++++ src/middleware/assert.js | 2 +- src/routes/write/posts.js | 2 ++ src/socket.io/posts/move.js | 44 +++++------------------- test/api.js | 6 ++++ 9 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 public/openapi/write/posts/pid/move.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 628f21836c..c1ea408afa 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -104,6 +104,8 @@ paths: $ref: 'write/posts/pid.yaml' /posts/{pid}/state: $ref: 'write/posts/pid/state.yaml' + /posts/{pid}/move: + $ref: 'write/posts/pid/move.yaml' /posts/{pid}/vote: $ref: 'write/posts/pid/vote.yaml' /posts/{pid}/bookmark: diff --git a/public/openapi/write/posts/pid/move.yaml b/public/openapi/write/posts/pid/move.yaml new file mode 100644 index 0000000000..7198554ffb --- /dev/null +++ b/public/openapi/write/posts/pid/move.yaml @@ -0,0 +1,36 @@ +put: + tags: + - posts + summary: move a post + description: This operation moves a post to a different topic. + parameters: + - in: path + name: pid + schema: + type: number + required: true + description: a valid post id + example: 5 + requestBody: + content: + application/json: + schema: + type: object + properties: + tid: + type: number + description: a valid topic id + example: 4 + responses: + '200': + description: Post successfully moved + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} \ No newline at end of file diff --git a/public/src/client/topic/move-post.js b/public/src/client/topic/move-post.js index 16eb4590fb..8a5e68fe32 100644 --- a/public/src/client/topic/move-post.js +++ b/public/src/client/topic/move-post.js @@ -2,8 +2,8 @@ define('forum/topic/move-post', [ - 'components', 'postSelect', 'translator', 'alerts', -], function (components, postSelect, translator, alerts) { + 'components', 'postSelect', 'translator', 'alerts', 'api', +], function (components, postSelect, translator, alerts, api) { var MovePost = {}; var moveModal; @@ -100,10 +100,10 @@ define('forum/topic/move-post', [ if (!ajaxify.data.template.topic || !data.tid) { return; } - socket.emit('posts.movePosts', { pids: data.pids, tid: data.tid }, function (err) { - if (err) { - return app.alertError(err.message); - } + + Promise.all(data.pids.map(pid => api.put(`/posts/${pid}/move`, { + tid: data.tid, + }))).then(() => { data.pids.forEach(function (pid) { components.get('post', 'pid', pid).fadeOut(500, function () { $(this).remove(); @@ -111,7 +111,7 @@ define('forum/topic/move-post', [ }); closeMoveModal(); - }); + }).catch(app.alertError); } function closeMoveModal() { diff --git a/src/api/posts.js b/src/api/posts.js index c1582765e2..301df87a2e 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -13,6 +13,7 @@ const events = require('../events'); const privileges = require('../privileges'); const apiHelpers = require('./helpers'); const websockets = require('../socket.io'); +const socketHelpers = require('../socket.io/helpers'); const postsAPI = module.exports; @@ -195,6 +196,27 @@ async function isMainAndLastPost(pid) { }; } +postsAPI.move = async function (caller, data) { + const canMove = await Promise.all([ + privileges.topics.isAdminOrMod(data.tid, caller.uid), + privileges.posts.canMove(data.pid, caller.uid), + ]); + if (!canMove.every(Boolean)) { + throw new Error('[[error:no-privileges]]'); + } + + await topics.movePostToTopic(caller.uid, data.pid, data.tid); + + const [postDeleted, topicDeleted] = await Promise.all([ + posts.getPostField(data.pid, 'deleted'), + topics.getTopicField(data.tid, 'deleted'), + ]); + + if (!postDeleted && !topicDeleted) { + socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, 'move', 'notifications:moved_your_post'); + } +}; + postsAPI.upvote = async function (caller, data) { return await apiHelpers.postCommand(caller, 'upvote', 'voted', 'notifications:upvoted_your_post_in', data); }; diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index 197e699c31..8cd094848c 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -38,6 +38,14 @@ Posts.delete = async (req, res) => { helpers.formatApiResponse(200, res); }; +Posts.move = async (req, res) => { + await api.posts.move(req, { + pid: req.params.pid, + tid: req.body.tid, + }); + helpers.formatApiResponse(200, res); +}; + async function mock(req) { const tid = await posts.getPostField(req.params.pid, 'tid'); return { pid: req.params.pid, room_id: `topic_${tid}` }; diff --git a/src/middleware/assert.js b/src/middleware/assert.js index 80fd009e7a..6f2617fdcb 100644 --- a/src/middleware/assert.js +++ b/src/middleware/assert.js @@ -46,7 +46,7 @@ Assert.topic = helpers.try(async (req, res, next) => { Assert.post = helpers.try(async (req, res, next) => { if (!await posts.exists(req.params.pid)) { - return controllerHelpers.formatApiResponse(404, res, new Error('[[error:no-topic]]')); + return controllerHelpers.formatApiResponse(404, res, new Error('[[error:no-post]]')); } next(); diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index c7a68e94c2..d842270e82 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -18,6 +18,8 @@ module.exports = function () { setupApiRoute(router, 'put', '/:pid/state', [...middlewares, middleware.assert.post], controllers.write.posts.restore); setupApiRoute(router, 'delete', '/:pid/state', [...middlewares, middleware.assert.post], controllers.write.posts.delete); + setupApiRoute(router, 'put', '/:pid/move', [...middlewares, middleware.assert.post, middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.move); + setupApiRoute(router, 'put', '/:pid/vote', [...middlewares, middleware.checkRequired.bind(null, ['delta']), middleware.assert.post], controllers.write.posts.vote); setupApiRoute(router, 'delete', '/:pid/vote', [...middlewares, middleware.assert.post], controllers.write.posts.unvote); diff --git a/src/socket.io/posts/move.js b/src/socket.io/posts/move.js index c1637fad51..d802454b97 100644 --- a/src/socket.io/posts/move.js +++ b/src/socket.io/posts/move.js @@ -1,45 +1,19 @@ 'use strict'; -const privileges = require('../../privileges'); -const topics = require('../../topics'); -const posts = require('../../posts'); -const socketHelpers = require('../helpers'); +const api = require('../../api'); +const sockets = require('..'); module.exports = function (SocketPosts) { SocketPosts.movePost = async function (socket, data) { - await SocketPosts.movePosts(socket, { pids: [data.pid], tid: data.tid }); + sockets.warnDeprecated(socket, 'PUT /api/v3/posts/:pid/move'); + await api.posts.move(socket, data); }; SocketPosts.movePosts = async function (socket, data) { - if (!socket.uid) { - throw new Error('[[error:not-logged-in]]'); - } - - if (!data || !Array.isArray(data.pids) || !data.tid) { - throw new Error('[[error:invalid-data]]'); - } - - const canMove = await privileges.topics.isAdminOrMod(data.tid, socket.uid); - if (!canMove) { - throw new Error('[[error:no-privileges]]'); - } - - for (const pid of data.pids) { - /* eslint-disable no-await-in-loop */ - const canMove = await privileges.posts.canMove(pid, socket.uid); - if (!canMove) { - throw new Error('[[error:no-privileges]]'); - } - await topics.movePostToTopic(socket.uid, pid, data.tid); - - const [postDeleted, topicDeleted] = await Promise.all([ - posts.getPostField(pid, 'deleted'), - topics.getTopicField(data.tid, 'deleted'), - ]); - - if (!postDeleted && !topicDeleted) { - socketHelpers.sendNotificationToPostOwner(pid, socket.uid, 'move', 'notifications:moved_your_post'); - } - } + sockets.warnDeprecated(socket, 'PUT /api/v3/posts/:pid/move'); + await Promise.all(data.pids.map(async pid => api.posts.move(socket, { + tid: data.tid, + pid, + }))); }; }; diff --git a/test/api.js b/test/api.js index 730682a7e9..7401b152ea 100644 --- a/test/api.js +++ b/test/api.js @@ -135,6 +135,12 @@ describe('API', async () => { title: 'Test Topic 2', content: 'Test topic 2 content', }); + await topics.post({ + uid: unprivUid, + cid: testCategory.cid, + title: 'Test Topic 3', + content: 'Test topic 3 content', + }); // Create a sample flag await flags.create('post', 1, unprivUid, 'sample reasons', Date.now()); From d85ad10d345ff4d2236aaff9c8aa111209409a4f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 18 Jan 2021 15:47:15 -0500 Subject: [PATCH 66/69] fix: restored sanity checks for post move socket calls --- src/socket.io/posts/move.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/socket.io/posts/move.js b/src/socket.io/posts/move.js index d802454b97..9424d73a06 100644 --- a/src/socket.io/posts/move.js +++ b/src/socket.io/posts/move.js @@ -4,13 +4,27 @@ const api = require('../../api'); const sockets = require('..'); module.exports = function (SocketPosts) { + function moveChecks(socket, typeCheck, data) { + if (!socket.uid) { + throw new Error('[[error:not-logged-in]]'); + } + + if (!data || !typeCheck || !data.tid) { + throw new Error('[[error:invalid-data]]'); + } + } + SocketPosts.movePost = async function (socket, data) { sockets.warnDeprecated(socket, 'PUT /api/v3/posts/:pid/move'); + + moveChecks(socket, isFinite(data.pid), data); await api.posts.move(socket, data); }; SocketPosts.movePosts = async function (socket, data) { sockets.warnDeprecated(socket, 'PUT /api/v3/posts/:pid/move'); + + moveChecks(socket, !Array.isArray(data.pids), data); await Promise.all(data.pids.map(async pid => api.posts.move(socket, { tid: data.tid, pid, From c608b0e8a32cfa384a3741c5b0c37dbdebb0195f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 18 Jan 2021 19:31:07 -0500 Subject: [PATCH 67/69] fix: https://github.com/NodeBB/nodebb-plugin-webhooks/issues/3 --- src/plugins/hooks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 0ed353e946..e603bc73eb 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -92,7 +92,7 @@ Hooks.unregister = function (id, hook, method) { Hooks.fire = async function (hook, params) { const hookList = plugins.loadedHooks[hook]; const hookType = hook.split(':')[0]; - if (global.env === 'development' && hook !== 'action:plugins.fireHook') { + if (global.env === 'development' && hook !== 'action:plugins.firehook') { winston.verbose('[plugins/fireHook] ' + hook); } @@ -102,8 +102,8 @@ Hooks.fire = async function (hook, params) { } const result = await hookTypeToMethod[hookType](hook, hookList, params); - if (hook !== 'action:plugins.fireHook') { - Hooks.fire('action:plugins.fireHook', { hook: hook, params: params }); + if (hook !== 'action:plugins.firehook') { + Hooks.fire('action:plugins.firehook', { hook: hook, params: params }); } if (result !== undefined) { return result; From a55051de2fb59a015244f6cefe1aff83baa576f3 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 20 Jan 2021 09:09:28 +0000 Subject: [PATCH 68/69] Latest translations and fallbacks --- public/language/it/flags.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/it/flags.json b/public/language/it/flags.json index 0cdb0f43c8..455907a628 100644 --- a/public/language/it/flags.json +++ b/public/language/it/flags.json @@ -13,7 +13,7 @@ "filter-active": "Ci sono uno o più filtri attivi in questa lista di segnalazioni", "filter-reset": "Rimuovi Filtri", "filters": "Opzioni Filtri", - "filter-reporterId": "Segnalatore UID", + "filter-reporterId": "UID segnalatore", "filter-targetUid": "UID segnalato", "filter-type": "Tipo Segnalazione", "filter-type-all": "Tutto il Contenuto", From b5cb2f8b9c34ff2646ab60c22aa92b674daad64f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Jan 2021 16:50:41 -0500 Subject: [PATCH 69/69] fix(deps): update dependency bootbox to v5 (#8751) Co-authored-by: Renovate Bot --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4d3979a83f..5d386ae87e 100644 --- a/install/package.json +++ b/install/package.json @@ -43,7 +43,7 @@ "bcryptjs": "2.4.3", "benchpressjs": "2.4.0", "body-parser": "^1.19.0", - "bootbox": "4.4.0", + "bootbox": "5.5.2", "bootstrap": "^3.4.1", "chart.js": "^2.9.3", "cli-graph": "^3.2.2",