From 8ceb35f537bb0cd88c199168e6738a9a53594c5a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 20 Jan 2019 16:10:44 -0500 Subject: [PATCH 001/270] fix: #7270 Flags graph label not translatable --- install/package.json | 4 ++-- public/language/en-GB/flags.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index ff763fbf7b..0d6bd92d40 100644 --- a/install/package.json +++ b/install/package.json @@ -90,9 +90,9 @@ "nodebb-plugin-spam-be-gone": "0.5.5", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.8", - "nodebb-theme-persona": "9.1.11", + "nodebb-theme-persona": "9.1.12", "nodebb-theme-slick": "1.2.19", - "nodebb-theme-vanilla": "10.1.16", + "nodebb-theme-vanilla": "10.1.17", "nodebb-widget-essentials": "4.0.12", "nodemailer": "^5.0.0", "passport": "^0.4.0", diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 35fc87011a..9b8658dceb 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -9,6 +9,7 @@ "updated": "Updated", "target-purged": "The content this flag referred to has been purged and is no longer available.", + "graph-label": "Daily Flags", "quick-filters": "Quick Filters", "filter-active": "There are one or more filters active in this list of flags", "filter-reset": "Remove Filters", From 40637828af9ef89441666259326d0125a85a1b6d Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Mon, 21 Jan 2019 09:26:23 +0000 Subject: [PATCH 002/270] Latest translations and fallbacks --- public/language/nl/admin/advanced/database.json | 12 ++++++------ public/language/nl/admin/general/dashboard.json | 4 ++-- public/language/nl/admin/manage/categories.json | 2 +- public/language/nl/admin/settings/general.json | 4 ++-- public/language/pt-PT/topic.json | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/language/nl/admin/advanced/database.json b/public/language/nl/admin/advanced/database.json index a3187bb795..b31a089494 100644 --- a/public/language/nl/admin/advanced/database.json +++ b/public/language/nl/admin/advanced/database.json @@ -18,16 +18,16 @@ "mongo.resident-memory": "Resident geheugen", "mongo.virtual-memory": "Virtueel geheugen", "mongo.mapped-memory": "Mapped geheugen", - "mongo.bytes-in": "Bytes In", - "mongo.bytes-out": "Bytes Out", - "mongo.num-requests": "Number of Requests", + "mongo.bytes-in": "Bytes Inkomend", + "mongo.bytes-out": "Bytes Uitgaand", + "mongo.num-requests": "Aantal requests", "mongo.raw-info": "MongoDB Raw Info", "redis": "Redis", "redis.version": "Redis versie", - "redis.keys": "Keys", - "redis.expires": "Expires", - "redis.avg-ttl": "Average TTL", + "redis.keys": "Sleutels", + "redis.expires": "Verloopt", + "redis.avg-ttl": "Gemiddelde TTL", "redis.connected-clients": "Verbonden clients", "redis.connected-slaves": "Verbonden slaves", "redis.blocked-clients": "Geblokkeerde clients", diff --git a/public/language/nl/admin/general/dashboard.json b/public/language/nl/admin/general/dashboard.json index cfec2cd754..7e99e1f24b 100644 --- a/public/language/nl/admin/general/dashboard.json +++ b/public/language/nl/admin/general/dashboard.json @@ -49,7 +49,7 @@ "active-users.users": "Users", "active-users.guests": "Guests", "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users.connections": "Connecties", "anonymous-registered-users": "Anonymous vs Registered Users", "anonymous": "Anonymous", @@ -71,6 +71,6 @@ "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.anonymous-users": "Anonymous Users", - "last-restarted-by": "Last restarted by", + "last-restarted-by": "Laatst herstart door", "no-users-browsing": "No users browsing" } diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index c14eadea0c..a48f5a9301 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -62,7 +62,7 @@ "alert.copy-success": "Settings Copied!", "alert.set-parent-category": "Set Parent Category", "alert.updated": "Updated Categories", - "alert.updated-success": "Category IDs %1 successfully updated.", + "alert.updated-success": "Category IDs %1 zijn succesvol geüpdatet.", "alert.upload-image": "Upload category image", "alert.find-user": "Find a User", "alert.user-search": "Search for a user here...", diff --git a/public/language/nl/admin/settings/general.json b/public/language/nl/admin/settings/general.json index c47481fe11..1977c9678a 100644 --- a/public/language/nl/admin/settings/general.json +++ b/public/language/nl/admin/settings/general.json @@ -6,7 +6,7 @@ "title.url-help": "Wanneer de titel word aangeklikt stuur gebruikers naar dit adress. Is het leeg gelaten dan worden gebruikers naar de forum hoofdpagina gestuurd.", "title.name": "Jouw Communiy Naam", "title.show-in-header": "Toon Site Titel in Header", - "browser-title": "Browser Title", + "browser-title": "Browser Titel", "browser-title-help": "Als geen browser titel is gespecificeerd dan word de site titel gebruikt", "title-layout": "Titel Lay-out", "title-layout-help": "Defineer hoe de browser titel gestructureerd word. bijv: {paginaTitel} | {browserTitel}", @@ -27,7 +27,7 @@ "favicon.upload": "Uploaden", "touch-icon": "Startscherm / Aanraakpictogram", "touch-icon.upload": "Uploaden", - "touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", + "touch-icon.help": "Aangeraden grootte en formaat: 192x192, alleen PNG formaat. Als er geen touch icoon is gespecificeerd zal NodeBB terugvallen op het favicon.", "outgoing-links": "Outgoing Links", "outgoing-links.warning-page": "Use Outgoing Links Warning Page", "search-default-sort-by": "Search default sort by", diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json index c2dc7f548e..74945e942e 100644 --- a/public/language/pt-PT/topic.json +++ b/public/language/pt-PT/topic.json @@ -101,7 +101,7 @@ "composer.title_placeholder": "Insere aqui o título do tópico...", "composer.handle_placeholder": "Nome", "composer.discard": "Descartar", - "composer.submit": "Submeter", + "composer.submit": "Publicar", "composer.replying_to": "Respondendo a %1", "composer.new_topic": "Novo tópico", "composer.uploading": "carregando...", From 831d07951242c5eb3a3b77ba159a1c1ee779a0d0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 21 Jan 2019 11:39:48 -0500 Subject: [PATCH 003/270] fix: #7274 incorrect handling of client script 404s --- src/controllers/404.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/404.js b/src/controllers/404.js index baa8d05650..b34926aa5e 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -9,7 +9,7 @@ var plugins = require('../plugins'); exports.handle404 = function handle404(req, res) { var relativePath = nconf.get('relative_path'); - var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js$'); + var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$'); if (plugins.hasListeners('action:meta.override404')) { return plugins.fireHook('action:meta.override404', { From 5ee173c2f6b66115dcbb3e1660ca76adcecf9c84 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 21 Jan 2019 14:16:06 -0500 Subject: [PATCH 004/270] fix: #7276 improper request for client-noskin.css --- public/src/app.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index bf68b520c8..4e30cc7c32 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -149,7 +149,7 @@ app.cacheBuster = null; Unread.initUnreadTopics(); Notifications.prepareDOM(); Chat.prepareDOM(); - app.reskin(data.config.bootswatchSkin); + app.reskin(data.header.bootswatchSkin); translator.switchTimeagoLanguage(callback); bootbox.setLocale(config.userLang); @@ -770,6 +770,17 @@ app.cacheBuster = null; return; } + var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { + return className.startsWith('skin-'); + }); + var currentSkin = currentSkinClassName[0].slice(5); + currentSkin = currentSkin !== 'noskin' ? currentSkin : ''; + + // Stop execution if skin didn't change + if (skinName === currentSkin) { + return; + } + var linkEl = document.createElement('link'); linkEl.rel = 'stylesheet'; linkEl.type = 'text/css'; @@ -778,13 +789,8 @@ app.cacheBuster = null; clientEl.parentNode.removeChild(clientEl); // Update body class with proper skin name - var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { - return className.startsWith('skin-'); - }); $('body').removeClass(currentSkinClassName.join(' ')); - if (skinName) { - $('body').addClass('skin-' + skinName); - } + $('body').addClass('skin-' + (skinName || 'noskin')); }; document.head.appendChild(linkEl); From 059a4be2048165953b4669b2e3f0e5ff81cc6bf6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 21 Jan 2019 15:29:32 -0500 Subject: [PATCH 005/270] feat: explicit handling of SSO success and failure --- src/controllers/index.js | 4 ++-- src/routes/authentication.js | 27 ++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index ed94cd4713..08c9cec157 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -92,7 +92,7 @@ Controllers.login = function (req, res, next) { var registrationType = meta.config.registrationType || 'normal'; var allowLoginWith = (meta.config.allowLoginWith || 'username-email'); - var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url'), ''); + var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url') + nconf.get('relative_path'), ''); var errorText; if (req.query.error === 'csrf-invalid') { @@ -214,7 +214,7 @@ Controllers.registerInterstitial = function (req, res, next) { // No interstitials, redirect to home const returnTo = req.session.returnTo || req.session.registration.returnTo; delete req.session.registration; - return helpers.redirect(res, returnTo || nconf.get('relative_path') + '/'); + return helpers.redirect(res, returnTo || '/'); } var renders = data.interstitials.map(function (interstitial) { return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {}); diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 81af7fe550..a4341ddf36 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -3,10 +3,10 @@ var async = require('async'); var passport = require('passport'); var passportLocal = require('passport-local').Strategy; -var nconf = require('nconf'); var winston = require('winston'); var controllers = require('../controllers'); +var helpers = require('../controllers/helpers'); var plugins = require('../plugins'); var loginStrategies = []; @@ -88,10 +88,27 @@ Auth.reloadRoutes = function (router, callback) { // passport seems to remove `req.session.returnTo` after it redirects req.session.registration.returnTo = req.session.returnTo; next(); - }, passport.authenticate(strategy.name, { - successReturnToOrRedirect: nconf.get('relative_path') + (strategy.successUrl !== undefined ? strategy.successUrl : '/'), - failureRedirect: nconf.get('relative_path') + (strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'), - })); + }, function (req, res, next) { + passport.authenticate(strategy.name, function (err, user) { + if (err) { + delete req.session.registration; + return next(err); + } + + if (!user) { + delete req.session.registration; + return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'); + } + + req.login(user, function (err) { + if (err) { + return next(err); + } + + helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/'); + }); + })(req, res, next); + }); }); router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register); From cee47f78b2af6a58d7d70457b22f986942c96540 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 21 Jan 2019 16:34:33 -0500 Subject: [PATCH 006/270] fix: #7289 timeago shorthand toggle fails on non-existant language - Exposing timeagoCodes to frontend - Timeago language and shorthand toggling will fall back to 'en' if the requested language does not exist --- public/src/modules/translator.js | 7 +++++++ src/controllers/api.js | 2 ++ 2 files changed, 9 insertions(+) diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 8bce483e44..0495b23306 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -586,6 +586,10 @@ if (!adaptor.timeagoShort) { var languageCode = utils.userLangToTimeagoCode(config.userLang); + if (!config.timeagoCodes.includes(languageCode + '-short')) { + languageCode = 'en'; + } + var originalSettings = assign({}, jQuery.timeago.settings.strings); jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () { adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings); @@ -602,6 +606,9 @@ delete adaptor.timeagoShort; var languageCode = utils.userLangToTimeagoCode(config.userLang); + if (!config.timeagoCodes.includes(languageCode + '-short')) { + languageCode = 'en'; + } jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(callback); }, diff --git a/src/controllers/api.js b/src/controllers/api.js index 5f37721935..746f67184f 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -12,6 +12,7 @@ var categories = require('../categories'); var privileges = require('../privileges'); var plugins = require('../plugins'); var translator = require('../translator'); +var languages = require('../languages'); var apiController = module.exports; @@ -62,6 +63,7 @@ apiController.loadConfig = function (req, callback) { config.bootswatchSkin = meta.config.bootswatchSkin || ''; config.enablePostHistory = (meta.config.enablePostHistory || 1) === 1; config.notificationAlertTimeout = meta.config.notificationAlertTimeout || 5000; + config.timeagoCodes = languages.timeagoCodes; if (config.useOutgoingLinksPage) { config.outgoingLinksWhitelist = meta.config['outgoingLinks:whitelist']; From f7191eb955f98a727f16d7db0ee2a4771bc151da Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 22 Jan 2019 12:28:06 +0000 Subject: [PATCH 007/270] chore(deps): update dependency eslint-plugin-import to v2.15.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0d6bd92d40..79323411a4 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "coveralls": "3.0.2", "eslint": "5.12.1", "eslint-config-airbnb-base": "13.1.0", - "eslint-plugin-import": "2.14.0", + "eslint-plugin-import": "2.15.0", "grunt": "1.0.3", "grunt-contrib-watch": "1.1.0", "husky": "1.3.1", From b48f1b4d9106aceb92a83abfaa8a423ca47177d1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 22 Jan 2019 13:42:53 -0500 Subject: [PATCH 008/270] fix(deps): #7270 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 79323411a4..dd88a30048 100644 --- a/install/package.json +++ b/install/package.json @@ -90,9 +90,9 @@ "nodebb-plugin-spam-be-gone": "0.5.5", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.8", - "nodebb-theme-persona": "9.1.12", + "nodebb-theme-persona": "9.1.13", "nodebb-theme-slick": "1.2.19", - "nodebb-theme-vanilla": "10.1.17", + "nodebb-theme-vanilla": "10.1.18", "nodebb-widget-essentials": "4.0.12", "nodemailer": "^5.0.0", "passport": "^0.4.0", From 0a662e488200a0a8faca2834075fd1b1dd1c2bac Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 23 Jan 2019 11:14:10 +0100 Subject: [PATCH 009/270] Add Disallow: /compose to robots.txt --- src/controllers/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/index.js b/src/controllers/index.js index 08c9cec157..494b0e9f02 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -252,6 +252,7 @@ Controllers.robots = function (req, res) { res.send('User-agent: *\n' + 'Disallow: ' + nconf.get('relative_path') + '/admin/\n' + 'Disallow: ' + nconf.get('relative_path') + '/reset/\n' + + 'Disallow: ' + nconf.get('relative_path') + '/compose\n' + 'Sitemap: ' + nconf.get('url') + '/sitemap.xml'); } }; From 1719cd77c2e78a7cb27993f30e829dc4688d10c2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 23 Jan 2019 16:41:01 +0000 Subject: [PATCH 010/270] chore(deps): update node:8.15.0 docker digest to cb66110 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 89b051103d..94f93699a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # The base image is the latest 8.x node (LTS) -FROM node:8.15.0@sha256:5aebe186c00da3308c8fde5b3a246d1927a56947a1b51f5c4308b7318adf74f4 +FROM node:8.15.0@sha256:cb66110c9c7d84bae9a6db8675f49d5c9e34d528023ef185b186e29ae5461051 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app From a7af019866eac6bd9375bbe8d092902794f58b1c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 23 Jan 2019 13:03:40 -0500 Subject: [PATCH 011/270] fix(deps): #7271, updating autoprefixer to latest version --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index dd88a30048..da0283d250 100644 --- a/install/package.json +++ b/install/package.json @@ -32,7 +32,7 @@ "ace-builds": "^1.2.9", "archiver": "^3.0.0", "async": "2.6.1", - "autoprefixer": "^9.0.0", + "autoprefixer": "^9.4.6", "bcryptjs": "2.4.3", "benchpressjs": "^1.2.5", "body-parser": "^1.18.2", From 4d64de764335ed4832f8c5cade8842a42f57067b Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 22 Jan 2019 14:33:46 +0000 Subject: [PATCH 012/270] fix(deps): update dependency postcss to v7.0.14 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index da0283d250..a60472aaaf 100644 --- a/install/package.json +++ b/install/package.json @@ -99,7 +99,7 @@ "passport-local": "1.0.0", "pg": "^7.4.0", "pg-cursor": "^2.0.0", - "postcss": "7.0.12", + "postcss": "7.0.14", "postcss-clean": "1.1.0", "promise-polyfill": "^8.0.0", "prompt": "^1.0.0", From 1aadbc3c94c1b213d5764e7da35ff761e822e963 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 23 Jan 2019 19:35:01 +0000 Subject: [PATCH 013/270] fix(deps): update dependency mongodb to v3.1.13 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a60472aaaf..73bbf4a763 100644 --- a/install/package.json +++ b/install/package.json @@ -75,7 +75,7 @@ "material-design-lite": "^1.3.0", "mime": "^2.2.0", "mkdirp": "^0.5.1", - "mongodb": "3.1.12", + "mongodb": "3.1.13", "morgan": "^1.9.0", "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", From 63061ffd37f860ad14201ae31d84dc4323091b4e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 24 Jan 2019 12:04:44 -0500 Subject: [PATCH 014/270] feat: new hook filter:user.logout - used for setting "next" for post logout redirection --- public/src/app.js | 5 +++++ src/controllers/authentication.js | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 4e30cc7c32..c1a21be21e 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -194,6 +194,11 @@ app.cacheBuster = null; $(window).trigger('action:app.loggedOut', data); if (data.next) { + if (data.next.startsWith('http')) { + window.location.href = data.next; + return; + } + ajaxify.go(data.next); } else { ajaxify.refresh(); diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 98b352050a..d4bf9620aa 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -498,10 +498,12 @@ authenticationController.logout = function (req, res, next) { return res.status(500); } - res.status(200).send({ + payload = { header: payload.header, config: res.locals.config, - }); + }; + plugins.fireHook('filter:user.logout', payload); + res.status(200).send(payload); }); } }, From a22a3a9861a36ebb141c4bfbf2d16039a427a114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 24 Jan 2019 12:42:36 -0500 Subject: [PATCH 015/270] fix: log error to prevent headers already sent retry on uniq index fail --- src/controllers/topics.js | 3 ++- src/database/mongo/sets.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 6e2f72785c..d5bdffbb3f 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -3,6 +3,7 @@ var async = require('async'); var nconf = require('nconf'); +var winston = require('winston'); var user = require('../user'); var meta = require('../meta'); @@ -172,7 +173,7 @@ topicsController.get = function getTopic(req, res, callback) { if (req.loggedIn) { topics.markAsRead([tid], req.uid, function (err, markedRead) { if (err) { - return callback(err); + return winston.error(err); } if (markedRead) { topics.pushUnreadCount(req.uid); diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 7d628d9b69..97f8371a35 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -55,6 +55,9 @@ module.exports = function (db, module) { } bulk.execute(function (err) { + if (err && err.message.startsWith('E11000 duplicate key error')) { + return process.nextTick(module.setsAdd, keys, value, callback); + } callback(err); }); }; From ef0e780896b65b40732993c2793c44f671ba494c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 24 Jan 2019 14:58:55 -0500 Subject: [PATCH 016/270] fix: use ACP config value for checking online status --- src/user/online.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/user/online.js b/src/user/online.js index 6cb19cd22f..5e788d4c75 100644 --- a/src/user/online.js +++ b/src/user/online.js @@ -5,6 +5,7 @@ var async = require('async'); var db = require('../database'); var topics = require('../topics'); var plugins = require('../plugins'); +var meta = require('../meta'); module.exports = function (User) { User.updateLastOnlineTime = function (uid, callback) { @@ -52,7 +53,7 @@ module.exports = function (User) { }, function (lastonline, next) { function checkOnline(lastonline) { - return now - lastonline < 300000; + return (now - lastonline) < (meta.config.onlineCutoff * 60000); } var isOnline; From 22cbcc3e37918db818bdc0d9535ca7f3e4444739 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 24 Jan 2019 15:02:44 -0500 Subject: [PATCH 017/270] fix(deps): update dependency nodebb-widget-essentials to v4.0.13 (#7293) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 73bbf4a763..51a445da83 100644 --- a/install/package.json +++ b/install/package.json @@ -93,7 +93,7 @@ "nodebb-theme-persona": "9.1.13", "nodebb-theme-slick": "1.2.19", "nodebb-theme-vanilla": "10.1.18", - "nodebb-widget-essentials": "4.0.12", + "nodebb-widget-essentials": "4.0.13", "nodemailer": "^5.0.0", "passport": "^0.4.0", "passport-local": "1.0.0", From 7064fd0678460b77603e07de96e398584231140b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 24 Jan 2019 15:14:12 -0500 Subject: [PATCH 018/270] fix: #7235 --- src/controllers/authentication.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index d4bf9620aa..31b59eee93 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -478,7 +478,10 @@ authenticationController.logout = function (req, res, next) { }); }, function (next) { - user.setUserField(req.uid, 'lastonline', Date.now() - 300000, next); + user.setUserField(req.uid, 'lastonline', Date.now() - (meta.config.onlineCutoff * 60000), next); + }, + function (next) { + db.sortedSetRemove('users:online', req.uid, next); }, function (next) { plugins.fireHook('static:user.loggedOut', { req: req, res: res, uid: req.uid }, next); From bc41848adb6c2a84c74a88598b87334a179ecabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 24 Jan 2019 15:17:24 -0500 Subject: [PATCH 019/270] fix: test --- test/messaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/messaging.js b/test/messaging.js index fa6bc96cf7..2dcf1bc833 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -349,7 +349,7 @@ describe('Messaging Library', function () { it('should notify offline users of message', function (done) { Messaging.notificationSendDelay = 100; - db.sortedSetAdd('users:online', Date.now() - 350000, herpUid, function (err) { + db.sortedSetAdd('users:online', Date.now() - ((meta.config.onlineCutoff * 60000) + 50000), herpUid, function (err) { assert.ifError(err); socketModules.chats.send({ uid: fooUid }, { roomId: roomId, message: 'second chat message' }, function (err) { assert.ifError(err); From 757bff2748e532a3442a47cbad4e842638d306d1 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Thu, 24 Jan 2019 21:28:58 +0000 Subject: [PATCH 020/270] chore: incrementing version number - v1.11.2 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 51a445da83..d8056a3e86 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.11.1", + "version": "1.11.2", "homepage": "http://www.nodebb.org", "repository": { "type": "git", @@ -169,4 +169,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From aec2f2102dd3a38050c01d00921567787aad337a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 24 Jan 2019 16:38:36 -0500 Subject: [PATCH 021/270] docs: updated changelog --- CHANGELOG.md | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26444d0188..358e82c3b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,315 @@ -#### 1.11.0 (2018-12-14) +#### 1.11.2 (2019-01-24) + +##### Chores + +* incrementing version number - v1.11.2 (757bff27) +* incrementing version number - v1.11.1 (2104877c) +* **deps:** + * update node:8.15.0 docker digest to cb66110 (1719cd77) + * update dependency eslint-plugin-import to v2.15.0 (f7191eb9) + * update dependency eslint to v5.12.1 (d928c54d) + * update dependency @commitlint/cli to v7.3.2 (6ae2b972) + * update node.js to v8.15.0 (ac39fe90) + * update dependency eslint to v5.12.0 (f96ef7bb) + * update commitlint monorepo to v7.3.1 (50594118) + * update dependency smtp-server to v3.5.0 (00063708) + * update dependency husky to v1.3.1 (719995a4) + * update dependency eslint to v5.11.0 (#7151) (26f3bdbf) + * update dependency husky to v1.3.0 (eb606281) + * update dependency jsdom to v13.1.0 (60e9430b) + * update dependency eslint to v5.10.0 (#7084) (dae861da) + * update dependency husky to v1.2.1 (63f4b569) + * update node:8.14.0 docker digest to dd2381f (7449ae3e) + * update node.js to v8.14.0 (8a5a031d) + * update dependency lint-staged to v8.1.0 (dd7f8a14) + * update dependency husky to v1.2.0 (aee21628) + * update node:8.12.0 docker digest to 5dae8ea (0ef451dd) + * update dependency husky to v1.1.4 (95d6ab06) + * update dependency eslint to v5.9.0 (92441794) + * pin dependencies (b0483f21) + * update dependency eslint-config-airbnb-base to v13 (#6599) (64b9dabf) + * update node.js to v8.12.0 (fa3afbd2) + * update dependency husky to v1.1.3 (6cee5b8e) + * update dependency lint-staged to v8.0.4 (9d258668) + * update dependency lint-staged to v8.0.3 (aaa6fe9e) + * update dependency lint-staged to v8 (95d7a5fa) + * update dependency jsdom to v13 (52f141c9) +* **husky:** setting up husky as recommended in docs (e8a3d929) + +##### Documentation Changes + +* updated changelog for v1.11.1 (c04e192d) + +##### New Features + +* new hook filter:user.logout (63061ffd) +* explicit handling of SSO success and failure (059a4be2) +* additional options for SSO plugins (2b9322e1) +* get rid of disk access (ed5d2d6d) +* support for one-click unsubscribe from email clients (#7203) (70a87d43) +* added new hook `static:sockets.validateSession` (#7189) (0263b4da) +* #7120 (f4ea2c43) +* #7032 (0c1ac4d6) +* small fixes (fef7e13c) +* name topic controller (b9b9d8b2) +* header (0cb9bba4) +* more naming (ae0fe5e8) +* give the rest of the middlewares names (f88db22c) +* give names to more middlewares (fdfbcc6e) +* give names to middlewares (53793e16) +* change sortedSetsScore (d2c2d56f) +* Allow getting logfile path from config (#7044) (f3e8e065) +* remove uid::ignored:cids (#7099) (263c9180) +* cache category tag whitelist (78fa7340) +* make user cards look less derpy (31bb2ae9) +* added new middleware authenticateOrGuest (4fba1492) +* closes #7070 (7ca62b83) +* added README.md in languages folder (648964fa) +* up composer (7eee8e1d) +* allow array results (54c127d1) +* #7023 (f581c052) +* close #7002, console message if mismatched origins (89c025d1) +* added changelog file to root of repo (e89b4fca) +* enabling commitlint (c58a41ed) +* allow disabling of GDPR features via ACP toggle, closes #6847 (4919e9ef) +* **deps:** update bootstrap to v3.4.0 (#7106) (d1ea5d15) +* **email:** don't escape html in notification bodies. (#7042) (d7c55bc3) + +##### Bug Fixes + +* test (bc41848a) +* #7235 (7064fd06) +* use ACP config value for checking online status (ef0e7808) +* log error to prevent headers already sent (a22a3a98) +* #7289 timeago shorthand toggle fails on non-existant language (cee47f78) +* #7276 improper request for client-noskin.css (5ee173c2) +* #7274 incorrect handling of client script 404s (831d0795) +* #7270 Flags graph label not translatable (8ceb35f5) +* #7266 body does not contain skin class (f122fc44) +* generate timeago codes from files (7524d3c3) +* removal of timeago fallback middleware (#7259) (c831ff0d) +* post queue notifs (ac655564) +* added missing translation and error state for password change (51b5fb98) +* #7236, header search stops working after header update (3859d417) +* #7226, added placeholder styling for fa-nbb-none (87c2d108) +* escape hook method (9328eeca) +* #7216, hide taskbar on chat modal invocation on mobile (a70db885) +* #7208 (428f587c) +* #7054 (a662f118) +* #7209 (b9833483) +* missing notification (1a3838e1) +* #7193, closes #7194 (7809ba28) +* #https://github.com/barisusakli/nodebb-plugin-dbsearch/issues/49 (6f1fb4eb) +* #7187 (28459d04) +* #7176, FUOC on app.reskin() (954af0f0) +* #7174 (9aa1aa68) +* #7181 (0d409610) +* #7142 (8da3b2a4) +* #7179 (03299736) +* #7169 Fixed logout being broken (b0eaa858) +* #7167, composer and chat not closing on logout (629b3554) +* shorter function (43e7cc0a) +* #7162 (2da0a657) +* uid filtering (72afc180) +* dont crash if default cover is invalid (41fb5cca) +* #7136 socket.disconnect() now called on invalid session (8e9de540) +* RTL not respected when changed in user settings, related to #7146 (4873a339) +* #7146 Better RTL handling on (de-)authentication (d81e0a5f) +* #7118, invoking autoLocale middleware on logout (900f0a0b) +* closes #6784 (#7137) (7fb29f42) +* 7100 (ab81cca7) +* #7139 (3917022a) +* #7116 (7e828404) +* #7138 (29a85aec) +* lint (b47f939b) +* #7091, #7093 (69e0dbbf) +* #7131 (d31684e8) +* remove cache (b2a74b41) +* loop (60390c01) +* #7124 (4650a760) +* unread badge (9f506268) +* move the check to get methods (99e0895e) +* #7115 (989879a6) +* #6979 (29b63ae7) +* upgrade script key (0eef3e1c) +* remove log (00afc5b3) +* #7108 (81697390) +* dont save data for non-positive uids (62f01a83) +* #7103 (f103390a) +* dont update cid::tids:votes if topic is pinned (2f57a4b9) +* #7102 (d117df77) +* #7102 (85a07e99) +* don't explode if there is no css el (74d0e88d) +* db info page (26ccd8f6) +* logAttempt conditional (a6c8e0ab) +* #7087, server-side protection against guest blocks (33d4956b) +* don't crash in flags.validate if user blocked target (81aa3a0b) +* dont send empty strings (555c092f) +* #7085 (fe0f95a2) +* #7086 (e55fb437) +* wrong variable #7085 (71163421) +* admins&mods when there are mutliple lines of users (de437e36) +* refreshing settings page on save if language changed (ed46c5e2) +* not calling authenticate middleware on resource direct access routes (eeaee8cc) +* #7038, autoLocale logic not playing nicely with no-refresh auths (#7059) (5f3d1c76) +* #7074 (2604cf63) +* #7071 buildSkinAsset won't rebuild continuously (a07d9898) +* #7063, logout code should do hard page nav to / or data.next (6df5668e) +* #7061 (eab297bd) +* skin not changing after login or logout, #7038 (28a1fa78) +* #7040 (a63ddbe2) +* #7041 (ec0c50d4) +* #7043 (8d7c3897) +* add missing render function (cb7c2d8c) +* #7033 (8808a033) +* #7037 (b86f1556) +* #6991, add timeout for version Github request (43c3bb02) +* #7030 (58d4376f) +* added admin/manage/uploads to tx config (7357926f) +* #7013, add cache buster to js-enabled.css (f6b92c1d) +* removal of scroll anchoring code in favour of browser handling (98c14e0e) +* custom navigation item not showing groups (d9452bf3) +* flags detail page crash if reporter blocks author (d027207f) +* #6922, skin assets not including plugin LESS files (a5022ce4) +* #6921, allow square brackets in usernames (da10ca08) +* interstitial redirects failing if done via ajaxify (3c8939a8) +* username trim on login, closes #6894 (157bea69) +* **deps:** + * update dependency nodebb-widget-essentials to v4.0.13 (#7293) (22cbcc3e) + * update dependency mongodb to v3.1.13 (1aadbc3c) + * update dependency postcss to v7.0.14 (4d64de76) + * #7271, updating autoprefixer to latest version (a7af0198) + * #7270 (b48f1b4d) + * update dependency sharp to v0.21.3 (#7267) (8a64667f) + * theme upgrades for #7266 (5607261c) + * update dependency mongodb to v3.1.12 (eeab7d20) + * update dependency mongodb to v3.1.11 (#7252) (b5f188b6) + * update dependency validator to v10.11.0 (77dc8fc7) + * update dependency nodebb-plugin-composer-default to v6.1.21 (2fbb2614) + * update dependency postcss to v7.0.12 (f1842295) + * update dependency postcss to v7.0.11 (57bec2fb) + * update dependency sharp to v0.21.2 (8f3c4b09) + * update dependency postcss to v7.0.10 (82475fe5) + * update dependency postcss to v7.0.9 (f171c169) + * update dependency nodebb-theme-vanilla to v10.1.15 (ea059e89) + * update dependency nodebb-theme-persona to v9.1.10 (96482569) + * update dependency nodebb-theme-persona to v9.1.9 (bbe05043) + * update dependency nodebb-theme-vanilla to v10.1.14 (6cc5dbc8) + * update dependency nodebb-theme-persona to v9.1.8 (e5443690) + * update dependency pg-cursor to v2 (29acad42) + * update dependency diff to v4 (#7198) (84e228bb) + * update dependency nodebb-plugin-mentions to v2.5.2 (#7199) (0a647316) + * update dependency nodebb-plugin-markdown to v8.8.7 (90b4d40e) + * update dependency rimraf to v2.6.3 (f4cc3122) + * update dependency spider-detector to v1.0.19 (#7177) (0faba325) + * update dependency nodemailer to v5 (4993b03c) + * update dependency json-2-csv to v3 (80cee665) + * update dependency nodebb-plugin-composer-default to v6.1.20 (07bf0b98) + * update dependency nodebb-theme-persona to v9.1.7 (#7161) (c68d4ae8) + * update dependency nodebb-plugin-composer-default to v6.1.19 (#7159) (07af46ea) + * update dependency nodebb-plugin-composer-default to v6.1.18 (#7158) (584b45fc) + * update dependency validator to v10.10.0 (#7152) (8003bed8) + * update dependency nodebb-plugin-mentions to v2.5.0 (792dce14) + * update dependency nodebb-theme-persona to v9.1.6 (#7141) (325b0293) + * update dependency nodebb-plugin-dbsearch to v3.0.4 (ddd07c1a) + * update dependency nodebb-widget-essentials to v4.0.12 (#7133) (f614a44d) + * update dependency nodebb-plugin-mentions to v2.4.0 (9ab31d7e) + * update dependency postcss to v7.0.7 (7ef8c3fd) + * update dependency sharp to v0.21.1 (#7082) (bf75f3e3) + * update dependency nodebb-theme-vanilla to v10.1.13 (#7114) (fc5598b9) + * update dependency nodebb-theme-slick to v1.2.19 (#7113) (56ad43aa) + * update dependency nodebb-theme-persona to v9.1.5 (#7112) (953f8fe5) + * update dependency nodebb-plugin-composer-default to v6.1.17 (3bcfd7fc) + * update dependency nodebb-theme-persona to v9.1.4 (b6ad5fd4) + * update dependency nodebb-plugin-markdown to v8.8.6 (#7079) (46fb365d) + * update dependency nodebb-theme-persona to v9.1.3 (#7075) (d2aea57a) + * update dependency nodebb-theme-persona to v9.1.2 (42e792ab) + * update dependency nodebb-theme-persona to v9.1.1 (#7069) (bdb33056) + * update dependency postcss to v7.0.6 (6b5428c5) + * update dependency nodebb-plugin-composer-default to v6.1.14 (#7058) (e48ed6e0) + * update dependency nodebb-plugin-composer-default to v6.1.13 (#7057) (ada1d6d0) + * update dependency nodebb-plugin-composer-default to v6.1.12 (#7056) (9f9f72da) + * update dependency nodebb-plugin-composer-default to v6.1.11 (#7055) (89acb896) + * update dependency nodebb-theme-slick to v1.2.18 (#7049) (b6cb77c1) + * update dependency nodebb-theme-slick to v1.2.17 (#7048) (7334c45b) + * update dependency nodebb-theme-slick to v1.2.16 (#7047) (1cb1af0c) + * update dependency connect-mongo to v2.0.3 (#7046) (d0d0c7f0) + * update dependency nodebb-plugin-dbsearch to v3.0.3 (#7035) (adb1b5f3) + * update dependency lru-cache to v4.1.5 (#7031) (887582eb) + * update dependency socket.io to v2.2.0 (b9d49867) + * update dependency socket.io-client to v2.2.0 (824bd541) + * update dependency nodebb-plugin-dbsearch to v3.0.2 (#7028) (11f1b409) + * update dependency nodebb-plugin-dbsearch to v3.0.1 (#7027) (e71f443c) + * update dependency nodebb-theme-vanilla to v10.1.12 (cf928f44) + * update dependency nodebb-theme-persona to v9.1.0 (179be9ed) + * update dependency nodebb-theme-persona to v9.0.63 (#7019) (68ae3eb6) + * update dependency nodebb-plugin-markdown to v8.8.5 (d3ab7d1b) + * update dependency nodebb-theme-persona to v9.0.60 (#6984) (cbd50a80) + * update dependency nodebb-theme-vanilla to v10.1.10 (#6982) (4c769487) + * update dependency nodebb-theme-slick to v1.2.15 (#6981) (acaf1a05) + * update dependency nodebb-theme-persona to v9.0.59 (#6980) (5863bb2c) + * update dependency lru-cache to v4.1.4 (#6977) (375ab769) + * update dependency connect-mongo to v2.0.2 (#6975) (e1597b83) + * update dependency nodebb-plugin-markdown to v8.8.4 (84d1013d) + * update dependency nodebb-plugin-composer-default to v6.1.8 (fee7e336) + * update dependency nodebb-plugin-markdown to v8.8.3 (b182a195) + * update dependency nodebb-plugin-composer-default to v6.1.7 (#6966) (1101f327) + * update dependency nodebb-theme-persona to v9.0.58 (#6964) (6ade156b) + * update dependency mongodb to v3.1.10 (#6962) (662215fa) + * update dependency nodebb-theme-persona to v9.0.57 (#6956) (1bf1a439) + * update dependency nodebb-theme-persona to v9.0.55 (#6955) (e06683f7) + * update dependency nodebb-plugin-composer-default to v6.1.6 (c51ceaf0) + * update dependency nodebb-theme-persona to v9.0.54 (bb940b01) + * update dependency nodebb-plugin-mentions to v2.2.12 (#6936) (e12a803b) + * update dependency nodebb-theme-vanilla to v10.1.9 (#6935) (b480c321) + * update dependency nodebb-theme-slick to v1.2.14 (#6934) (9cdd5316) + * update dependency nodebb-theme-persona to v9.0.53 (#6933) (9ee1c2f8) + * update dependency nodebb-plugin-dbsearch to v2.0.23 (#6931) (dba1db9c) + * update dependency jsesc to v2.5.2 (511b4edc) + * update dependency validator to v10.9.0 (032caafa) + * update dependency spdx-license-list to v5 (a639b6b8) + * update dependency nodebb-theme-vanilla to v10.1.8 (eb0a322d) + * update dependency nodebb-theme-persona to v9.0.52 (6566a0cb) + * update dependency nodebb-plugin-dbsearch to v2.0.22 (#6916) (7808e58c) + * update dependency mongodb to v3.1.9 (#6914) (9a9f2af9) + * update dependency nodebb-theme-persona to v9.0.51 (e2274fe0) + * update dependency nodebb-theme-slick to v1.2.13 (3005428d) + * update dependency nodebb-theme-persona to v9.0.50 (#6902) (22140a20) + * update dependency nodebb-plugin-markdown to v8.8.2 (0b4c9a80) + * update dependency nodebb-theme-vanilla to v10.1.7 (3150a2fc) + * update dependency nodebb-theme-slick to v1.2.12 (#6881) (9bcda7f7) + * update dependency nodebb-theme-persona to v9.0.49 (#6880) (e0dc00da) + * update dependency nodebb-theme-persona to v9.0.48 (2b6f5eec) +* **i18n:** pushed notifications source to tx, pulled fallbacks (8dd8370b) +* **uploads:** ugly filenames on uploaded asset downloading (f96208a0) +* **acp:** + * small UI fixes for ACP privileges category selector (#6946) (57b39d5b) + * hard-to-discover dropdown selector in ACP (b3f96d28) +* **l10n:** some translations (34cbd1fc) + +##### Other Changes + +* //github.com/NodeBB/nodebb-theme-persona/issues/363 (702be3f6) +* //github.com/NodeBB/NodeBB/issues/6433 (7e00d6b9) +* #6408 (f0f30041) +* #6425 (fbf52407) +* //github.com/NodeBB/NodeBB/issues/6073 (5da24b41) +* #5862, setting chat list height even if no message list is present (bc9a1250) +* //github.com/Schamper/nodebb-plugin-poll/issues/86 (c0f39032) + +##### Refactors + +* use loash when possible (#7230) (e1ca2d81) + +##### Code Style Changes + +* lint fix (fbe6ccd7) +* **eslint:** + * match operator-linebreak preferences (ba619c7e) + * newlines in public/src as well (f7bd398e) + * enforcing newline on chained calls (95cc27f1) + +#### 1.11.1 (2018-12-14) ##### Chores From 247ac8285816b0d8ad757e333fd13b9a1f5b83f5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 24 Jan 2019 16:25:16 +0000 Subject: [PATCH 022/270] fix(deps): update dependency nodebb-plugin-spam-be-gone to v0.6.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d8056a3e86..e1ae6b40d9 100644 --- a/install/package.json +++ b/install/package.json @@ -87,7 +87,7 @@ "nodebb-plugin-markdown": "8.8.7", "nodebb-plugin-mentions": "2.5.2", "nodebb-plugin-soundpack-default": "1.0.0", - "nodebb-plugin-spam-be-gone": "0.5.5", + "nodebb-plugin-spam-be-gone": "0.6.0", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.8", "nodebb-theme-persona": "9.1.13", From d03220cdaffe6f08eff996145cf4c088168a8db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 25 Jan 2019 11:08:51 -0500 Subject: [PATCH 023/270] fix: #7094 --- public/src/client/topic/events.js | 1 + public/src/client/topic/posts.js | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index fa2b07d1c4..edfa5068b5 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -130,6 +130,7 @@ define('forum/topic/events', [ editedPostEl.html(translator.unescape(data.post.content)); editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive'); images.wrapImagesInLinks(editedPostEl.parent()); + posts.addBlockquoteEllipses(editedPostEl.parent()); editedPostEl.fadeIn(250); var editData = { diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index ffbc0a5ace..f794354abc 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -253,7 +253,7 @@ define('forum/topic/posts', [ images.wrapImagesInLinks(posts); Posts.showBottomPostBar(); posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); - addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote')); + Posts.addBlockquoteEllipses(posts); hidePostToolsForDeletedPosts(posts); }; @@ -287,14 +287,15 @@ define('forum/topic/posts', [ }); } - function addBlockquoteEllipses(blockquotes) { + Posts.addBlockquoteEllipses = function (posts) { + var blockquotes = posts.find('[component="post/content"] > blockquote > blockquote'); blockquotes.each(function () { var $this = $(this); if ($this.find(':hidden:not(br)').length && !$this.find('.toggle').length) { $this.append(''); } }); - } + }; return Posts; }); From 7fb3c16882800acb8bc2f737aa8e884a51248d45 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 25 Jan 2019 11:43:38 -0500 Subject: [PATCH 024/270] fix: #7296, local login privilege available to registered-users only --- public/src/modules/helpers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 210164b956..8dc1530a1c 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -179,7 +179,11 @@ } return states.map(function (priv) { var guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote']; - var disabled = member === 'guests' && guestDisabled.includes(priv.name); + var spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read']; + var disabled = + (member === 'guests' && guestDisabled.includes(priv.name)) || + (member === 'spiders' && !spidersEnabled.includes(priv.name)) || + (member !== 'registered-users' && priv.name === 'groups:local:login'); return ''; }).join(''); From 7a9780f62a0e2ef34fd0533ef3fdc54491422fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 25 Jan 2019 11:47:57 -0500 Subject: [PATCH 025/270] fix: #7215 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e1ae6b40d9..808f599f7d 100644 --- a/install/package.json +++ b/install/package.json @@ -115,7 +115,7 @@ "socket.io": "2.2.0", "socket.io-adapter-cluster": "^1.0.1", "socket.io-adapter-mongo": "^2.0.1", - "socket.io-adapter-postgres": "^1.0.1", + "socket.io-adapter-postgres": "^1.2.0", "socket.io-client": "2.2.0", "socket.io-redis": "5.2.0", "socketio-wildcard": "2.0.0", From cf918078e5d3c9d3ee158e42c5441acaa7810563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 25 Jan 2019 12:06:03 -0500 Subject: [PATCH 026/270] fix: #7211 --- .../en-GB/admin/general/navigation.json | 1 + src/views/admin/general/navigation.tpl | 43 ++++++++++++------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/public/language/en-GB/admin/general/navigation.json b/public/language/en-GB/admin/general/navigation.json index 8313be9656..13dd01aae7 100644 --- a/public/language/en-GB/admin/general/navigation.json +++ b/public/language/en-GB/admin/general/navigation.json @@ -5,6 +5,7 @@ "tooltip": "Tooltip:", "text": "Text:", "text-class": "Text Class: optional", + "class": "Class: optional", "id": "ID: optional", "properties": "Properties:", diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index eb1c0edeb7..c0d72f15ab 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -19,8 +19,7 @@
  • -
    - +

    @@ -29,36 +28,50 @@
    +
    +
    +
    +
    - - + +
    -
    -
    - - -
    - -
    - - -
    - +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    [[admin/general/navigation:groups]]
    From fefec8ba57b74be6bc0b44ee2a727f02cb978eb6 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sat, 26 Jan 2019 09:26:04 +0000 Subject: [PATCH 027/270] Latest translations and fallbacks --- .../language/ko/admin/advanced/database.json | 2 +- public/language/ko/admin/advanced/errors.json | 2 +- public/language/ko/admin/manage/tags.json | 2 +- public/language/ko/admin/manage/users.json | 16 +++++----- public/language/ko/admin/menu.json | 6 ++-- .../language/ko/admin/settings/advanced.json | 2 +- .../language/ko/admin/settings/general.json | 2 +- public/language/ko/admin/settings/post.json | 12 +++---- .../language/ko/admin/settings/uploads.json | 6 ++-- public/language/ko/admin/settings/user.json | 32 +++++++++---------- public/language/ko/category.json | 2 +- public/language/ko/error.json | 6 ++-- public/language/ko/language.json | 2 +- public/language/ko/modules.json | 4 +-- public/language/ko/notifications.json | 4 +-- public/language/ko/reset_password.json | 2 +- public/language/ko/search.json | 8 ++--- public/language/ko/topic.json | 16 +++++----- public/language/ko/user.json | 4 +-- 19 files changed, 65 insertions(+), 65 deletions(-) diff --git a/public/language/ko/admin/advanced/database.json b/public/language/ko/admin/advanced/database.json index 9504b749de..209bf078ac 100644 --- a/public/language/ko/admin/advanced/database.json +++ b/public/language/ko/admin/advanced/database.json @@ -27,7 +27,7 @@ "redis.version": "Redis 버젼", "redis.keys": "Keys", "redis.expires": "Expires", - "redis.avg-ttl": "Average TTL", + "redis.avg-ttl": "평균 TTL", "redis.connected-clients": "연결된 클라이언트", "redis.connected-slaves": "연결된 slaves", "redis.blocked-clients": "차단된 클라이언트", diff --git a/public/language/ko/admin/advanced/errors.json b/public/language/ko/admin/advanced/errors.json index af520c8d00..635cd74431 100644 --- a/public/language/ko/admin/advanced/errors.json +++ b/public/language/ko/admin/advanced/errors.json @@ -1,6 +1,6 @@ { "figure-x": "그림 %1", - "error-events-per-day": "하루당 %1개의 이벤트 ", + "error-events-per-day": "%1 이벤트의 일 개수", "error.404": "404 찾을 수 없음", "error.503": "503 서비스를 사용할 수 없음", "manage-error-log": "오류 로그 관리", diff --git a/public/language/ko/admin/manage/tags.json b/public/language/ko/admin/manage/tags.json index 8e0fde510d..2f744a3ca3 100644 --- a/public/language/ko/admin/manage/tags.json +++ b/public/language/ko/admin/manage/tags.json @@ -6,7 +6,7 @@ "description": "태그를 클릭하거나 드래그해서 선택하고, Shift 를 눌러 다중 선택하세요.", "create": "태그 생성", "modify": "태그 수정", - "rename": "Rename Tags", + "rename": "태그 이름 바꾸기", "delete": "선택된 태그 삭제", "search": "태그 검색", "settings": "태그 설정 페이지를 방문하시려면 클릭하세요", diff --git a/public/language/ko/admin/manage/users.json b/public/language/ko/admin/manage/users.json index e81fb03392..3231cb4727 100644 --- a/public/language/ko/admin/manage/users.json +++ b/public/language/ko/admin/manage/users.json @@ -45,7 +45,7 @@ "users.username": "사용자명", "users.email": "이메일", "users.postcount": "글 개수", - "users.reputation": "등급", + "users.reputation": "평판", "users.flags": "신고", "users.joined": "가입됨", "users.last-online": "마지막 로그인", @@ -72,14 +72,14 @@ "alerts.flag-reset-success": "신고가 리셋됐습니다!", "alerts.no-remove-yourself-admin": "관리자이기 때문에 본인을 삭제할 수 없습니다!", "alerts.make-admin-success": "이 사용자는 이제 최고관리자 입니다.", - "alerts.confirm-remove-admin": "Do you really want to remove this administrator?", - "alerts.remove-admin-success": "User is no longer administrator.", - "alerts.make-global-mod-success": "User is now global moderator.", + "alerts.confirm-remove-admin": "정말 이 관리자를 삭제하시겠습니까?", + "alerts.remove-admin-success": "사용자는 더이상 관리자가 아닙니다.", + "alerts.make-global-mod-success": "사용자는 이제 전역 중재자입니다.", "alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?", - "alerts.remove-global-mod-success": "User is no longer global moderator.", - "alerts.make-moderator-success": "User is now moderator.", - "alerts.confirm-remove-moderator": "Do you really want to remove this moderator?", - "alerts.remove-moderator-success": "User is no longer moderator.", + "alerts.remove-global-mod-success": "사용자는 더이상 전역 중재자가 아닙니다.", + "alerts.make-moderator-success": "사용자는 이제 중재자입니다.", + "alerts.confirm-remove-moderator": "정말 이 중재자를 삭제하시겠습니까?", + "alerts.remove-moderator-success": "사용자는 더이상 중재자가 아닙니다.", "alerts.confirm-validate-email": "이 사용자(들)의 이메일을 인증하시겠습니까?", "alerts.validate-email-success": "이메일 인증됨", "alerts.password-reset-confirm": "이 사용자(들)에게 패스워드 리셋 이메일을 보내시겠습니까?", diff --git a/public/language/ko/admin/menu.json b/public/language/ko/admin/menu.json index e2c30adbaf..92fad72ac5 100644 --- a/public/language/ko/admin/menu.json +++ b/public/language/ko/admin/menu.json @@ -21,10 +21,10 @@ "section-settings": "설정", "settings/general": "일반", - "settings/reputation": "등급", + "settings/reputation": "평판", "settings/email": "이메일", "settings/user": "사용자", - "settings/group": "룹", + "settings/group": "그룹", "settings/guest": "미가입 사용자", "settings/uploads": "로드", "settings/post": "글", @@ -42,7 +42,7 @@ "section-appearance": "스타일", "appearance/themes": "테마", "appearance/skins": "스킨", - "appearance/customise": "사용자 정의 콘텐츠 ((HTML/JS/CSS)", + "appearance/customise": "사용자 정의 콘텐츠 (HTML/JS/CSS)", "section-extend": "추가 기능", "extend/plugins": "플러그인", diff --git a/public/language/ko/admin/settings/advanced.json b/public/language/ko/admin/settings/advanced.json index 5896bb26e6..576ff41fe5 100644 --- a/public/language/ko/admin/settings/advanced.json +++ b/public/language/ko/admin/settings/advanced.json @@ -13,7 +13,7 @@ "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", - "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.enabled": "HSTS 활성화 (권장)", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ko/admin/settings/general.json b/public/language/ko/admin/settings/general.json index 4c4c5cdcb0..dc0eab700d 100644 --- a/public/language/ko/admin/settings/general.json +++ b/public/language/ko/admin/settings/general.json @@ -2,7 +2,7 @@ "site-settings": "사이트 관리", "title": "사이트 제목", "title.url": "URL", - "title.url-placeholder": "The URL of the site title", + "title.url-placeholder": "사이트 제목의 URL", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", "title.name": "커뮤니티 이름", "title.show-in-header": "헤더에 사이트 제목 표시", diff --git a/public/language/ko/admin/settings/post.json b/public/language/ko/admin/settings/post.json index 7d3cea3e49..30973373a9 100644 --- a/public/language/ko/admin/settings/post.json +++ b/public/language/ko/admin/settings/post.json @@ -3,14 +3,14 @@ "sorting.post-default": "기본 포스트 정렬", "sorting.oldest-to-newest": "오래된 순", "sorting.newest-to-oldest": "최신 순", - "sorting.most-votes": "추천수 순으로 정렬", - "sorting.most-posts": "Most Posts", + "sorting.most-votes": "투표수 순", + "sorting.most-posts": "게시글 개수 순", "sorting.topic-default": "게시물 정렬기준 기본값", - "length": "Post Length", + "length": "게시글 길이", "restrictions": "글 작성 제약사항", - "restrictions-new": "New User Restrictions", + "restrictions-new": "신규 사용자 제한", "restrictions.post-queue": "게시 대기열 사용", - "restrictions-new.post-queue": "Enable new user restrictions", + "restrictions-new.post-queue": "신규 사용자 제한 활성화", "restrictions.post-queue-help": "게시 대기열을 사용하면 사용자의 게시물이 대기열에 넣어집니다.", "restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users.", "restrictions.seconds-between": "Seconds between posts", @@ -50,5 +50,5 @@ "composer.custom-help": "사용자 설정 \"도움말\" 내용", "ip-tracking": "IP 추적", "ip-tracking.each-post": "모든 글의 IP 주소 추적", - "enable-post-history": "Enable Post History" + "enable-post-history": "게시글 기록 활성화" } \ No newline at end of file diff --git a/public/language/ko/admin/settings/uploads.json b/public/language/ko/admin/settings/uploads.json index 04162a1067..1f76d2a38b 100644 --- a/public/language/ko/admin/settings/uploads.json +++ b/public/language/ko/admin/settings/uploads.json @@ -11,7 +11,7 @@ "resize-image-quality": "Quality to use when resizing images", "resize-image-quality-help": "Use a lower quality setting to reduce the file size of resized images.", "max-file-size": "최대 파일 사이즈(KB)", - "max-file-size-help": "(in kibibytes, default: 2048 KiB)", + "max-file-size-help": "(키비바이트로, 기본: 2048 KiB)", "reject-image-width": "Maximum Image Width (in pixels)", "reject-image-width-help": "Images wider than this value will be rejected.", "reject-image-height": "Maximum Image Height (in pixels)", @@ -28,9 +28,9 @@ "profile-image-dimension": "프로필 사진 규격", "profile-image-dimension-help": "(단위: 픽셀, 기본값: 128 픽셀)", "max-profile-image-size": "프로필 사진 최대 크기", - "max-profile-image-size-help": "(in kibibytes, default: 256 KiB)", + "max-profile-image-size-help": "(키비바이트로, 기본: 256 KiB)", "max-cover-image-size": "커버 사진 최대 크기", - "max-cover-image-size-help": "(in kibibytes, default: 2,048 KiB)", + "max-cover-image-size-help": "(키비바이트로, 기본: 2,048 KiB)", "keep-all-user-images": "이전 프로필 사진과 커버 사진 서버에 저장", "profile-covers": "프로필 커버 사진", "default-covers": "기본 커버 사진", diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json index 19ff1c4711..1f1bb8b07f 100644 --- a/public/language/ko/admin/settings/user.json +++ b/public/language/ko/admin/settings/user.json @@ -1,35 +1,35 @@ { "authentication": "로그인", "require-email-confirmation": "이메일 인증 필수화", - "email-confirm-interval": "다음의 경우 사용자는 인증 이메일을 재전송할 수 없습니다:", - "email-confirm-email2": "분 후", + "email-confirm-interval": "사용자는", + "email-confirm-email2": "분이 지나기 전까지 확인 메일을 다시 보낼수 없습니다", "allow-login-with": "로그인 허용 수단", "allow-login-with.username-email": "사용자명 또는 이메일", "allow-login-with.username": "사용자명", "allow-login-with.email": "이메일", "account-settings": "계정 관리", - "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled": "GDPR 동의 수집 활성화", + "gdpr_enabled_help": "활성화하면, 모든 새 가입자는 General Data Protection Regulation (GDPR) 하의 데이터 수집 및 사용에 대한 동의를 명시적으로 하도록 요구될 것입니다. 참고: GDPR을 활성화하는 것은 이전에 존재한 사용자에게 동의를 하도록 강제하지 않습니다. 그렇게 하려면, GDPR 플러그인을 설치해야 합니다.", "disable-username-changes": "사용자명 변경 비활성화", "disable-email-changes": "이메일 주소 변경 비활성화", "disable-password-changes": "패스워드 변경 비활성화", "allow-account-deletion": "계정 삭제 허용", "user-info-private": "Hide user list and data from guests", - "hide-fullname": "Hide fullname from users", - "hide-email": "Hide email from users", + "hide-fullname": "사용자에게서 이름 숨기기", + "hide-email": "사용자에게서 이메일 숨기기", "themes": "테마", "disable-user-skins": "일반 사용자가 스킨 지정 금지", "account-protection": "계정 보호", - "admin-relogin-duration": "Admin relogin duration (minutes)", + "admin-relogin-duration": "관리자 재 로그인 지속 시간 (분)", "admin-relogin-duration-help": "After a set amount of time accessing the admin section will require re-login, set to 0 to disable", "login-attempts": "시간당 가능한 로그인 시도 횟수", "login-attempts-help": "사용자의 로그인 시도가 이 횟수제한을 초과하면 정해진 시간만큼 해당 계정이 잠깁니다.", "lockout-duration": "계정 잠금 기간 (분)", - "login-days": "사용자 로그인 세션 유지 기간 (일)", + "login-days": "사용자 로그인 세션 유지일", "password-expiry-days": "주기적으로 패스워드 리셋", - "session-time": "Session Time", - "session-time-days": "Days", - "session-time-seconds": "Seconds", + "session-time": "세션 시간", + "session-time-days": "일", + "session-time-seconds": "초", "session-time-help": "These values are used to govern how long a user stays logged in when they check "Remember Me" on login. Note that only one of these values will be used. If there is no seconds value we fall back to days. If there is no days value we default to 14 days.", "online-cutoff": "Minutes after user is considered inactive", "online-cutoff-help": "If user performs no actions for this duration, they are considered inactive and they do not receive realtime updates.", @@ -70,9 +70,9 @@ "email-post-notif": "내가 구독한 주제에 답글이 달리면 메일 보내기", "follow-created-topics": "작성한 게시물 팔로우", "follow-replied-topics": "답글 단 게시물을 팔로우", - "default-notification-settings": "Default notification settings", - "categoryWatchState": "Default category watch state", - "categoryWatchState.watching": "Watching", - "categoryWatchState.notwatching": "Not Watching", - "categoryWatchState.ignoring": "Ignoring" + "default-notification-settings": "기본 알림 설정", + "categoryWatchState": "기본 카테고리 주시 상태", + "categoryWatchState.watching": "주시하기", + "categoryWatchState.notwatching": "주시하지 않기", + "categoryWatchState.ignoring": "무시하기" } \ No newline at end of file diff --git a/public/language/ko/category.json b/public/language/ko/category.json index f8ac18a799..b085e16432 100644 --- a/public/language/ko/category.json +++ b/public/language/ko/category.json @@ -10,7 +10,7 @@ "watch": "관심 주제", "ignore": "관심 해제", "watching": "관심있음", - "not-watching": "Not Watching", + "not-watching": "주시하지 않기", "ignoring": "무시하기", "watching.description": "읽지 않은 글 및 최근 글 보기", "not-watching.description": "읽지 않은 글 및 최근 글 보지 않기", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index b4504edde5..d04ea6a294 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -28,7 +28,7 @@ "email-not-confirmed-email-sent": "아직 이메일이 인증되지 않았습니다. 메일함을 확인 해주세요.", "no-email-to-confirm": "이메일 인증이 필요합니다. 이곳을 클릭하여 이메일 입력하세요.", "email-confirm-failed": "이메일 인증이 실패하였습니다. 잠시 후에 다시 시도하세요.", - "confirm-email-already-sent": "인증 메일이 이미 발송되었습니다. %1 분 이후에 재 발송이 가능합니다.", + "confirm-email-already-sent": "확인 메일이 이미 발송되었습니다. 다시 보내려면 %1 분을 기다리세요.", "sendmail-not-found": "Sendmail 실행파일을 찾을 수 없었습니다. 사용자가 sendmail을 설치했고 실행 가능한지 확인해 주시기 바랍니다.", "username-too-short": "사용자 이름이 너무 짧습니다.", "username-too-long": "사용자 이름이 너무 깁니다.", @@ -122,10 +122,10 @@ "chat-deleted-already": "이미 삭제된 대화 메시지입니다.", "chat-restored-already": "이 채팅 메시지는 이미 복원되었습니다.", "already-voting-for-this-post": "이미 이 포스트에 투표하셨습니다.", - "reputation-system-disabled": "인지도 시스템이 비활성 상태입니다.", + "reputation-system-disabled": "평판 시스템이 비활성화되어있습니다.", "downvoting-disabled": "비추천 기능이 비활성 상태입니다.", "not-enough-reputation-to-downvote": "인지도가 낮아 이 포스트를 비추천할 수 없습니다.", - "not-enough-reputation-to-flag": "인지도가 낮아 이 포스트를 신고할 수 없습니다.", + "not-enough-reputation-to-flag": "평판이 낮아 이 포스트를 신고할 수 없습니다.", "not-enough-reputation-min-rep-website": "인지도가 낮아 웹사이트를 추가 할 수 없습니다.", "not-enough-reputation-min-rep-aboutme": "인지도가 낮아 자기소개를 추가 할 수 없습니다.", "not-enough-reputation-min-rep-signature": "인지도가 낮아 서명을 추가 할 수 없습니다.", diff --git a/public/language/ko/language.json b/public/language/ko/language.json index f052cb6449..4e3a5a6ef7 100644 --- a/public/language/ko/language.json +++ b/public/language/ko/language.json @@ -1,5 +1,5 @@ { - "name": "영어 (영국/캐나다)", + "name": "한국어 (대한민국)", "code": "ko", "dir": "ltr" } \ No newline at end of file diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 31846c1a3a..5271148a9c 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -1,5 +1,5 @@ { - "chat.chatting_with": "Chat with", + "chat.chatting_with": "대화하기", "chat.placeholder": "메시지를 여기에 입력한 후 엔터를 눌러 전송하세요.", "chat.send": "전송", "chat.no_active": "활성화된 채팅이 없습니다.", @@ -21,7 +21,7 @@ "chat.three_months": "3개월", "chat.delete_message_confirm": "이 메세지를 삭제 하시겠습니까?", "chat.retrieving-users": "Retrieving users...", - "chat.manage-room": "Manage Chat Room", + "chat.manage-room": "대화방 관리", "chat.add-user-help": "Search for users here. When selected, the user will be added to the chat. The new user will not be able to see chat messages written before they were added to the conversation. Only room owners () may remove users from chat rooms.", "chat.confirm-chat-with-dnd-user": "이 사용자는 자신의 상태를 DnD (방해금지)로 설정했습니다. 그래도 대화를 요청 하시겠습니까?", "chat.rename-room": "방 이름 바꾸기", diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json index b9b7954075..ebe767bddc 100644 --- a/public/language/ko/notifications.json +++ b/public/language/ko/notifications.json @@ -56,9 +56,9 @@ "notificationType_follow": "누군가 사용자님을 팔로우 하면", "notificationType_new-chat": "채팅 메시지를 받으면", "notificationType_group-invite": "그룹 초대를 받으면", - "notificationType_group-request-membership": "When someone requests to join a group you own", + "notificationType_group-request-membership": "누군가가 당신이 소유한 그룹에 참여를 요청했을 때", "notificationType_new-register": "When someone gets added to registration queue", - "notificationType_post-queue": "When a new post is queued", + "notificationType_post-queue": "새로운 게시글이 등록되었을 때", "notificationType_new-post-flag": "게시글이 신고되면", "notificationType_new-user-flag": "사용자가 신고되면" } \ No newline at end of file diff --git a/public/language/ko/reset_password.json b/public/language/ko/reset_password.json index 7b32496822..d018692d66 100644 --- a/public/language/ko/reset_password.json +++ b/public/language/ko/reset_password.json @@ -9,7 +9,7 @@ "repeat_password": "패스워드 확인", "enter_email": "이메일 주소를 입력하면 패스워드를 재설정하는 방법을 메일로 알려드립니다.", "enter_email_address": "이메일 주소를 입력하세요.", - "password_reset_sent": "A password reset email has been sent to the specified address. Please note that only one email will be sent per minute.", + "password_reset_sent": "비밀번호 재설정 이메일이 입력한 이메일로 전송되었습니다. 1분에 한 개의 이메일만 보내질 것이라는 점을 참고해주세요.", "invalid_email": "올바르지 않거나 가입되지 않은 이메일입니다.", "password_too_short": "입력한 패스워드가 너무 짧습니다, 다시 입력하세요.", "passwords_do_not_match": "패스워드와 패스워드 확인이 일치하지 않습니다.", diff --git a/public/language/ko/search.json b/public/language/ko/search.json index 10624186c3..a51d04cd79 100644 --- a/public/language/ko/search.json +++ b/public/language/ko/search.json @@ -5,8 +5,8 @@ "in": "검색 기준", "titles": "제목", "titles-posts": "제목과 내용", - "match-words": "Match words", - "all": "All", + "match-words": "일치하는 단어 순", + "all": "전체", "any": "Any", "posted-by": "작성자", "in-categories": "게시판 지정", @@ -17,7 +17,7 @@ "at-most": "최대", "relevance": "관련도", "post-time": "작성시간", - "votes": "Votes", + "votes": "추천", "newer-than": "이전", "older-than": "이후", "any-date": "모든 시간", @@ -31,7 +31,7 @@ "sort-by": "정렬 기준", "last-reply-time": "마지막으로 답글이 달린 시간", "topic-title": "게시물 제목", - "topic-votes": "Topic votes", + "topic-votes": "게시물 투표", "number-of-replies": "답글 수", "number-of-views": "조회 수", "topic-start-date": "게시물이 작성된 날짜", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index ec9759b160..29b9983303 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -30,12 +30,12 @@ "locked": "잠긴 게시물", "pinned": "고정된 게시물", "moved": "이동된 게시물", - "copy-ip": "Copy IP", - "ban-ip": "Ban IP", - "view-history": "Edit History", + "copy-ip": "IP 복사", + "ban-ip": "IP 차단", + "view-history": "편집 기록", "bookmark_instructions": "이 스레드에서 읽은 마지막 포스트로 이동하시려면 여기를 클릭 하세요.", "flag_title": "이 포스트를 신고", - "merged_message": "This topic has been merged into %2", + "merged_message": "이 게시물은 %2(으)로 병합되었습니다", "deleted_message": "이 게시물은 삭제됐습니다. 게시물 관리 권한이 있는 사용자만 볼 수 있습니다.", "following_topic.message": "이제 이 게시물에 새 답글이 달리면 알림을 받습니다.", "not_following_topic.message": "이 게시물을 아직 읽지 않은 게시물 목록에서 볼 수 있지만, 이 주제에 달린 포스트에 대해서는 알림을 받지 않습니다.", @@ -96,7 +96,7 @@ "fork_pid_count": "%1 개의 포스트(들)이 선택되었습니다", "fork_success": "게시물이 분리되었습니다! 분리된 게시물을 보려면 여기를 클릭 하세요.", "delete_posts_instruction": "삭제할 포스트를 선택하세요.", - "merge_topics_instruction": "Click the topics you want to merge", + "merge_topics_instruction": "병합할 게시물을 클릭하세요", "move_posts_instruction": "Click the posts you want to move", "composer.title_placeholder": "게시물 제목을 입력하세요.", "composer.handle_placeholder": "이름", @@ -118,8 +118,8 @@ "sort_by": "정렬 기준", "oldest_to_newest": "오래된 순으로 정렬", "newest_to_oldest": "최신 순으로 정렬", - "most_votes": "Most Votes", - "most_posts": "Most Posts", + "most_votes": "투표수 순", + "most_posts": "게시글 개수 순", "stale.title": "새로운 게시물을 생성 하시겠습니까?", "stale.warning": "현재 답글을 작성중인 게시물은 오래전에 작성 되었습니다. 새로 게시물을 생성하시고 이 게시물을 인용 하시겠습니까?", "stale.create": "새로운 게시물 작성", @@ -127,7 +127,7 @@ "link_back": "답글: [%1](%2)", "diffs.title": "글 수정 기록", "diffs.description": "This post has %1 revisions. Click one of the revisions below to see the post content at that point in time.", - "diffs.no-revisions-description": "This post has %1 revisions.", + "diffs.no-revisions-description": "이 게시글에는 %1번의 수정이 있습니다.", "diffs.current-revision": "현재 리비젼", "diffs.original-revision": "원래의 리비젼" } \ No newline at end of file diff --git a/public/language/ko/user.json b/public/language/ko/user.json index 83e2946fcd..44c7987797 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -23,12 +23,12 @@ "lastonline": "최근 온라인 시점", "profile": "프로필", "profile_views": "프로필 조회 수", - "reputation": "등급", + "reputation": "평판", "bookmarks": "즐겨찾기", "watched_categories": "주시중인 카테고리", "watched": "관심있는 게시물", "ignored": "무시됨", - "default-category-watch-state": "Default category watch state", + "default-category-watch-state": "기본 카테고리 주시 상태", "followers": "팔로워", "following": "팔로잉", "blocks": "차단", From df6f75eb056a8fc1333c2ccf59caeff9d3503034 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 26 Jan 2019 13:48:18 -0500 Subject: [PATCH 028/270] fix: #7298 privilege header misalignment --- .../language/en-GB/admin/manage/categories.json | 1 + src/privileges/categories.js | 5 ++++- .../admin/partials/categories/privileges.tpl | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index c14eadea0c..85aeeb8069 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -35,6 +35,7 @@ "privileges.section-viewing": "Viewing Privileges", "privileges.section-posting": "Posting Privileges", "privileges.section-moderation": "Moderation Privileges", + "privileges.section-other": "Other", "privileges.section-user": "User", "privileges.search-user": "Add User", "privileges.no-users": "No user-specific privileges in this category.", diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 9e2d580afc..9eda36d211 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -34,7 +34,10 @@ module.exports = function (privileges) { }, function (payload, next) { // This is a hack because I can't do {labels.users.length} to echo the count in templates.js - payload.columnCount = payload.labels.users.length + 2; + payload.columnCountUser = payload.labels.users.length + 2; + payload.columnCountUserOther = payload.labels.users.length - privileges.privilegeLabels.length; + payload.columnCountGroup = payload.labels.groups.length + 2; + payload.columnCountGroupOther = payload.labels.groups.length - privileges.privilegeLabels.length; next(null, payload); }, ], callback); diff --git a/src/views/admin/partials/categories/privileges.tpl b/src/views/admin/partials/categories/privileges.tpl index 4fd009827f..9e0b8632d2 100644 --- a/src/views/admin/partials/categories/privileges.tpl +++ b/src/views/admin/partials/categories/privileges.tpl @@ -11,6 +11,11 @@ [[admin/manage/categories:privileges.section-moderation]] + + + [[admin/manage/categories:privileges.section-other]] + + [[admin/manage/categories:privileges.section-user]] @@ -35,7 +40,7 @@ - + @@ -43,7 +48,7 @@ - + [[admin/manage/categories:privileges.no-users]] + + +
    @@ -118,4 +129,4 @@
    - \ No newline at end of file + From d94f6248ca01adaac481c80c3e850f1230f07977 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Thu, 24 Jan 2019 18:13:35 +0200 Subject: [PATCH 047/270] related to https://github.com/NodeBB/NodeBB/issues/7212 --- src/views/admin/manage/registration.tpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index 1a7af2060d..7f27cd2966 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -77,11 +77,11 @@
    - - - + + +
    From 14addef67bd2252686490c495587a620e0b458dd Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 25 Jan 2019 15:57:26 +0200 Subject: [PATCH 048/270] indentation fix #7284 --- src/views/admin/manage/registration.tpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index 7f27cd2966..6171251305 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -31,7 +31,7 @@ - + {users.username} @@ -41,7 +41,7 @@ - + {users.email} @@ -50,9 +50,9 @@ - - - + + + {users.ip} From 6cd8248cae1a1cfd43e980416c2f00b92b811da4 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 25 Jan 2019 16:18:56 +0200 Subject: [PATCH 049/270] added empty array for customActions and another indentation fix attempt, related to #7284 --- src/user/approval.js | 10 ++++++++++ src/views/admin/manage/registration.tpl | 18 +++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/user/approval.js b/src/user/approval.js index 9429fef270..90bb71b324 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -200,6 +200,16 @@ module.exports = function (User) { getIPMatchedUsers(user, function (err) { next(err, user); }); + user.customActions = [].concat(user.customActions); + /* + // then spam prevention plugins, using the "filter:user.getRegistrationQueue" hook can be like: + user.customActions.push({ + title: '[[spam-be-gone:report-user]]', + id: 'report-spam-user-' + user.username, + class: 'btn-warning report-spam-user', + icon: 'fa-flag' + }); + */ }, next); }, function (users, next) { diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index 6171251305..22048eebfe 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -30,9 +30,9 @@ - - - + + + {users.username} @@ -40,9 +40,9 @@ - - - + + + {users.email} @@ -50,9 +50,9 @@ - - - + + + {users.ip} From 6ef507352178aa49f3d253d58d51e32b45446a43 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Tue, 29 Jan 2019 15:15:52 +0200 Subject: [PATCH 050/270] spam-be-gone patched --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index da29fe64b7..c60e7510a7 100644 --- a/install/package.json +++ b/install/package.json @@ -87,7 +87,7 @@ "nodebb-plugin-markdown": "8.8.8", "nodebb-plugin-mentions": "2.5.2", "nodebb-plugin-soundpack-default": "1.0.0", - "nodebb-plugin-spam-be-gone": "0.6.0", + "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.8", "nodebb-theme-persona": "9.1.13", From 5353960ae7820d38e386592682e3991e1401d8d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 29 Jan 2019 13:11:45 -0500 Subject: [PATCH 051/270] fix: #7316 --- public/src/admin/manage/registration.js | 4 +- src/controllers/globalmods.js | 16 ++++++ src/routes/index.js | 1 + src/socket.io/admin/user.js | 38 -------------- src/socket.io/user.js | 1 + src/socket.io/user/registration.js | 70 +++++++++++++++++++++++++ test/controllers-admin.js | 18 +++++++ test/user.js | 22 ++++---- 8 files changed, 120 insertions(+), 50 deletions(-) create mode 100644 src/socket.io/user/registration.js diff --git a/public/src/admin/manage/registration.js b/public/src/admin/manage/registration.js index 048aafbf96..08f8b6722b 100644 --- a/public/src/admin/manage/registration.js +++ b/public/src/admin/manage/registration.js @@ -9,7 +9,7 @@ define('admin/manage/registration', function () { var parent = $(this).parents('[data-username]'); var action = $(this).attr('data-action'); var username = parent.attr('data-username'); - var method = action === 'accept' ? 'admin.user.acceptRegistration' : 'admin.user.rejectRegistration'; + var method = action === 'accept' ? 'user.acceptRegistration' : 'user.rejectRegistration'; socket.emit(method, { username: username }, function (err) { if (err) { @@ -25,7 +25,7 @@ define('admin/manage/registration', function () { var email = parent.attr('data-invitation-mail'); var invitedBy = parent.attr('data-invited-by'); var action = $(this).attr('data-action'); - var method = 'admin.user.deleteInvitation'; + var method = 'user.deleteInvitation'; var removeRow = function () { var nextRow = parent.next(); diff --git a/src/controllers/globalmods.js b/src/controllers/globalmods.js index 1cae97a4e2..ed564c9093 100644 --- a/src/controllers/globalmods.js +++ b/src/controllers/globalmods.js @@ -4,6 +4,7 @@ var async = require('async'); var user = require('../user'); var adminBlacklistController = require('./admin/blacklist'); +var usersController = require('./admin/users'); var globalModsController = module.exports; @@ -20,3 +21,18 @@ globalModsController.ipBlacklist = function (req, res, next) { }, ], next); }; + + +globalModsController.registrationQueue = function (req, res, next) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(req.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return next(); + } + usersController.registrationQueue(req, res, next); + }, + ], next); +}; diff --git a/src/routes/index.js b/src/routes/index.js index 40abf06e42..e62b115059 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -46,6 +46,7 @@ function modRoutes(app, middleware, controllers) { function globalModRoutes(app, middleware, controllers) { setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist); + setupPageRoute(app, '/registration-queue', middleware, [], controllers.globalMods.registrationQueue); } function topicRoutes(app, middleware, controllers) { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index de27e1f07d..6bbf7d57ae 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -221,44 +221,6 @@ User.search = function (socket, data, callback) { ], callback); }; -User.deleteInvitation = function (socket, data, callback) { - user.deleteInvitation(data.invitedBy, data.email, callback); -}; - -User.acceptRegistration = function (socket, data, callback) { - async.waterfall([ - function (next) { - user.acceptRegistration(data.username, next); - }, - function (uid, next) { - events.log({ - type: 'registration-approved', - uid: socket.uid, - ip: socket.ip, - targetUid: uid, - }); - next(null, uid); - }, - ], callback); -}; - -User.rejectRegistration = function (socket, data, callback) { - async.waterfall([ - function (next) { - user.rejectRegistration(data.username, next); - }, - function (next) { - events.log({ - type: 'registration-rejected', - uid: socket.uid, - ip: socket.ip, - username: data.username, - }); - next(); - }, - ], callback); -}; - User.restartJobs = function (socket, data, callback) { user.startJobs(callback); }; diff --git a/src/socket.io/user.js b/src/socket.io/user.js index db1c49d48f..3db21d34d8 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -22,6 +22,7 @@ require('./user/search')(SocketUser); require('./user/status')(SocketUser); require('./user/picture')(SocketUser); require('./user/ban')(SocketUser); +require('./user/registration')(SocketUser); SocketUser.exists = function (socket, data, callback) { if (!data || !data.username) { diff --git a/src/socket.io/user/registration.js b/src/socket.io/user/registration.js new file mode 100644 index 0000000000..aee1382eb2 --- /dev/null +++ b/src/socket.io/user/registration.js @@ -0,0 +1,70 @@ +'use strict'; + +var async = require('async'); +var user = require('../../user'); +var events = require('../../events'); + +module.exports = function (SocketUser) { + SocketUser.acceptRegistration = function (socket, data, callback) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(socket.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return next(new Error('[[error:no-privileges]]')); + } + + user.acceptRegistration(data.username, next); + }, + function (uid, next) { + events.log({ + type: 'registration-approved', + uid: socket.uid, + ip: socket.ip, + targetUid: uid, + }); + next(null, uid); + }, + ], callback); + }; + + SocketUser.rejectRegistration = function (socket, data, callback) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(socket.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return next(new Error('[[error:no-privileges]]')); + } + + user.rejectRegistration(data.username, next); + }, + function (next) { + events.log({ + type: 'registration-rejected', + uid: socket.uid, + ip: socket.ip, + username: data.username, + }); + next(); + }, + ], callback); + }; + + SocketUser.deleteInvitation = function (socket, data, callback) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(socket.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return next(new Error('[[error:no-privileges]]')); + } + + user.deleteInvitation(data.invitedBy, data.email, next); + }, + ], callback); + }; +}; diff --git a/test/controllers-admin.js b/test/controllers-admin.js index 7f72de3c05..9672f6ad4a 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -300,6 +300,24 @@ describe('Admin Controllers', function () { }); }); + it('should 404 if users is not privileged', function (done) { + request(nconf.get('url') + '/api/registration-queue', { json: true }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 404); + assert(body); + done(); + }); + }); + + it('should load /api/registration-queue', function (done) { + request(nconf.get('url') + '/api/registration-queue', { jar: jar, json: true }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + it('should load /admin/manage/admins-mods', function (done) { request(nconf.get('url') + '/api/admin/manage/admins-mods', { jar: jar, json: true }, function (err, res, body) { assert.ifError(err); diff --git a/test/user.js b/test/user.js index c59021cbde..2d5505a6a8 100644 --- a/test/user.js +++ b/test/user.js @@ -1638,7 +1638,7 @@ describe('User', function () { }); it('should reject user registration', function (done) { - socketAdmin.user.rejectRegistration({ uid: adminUid }, { username: 'rejectme' }, function (err) { + socketUser.rejectRegistration({ uid: adminUid }, { username: 'rejectme' }, function (err) { assert.ifError(err); User.getRegistrationQueue(0, -1, function (err, users) { assert.ifError(err); @@ -1657,7 +1657,7 @@ describe('User', function () { gdpr_consent: true, }, function (err) { assert.ifError(err); - socketAdmin.user.acceptRegistration({ uid: adminUid }, { username: 'acceptme' }, function (err, uid) { + socketUser.acceptRegistration({ uid: adminUid }, { username: 'acceptme' }, function (err, uid) { assert.ifError(err); User.exists(uid, function (err, exists) { assert.ifError(err); @@ -1676,15 +1676,17 @@ describe('User', function () { describe('invites', function () { var socketUser = require('../src/socket.io/user'); var inviterUid; + var adminUid; before(function (done) { - User.create({ - username: 'inviter', - email: 'inviter@nodebb.org', - }, function (err, uid) { + async.parallel({ + inviter: async.apply(User.create, { username: 'inviter', email: 'inviter@nodebb.org' }), + admin: async.apply(User.create, { username: 'adminInvite' }), + }, function (err, results) { assert.ifError(err); - inviterUid = uid; - done(); + inviterUid = results.inviter; + adminUid = results.admin; + groups.join('administrators', adminUid, done); }); }); @@ -1793,8 +1795,8 @@ describe('User', function () { }); it('should delete invitation', function (done) { - var socketAdmin = require('../src/socket.io/admin'); - socketAdmin.user.deleteInvitation({ uid: inviterUid }, { invitedBy: 'inviter', email: 'invite1@test.com' }, function (err) { + var socketUser = require('../src/socket.io/user'); + socketUser.deleteInvitation({ uid: adminUid }, { invitedBy: 'inviter', email: 'invite1@test.com' }, function (err) { assert.ifError(err); db.isSetMember('invitation:uid:' + inviterUid, 'invite1@test.com', function (err, isMember) { assert.ifError(err); From 0366cfd8eccd214223f3a2ee70fe4e56f5fa01af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 29 Jan 2019 14:10:40 -0500 Subject: [PATCH 052/270] fix: allow regular groups to local login --- public/src/modules/helpers.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 8dc1530a1c..de5998469b 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -178,12 +178,11 @@ } } return states.map(function (priv) { - var guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote']; + var guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login']; var spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read']; var disabled = (member === 'guests' && guestDisabled.includes(priv.name)) || - (member === 'spiders' && !spidersEnabled.includes(priv.name)) || - (member !== 'registered-users' && priv.name === 'groups:local:login'); + (member === 'spiders' && !spidersEnabled.includes(priv.name)); return ''; }).join(''); From 7e872d32f07d348bed04a56acd557d7dd082d133 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Tue, 29 Jan 2019 14:16:27 -0500 Subject: [PATCH 053/270] fix(deps): update dependency winston to v3.2.1 (#7317) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c60e7510a7..98b8d7500c 100644 --- a/install/package.json +++ b/install/package.json @@ -126,7 +126,7 @@ "toobusy-js": "^0.5.1", "uglify-es": "^3.3.9", "validator": "10.11.0", - "winston": "3.2.0", + "winston": "3.2.1", "xml": "^1.0.1", "xregexp": "^4.1.1", "zxcvbn": "^4.4.2" From 7abcb0f199ecaabf43a7faf9a5ff95f94b85ce2f Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 30 Jan 2019 09:27:30 +0000 Subject: [PATCH 054/270] Latest translations and fallbacks --- public/language/bn/email.json | 24 +++++++++---------- public/language/bn/global.json | 8 +++---- public/language/bn/users.json | 4 ++-- .../language/pt-PT/admin/settings/email.json | 6 ++--- .../pt-PT/admin/settings/uploads.json | 2 +- public/language/pt-PT/global.json | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/public/language/bn/email.json b/public/language/bn/email.json index b3e443d4d2..a707dfaf9c 100644 --- a/public/language/bn/email.json +++ b/public/language/bn/email.json @@ -1,18 +1,18 @@ { - "test-email.subject": "Test Email", - "password-reset-requested": "Password Reset Requested!", + "test-email.subject": "পরীক্ষামূলক ইমেইল", + "password-reset-requested": "নতুন পাসওয়ার্ডের জন্য অনুরোধ করা হয়েছে!", "welcome-to": "%1 এ স্বাগতম", "invite": "%1 থেকে আমন্ত্রণ", "greeting_no_name": "স্বাগতম", "greeting_with_name": "স্বাগতম %1", - "email.verify-your-email.subject": "Please verify your email", - "email.verify.text1": "Your email address has changed!", + "email.verify-your-email.subject": "দয়া করে ইমেইল যাচাই করুন", + "email.verify.text1": "আপনার ইমেইল পরিবর্তন হয়েছে!", "welcome.text1": "%1 এ নিবন্ধন করার জন্য আপনাকে ধন্যবাদ!", "welcome.text2": "আপনার একাউন্ট এ্যাক্টিভেট করার জন্য, আপনি যে ইমেইল এড্রেস ব্যাবহার করে নিবন্ধন করেছেন তা যাচাই করতে হবে", "welcome.text3": "An administrator has accepted your registration application. You can login with your username/password now.", "welcome.cta": "আপনার ইমেইল এড্রেস নিশ্চিত করার জন্য এখানে ক্লিক করুন", "invitation.text1": "%1 আপনাকে %2 তে যোগ দিতে আমন্ত্রণ জানিয়েছেন ", - "invitation.text2": "Your invitation will expire in %1 days.", + "invitation.text2": "আপনার আমন্ত্রন পত্র %1 দিন পর বাতিল হয়ে যাবে", "invitation.ctr": "আপনার একাউন্ট খুলতে এখানে ক্লিক করুন", "reset.text1": "আমরা আপনার পাসওয়ার্ড রিসেট করার অনুরোধ পেয়েছি, সম্ভবত আপনি আপনার পাসওয়ার্ড ভুলে গিয়েছেন বলেই। তবে যদি তা না হয়ে থাকে, তাহলে এই মেইলকে উপেক্ষা করতে পারেন।", "reset.text2": "পাসওয়ার্ড রিসেট করতে নিচের লিংকে ক্লিক করুন", @@ -25,21 +25,21 @@ "digest.cta": "%1 ভিজিট করতে এখানে ক্লিক করুন", "digest.unsub.info": "আপনার সাবস্ক্রীপশন সেটিংসের কারনে আপনাকে এই ডাইজেষ্টটি পাঠানো হয়েছে।", "digest.no_topics": "%1 এ কোন সক্রিয় টপিক নেই।", - "digest.day": "day", - "digest.week": "week", - "digest.month": "month", + "digest.day": "দিন", + "digest.week": "সপ্তাহ", + "digest.month": "মাস", "digest.subject": "Digest for %1", "notif.chat.subject": "%1 এর থেকে নতুন মেসেজ এসেছে।", "notif.chat.cta": "কথপোকথন চালিয়ে যেতে এখানে ক্লিক করুন", "notif.chat.unsub.info": "আপনার সাবস্ক্রীপশন সেটিংসের কারনে আপনার এই নোটিফিকেশন পাঠানো হয়েছে", "notif.post.cta": "পুরো বিষয়টি পড়তে এখানে ক্লিক করুন", "notif.post.unsub.info": "আপনার সাবস্ক্রিপশন সেটিংসের কারনে আপনার এই বার্তাটি পাঠানো হয়েছে", - "notif.cta": "Click here to go to forum", + "notif.cta": "ফোরামে যেতে এখানে ক্লিক করুন", "test.text1": "আপনি সঠিকভাবে নোডবিবির জন্য মেইলার সেটাপ করেছেন কিনা নিশ্চিত করার জন্য এই টেষ্ট ইমেইল পাঠানো হয়েছে", "unsub.cta": "সেটিংসগুলো পরিবর্তন করতে এখানে ক্লিক করুন", - "banned.subject": "You have been banned from %1", - "banned.text1": "The user %1 has been banned from %2.", + "banned.subject": "আপনি %1 এ নিষিদ্ধ হয়েছেন", + "banned.text1": "ব্যবহারকারি %1 %2 তে নিষিদ্ধ হয়েছেন", "banned.text2": "This ban will last until %1.", - "banned.text3": "This is the reason why you have been banned:", + "banned.text3": "এই কারনে আপনি নিষিদ্ধ হয়েছেন :", "closing": "ধন্যবাদ!" } \ No newline at end of file diff --git a/public/language/bn/global.json b/public/language/bn/global.json index dc56a364c5..0f00d5112f 100644 --- a/public/language/bn/global.json +++ b/public/language/bn/global.json @@ -3,13 +3,13 @@ "search": "অনুসন্ধান", "buttons.close": "বন্ধ", "403.title": "প্রবেশাধিকার প্রত্যাখ্যাত", - "403.message": "You seem to have stumbled upon a page that you do not have access to.", - "403.login": "Perhaps you should try logging in?", + "403.message": "আপনি এমন জায়গাতে যেতে চাচ্ছেন যেখানে আপনার প্রবেশাধিকার নেই।", + "403.login": "সম্ভবত আপনার লগইন করা উচিত", "404.title": "পাওয়া যায়নি", - "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.", + "404.message": "আপনি এমন জায়গাতে যেতে চাচ্ছেন যার কোন অস্তিত্ব নাই। প্রথম পাতায় ফিরে যান ।", "500.title": "Internal Error.", "500.message": "ওহো! কিছু ভুল হয়েছে মনে হচ্ছে!", - "400.title": "Bad Request.", + "400.title": "ভুল ঠিকানা", "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.", "register": "নিবন্ধন", "login": "প্রবেশ", diff --git a/public/language/bn/users.json b/public/language/bn/users.json index b0fc3fc663..e7ba22f9f6 100644 --- a/public/language/bn/users.json +++ b/public/language/bn/users.json @@ -2,7 +2,7 @@ "latest_users": "সর্বশেষ নিবন্ধিত সদস্যরা:", "top_posters": "সর্বোচ্চ পোষ্টকারী", "most_reputation": "সর্বোচ্চ সম্মাননাধারী", - "most_flags": "Most Flags", + "most_flags": "সর্বোচ্চ অভিযোগ", "search": "খুঁজুন", "enter_username": "ইউজারনেম এর ভিত্তিতে সার্চ করুন", "load_more": "আরো লোড করুন", @@ -10,7 +10,7 @@ "filter-by": "ফিল্টার করার ধরন", "online-only": "শুধুমাত্র অনলাইন", "invite": "ইনভাইট", - "prompt-email": "Emails:", + "prompt-email": "ইমেইল", "invitation-email-sent": "%1 কে একটি ইনভাইটেশন ইমেইল পাঠানো হয়েছে", "user_list": "সদস্য তালিকা", "recent_topics": "সাম্প্রতিক টপিক", diff --git a/public/language/pt-PT/admin/settings/email.json b/public/language/pt-PT/admin/settings/email.json index bbe89115b3..67bc99a401 100644 --- a/public/language/pt-PT/admin/settings/email.json +++ b/public/language/pt-PT/admin/settings/email.json @@ -24,10 +24,10 @@ "smtp-transport.password": "Palavra-passe", "template": "Edit Email Template", - "template.select": "Select Email Template", - "template.revert": "Revert to Original", + "template.select": "Escolher Modelo de E-mail", + "template.revert": "Reverter para o Original", "testing": "Teste de E-mail", - "testing.select": "Select Email Template", + "testing.select": "Escolher Modelo de E-mail", "testing.send": "Enviar E-mail de Teste", "testing.send-help": "The test email will be sent to the currently logged in user's email address.", "subscriptions": "Email Subscriptions", diff --git a/public/language/pt-PT/admin/settings/uploads.json b/public/language/pt-PT/admin/settings/uploads.json index 437cd2816a..9ae1437f3b 100644 --- a/public/language/pt-PT/admin/settings/uploads.json +++ b/public/language/pt-PT/admin/settings/uploads.json @@ -31,7 +31,7 @@ "max-profile-image-size-help": "(in kibibytes, default: 256 KiB)", "max-cover-image-size": "Maximum Cover Image File Size", "max-cover-image-size-help": "(in kibibytes, default: 2,048 KiB)", - "keep-all-user-images": "Keep old versions of avatars and profile covers on the server", + "keep-all-user-images": "Manter versões antigas das fotos de perfil e das fotos de capa no servidor", "profile-covers": "Profile Covers", "default-covers": "Default Cover Images", "default-covers-help": "Add comma-separated default cover images for accounts that don't have an uploaded cover image" diff --git a/public/language/pt-PT/global.json b/public/language/pt-PT/global.json index f995210579..700dd16a8e 100644 --- a/public/language/pt-PT/global.json +++ b/public/language/pt-PT/global.json @@ -108,6 +108,6 @@ "cookies.learn_more": "Aprende Mais", "edited": "Edited", "disabled": "Desativado", - "select": "Select", + "select": "Selecionar", "user-search-prompt": "Digita algo aqui para encontrar utilizadores..." } \ No newline at end of file From cd2f72fb731c3be11a5334fa1fca6aaf008d320f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 30 Jan 2019 10:32:50 -0500 Subject: [PATCH 055/270] feat: pass topic creation data to action:topic.save --- src/topics/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/create.js b/src/topics/create.js index ffb2077cb8..60c2ea78e4 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -85,7 +85,7 @@ module.exports = function (Topics) { ], next); }, function (results, next) { - plugins.fireHook('action:topic.save', { topic: _.clone(topicData) }); + plugins.fireHook('action:topic.save', { topic: _.clone(topicData), data: data }); next(null, topicData.tid); }, ], callback); From c7abf07a3d87a454978117c4771847f027fb3191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 30 Jan 2019 10:33:15 -0500 Subject: [PATCH 056/270] fix: #7324 added disableToType option --- public/src/client/search.js | 4 +++- public/src/utils.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/src/client/search.js b/public/src/client/search.js index efcd6c347f..54e81242a3 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -65,7 +65,9 @@ define('forum/search', ['search', 'autocomplete', 'storage'], function (searchMo } function fillOutForm() { - var params = utils.params(); + var params = utils.params({ + disableToType: true, + }); var searchData = searchModule.getSearchPreferences(); var formData = utils.merge(searchData, params); diff --git a/public/src/utils.js b/public/src/utils.js index 05e069ed14..e3b0347de0 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -649,7 +649,7 @@ params.forEach(function (param) { var val = param.split('='); var key = decodeURI(val[0]); - var value = options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1])); + var value = options.disableToType || options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1])); if (key) { if (key.substr(-2, 2) === '[]') { From 99f82fb366700952f4d328f462b6ff4a8dc065f8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 30 Jan 2019 17:58:53 +0000 Subject: [PATCH 057/270] fix(deps): update dependency nodebb-plugin-composer-default to v6.2.2 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 98b8d7500c..bb0ae5dbea 100644 --- a/install/package.json +++ b/install/package.json @@ -80,7 +80,7 @@ "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", "nconf": "^0.10.0", - "nodebb-plugin-composer-default": "6.2.1", + "nodebb-plugin-composer-default": "6.2.2", "nodebb-plugin-dbsearch": "3.0.6", "nodebb-plugin-emoji": "^2.2.5", "nodebb-plugin-emoji-android": "2.0.0", From f6cfbbb520ecae4df9f0f99b1ba8e6d453a3ab7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 30 Jan 2019 14:39:01 -0500 Subject: [PATCH 058/270] fix: don't crash if body doesn't have `skin-` --- public/src/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/src/app.js b/public/src/app.js index c1a21be21e..a300db8901 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -778,6 +778,9 @@ app.cacheBuster = null; var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { return className.startsWith('skin-'); }); + if (!currentSkinClassName[0]) { + return; + } var currentSkin = currentSkinClassName[0].slice(5); currentSkin = currentSkin !== 'noskin' ? currentSkin : ''; From 6e69a9ab31f1fabb8bf282f3d339b2f13d9008e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jan 2019 12:54:23 -0500 Subject: [PATCH 059/270] fix: merge post notifs --- src/notifications.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/notifications.js b/src/notifications.js index 2f0f5ab916..b2078d2928 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -507,6 +507,7 @@ Notifications.merge = function (notifications, callback) { 'notifications:user_flagged_post_in', 'notifications:user_flagged_user', 'new_register', + 'post-queue', ]; var isolated; var differentiators; From d75a0d772473db0921e1c08b3fc02bc3cfbdc532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 31 Jan 2019 13:13:59 -0500 Subject: [PATCH 060/270] feat: show more unread notifs --- src/user/notifications.js | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index 185c084324..244cee7ba0 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -16,20 +16,25 @@ UserNotifications.get = function (uid, callback) { if (parseInt(uid, 10) <= 0) { return setImmediate(callback, null, { read: [], unread: [] }); } + + let unread; async.waterfall([ function (next) { - getNotifications(uid, 0, 9, next); + getNotificationsFromSet('uid:' + uid + ':notifications:unread', uid, 0, 29, next); }, - function (notifications, next) { - notifications.read = notifications.read.filter(Boolean); - notifications.unread = notifications.unread.filter(Boolean); - - var maxNotifs = 15; - if (notifications.read.length + notifications.unread.length > maxNotifs) { - notifications.read.length = maxNotifs - notifications.unread.length; + function (_unread, next) { + unread = _unread.filter(Boolean); + if (unread.length < 30) { + getNotificationsFromSet('uid:' + uid + ':notifications:read', uid, 0, 29 - unread.length, next); + } else { + next(null, []); } - - next(null, notifications); + }, + function (read, next) { + next(null, { + read: read.filter(Boolean), + unread: unread, + }); }, ], callback); }; @@ -102,18 +107,7 @@ function deleteUserNids(nids, uid, callback) { ], nids, callback); } -function getNotifications(uid, start, stop, callback) { - async.parallel({ - unread: function (next) { - getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, start, stop, next); - }, - read: function (next) { - getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, start, stop, next); - }, - }, callback); -} - -function getNotificationsFromSet(set, read, uid, start, stop, callback) { +function getNotificationsFromSet(set, uid, start, stop, callback) { async.waterfall([ function (next) { db.getSortedSetRevRange(set, start, stop, next); From b350be27774a67f289f39061735d68740ea42465 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 31 Jan 2019 14:31:04 -0500 Subject: [PATCH 061/270] fix(deps): update dependency nodebb-theme-lavender to v5.0.9 (#7322) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index bb0ae5dbea..5da99949c0 100644 --- a/install/package.json +++ b/install/package.json @@ -89,7 +89,7 @@ "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", - "nodebb-theme-lavender": "5.0.8", + "nodebb-theme-lavender": "5.0.9", "nodebb-theme-persona": "9.1.13", "nodebb-theme-slick": "1.2.19", "nodebb-theme-vanilla": "10.1.19", From a82bd3d0f370e5a7e6e6965422fa74c7935a4341 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 31 Jan 2019 14:31:29 -0500 Subject: [PATCH 062/270] fix(deps): update dependency nodebb-theme-slick to v1.2.20 (#7197) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5da99949c0..059e2e02df 100644 --- a/install/package.json +++ b/install/package.json @@ -91,7 +91,7 @@ "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", "nodebb-theme-persona": "9.1.13", - "nodebb-theme-slick": "1.2.19", + "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", "nodemailer": "^5.0.0", From 8f69ffd41d87452132aa9ce83e683e8378fc6f53 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 31 Jan 2019 14:32:47 -0500 Subject: [PATCH 063/270] fix(deps): update dependency nodebb-theme-persona to v9.1.15 (#7295) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 059e2e02df..ecc7ccfe9c 100644 --- a/install/package.json +++ b/install/package.json @@ -90,7 +90,7 @@ "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", - "nodebb-theme-persona": "9.1.13", + "nodebb-theme-persona": "9.1.15", "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", From 5031bfe8c0ea364f27d4f23447122b9478ebd4a0 Mon Sep 17 00:00:00 2001 From: Andrew Rodrigues Date: Mon, 4 Feb 2019 13:54:48 -0500 Subject: [PATCH 064/270] feat: allow themes to define custom classes for categories via filter:admin.category.get `datalist` isn't supported in all browsers but this will fallback to the original behavior. The first item in the list is the placeholder. --- src/controllers/admin/categories.js | 3 +++ src/views/admin/manage/category.tpl | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 4c443da076..2ab344607e 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -35,14 +35,17 @@ categoriesController.get = function (req, res, callback) { req: req, res: res, category: category, + customClasses: [], allCategories: data.allCategories, }, next); }, function (data) { data.category.name = translator.escape(String(data.category.name)); + res.render('admin/manage/category', { category: data.category, allCategories: data.allCategories, + customClasses: data.customClasses, }); }, ], callback); diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index 4b70b1f8a2..89adea01f3 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -60,7 +60,12 @@ - + + + + + +
    From 9d1fcf4e3650e7a236206f45b4b4c4770b66f88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 4 Feb 2019 14:16:47 -0500 Subject: [PATCH 065/270] fix: closes #7217 --- .../language/en-GB/admin/manage/privileges.json | 1 + public/language/en-GB/admin/settings/group.json | 2 -- public/src/modules/helpers.js | 2 +- src/controllers/groups.js | 12 ++++++++---- src/privileges/global.js | 2 ++ src/socket.io/groups.js | 17 +++++++++++++---- src/upgrades/1.12.0/group_create_privilege.js | 16 ++++++++++++++++ src/views/admin/settings/group.tpl | 11 ----------- test/categories.js | 2 ++ test/groups.js | 17 ++--------------- 10 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 src/upgrades/1.12.0/group_create_privilege.js diff --git a/public/language/en-GB/admin/manage/privileges.json b/public/language/en-GB/admin/manage/privileges.json index 07d0a4de50..6cadf3798c 100644 --- a/public/language/en-GB/admin/manage/privileges.json +++ b/public/language/en-GB/admin/manage/privileges.json @@ -11,6 +11,7 @@ "search-users": "Search Users", "search-tags": "Search Tags", "allow-local-login": "Local Login", + "allow-group-creation": "Group Create", "find-category": "Find Category", "access-category": "Access Category", diff --git a/public/language/en-GB/admin/settings/group.json b/public/language/en-GB/admin/settings/group.json index fe3e39915b..8275cbd367 100644 --- a/public/language/en-GB/admin/settings/group.json +++ b/public/language/en-GB/admin/settings/group.json @@ -3,8 +3,6 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", - "allow-creation": "Allow Group Creation", - "allow-creation-help": "If enabled, users can create groups (Default: disabled)", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "cover-image": "Group Cover Image", diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index de5998469b..d649518949 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -178,7 +178,7 @@ } } return states.map(function (priv) { - var guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login']; + var guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login', 'groups:group:create']; var spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read']; var disabled = (member === 'guests' && guestDisabled.includes(priv.name)) || diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 96e6c0bbbb..041ba878df 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -8,6 +8,7 @@ var groups = require('../groups'); var user = require('../user'); var helpers = require('./helpers'); var pagination = require('../pagination'); +var privileges = require('../privileges'); var groupsController = module.exports; @@ -34,12 +35,15 @@ groupsController.getGroupsFromSet = function (uid, sort, start, stop, callback) async.waterfall([ function (next) { - groups.getGroupsFromSet(set, uid, start, stop, next); + async.parallel({ + groupsData: async.apply(groups.getGroupsFromSet, set, uid, start, stop), + allowGroupCreation: async.apply(privileges.global.can, 'group:create', uid), + }, next); }, - function (groupsData, next) { + function (results, next) { next(null, { - groups: groupsData, - allowGroupCreation: meta.config.allowGroupCreation, + groups: results.groupsData, + allowGroupCreation: results.allowGroupCreation, nextStart: stop + 1, }); }, diff --git a/src/privileges/global.js b/src/privileges/global.js index 5d0cb88213..aee98a836d 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -22,6 +22,7 @@ module.exports = function (privileges) { { name: '[[admin/manage/privileges:search-users]]' }, { name: '[[admin/manage/privileges:search-tags]]' }, { name: '[[admin/manage/privileges:allow-local-login]]' }, + { name: '[[admin/manage/privileges:allow-group-creation]]' }, ]; privileges.global.userPrivilegeList = [ @@ -34,6 +35,7 @@ module.exports = function (privileges) { 'search:users', 'search:tags', 'local:login', + 'group:create', ]; privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) { diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index e4f7a0a985..8301974a7d 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -8,6 +8,7 @@ var user = require('../user'); var utils = require('../utils'); var groupsController = require('../controllers/groups'); var events = require('../events'); +var privileges = require('../privileges'); var SocketGroups = module.exports; @@ -238,14 +239,22 @@ SocketGroups.kick = isOwner(function (socket, data, callback) { SocketGroups.create = function (socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); - } else if (!meta.config.allowGroupCreation) { - return callback(new Error('[[error:group-creation-disabled]]')); } else if (groups.isPrivilegeGroup(data.name)) { return callback(new Error('[[error:invalid-group-name]]')); } - data.ownerUid = socket.uid; - groups.create(data, callback); + async.waterfall([ + function (next) { + privileges.global.can('group:create', socket.uid, next); + }, + function (canCreate, next) { + if (!canCreate) { + return next(new Error('[[error:no-privileges]]')); + } + data.ownerUid = socket.uid; + groups.create(data, next); + }, + ], callback); }; SocketGroups.delete = isOwner(function (socket, data, callback) { diff --git a/src/upgrades/1.12.0/group_create_privilege.js b/src/upgrades/1.12.0/group_create_privilege.js new file mode 100644 index 0000000000..59f45ced84 --- /dev/null +++ b/src/upgrades/1.12.0/group_create_privilege.js @@ -0,0 +1,16 @@ +'use strict'; + +var privileges = require('../../privileges'); + +module.exports = { + name: 'Update category watch data', + timestamp: Date.UTC(2019, 0, 4), + method: function (callback) { + var meta = require('../../meta'); + if (parseInt(meta.config.allowGroupCreation, 10) === 1) { + privileges.global.give(['groups:create'], 'registered-users', callback); + } else { + setImmediate(callback); + } + }, +}; diff --git a/src/views/admin/settings/group.tpl b/src/views/admin/settings/group.tpl index 5319ef5309..d46b664116 100644 --- a/src/views/admin/settings/group.tpl +++ b/src/views/admin/settings/group.tpl @@ -18,17 +18,6 @@ [[admin/settings/group:private-groups.warning]]

    -
    - -
    - -

    - [[admin/settings/group:allow-creation-help]] -

    -
    - -
    -
    [[admin/settings/tags:privacy]]
    -
    - -
    - -
    - -
    -
    -
    [[admin/settings/tags:related-topics]]
    diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl index a10867a271..874e55eb60 100644 --- a/src/views/admin/settings/user.tpl +++ b/src/views/admin/settings/user.tpl @@ -65,12 +65,6 @@ [[admin/settings/user:allow-account-deletion]]
    -
    - -
    From fc6767e1400793b5eecd89164ed2b3347163d099 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 Feb 2019 18:36:58 -0500 Subject: [PATCH 076/270] fix: #7098 --- public/src/client/topic/postTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index f4f69ef215..4d29d966dd 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -248,7 +248,7 @@ define('forum/topic/postTools', [ tid: tid, pid: toPid, topicName: ajaxify.data.titleRaw, - text: username ? username + ' ' : '', + text: username ? username + ' ' : ($('[component="topic/quickreply/text"]').val() || ''), }); } }); From daadcc4889a91b9bbd279d49db348610cc079ccc Mon Sep 17 00:00:00 2001 From: Andrew Rodrigues Date: Thu, 7 Feb 2019 15:39:22 -0500 Subject: [PATCH 077/270] feat: add `action:alert.new`, `action:alert.update` hooks --- public/src/modules/alerts.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 0094c88f96..83e69ca1eb 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -52,6 +52,8 @@ define('alerts', ['translator', 'components', 'benchpress'], function (translato fadeOut(alert); }); } + + $(window).trigger('action:alert.new', { alert: alert, params: params }); }); }); } @@ -88,6 +90,8 @@ define('alerts', ['translator', 'components', 'benchpress'], function (translato fadeOut(alert); }); } + + $(window).trigger('action:alert.update', { alert: alert, params: params }); } function fadeOut(alert) { From d864da1a2ec97a527f2698a2519e544820028869 Mon Sep 17 00:00:00 2001 From: Andrew Rodrigues Date: Thu, 7 Feb 2019 15:41:20 -0500 Subject: [PATCH 078/270] fix: move `action:alert.update` to after translator --- public/src/modules/alerts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 83e69ca1eb..01073ad9b1 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -76,6 +76,7 @@ define('alerts', ['translator', 'components', 'benchpress'], function (translato translator.translate(alert.html(), function (translatedHTML) { alert.children().fadeIn(100); alert.html(translatedHTML); + $(window).trigger('action:alert.update', { alert: alert, params: params }); }); // Handle changes in the clickfn @@ -90,8 +91,6 @@ define('alerts', ['translator', 'components', 'benchpress'], function (translato fadeOut(alert); }); } - - $(window).trigger('action:alert.update', { alert: alert, params: params }); } function fadeOut(alert) { From fab52b840c2cb401c15c9b0f6c2e377a99476159 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 Feb 2019 16:07:44 -0500 Subject: [PATCH 079/270] fix: prevent crash if results.stats or results.serverStatus is undefined --- src/database/mongo.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 21c5a97638..7f5bb21b68 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -226,7 +226,8 @@ mongoModule.info = function (db, callback) { }, next); }, function (results, next) { - var stats = results.stats; + var stats = results.stats || {}; + results.serverStatus = results.serverStatus || {}; var scale = 1024 * 1024 * 1024; results.listCollections = results.listCollections.map(function (collectionInfo) { @@ -241,13 +242,12 @@ mongoModule.info = function (db, callback) { }; }); - stats.mem = results.serverStatus.mem; - stats.mem = results.serverStatus.mem; + stats.mem = results.serverStatus.mem || {}; stats.mem.resident = (stats.mem.resident / 1024).toFixed(3); stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(3); stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(3); stats.collectionData = results.listCollections; - stats.network = results.serverStatus.network; + stats.network = results.serverStatus.network || {}; stats.network.bytesIn = (stats.network.bytesIn / scale).toFixed(3); stats.network.bytesOut = (stats.network.bytesOut / scale).toFixed(3); stats.network.numRequests = utils.addCommas(stats.network.numRequests); From 250674c8a857aad559885c4e98df30719dedbf6d Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 8 Feb 2019 09:25:58 +0000 Subject: [PATCH 080/270] Latest translations and fallbacks --- public/language/pl/admin/manage/post-queue.json | 2 +- public/language/zh-CN/admin/advanced/database.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/language/pl/admin/manage/post-queue.json b/public/language/pl/admin/manage/post-queue.json index 0942d503ee..8d8e1f48c6 100644 --- a/public/language/pl/admin/manage/post-queue.json +++ b/public/language/pl/admin/manage/post-queue.json @@ -1,6 +1,6 @@ { "post-queue": "Kolejka postów", - "description": "Brak postów w kolejce.
    W celu włączenia tej funkcji, przejdź do Ustawienia → Posty → Ograniczenia pisania i włącz Kolejkę postów. ", + "description": "Brak postów w kolejce.
    W celu włączenia tej funkcji, przejdź do Ustawienia → Posty → Ograniczenia pisania i włącz Kolejkę postów. ", "user": "Użytkownik", "category": "Kategoria", "title": "Tytuł", diff --git a/public/language/zh-CN/admin/advanced/database.json b/public/language/zh-CN/admin/advanced/database.json index fad0bff091..d7cc2487ed 100644 --- a/public/language/zh-CN/admin/advanced/database.json +++ b/public/language/zh-CN/admin/advanced/database.json @@ -18,8 +18,8 @@ "mongo.resident-memory": "驻留内存", "mongo.virtual-memory": "虚拟内存", "mongo.mapped-memory": "已映射内存", - "mongo.bytes-in": "Bytes In", - "mongo.bytes-out": "Bytes Out", + "mongo.bytes-in": "字节输入", + "mongo.bytes-out": "字节输出", "mongo.num-requests": "请求数量", "mongo.raw-info": "MongoDB 原始信息", @@ -36,10 +36,10 @@ "redis.total-connections-recieved": "已接收的连接总数", "redis.total-commands-processed": "已执行命令总数", "redis.iops": "每秒实时操作数", - "redis.iinput": "Instantaneous Input Per Second", - "redis.ioutput": "Instantaneous Output Per Second", - "redis.total-input": "Total Input", - "redis.total-output": "Total Ouput", + "redis.iinput": "每秒实时输入", + "redis.ioutput": "每秒实时输出", + "redis.total-input": "总输入", + "redis.total-output": "总输出", "redis.keyspace-hits": "Keyspace 命中", "redis.keyspace-misses": "Keyspace 未命中", From fbbe2ab758e34193643b8c8385f7b0184c7905a1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 09:34:42 -0500 Subject: [PATCH 081/270] fix(deps): bump contenteditable to v0.1.1, #7325 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 36b73d5200..8c8fb496bb 100644 --- a/install/package.json +++ b/install/package.json @@ -122,7 +122,7 @@ "spdx-license-list": "^5.0.0", "spider-detector": "1.0.19", "textcomplete": "^0.17.1", - "textcomplete.contenteditable": "^0.1.0", + "textcomplete.contenteditable": "^0.1.1", "toobusy-js": "^0.5.1", "uglify-es": "^3.3.9", "validator": "10.11.0", From 07b29d59017e4d9dff4945e3a5974dff410f7acd Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 8 Feb 2019 14:35:12 +0000 Subject: [PATCH 082/270] fix(deps): update dependency nodebb-plugin-markdown to v8.9.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8c8fb496bb..8122b8b4e7 100644 --- a/install/package.json +++ b/install/package.json @@ -84,7 +84,7 @@ "nodebb-plugin-dbsearch": "3.0.6", "nodebb-plugin-emoji": "^2.2.5", "nodebb-plugin-emoji-android": "2.0.0", - "nodebb-plugin-markdown": "8.8.8", + "nodebb-plugin-markdown": "8.9.0", "nodebb-plugin-mentions": "2.5.2", "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.6.1", From 65df67117db200d84c0b4854bd55558f2c8bf8e8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 8 Feb 2019 14:55:51 +0000 Subject: [PATCH 083/270] fix(deps): update dependency nodebb-theme-persona to v9.1.16 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8122b8b4e7..0b29b412be 100644 --- a/install/package.json +++ b/install/package.json @@ -90,7 +90,7 @@ "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", - "nodebb-theme-persona": "9.1.15", + "nodebb-theme-persona": "9.1.16", "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", From 0c09b7402d8f2dcc8dc84a34cce1d70e29476ebc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 10:50:15 -0500 Subject: [PATCH 084/270] feat: logging password resets and errors into event log closes #7343, also adds tests for password reset socket calls --- public/language/en-GB/error.json | 1 + src/socket.io/user.js | 21 +++++----- test/socket.io.js | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 5adc3351e2..072fbdcc88 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -39,6 +39,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "password-too-long": "Password too long", + "reset-rate-limited": "Too many password reset requests (rate limited)", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 3db21d34d8..add94f3804 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -1,7 +1,6 @@ 'use strict'; var async = require('async'); -var winston = require('winston'); var user = require('../user'); var topics = require('../topics'); @@ -102,17 +101,17 @@ SocketUser.reset.send = function (socket, email, callback) { } user.reset.send(email, function (err) { - if (err) { - switch (err.message) { - case '[[error:invalid-email]]': - winston.warn('[user/reset] Invalid email attempt: ' + email + ' by IP ' + socket.ip + (socket.uid ? ' (uid: ' + socket.uid + ')' : '')); - err = null; - break; + events.log({ + type: 'password-reset', + text: err ? err.message : '[[success:success]]', + ip: socket.ip, + uid: socket.uid, + email: email, + }); - case '[[error:reset-rate-limited]]': - err = null; - break; - } + const internalErrors = ['[[error:invalid-email]]', '[[error:reset-rate-limited]]']; + if (err && internalErrors.includes(err.message)) { + err = null; } setTimeout(callback.bind(err), 2500); diff --git a/test/socket.io.js b/test/socket.io.js index 72b883e841..4e71fd7a8d 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -17,6 +17,7 @@ var groups = require('../src/groups'); var categories = require('../src/categories'); var helpers = require('./helpers'); var meta = require('../src/meta'); +const events = require('../src/events'); var socketAdmin = require('../src/socket.io/admin'); @@ -634,4 +635,72 @@ describe('socket.io', function () { done(); }); }); + + describe('password reset', function () { + const socketUser = require('../src/socket.io/user'); + + it('should not error on valid email', function (done) { + socketUser.reset.send({ uid: 0 }, 'regular@test.com', function (err) { + assert.ifError(err); + + async.parallel({ + count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()), + event: async.apply(events.getEvents, '', 0, 0), + }, function (err, data) { + assert.ifError(err); + assert.strictEqual(data.count, 1); + + // Event validity + assert.strictEqual(data.event.length, 1); + const event = data.event[0]; + assert.strictEqual(event.type, 'password-reset'); + assert.strictEqual(event.text, '[[success:success]]'); + + done(); + }); + }); + }); + + it('should not generate code if rate limited', function (done) { + socketUser.reset.send({ uid: 0 }, 'regular@test.com', function (err) { + assert.ifError(err); + + async.parallel({ + count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()), + event: async.apply(events.getEvents, '', 0, 0), + }, function (err, data) { + assert.ifError(err); + assert.strictEqual(data.count, 1); // should still equal 1 + + // Event validity + assert.strictEqual(data.event.length, 1); + const event = data.event[0]; + assert.strictEqual(event.type, 'password-reset'); + assert.strictEqual(event.text, '[[error:reset-rate-limited]]'); + + done(); + }); + }); + }); + + it('should not error on invalid email (but not generate reset code)', function (done) { + socketUser.reset.send({ uid: 0 }, 'irregular@test.com', function (err) { + assert.ifError(err); + + db.sortedSetCount('reset:issueDate', 0, Date.now(), function (err, count) { + assert.ifError(err); + assert.strictEqual(count, 1); + done(); + }); + }); + }); + + it('should error on no email', function (done) { + socketUser.reset.send({ uid: 0 }, '', function (err) { + assert(err instanceof Error); + assert.strictEqual(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + }); }); From 96b6ce1f3c725f0ace41b4536c9ca10fa05bfe65 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 10:54:25 -0500 Subject: [PATCH 085/270] fix: eslint failure from daadcc4889a91b9bbd279d49db348610cc079ccc --- public/src/modules/alerts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 01073ad9b1..fc7cbe9558 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -52,7 +52,7 @@ define('alerts', ['translator', 'components', 'benchpress'], function (translato fadeOut(alert); }); } - + $(window).trigger('action:alert.new', { alert: alert, params: params }); }); }); From 30ff4582e10b39cc3a8002a9afc2b7396061a472 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 8 Feb 2019 15:37:00 +0000 Subject: [PATCH 086/270] fix(deps): update dependency nodebb-plugin-composer-default to v6.2.4 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0b29b412be..203531a59d 100644 --- a/install/package.json +++ b/install/package.json @@ -80,7 +80,7 @@ "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", "nconf": "^0.10.0", - "nodebb-plugin-composer-default": "6.2.3", + "nodebb-plugin-composer-default": "6.2.4", "nodebb-plugin-dbsearch": "3.0.6", "nodebb-plugin-emoji": "^2.2.5", "nodebb-plugin-emoji-android": "2.0.0", From 8c68780e54307693f8191d362fdcf930ed453c51 Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Fri, 8 Feb 2019 14:38:21 -0500 Subject: [PATCH 087/270] fix: #7350 --- src/upgrades/1.10.2/fix_category_topic_zsets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrades/1.10.2/fix_category_topic_zsets.js b/src/upgrades/1.10.2/fix_category_topic_zsets.js index c9d67231ff..b469f5c480 100644 --- a/src/upgrades/1.10.2/fix_category_topic_zsets.js +++ b/src/upgrades/1.10.2/fix_category_topic_zsets.js @@ -24,7 +24,7 @@ module.exports = { if (parseInt(topicData.pinned, 10) === 1) { return setImmediate(next); } - + topicData.postcount = parseInt(topicData.postcount, 10) || 0; db.sortedSetAdd('cid:' + topicData.cid + ':tids:posts', topicData.postcount, tid, next); }, function (next) { From 91a7b9070ac69872a856913de99a32a7763dbcc4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 16:09:25 -0500 Subject: [PATCH 088/270] fix: #7346, panel-header widget colours in ACP --- public/less/admin/admin.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 9a772ae08e..dc657a37ec 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -117,20 +117,20 @@ body { .box-header-font; } - .panel { + .panel:not([data-container-html]) { background-color: #FFF; box-sizing: border-box; border-radius: 3px; box-shadow: 0px 1px 3px 0px rgba(165, 165, 165, 0.75); margin-bottom: 20px; - &.panel-default .panel-heading { + &.panel-default >.panel-heading { .acp-panel-heading; background: #fefefe; color: #333; } - &.panel-danger .panel-heading { + &.panel-danger >.panel-heading { .acp-panel-heading; } } From aca0556052d0b25460229762581025507933a95d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 16:26:38 -0500 Subject: [PATCH 089/270] fix: #7261, banned users still get digests --- src/user/digest.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/user/digest.js b/src/user/digest.js index f5940e911f..8feb9b4dd9 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -77,6 +77,13 @@ Digest.getSubscribers = function (interval, callback) { next(err, subs); }); }, + function (subscribers, next) { + async.filter(subscribers, function (uid, next) { + user.isBanned(uid, function (err, banned) { + next(err, !banned); + }); + }, next); + }, function (subscribers, next) { plugins.fireHook('filter:digest.subscribers', { interval: interval, From 2e2c3ac1101b86fe8289fa5bca3b23a7a2848cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 10:14:43 -0500 Subject: [PATCH 090/270] fix: #7352 --- src/database/redis.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/database/redis.js b/src/database/redis.js index 2828f84915..58a427d8c8 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -114,6 +114,9 @@ redisModule.connect = function (options, callback) { throw err; } }); + } else { + callbackCalled = true; + return callback(new Error('[[error:no-database-selected]]')); } return cxn; @@ -186,9 +189,11 @@ redisModule.info = function (cxn, callback) { }); const keyInfo = redisData['db' + nconf.get('redis:database')]; - redisData.keys = keyInfo.split(',')[0].replace('keys=', ''); - redisData.expires = keyInfo.split(',')[1].replace('expires=', ''); - redisData.avg_ttl = keyInfo.split(',')[2].replace('avg_ttl=', ''); + if (keyInfo) { + redisData.keys = keyInfo.split(',')[0].replace('keys=', ''); + redisData.expires = keyInfo.split(',')[1].replace('expires=', ''); + redisData.avg_ttl = keyInfo.split(',')[2].replace('avg_ttl=', ''); + } redisData.instantaneous_input = (redisData.instantaneous_input_kbps / 1024).toFixed(3); redisData.instantaneous_output = (redisData.instantaneous_output_kbps / 1024).toFixed(3); From f2d7f75ee244bca3531988967f2f508237137d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 12:23:10 -0500 Subject: [PATCH 091/270] feat: check CI failure --- src/install.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/install.js b/src/install.js index f95795e822..92d4161c9e 100644 --- a/src/install.js +++ b/src/install.js @@ -165,6 +165,9 @@ function completeConfigSetup(config, next) { } nconf.overrides(config); + + console.log(config, nconf.get('redis')); + async.waterfall([ function (next) { require('./database').init(next); From ba90bf311440eb9c2de45909fe9a53d085a7999c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 13:13:42 -0500 Subject: [PATCH 092/270] feat: check overide --- src/install.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/install.js b/src/install.js index 92d4161c9e..908100b6e0 100644 --- a/src/install.js +++ b/src/install.js @@ -163,10 +163,10 @@ function completeConfigSetup(config, next) { if (nconf.get('package_manager')) { config.package_manager = nconf.get('package_manager'); } - + console.log('before override', config, nconf.get('redis')); nconf.overrides(config); - console.log(config, nconf.get('redis')); + console.log('after override', config, nconf.get('redis')); async.waterfall([ function (next) { From bcd62586e29a4f0cc0ae112347d932c7fea2dbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 13:41:34 -0500 Subject: [PATCH 093/270] fix: database 0 was being replaced with undefined because 0 is falsy --- src/install.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index 908100b6e0..7d204eadbb 100644 --- a/src/install.js +++ b/src/install.js @@ -132,7 +132,13 @@ function setupConfig(next) { var allQuestions = questions.main.concat(questions.optional).concat(redisQuestions).concat(mongoQuestions).concat(postgresQuestions); allQuestions.forEach(function (question) { - config[question.name] = install.values[question.name] || question.default || undefined; + if (install.values.hasOwnProperty(question.name)) { + config[question.name] = install.values[question.name]; + } else if (question.hasOwnProperty('default')) { + config[question.name] = question.default; + } else { + config[question.name] = undefined; + } }); setImmediate(next, null, config); } else { From 39e3527585f2845f40f07955839aaaac8fd63626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 13:54:08 -0500 Subject: [PATCH 094/270] fix: don't use same db as prod values --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 178fe2b63d..9dde9454e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ before_install: before_script: - sleep 15 # wait for mongodb to be ready - "mongo mydb_test --eval 'db.createUser({user:\"travis\", pwd: \"test\", roles: []});'" - - sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":0}\"; fi" - - sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567/forum\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":0}\"; fi" + - sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":\\\"travis_ci_test\\\"}\"; fi" + - sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567/forum\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":1}\"; fi" - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database nodebb;' -U postgres; psql -c 'create database travis_ci_test;' -U postgres; node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"postgres\\\",\\\"postgres:host\\\":\\\"127.0.0.1\\\",\\\"postgres:port\\\":5432,\\\"postgres:password\\\":\\\"\\\",\\\"postgres:database\\\":\\\"nodebb\\\",\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":5432,\\\"username\\\":\\\"postgres\\\",\\\"database\\\":\\\"travis_ci_test\\\"}\"; fi" after_success: - "npm run coveralls" From fe63fca8468f478b26a0505f3dee635d8d8d6e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 9 Feb 2019 14:32:44 -0500 Subject: [PATCH 095/270] fix: remove debug logs --- src/install.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/install.js b/src/install.js index 7d204eadbb..fdb53c0e1e 100644 --- a/src/install.js +++ b/src/install.js @@ -169,11 +169,7 @@ function completeConfigSetup(config, next) { if (nconf.get('package_manager')) { config.package_manager = nconf.get('package_manager'); } - console.log('before override', config, nconf.get('redis')); nconf.overrides(config); - - console.log('after override', config, nconf.get('redis')); - async.waterfall([ function (next) { require('./database').init(next); From 0fffcb385565396cf1f3f165cb7bc65e149648b1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 9 Feb 2019 14:50:09 -0500 Subject: [PATCH 096/270] fix: #7231, missing success alert on group name change in ACP --- public/src/admin/manage/group.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 92ef056630..9b38c0300f 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -102,11 +102,11 @@ define('admin/manage/group', [ var newName = $('#change-group-name').val(); // If the group name changed, change url - if (groupName === newName) { - app.alertSuccess('[[admin/manage/groups:edit.save-success]]'); - } else { + if (groupName !== newName) { ajaxify.go('admin/manage/groups/' + encodeURIComponent(newName), undefined, true); } + + app.alertSuccess('[[admin/manage/groups:edit.save-success]]'); }); return false; }); From 5917dec2881bed7b7b5df8d6c9c88a4ea7a1b7b8 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 Feb 2019 11:23:18 -0500 Subject: [PATCH 097/270] fix: only allow numbers as scores (#7356) * zadd score checks * fix: only allow numbers as scores * fix: convert values to strings --- src/database/mongo/helpers.js | 6 +----- src/database/mongo/sorted/add.js | 14 ++++++++++-- src/database/postgres/helpers.js | 6 +----- src/database/postgres/sorted/add.js | 15 ++++++++++--- src/database/redis/sorted/add.js | 22 ++++++++++++++----- test/database/sorted.js | 33 +++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index a02c658a3d..928f7516f2 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -46,9 +46,5 @@ helpers.deserializeData = function (data) { }; helpers.valueToString = function (value) { - if (value === null || value === undefined) { - return value; - } - - return value.toString(); + return String(value); }; diff --git a/src/database/mongo/sorted/add.js b/src/database/mongo/sorted/add.js index e71bb0568a..4694b03cc4 100644 --- a/src/database/mongo/sorted/add.js +++ b/src/database/mongo/sorted/add.js @@ -2,6 +2,7 @@ module.exports = function (db, module) { var helpers = module.helpers.mongo; + var utils = require('../../../utils'); module.sortedSetAdd = function (key, score, value, callback) { callback = callback || helpers.noop; @@ -11,7 +12,9 @@ module.exports = function (db, module) { if (Array.isArray(score) && Array.isArray(value)) { return sortedSetAddBulk(key, score, value, callback); } - + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); + } value = helpers.valueToString(value); db.collection('objects').updateOne({ _key: key, value: value }, { $set: { score: parseFloat(score) } }, { upsert: true, w: 1 }, function (err) { @@ -29,7 +32,11 @@ module.exports = function (db, module) { if (scores.length !== values.length) { return callback(new Error('[[error:invalid-data]]')); } - + for (let i = 0; i < scores.length; i += 1) { + if (!utils.isNumber(scores[i])) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + scores[i] + ']]')); + } + } values = values.map(helpers.valueToString); var bulk = db.collection('objects').initializeUnorderedBulkOp(); @@ -48,6 +55,9 @@ module.exports = function (db, module) { if (!Array.isArray(keys) || !keys.length) { return callback(); } + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); + } value = helpers.valueToString(value); var bulk = db.collection('objects').initializeUnorderedBulkOp(); diff --git a/src/database/postgres/helpers.js b/src/database/postgres/helpers.js index 1b9a4d2cf6..61965ba588 100644 --- a/src/database/postgres/helpers.js +++ b/src/database/postgres/helpers.js @@ -3,11 +3,7 @@ var helpers = {}; helpers.valueToString = function (value) { - if (value === null || value === undefined) { - return value; - } - - return value.toString(); + return String(value); }; helpers.removeDuplicateValues = function (values) { diff --git a/src/database/postgres/sorted/add.js b/src/database/postgres/sorted/add.js index a187091746..7121df4f97 100644 --- a/src/database/postgres/sorted/add.js +++ b/src/database/postgres/sorted/add.js @@ -4,6 +4,7 @@ var async = require('async'); module.exports = function (db, module) { var helpers = module.helpers.postgres; + var utils = require('../../../utils'); module.sortedSetAdd = function (key, score, value, callback) { callback = callback || helpers.noop; @@ -15,7 +16,9 @@ module.exports = function (db, module) { if (Array.isArray(score) && Array.isArray(value)) { return sortedSetAddBulk(key, score, value, callback); } - + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); + } value = helpers.valueToString(value); score = parseFloat(score); @@ -46,7 +49,11 @@ VALUES ($1::TEXT, $2::TEXT, $3::NUMERIC) if (scores.length !== values.length) { return callback(new Error('[[error:invalid-data]]')); } - + for (let i = 0; i < scores.length; i += 1) { + if (!utils.isNumber(scores[i])) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + scores[i] + ']]')); + } + } values = values.map(helpers.valueToString); scores = scores.map(function (score) { return parseFloat(score); @@ -81,7 +88,9 @@ SELECT $1::TEXT, v, s if (!Array.isArray(keys) || !keys.length) { return callback(); } - + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); + } value = helpers.valueToString(value); score = parseFloat(score); diff --git a/src/database/redis/sorted/add.js b/src/database/redis/sorted/add.js index e6f5861704..22b9c3b69f 100644 --- a/src/database/redis/sorted/add.js +++ b/src/database/redis/sorted/add.js @@ -1,6 +1,8 @@ 'use strict'; module.exports = function (redisClient, module) { + const utils = require('../../../utils'); + module.sortedSetAdd = function (key, score, value, callback) { callback = callback || function () {}; if (!key) { @@ -9,7 +11,10 @@ module.exports = function (redisClient, module) { if (Array.isArray(score) && Array.isArray(value)) { return sortedSetAddMulti(key, score, value, callback); } - redisClient.zadd(key, score, value, function (err) { + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); + } + redisClient.zadd(key, score, String(value), function (err) { callback(err); }); }; @@ -22,11 +27,15 @@ module.exports = function (redisClient, module) { if (scores.length !== values.length) { return callback(new Error('[[error:invalid-data]]')); } - + for (let i = 0; i < scores.length; i += 1) { + if (!utils.isNumber(scores[i])) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + scores[i] + ']]')); + } + } var args = [key]; for (var i = 0; i < scores.length; i += 1) { - args.push(scores[i], values[i]); + args.push(scores[i], String(values[i])); } redisClient.zadd(args, function (err) { @@ -37,13 +46,16 @@ module.exports = function (redisClient, module) { module.sortedSetsAdd = function (keys, score, value, callback) { callback = callback || function () {}; if (!Array.isArray(keys) || !keys.length) { - return callback(); + return setImmediate(callback); + } + if (!utils.isNumber(score)) { + return setImmediate(callback, new Error('[[error:invalid-score, ' + score + ']]')); } var batch = redisClient.batch(); for (var i = 0; i < keys.length; i += 1) { if (keys[i]) { - batch.zadd(keys[i], score, value); + batch.zadd(keys[i], score, String(value)); } } diff --git a/test/database/sorted.js b/test/database/sorted.js index 35adcdfd8a..2f7d671beb 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -57,6 +57,31 @@ describe('Sorted Set methods', function () { }); }); }); + + it('should error if score is null', function (done) { + db.sortedSetAdd('errorScore', null, 'value1', function (err) { + assert.equal(err.message, '[[error:invalid-score, null]]'); + done(); + }); + }); + + it('should error if any score is undefined', function (done) { + db.sortedSetAdd('errorScore', [1, undefined], ['value1', 'value2'], function (err) { + assert.equal(err.message, '[[error:invalid-score, undefined]]'); + done(); + }); + }); + + it('should add null value as `null` string', function (done) { + db.sortedSetAdd('nullValueZSet', 1, null, function (err) { + assert.ifError(err); + db.getSortedSetRange('nullValueZSet', 0, -1, function (err, values) { + assert.ifError(err); + assert.strictEqual(values[0], 'null'); + done(); + }); + }); + }); }); describe('sortedSetsAdd()', function () { @@ -67,6 +92,14 @@ describe('Sorted Set methods', function () { done(); }); }); + + + it('should error if score is null', function (done) { + db.sortedSetsAdd(['sorted1', 'sorted2'], null, 'value1', function (err) { + assert.equal(err.message, '[[error:invalid-score, null]]'); + done(); + }); + }); }); describe('getSortedSetRange()', function () { From c24dcf78b7271c9dcfa513947d90c3884c7fda0e 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 Feb 2019 11:52:17 -0500 Subject: [PATCH 098/270] fix: uid checks --- src/groups/membership.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/groups/membership.js b/src/groups/membership.js index 09b8242202..86ce185487 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -351,14 +351,14 @@ module.exports = function (Groups) { }; Groups.isInvited = function (uid, groupName, callback) { - if (uid <= 0) { + if (!(parseInt(uid, 10) > 0)) { return setImmediate(callback, null, false); } db.isSetMember('group:' + groupName + ':invited', uid, callback); }; Groups.isPending = function (uid, groupName, callback) { - if (uid <= 0) { + if (!(parseInt(uid, 10) > 0)) { return setImmediate(callback, null, false); } db.isSetMember('group:' + groupName + ':pending', uid, callback); From efd7d953de6638f62f8cc4a00fe85329c6a44a2e 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 Feb 2019 11:55:38 -0500 Subject: [PATCH 099/270] fix: uid check --- src/groups/ownership.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups/ownership.js b/src/groups/ownership.js index 45eb7c3383..527a1b7d0e 100644 --- a/src/groups/ownership.js +++ b/src/groups/ownership.js @@ -8,7 +8,7 @@ module.exports = function (Groups) { Groups.ownership = {}; Groups.ownership.isOwner = function (uid, groupName, callback) { - if (uid <= 0) { + if (!(parseInt(uid, 10) > 0)) { return setImmediate(callback, null, false); } db.isSetMember('group:' + groupName + ':owners', uid, callback); From c6ad8fae2a181c4f6e073661e9a55979537f5d05 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 Feb 2019 14:29:25 -0500 Subject: [PATCH 100/270] fix: #7354 --- src/controllers/accounts/helpers.js | 1 - src/controllers/admin/users.js | 2 -- src/socket.io/admin/user.js | 3 +-- src/user/approval.js | 4 +--- src/user/create.js | 3 +-- src/user/data.js | 4 ++++ test/user.js | 11 +++++++++++ 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index a0ecfcfe1c..9e46fa3e63 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -189,7 +189,6 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { userData.websiteLink = !userData.website.startsWith('http') ? 'http://' + userData.website : userData.website; userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), ''); - userData.email = validator.escape(String(userData.email || '')); userData.fullname = validator.escape(String(userData.fullname || '')); userData.location = validator.escape(String(userData.location || '')); userData.signature = validator.escape(String(userData.signature || '')); diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index c31de51453..743834e1d7 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -1,7 +1,6 @@ 'use strict'; var async = require('async'); -var validator = require('validator'); var nconf = require('nconf'); var user = require('../../user'); @@ -156,7 +155,6 @@ function getUsers(set, section, min, max, req, res, next) { }, function (results) { results.users = results.users.filter(function (user) { - user.email = validator.escape(String(user.email || '')); return user && parseInt(user.uid, 10); }); var data = { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 6bbf7d57ae..efd193ede0 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -1,7 +1,6 @@ 'use strict'; var async = require('async'); -var validator = require('validator'); var winston = require('winston'); var db = require('../../database'); @@ -210,7 +209,7 @@ User.search = function (socket, data, callback) { function (userInfo, next) { searchData.users.forEach(function (user, index) { if (user && userInfo[index]) { - user.email = validator.escape(String(userInfo[index].email || '')); + user.email = userInfo[index].email; user.flags = userInfo[index].flags || 0; user.lastonlineISO = userInfo[index].lastonlineISO; user.joindateISO = userInfo[index].joindateISO; diff --git a/src/user/approval.js b/src/user/approval.js index 90bb71b324..f0a5a77b98 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -180,9 +180,7 @@ module.exports = function (User) { }, function (_data, next) { data = _data; - var keys = data.filter(Boolean).map(function (user) { - return 'registration:queue:name:' + user.value; - }); + var keys = data.filter(Boolean).map(user => 'registration:queue:name:' + user.value); db.getObjects(keys, next); }, function (users, next) { diff --git a/src/user/create.js b/src/user/create.js index 9ab23a33dc..26395e2e4e 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -1,7 +1,6 @@ 'use strict'; var async = require('async'); -var validator = require('validator'); var zxcvbn = require('zxcvbn'); var db = require('../database'); var utils = require('../utils'); @@ -15,7 +14,7 @@ module.exports = function (User) { data.username = data.username.trim(); data.userslug = utils.slugify(data.username); if (data.email !== undefined) { - data.email = validator.escape(String(data.email).trim()); + data.email = String(data.email).trim(); } var timestamp = data.timestamp || Date.now(); var userData; diff --git a/src/user/data.js b/src/user/data.js index 22fb8e8df7..c411aef9c1 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -159,6 +159,10 @@ module.exports = function (User) { user.username = validator.escape(user.username ? user.username.toString() : ''); } + if (user.hasOwnProperty('email')) { + user.email = validator.escape(user.email ? user.email.toString() : ''); + } + if (!parseInt(user.uid, 10)) { user.uid = 0; user.username = (user.hasOwnProperty('oldUid') && parseInt(user.oldUid, 10)) ? '[[global:former_user]]' : '[[global:guest]]'; diff --git a/test/user.js b/test/user.js index 2d5505a6a8..f2ff257951 100644 --- a/test/user.js +++ b/test/user.js @@ -60,6 +60,17 @@ describe('User', function () { }); }); + it('should be created properly', function (done) { + User.create({ username: 'weirdemail', email: '

    test

    @gmail.com' }, function (err, uid) { + assert.ifError(err); + User.getUserData(uid, function (err, data) { + assert.ifError(err); + assert.equal(data.email, '<h1>test</h1>@gmail.com'); + done(); + }); + }); + }); + it('should have a valid email, if using an email', function (done) { User.create({ username: userData.username, password: userData.password, email: 'fakeMail' }, function (err) { assert(err); From e8f3c25676a9aa4136017265c8025824814081e8 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 11 Feb 2019 16:33:40 -0500 Subject: [PATCH 101/270] fix: re-introducing indeterminate checkbox state to ACP privs --- .../language/en-GB/admin/manage/categories.json | 2 +- public/less/admin/paper/bootswatch.less | 16 ++++++++++++++++ public/src/admin/manage/privileges.js | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 85aeeb8069..60524dd329 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -29,7 +29,7 @@ "select-category": "Select Category", "set-parent-category": "Set Parent Category", - "privileges.description": "You can configure the access control privileges for this category in this section. Privileges can be granted on a per-user or a per-group basis. Select the domain of effect from the dropdown below.", + "privileges.description": "You can configure the access control privileges for portions of the site in this section. Privileges can be granted on a per-user or a per-group basis. Select the domain of effect from the dropdown below.", "privileges.category-selector": "Configuring privileges for ", "privileges.warning": "Note: Privilege settings take effect immediately. It is not necessary to save the category after adjusting these settings.", "privileges.section-viewing": "Viewing Privileges", diff --git a/public/less/admin/paper/bootswatch.less b/public/less/admin/paper/bootswatch.less index cceb643cf6..36957a3092 100644 --- a/public/less/admin/paper/bootswatch.less +++ b/public/less/admin/paper/bootswatch.less @@ -328,6 +328,22 @@ input[type="checkbox"], .transition(240ms); } + &:indeterminate:before { + content: ""; + position: absolute; + top: 6px; + left: 6px; + display: table; + width: 6px; + height: 12px; + border-top: 2px solid #fff; + } + + &:indeterminate:after { + background-color: @brand-primary; + border-color: @brand-primary; + } + &:checked:before { content: ""; position: absolute; diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js index ef575e9fbd..f43ebef04f 100644 --- a/public/src/admin/manage/privileges.js +++ b/public/src/admin/manage/privileges.js @@ -85,7 +85,7 @@ define('admin/manage/privileges', [ } }); for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) { - var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input'); + var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"],[data-group-name="spiders"]) td[data-privilege="' + privs[x] + '"] input'); inputs.each(function (idx, el) { if (!el.checked) { el.indeterminate = true; From 2996a5dc25dba27c2c48504aff91203ce6615c55 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 Feb 2019 09:19:19 -0500 Subject: [PATCH 102/270] fix: #7359 --- src/controllers/admin/categories.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 2ab344607e..73c8eb74db 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -15,7 +15,20 @@ categoriesController.get = function (req, res, callback) { async.parallel({ category: async.apply(categories.getCategories, [req.params.category_id], req.uid), parent: async.apply(categories.getParents, [req.params.category_id]), - allCategories: async.apply(categories.buildForSelect, req.uid, 'read'), + allCategories: function (next) { + async.waterfall([ + function (next) { + categories.getAllCidsFromSet('categories:cid', next); + }, + function (cids, next) { + categories.getCategories(cids, req.uid, next); + }, + function (categoryData, next) { + categoryData = categories.getTree(categoryData); + categories.buildForSelectCategories(categoryData, next); + }, + ], next); + }, }, next); }, function (data, next) { From 45c322aeb006b6fde04f0c4c3952529f807d59aa 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 Feb 2019 09:46:25 -0500 Subject: [PATCH 103/270] fix: #7357 --- public/src/client/login.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/login.js b/public/src/client/login.js index 70c96088de..b571ebf021 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -69,7 +69,7 @@ define('forum/login', [], function () { return false; }); - if ($('#content #username').attr('readonly')) { + if ($('#content #username').val()) { $('#content #password').val('').focus(); } else { $('#content #username').focus(); From 6c2f48f189fabd2c8b4a1fe21249a7eed2f4a09d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Feb 2019 10:54:12 -0500 Subject: [PATCH 104/270] fix: #7366 --- public/src/client/chats.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 5ffa05f4a4..3f2dc5ee92 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -358,6 +358,10 @@ define('forum/chats', [ }; Chats.createAutoComplete = function (element) { + if (!element.length) { + return; + } + var data = { element: element, strategies: [], From ab0e547d2324d998ce149334ed4ec397fcd874d0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 13 Feb 2019 11:02:57 -0500 Subject: [PATCH 105/270] fix: autocomplete not triggered if chat switched --- public/src/client/chats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 3f2dc5ee92..f5b1d094cb 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -28,7 +28,6 @@ define('forum/chats', [ recentChats.init(); Chats.addEventListeners(); - Chats.createAutoComplete($('[component="chat/input"]')); Chats.resizeMainWindow(); if (env === 'md' || env === 'lg') { @@ -59,6 +58,7 @@ define('forum/chats', [ Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content')); Chats.addCharactersLeftHandler($('[component="chat/main-wrapper"]')); Chats.addIPHandler($('[component="chat/main-wrapper"]')); + Chats.createAutoComplete($('[component="chat/input"]')); $('[data-action="close"]').on('click', function () { Chats.switchChat(); From 264eadde178ac83f8e90ac5d552dd0a7afe55f35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 13 Feb 2019 14:40:24 -0500 Subject: [PATCH 106/270] fix(deps): update dependency async to v2.6.2 (#7365) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 203531a59d..7ccb3a699f 100644 --- a/install/package.json +++ b/install/package.json @@ -31,7 +31,7 @@ "dependencies": { "ace-builds": "^1.2.9", "archiver": "^3.0.0", - "async": "2.6.1", + "async": "2.6.2", "autoprefixer": "^9.4.6", "bcryptjs": "2.4.3", "benchpressjs": "^1.2.5", From d0874f34b12eefca6af6001b44906645f8d1eba2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Fri, 15 Feb 2019 10:40:54 -0500 Subject: [PATCH 107/270] fix(deps): update dependency nodebb-plugin-composer-default to v6.2.5 (#7374) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 7ccb3a699f..b5aedd7aba 100644 --- a/install/package.json +++ b/install/package.json @@ -80,7 +80,7 @@ "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", "nconf": "^0.10.0", - "nodebb-plugin-composer-default": "6.2.4", + "nodebb-plugin-composer-default": "6.2.5", "nodebb-plugin-dbsearch": "3.0.6", "nodebb-plugin-emoji": "^2.2.5", "nodebb-plugin-emoji-android": "2.0.0", From c2e7ae7f41db1967a65be70b706e4e007e8e2fb5 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 Feb 2019 10:51:18 -0500 Subject: [PATCH 108/270] fix: #7373 --- public/less/admin/manage/categories.less | 2 +- src/views/admin/partials/categories/category-rows.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/less/admin/manage/categories.less b/public/less/admin/manage/categories.less index 28eb9c7ff9..ce2293beae 100644 --- a/public/less/admin/manage/categories.less +++ b/public/less/admin/manage/categories.less @@ -42,7 +42,7 @@ div.categories { } } - .disabled { + .disabled > .category-row { .icon, .category-header, .description { opacity: 0.5; diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl index c4bfd008a5..7255dbf613 100644 --- a/src/views/admin/partials/categories/category-rows.tpl +++ b/src/views/admin/partials/categories/category-rows.tpl @@ -1,7 +1,7 @@
    • class="disabled"> -
      +
      From d5ece9a4c3e0893d3734fbda9d24a61e522d4dfb 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 Feb 2019 11:17:27 -0500 Subject: [PATCH 109/270] fix: don't refresh page when enabling/disabling categories --- public/src/admin/manage/categories.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 409e5fd02f..e85e93d873 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -27,12 +27,20 @@ define('admin/manage/categories', [ var cid = $this.attr('data-cid'); var parentEl = $this.parents('li[data-cid="' + cid + '"]'); var disabled = parentEl.hasClass('disabled'); - - var children = parentEl.find('li[data-cid]').map(function () { + var childrenEls = parentEl.find('li[data-cid]'); + var childrenCids = childrenEls.map(function () { return $(this).attr('data-cid'); }).get(); - Categories.toggle([cid].concat(children), !disabled); + parentEl.toggleClass('disabled', !disabled); + childrenEls.toggleClass('disabled', !disabled); + + $this.translateText(!disabled ? '[[admin/manage/categories:enable]]' : '[[admin/manage/categories:disable]]'); + $this.toggleClass('btn-primary', !disabled).toggleClass('btn-danger', disabled); + childrenEls.find('button[data-action="toggle"]').translateText(!disabled ? '[[admin/manage/categories:enable]]' : '[[admin/manage/categories:disable]]'); + childrenEls.find('button[data-action="toggle"]').toggleClass('btn-primary', !disabled).toggleClass('btn-danger', disabled); + + Categories.toggle([cid].concat(childrenCids), !disabled); return false; }); @@ -162,7 +170,6 @@ define('admin/manage/categories', [ if (err) { return app.alertError(err.message); } - ajaxify.refresh(); }); }; From 949b10f132bbea7900048b993122629b7bfc227f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 15 Feb 2019 16:41:49 -0500 Subject: [PATCH 110/270] fix: no relative path needed in req.session.returnTo re: julianlam/nodebb-plugin-session-sharing#73 --- src/controllers/helpers.js | 4 ++-- src/middleware/user.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 6068de3091..efdf97e811 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -136,10 +136,10 @@ helpers.notAllowed = function (req, res, error) { }); } } else if (res.locals.isAPI) { - req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, ''); + req.session.returnTo = req.url.replace(/^\/api/, ''); res.status(401).json('not-authorized'); } else { - req.session.returnTo = nconf.get('relative_path') + req.url; + req.session.returnTo = req.url; res.redirect(nconf.get('relative_path') + '/login'); } }); diff --git a/src/middleware/user.js b/src/middleware/user.js index 86cb998332..f43319e1dd 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -229,7 +229,7 @@ module.exports = function (middleware) { } returnTo = returnTo.replace(/^\/api/, ''); - req.session.returnTo = nconf.get('relative_path') + returnTo; + req.session.returnTo = returnTo; req.session.forceLogin = 1; if (res.locals.isAPI) { res.status(401).json({}); From aed5b29dd0243a8d9efbc820ec52908c713492e9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 11 Feb 2019 22:51:06 +0000 Subject: [PATCH 111/270] chore(deps): update commitlint monorepo --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index b5aedd7aba..281210b977 100644 --- a/install/package.json +++ b/install/package.json @@ -132,8 +132,8 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@commitlint/cli": "7.4.0", - "@commitlint/config-angular": "7.3.1", + "@commitlint/cli": "7.5.2", + "@commitlint/config-angular": "7.5.0", "coveralls": "3.0.2", "eslint": "5.12.1", "eslint-config-airbnb-base": "13.1.0", From 9d8d2d0dc1eda6233b8bff4a2a6172296e3183c3 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 15 Feb 2019 20:35:36 +0000 Subject: [PATCH 112/270] chore(deps): update dependency eslint to v5.14.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 281210b977..35cc305d62 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "@commitlint/cli": "7.5.2", "@commitlint/config-angular": "7.5.0", "coveralls": "3.0.2", - "eslint": "5.12.1", + "eslint": "5.14.0", "eslint-config-airbnb-base": "13.1.0", "eslint-plugin-import": "2.16.0", "grunt": "1.0.3", From 18c90913ee173c47a38edb9364c18f4c1a012ab5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 14 Feb 2019 11:24:05 +0000 Subject: [PATCH 113/270] chore(deps): update dependency lint-staged to v8.1.4 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 35cc305d62..0c72747b78 100644 --- a/install/package.json +++ b/install/package.json @@ -142,7 +142,7 @@ "grunt-contrib-watch": "1.1.0", "husky": "1.3.1", "jsdom": "13.1.0", - "lint-staged": "8.1.1", + "lint-staged": "8.1.4", "mocha": "5.2.0", "mocha-lcov-reporter": "1.3.0", "nyc": "13.1.0", From a78f5da5eec59236540470c9cd649fc1d43fcdca Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 14 Feb 2019 23:20:42 +0000 Subject: [PATCH 114/270] chore(deps): update dependency nyc to v13.3.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0c72747b78..0c01eb2d3a 100644 --- a/install/package.json +++ b/install/package.json @@ -145,7 +145,7 @@ "lint-staged": "8.1.4", "mocha": "5.2.0", "mocha-lcov-reporter": "1.3.0", - "nyc": "13.1.0", + "nyc": "13.3.0", "smtp-server": "3.5.0" }, "bugs": { From edcb531401c3418cc971dfe64d9aebfddb5e91b2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 7 Feb 2019 18:47:48 +0000 Subject: [PATCH 115/270] chore(deps): update node:8.15.0 docker digest to a8a9d8e --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 94f93699a7..9551d92b1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # The base image is the latest 8.x node (LTS) -FROM node:8.15.0@sha256:cb66110c9c7d84bae9a6db8675f49d5c9e34d528023ef185b186e29ae5461051 +FROM node:8.15.0@sha256:a8a9d8eaab36bbd188612375a54fb7f57418458812dabd50769ddd3598bc24fc RUN mkdir -p /usr/src/app WORKDIR /usr/src/app From cb5e3d8333fda09d6939ecaf1a00346f331402f5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 15 Feb 2019 21:56:59 +0000 Subject: [PATCH 116/270] chore(deps): update dependency jsdom to v13.2.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0c01eb2d3a..b5add7b013 100644 --- a/install/package.json +++ b/install/package.json @@ -141,7 +141,7 @@ "grunt": "1.0.3", "grunt-contrib-watch": "1.1.0", "husky": "1.3.1", - "jsdom": "13.1.0", + "jsdom": "13.2.0", "lint-staged": "8.1.4", "mocha": "5.2.0", "mocha-lcov-reporter": "1.3.0", From 45181987861ba052cd252c8a78122c17ba093ef8 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sat, 16 Feb 2019 09:24:42 +0000 Subject: [PATCH 117/270] Latest translations and fallbacks --- .../language/he/admin/general/dashboard.json | 100 +++++++++--------- public/language/zh-CN/email.json | 2 +- public/language/zh-CN/error.json | 6 +- public/language/zh-CN/notifications.json | 4 +- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/public/language/he/admin/general/dashboard.json b/public/language/he/admin/general/dashboard.json index cd420c4ff4..4a8617e9e8 100644 --- a/public/language/he/admin/general/dashboard.json +++ b/public/language/he/admin/general/dashboard.json @@ -1,76 +1,76 @@ { - "forum-traffic": "Forum Traffic", - "page-views": "Page Views", - "unique-visitors": "Unique Visitors", + "forum-traffic": "תעבורת הפורום", + "page-views": "צפיות בדפים", + "unique-visitors": "מבקרים ייחודיים", "users": "משתמשים", "posts": "פוסטים", "topics": "נושאים", "page-views-seven": "7 ימים אחרונים", "page-views-thirty": "30 ימים אחרונים", "page-views-last-day": "24 שעות אחרונות", - "page-views-custom": "Custom Date Range", - "page-views-custom-start": "Range Start", - "page-views-custom-end": "Range End", - "page-views-custom-help": "Enter a date range of page views you would like to view. If no date picker is available, the accepted format is YYYY-MM-DD", - "page-views-custom-error": "Please enter a valid date range in the format YYYY-MM-DD", + "page-views-custom": "טווח תאריך ידני", + "page-views-custom-start": "תחילת טווח", + "page-views-custom-end": "סוף טווח", + "page-views-custom-help": "הכנס טווח תאריך עבור התקופה שבה תרצה לצפות בצפיות העמודים. הפורמט הנדרש הוא YYYY-MM-DD", + "page-views-custom-error": "נא הזן טווח תאריכים תקין כלהלן YYYY-MM-DD", "stats.day": "יום", "stats.week": "שבוע", "stats.month": "חודש", - "stats.all": "All Time", + "stats.all": "כל הזמנים", - "updates": "Updates", - "running-version": "You are running NodeBB v%1.", - "keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.", - "up-to-date": "

      You are up-to-date

      ", + "updates": "עדכונים", + "running-version": "אתה עובד עם NodeBB גרסה%1", + "keep-updated": "תמיד תוודא שמערכת NodeBB שלך עדכנית לטובת עדכוני אבטחה ותיקוני באגים", + "up-to-date": "

      אתה עדכני

      ", "upgrade-available": "

      A new version (v%1) has been released. Consider upgrading your NodeBB.

      ", "prerelease-upgrade-available": "

      This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider upgrading your NodeBB.

      ", "prerelease-warning": "

      This is a pre-release version of NodeBB. Unintended bugs may occur.

      ", "running-in-development": "Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.", - "latest-lookup-failed": "

      Failed to look up latest available version of NodeBB

      ", + "latest-lookup-failed": "

      נכשל בבדיקת גרסה חדשה זמינה עבור NodeBB

      ", - "notices": "Notices", - "restart-not-required": "Restart not required", - "restart-required": "Restart required", - "search-plugin-installed": "Search Plugin installed", - "search-plugin-not-installed": "Search Plugin not installed", - "search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality", + "notices": "הודעות", + "restart-not-required": "לא נדרש אתחול מחדש", + "restart-required": "נדרש אתחול מחדש", + "search-plugin-installed": "תוסף חיפוש הותקן", + "search-plugin-not-installed": "תוסף חיפוש לא הותקן", + "search-plugin-tooltip": "התקן את תוסף החיפוש מעמוד התוספים על מנת להפעיל את אפשרות החיפוש", - "control-panel": "System Control", - "rebuild-and-restart": "Rebuild & Restart", - "restart": "Restart", - "restart-warning": "Rebuilding or Restarting your NodeBB will drop all existing connections for a few seconds.", - "restart-disabled": "Rebuilding and Restarting your NodeBB has been disabled as you do not seem to be running it via the appropriate daemon.", - "maintenance-mode": "Maintenance Mode", - "maintenance-mode-title": "Click here to set up maintenance mode for NodeBB", - "realtime-chart-updates": "Realtime Chart Updates", + "control-panel": "שליטה מערכתית", + "rebuild-and-restart": "בנייה מחדש ואתחול", + "restart": "אתחול", + "restart-warning": "בנייה או אתחול מערכת NodeBB תנתק את כל המשתמשים הקיימים למספר שניות", + "restart-disabled": "בניית ואתחול מערכת NodeBB לא אפשרית, מאחר ואתה לא מריץ את המערכת בדרך הנדרשת", + "maintenance-mode": "מצב תחזוקה", + "maintenance-mode-title": "לחץ כאן על מנת להכניס את NodeBB למצב תחזוקה", + "realtime-chart-updates": "עדכון זמן אמת של הגרף", - "active-users": "Active Users", + "active-users": "משתמשים פעילים", "active-users.users": "משתמשים", "active-users.guests": "אורחים", - "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users.total": "סך הכל", + "active-users.connections": "חיבורים", - "anonymous-registered-users": "Anonymous vs Registered Users", - "anonymous": "Anonymous", - "registered": "Registered", + "anonymous-registered-users": "משתמשים רשומים כנגד אורחים", + "anonymous": "אורחים", + "registered": "רשום", - "user-presence": "User Presence", - "on-categories": "On categories list", - "reading-posts": "Reading posts", - "browsing-topics": "Browsing topics", - "recent": "Recent", - "unread": "Unread", + "user-presence": "נוכחות משתמש", + "on-categories": "על רשימת הקטגוריות", + "reading-posts": "קריאת פוסטים", + "browsing-topics": "חיפוש נושאים", + "recent": "לאחרונה", + "unread": "לא נקראו", - "high-presence-topics": "High Presence Topics", + "high-presence-topics": "פוסטים עם נוכחות גבוהה", - "graphs.page-views": "Page Views", - "graphs.page-views-registered": "Page Views Registered", - "graphs.page-views-guest": "Page Views Guest", - "graphs.page-views-bot": "Page Views Bot", - "graphs.unique-visitors": "Unique Visitors", - "graphs.registered-users": "Registered Users", - "graphs.anonymous-users": "Anonymous Users", - "last-restarted-by": "Last restarted by", - "no-users-browsing": "No users browsing" + "graphs.page-views": "צפיות בדפים", + "graphs.page-views-registered": "צפיות בדפים של רשומים", + "graphs.page-views-guest": "צפיות בדפים של אורחים", + "graphs.page-views-bot": "צפיות של בוטים", + "graphs.unique-visitors": "מבקרים ייחודיים", + "graphs.registered-users": "משתמשים רשומים", + "graphs.anonymous-users": "משתמשים אנונימיים", + "last-restarted-by": "אותחל לארונה על ידי", + "no-users-browsing": "אין משתמשים גולשים" } diff --git a/public/language/zh-CN/email.json b/public/language/zh-CN/email.json index 1e29c7854d..e72bcd3083 100644 --- a/public/language/zh-CN/email.json +++ b/public/language/zh-CN/email.json @@ -1,5 +1,5 @@ { - "test-email.subject": "Test Email", + "test-email.subject": "测试邮件", "password-reset-requested": "Password Reset Requested!", "welcome-to": "欢迎来到 %1", "invite": "来自%1的邀请", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index b07bf6470c..d3f66378a2 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -153,8 +153,8 @@ "cant-move-to-same-topic": "无法将帖子移动到相同的主题中!", "cannot-block-self": "您不能把自己屏蔽!", "cannot-block-privileged": "您不能屏蔽管理员或者全局版主", - "cannot-block-guest": "Guest are not able to block other users", - "already-blocked": "This user is already blocked", - "already-unblocked": "This user is already unblocked", + "cannot-block-guest": "游客无法屏蔽其他用户", + "already-blocked": "此用户已被屏蔽", + "already-unblocked": "此用户已被取消屏蔽", "no-connection": "您的网络连接似乎存在问题" } \ No newline at end of file diff --git a/public/language/zh-CN/notifications.json b/public/language/zh-CN/notifications.json index 32e8a6b9f2..31a7161d3d 100644 --- a/public/language/zh-CN/notifications.json +++ b/public/language/zh-CN/notifications.json @@ -8,7 +8,7 @@ "outgoing_link_message": "您正在离开 %1", "continue_to": "继续前往 %1", "return_to": "返回 %1", - "new_notification": "You have a new notification", + "new_notification": "您有一个新的通知", "you_have_unread_notifications": "您有未读的通知。", "all": "所有", "topics": "主题", @@ -56,7 +56,7 @@ "notificationType_follow": "当有人关注您时", "notificationType_new-chat": "当您收到聊天消息时", "notificationType_group-invite": "当您收到群组邀请时", - "notificationType_group-request-membership": "When someone requests to join a group you own", + "notificationType_group-request-membership": "当有人请求加入您拥有的用户组时", "notificationType_new-register": "当有人被添加到申请队列时", "notificationType_post-queue": "当有新帖子等待审核时", "notificationType_new-post-flag": "当有新的帖子举报时", From c01d43e01df2fe0a4d13a21e150431d3fbeff24e 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 Feb 2019 16:20:58 -0500 Subject: [PATCH 118/270] feat: quick search --- public/language/en-GB/search.json | 3 ++- public/src/app.js | 38 +++++++++++++++++++++++++++++++ public/src/modules/search.js | 14 ++++++++++++ src/controllers/search.js | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/public/language/en-GB/search.json b/public/language/en-GB/search.json index 061cd0423c..48a4acb446 100644 --- a/public/language/en-GB/search.json +++ b/public/language/en-GB/search.json @@ -43,5 +43,6 @@ "clear-preferences": "Clear preferences", "search-preferences-saved": "Search preferences saved", "search-preferences-cleared": "Search preferences cleared", - "show-results-as": "Show results as" + "show-results-as": "Show results as", + "see-more-results": "See more results" } diff --git a/public/src/app.js b/public/src/app.js index a300db8901..6b0dcb4cec 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -568,6 +568,7 @@ app.cacheBuster = null; var searchButton = $('#search-button'); var searchFields = $('#search-fields'); var searchInput = $('#search-fields input'); + var quickSearchResults = $('#quick-search-results'); $('#search-form .advanced-search-link').on('mousedown', function () { ajaxify.go('/search'); @@ -575,10 +576,47 @@ app.cacheBuster = null; $('#search-form').on('submit', dismissSearch); searchInput.on('blur', dismissSearch); + searchInput.on('focus', function () { + if (searchInput.val() && quickSearchResults.children().length) { + quickSearchResults.removeClass('hidden').show(); + } + }); + + var searchTimeoutId = 0; + searchInput.on('keyup', function () { + if (searchTimeoutId) { + clearTimeout(searchTimeoutId); + searchTimeoutId = 0; + } + if (searchInput.val().length < 3) { + return; + } + + searchTimeoutId = setTimeout(function () { + require(['search'], function (search) { + var data = search.getSearchPreferences(); + data.term = searchInput.val(); + data.in = 'titles'; + data.searchOnly = 1; + search.api(data, function (data) { + if (!data.matchCount) { + return; + } + + app.parseAndTranslate('partials/quick-search-results', data, function (html) { + quickSearchResults.html(html).removeClass('hidden').show(); + }); + }); + }); + }, 400); + }); function dismissSearch() { searchFields.addClass('hidden'); searchButton.removeClass('hidden'); + setTimeout(function () { + quickSearchResults.addClass('hidden'); + }, 100); } searchButton.on('click', function (e) { diff --git a/public/src/modules/search.js b/public/src/modules/search.js index ea0f775fd1..5da78c2b8f 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -23,6 +23,16 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat } }; + Search.api = function (data, callback) { + var apiURL = '/api/search?' + createQueryString(data); + data.searchOnly = undefined; + var searchURL = '/search?' + createQueryString(data); + $.get(apiURL, function (result) { + result.url = searchURL; + callback(result); + }); + }; + function createQueryString(data) { var searchIn = data.in || 'titlesposts'; var postedBy = data.by || ''; @@ -76,6 +86,10 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat query.showAs = data.showAs; } + if (data.searchOnly) { + query.searchOnly = data.searchOnly; + } + $(window).trigger('action:search.createQueryString', { query: query, data: data, diff --git a/src/controllers/search.js b/src/controllers/search.js index 443d2801c3..0ee2ec4cc0 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -67,6 +67,7 @@ searchController.search = function (req, res, next) { var searchData = results.search; searchData.pagination = pagination.create(page, searchData.pageCount, req.query); + searchData.multiplePages = searchData.pageCount > 1; searchData.search_query = validator.escape(String(req.query.term || '')); searchData.term = req.query.term; From 546e04e153b313f19fdedf2e3fc77412c3be2d74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Sat, 16 Feb 2019 16:24:58 -0500 Subject: [PATCH 119/270] fix(deps): update dependency nodebb-theme-persona to v9.1.17 (#7379) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b5add7b013..2e4ebf61e0 100644 --- a/install/package.json +++ b/install/package.json @@ -90,7 +90,7 @@ "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", - "nodebb-theme-persona": "9.1.16", + "nodebb-theme-persona": "9.1.17", "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", From 042b81a03ec34ee16a8853cdbc299118f7b78aad Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 18 Feb 2019 03:38:31 +0000 Subject: [PATCH 120/270] fix(deps): update dependency nodebb-theme-persona to v9.1.18 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2e4ebf61e0..cb2a974102 100644 --- a/install/package.json +++ b/install/package.json @@ -90,7 +90,7 @@ "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", - "nodebb-theme-persona": "9.1.17", + "nodebb-theme-persona": "9.1.18", "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", From 4df9c206fbcd04ef2ad5a49ae46e5c3b6e63863a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 18 Feb 2019 17:31:05 +0000 Subject: [PATCH 121/270] chore(deps): update dependency eslint to v5.14.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index cb2a974102..37a343cb6f 100644 --- a/install/package.json +++ b/install/package.json @@ -135,7 +135,7 @@ "@commitlint/cli": "7.5.2", "@commitlint/config-angular": "7.5.0", "coveralls": "3.0.2", - "eslint": "5.14.0", + "eslint": "5.14.1", "eslint-config-airbnb-base": "13.1.0", "eslint-plugin-import": "2.16.0", "grunt": "1.0.3", From 57069a5c489101dee1e9171da56946cf8ecf9455 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 18 Feb 2019 15:33:49 -0500 Subject: [PATCH 122/270] fix: #7385 better handling for errors in Adv>Databases --- .../language/en-GB/admin/advanced/database.json | 1 + src/controllers/admin/database.js | 17 ++++++++++++----- src/views/admin/advanced/database.tpl | 7 +++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/public/language/en-GB/admin/advanced/database.json b/public/language/en-GB/admin/advanced/database.json index 9fccd39b48..9167b381ed 100644 --- a/public/language/en-GB/admin/advanced/database.json +++ b/public/language/en-GB/admin/advanced/database.json @@ -22,6 +22,7 @@ "mongo.bytes-out": "Bytes Out", "mongo.num-requests": "Number of Requests", "mongo.raw-info": "MongoDB Raw Info", + "mongo.unauthorized": "NodeBB was unable to query the MongoDB database for relevant statistics. Please ensure that the user in use by NodeBB contains the "clusterMonitor" role for the "admin" database.", "redis": "Redis", "redis.version": "Redis Version", diff --git a/src/controllers/admin/database.js b/src/controllers/admin/database.js index 147747822c..a67ccd5aa4 100644 --- a/src/controllers/admin/database.js +++ b/src/controllers/admin/database.js @@ -5,7 +5,7 @@ var nconf = require('nconf'); var databaseController = module.exports; -databaseController.get = function (req, res, next) { +databaseController.get = function (req, res) { async.waterfall([ function (next) { async.parallel({ @@ -35,8 +35,15 @@ databaseController.get = function (req, res, next) { }, }, next); }, - function (results) { - res.render('admin/advanced/database', results); - }, - ], next); + ], function (err, results) { + Object.assign(results, { error: err }); + + // Override mongo error with more human-readable error + if (err.name === 'MongoError' && err.codeName === 'Unauthorized') { + err.friendlyMessage = '[[admin/advanced/database:mongo.unauthorized]]'; + delete results.mongo; + } + + res.render('admin/advanced/database', results); + }); }; diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl index 086b24e179..3e6a6b541e 100644 --- a/src/views/admin/advanced/database.tpl +++ b/src/views/admin/advanced/database.tpl @@ -1,5 +1,12 @@
      + +
      + {error.friendlyMessage} +
      + {error.errmsg} +
      +
      [[admin/advanced/database:mongo]]
      From 7a5344533e79fb8a905fbf19e4b61da7f1f02466 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Feb 2019 11:20:50 -0500 Subject: [PATCH 123/270] fix: tests for #7385 --- src/controllers/admin/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/admin/database.js b/src/controllers/admin/database.js index a67ccd5aa4..3a3946395f 100644 --- a/src/controllers/admin/database.js +++ b/src/controllers/admin/database.js @@ -39,7 +39,7 @@ databaseController.get = function (req, res) { Object.assign(results, { error: err }); // Override mongo error with more human-readable error - if (err.name === 'MongoError' && err.codeName === 'Unauthorized') { + if (err && err.name === 'MongoError' && err.codeName === 'Unauthorized') { err.friendlyMessage = '[[admin/advanced/database:mongo.unauthorized]]'; delete results.mongo; } From d2b839676437557938264ec44b54dfc61dbc8e6c Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Tue, 19 Feb 2019 11:26:26 -0500 Subject: [PATCH 124/270] feat: make topic search a function --- public/src/app.js | 65 +++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 6b0dcb4cec..bcb1cfd325 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -564,6 +564,41 @@ app.cacheBuster = null; }); } + app.enableTopicSearch = function (options) { + var quickSearchResults = options.resultEl; + var inputEl = options.inputEl; + var template = options.template || 'partials/quick-search-results'; + var searchTimeoutId = 0; + inputEl.on('keyup', function () { + if (searchTimeoutId) { + clearTimeout(searchTimeoutId); + searchTimeoutId = 0; + } + if (inputEl.val().length < 3) { + return; + } + + searchTimeoutId = setTimeout(function () { + require(['search'], function (search) { + var data = search.getSearchPreferences(); + data.term = inputEl.val(); + data.in = 'titles'; + data.searchOnly = 1; + search.api(data, function (data) { + if (!data.matchCount) { + quickSearchResults.html('').addClass('hidden'); + return; + } + + app.parseAndTranslate(template, data, function (html) { + quickSearchResults.html(html).removeClass('hidden').show(); + }); + }); + }); + }, 400); + }); + }; + app.handleSearch = function () { var searchButton = $('#search-button'); var searchFields = $('#search-fields'); @@ -582,33 +617,9 @@ app.cacheBuster = null; } }); - var searchTimeoutId = 0; - searchInput.on('keyup', function () { - if (searchTimeoutId) { - clearTimeout(searchTimeoutId); - searchTimeoutId = 0; - } - if (searchInput.val().length < 3) { - return; - } - - searchTimeoutId = setTimeout(function () { - require(['search'], function (search) { - var data = search.getSearchPreferences(); - data.term = searchInput.val(); - data.in = 'titles'; - data.searchOnly = 1; - search.api(data, function (data) { - if (!data.matchCount) { - return; - } - - app.parseAndTranslate('partials/quick-search-results', data, function (html) { - quickSearchResults.html(html).removeClass('hidden').show(); - }); - }); - }); - }, 400); + app.enableTopicSearch({ + inputEl: searchInput, + resultEl: quickSearchResults, }); function dismissSearch() { From fab32a4963a631290644f6ce477ab93e93fb44ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Tue, 19 Feb 2019 11:37:26 -0500 Subject: [PATCH 125/270] fix(deps): update dependency nodebb-plugin-composer-default to v6.2.6 (#7389) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 37a343cb6f..32c38685e9 100644 --- a/install/package.json +++ b/install/package.json @@ -80,7 +80,7 @@ "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", "nconf": "^0.10.0", - "nodebb-plugin-composer-default": "6.2.5", + "nodebb-plugin-composer-default": "6.2.6", "nodebb-plugin-dbsearch": "3.0.6", "nodebb-plugin-emoji": "^2.2.5", "nodebb-plugin-emoji-android": "2.0.0", From eafe76debb5cc07d7459c06ef1734847ee3b0c60 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Feb 2019 11:41:16 -0500 Subject: [PATCH 126/270] feat: add vote status to getPostData API call --- src/controllers/api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/api.js b/src/controllers/api.js index 746f67184f..8649259b2b 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -133,14 +133,16 @@ apiController.getPostData = function (pid, uid, callback) { post: function (next) { posts.getPostData(pid, next); }, + voted: async.apply(posts.hasVoted, pid, uid), }, function (err, results) { if (err || !results.post) { return callback(err); } var post = results.post; - var privileges = results.privileges[0]; + Object.assign(post, results.voted); + var privileges = results.privileges[0]; if (!privileges.read || !privileges['topics:read']) { return callback(); } From 158f68eb4731bf8b7c3b3393c3c4bd5d8c191d95 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Feb 2019 11:54:12 -0500 Subject: [PATCH 127/270] fix: search.api not working on subfolder --- public/src/modules/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 5da78c2b8f..b6b6593d49 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -24,9 +24,9 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat }; Search.api = function (data, callback) { - var apiURL = '/api/search?' + createQueryString(data); + var apiURL = config.relative_path + '/api/search?' + createQueryString(data); data.searchOnly = undefined; - var searchURL = '/search?' + createQueryString(data); + var searchURL = config.relative_path + '/search?' + createQueryString(data); $.get(apiURL, function (result) { result.url = searchURL; callback(result); From f972f752027b03343c8edf4ba95f0eeeb7f980e0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Feb 2019 15:47:37 -0500 Subject: [PATCH 128/270] fix: incorrect returnTo set in registerComplete --- src/controllers/authentication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 31b59eee93..5901cf5ead 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -173,7 +173,7 @@ authenticationController.registerComplete = function (req, res, next) { return res.redirect(nconf.get('relative_path') + '/?register=' + encodeURIComponent(data.message)); } if (req.session.returnTo) { - res.redirect(req.session.returnTo); + res.redirect(nconf.get('relative_path') + req.session.returnTo); } else { res.redirect(nconf.get('relative_path') + '/'); } From d722f3b8b6df62338a064f1b1eca546f8335e366 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 19 Feb 2019 15:49:34 -0500 Subject: [PATCH 129/270] fix: mounting of all-route middlewares to router instead of app related to julianlam/nodebb-plugin-session-sharing#73 These three lines haven't changed in two years, but it makes more sense for them to be called against the relative path router, otherwise req.originalUrl contains the relative path, which is not necessary. --- src/routes/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 2a6d787f78..b6809d1e54 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -95,12 +95,11 @@ module.exports = function (app, middleware, callback) { router.render = function () { app.render.apply(app, arguments); }; - var relativePath = nconf.get('relative_path'); var ensureLoggedIn = require('connect-ensure-login'); - app.all(relativePath + '(/+api|/+api/*?)', middleware.prepareAPI); - app.all(relativePath + '(/+api/admin|/+api/admin/*?)', middleware.isAdmin); - app.all(relativePath + '(/+admin|/+admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.isAdmin); + router.all('(/+api|/+api/*?)', middleware.prepareAPI); + router.all('(/+api/admin|/+api/admin/*?)', middleware.isAdmin); + router.all('(/+admin|/+admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.isAdmin); app.use(middleware.stripLeadingSlashes); From ea66fc3fa0f022ad07d67ed9efe2cf71bddcec1a Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 20 Feb 2019 09:25:14 +0000 Subject: [PATCH 130/270] Latest translations and fallbacks --- public/language/ko/admin/settings/general.json | 2 +- public/language/ko/admin/settings/uploads.json | 4 ++-- public/language/ko/error.json | 6 +++--- public/language/ko/modules.json | 4 ++-- public/language/ko/search.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/public/language/ko/admin/settings/general.json b/public/language/ko/admin/settings/general.json index dc0eab700d..3b30ac4167 100644 --- a/public/language/ko/admin/settings/general.json +++ b/public/language/ko/admin/settings/general.json @@ -3,7 +3,7 @@ "title": "사이트 제목", "title.url": "URL", "title.url-placeholder": "사이트 제목의 URL", - "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", + "title.url-help": "제목을 클릭하면 사용자가 이 주소로 이동합니다. 비워두면 사용자가 포럼 인덱스로 이동합니다.", "title.name": "커뮤니티 이름", "title.show-in-header": "헤더에 사이트 제목 표시", "browser-title": "브라우저 타이틀", diff --git a/public/language/ko/admin/settings/uploads.json b/public/language/ko/admin/settings/uploads.json index 1f76d2a38b..edf8d7e8de 100644 --- a/public/language/ko/admin/settings/uploads.json +++ b/public/language/ko/admin/settings/uploads.json @@ -2,8 +2,8 @@ "posts": "게시물", "allow-files": "사용자가 (이미지가 아닌) 일반 파일을 업로드하는것을 허용", "private": "업로드된 파일들을 개인만 볼 수 있게 바꿉니다.", - "private-extensions": "File extensions to make private", - "private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. pdf,xls,doc). An empty list means all files are private.", + "private-extensions": "파일 확장자를 비밀로 만들기", + "private-uploads-extensions-help": "여기에서 비공개로 설정하려면 쉼표로 구분 된 파일 확장명 목록을 입력하세요. (예: pdf, xls, doc). 빈 목록은 모든 파일이 비공개임을 의미합니다.", "resize-image-width-threshold": "Resize images if they are wider than specified width", "resize-image-width-threshold-help": "(in pixels, default: 1520 pixels, set to 0 to disable)", "resize-image-width": "Resize images down to specified width", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index d04ea6a294..66ada66bbb 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -117,8 +117,8 @@ "cant-edit-chat-message": "이 메세지를 수정 할 권한이 없습니다.", "cant-remove-last-user": "마지막 사용자를 삭제할 수 없습니다.", "cant-delete-chat-message": "이 메세지를 삭제할 권한이 없습니다.", - "chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting", - "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", + "chat-edit-duration-expired": "채팅 메시지를 게시한 뒤 %1초 뒤부터 메시지를 수정할 수 있습니다.", + "chat-delete-duration-expired": "채팅 메시지를 게시한 뒤 %1초 뒤부터 삭제가 가능합니다.", "chat-deleted-already": "이미 삭제된 대화 메시지입니다.", "chat-restored-already": "이 채팅 메시지는 이미 복원되었습니다.", "already-voting-for-this-post": "이미 이 포스트에 투표하셨습니다.", @@ -138,7 +138,7 @@ "parse-error": "서버로 부터의 응답을 읽는 동안 문제가 발생했습니다.", "wrong-login-type-email": "이메일 주소를 통해 로그인하세요.", "wrong-login-type-username": "사용자명을 통해 로그인하세요.", - "sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first", + "sso-registration-disabled": "%1 계정의 가입이 비활성화되었습니다. 이메일 주소로 먼저 가입하세요.", "sso-multiple-association": "You cannot associate multiple accounts from this service to your NodeBB account. Please dissociate your existing account and try again.", "invite-maximum-met": "초대 한도 만큼의 사용자를 초대했습니다. (%2명 중 %1을 초대)", "no-session-found": "로그인 세션을 찾을 수 없습니다.", diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 5271148a9c..b258d85c21 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -28,9 +28,9 @@ "chat.rename-placeholder": "여기에 방명을 입력하십시오", "chat.rename-help": "The room name set here will be viewable by all participants in the room.", "chat.leave": "채팅에서 나가기", - "chat.leave-prompt": "Are you sure you wish to leave this chat?", + "chat.leave-prompt": "정말로 이 채팅에서 나가시겠어요?", "chat.leave-help": "Leaving this chat will remove you from future correspondence in this chat. If you are re-added in the future, you will not see any chat history from prior to your re-joining.", - "chat.in-room": "In this room", + "chat.in-room": "이 방에서", "chat.kick": "추방", "chat.show-ip": "IP 보이기", "chat.owner": "방 소유자", diff --git a/public/language/ko/search.json b/public/language/ko/search.json index a51d04cd79..2a427a2c9d 100644 --- a/public/language/ko/search.json +++ b/public/language/ko/search.json @@ -7,7 +7,7 @@ "titles-posts": "제목과 내용", "match-words": "일치하는 단어 순", "all": "전체", - "any": "Any", + "any": "아무거나", "posted-by": "작성자", "in-categories": "게시판 지정", "search-child-categories": "하위 게시판도 검색", From 8a0e1280d66dbbb1e2c7af1f1e95862186d7704d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 20 Feb 2019 11:52:04 -0500 Subject: [PATCH 131/270] feat: quick search --- public/language/en-GB/search.json | 2 +- public/src/app.js | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/public/language/en-GB/search.json b/public/language/en-GB/search.json index 48a4acb446..55b0c5793d 100644 --- a/public/language/en-GB/search.json +++ b/public/language/en-GB/search.json @@ -44,5 +44,5 @@ "search-preferences-saved": "Search preferences saved", "search-preferences-cleared": "Search preferences cleared", "show-results-as": "Show results as", - "see-more-results": "See more results" + "see-more-results": "See more results (%1)" } diff --git a/public/src/app.js b/public/src/app.js index bcb1cfd325..df199eeab9 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -580,17 +580,21 @@ app.cacheBuster = null; searchTimeoutId = setTimeout(function () { require(['search'], function (search) { - var data = search.getSearchPreferences(); - data.term = inputEl.val(); - data.in = 'titles'; - data.searchOnly = 1; + var data = { + term: inputEl.val(), + in: 'titles', + searchOnly: 1, + }; search.api(data, function (data) { if (!data.matchCount) { quickSearchResults.html('').addClass('hidden'); return; } - + data.posts.forEach(function (p) { + p.snippet = $(p.content).text().slice(0, 80) + '...'; + }); app.parseAndTranslate(template, data, function (html) { + html.find('.timeago').timeago(); quickSearchResults.html(html).removeClass('hidden').show(); }); }); From fc830c0f41a3909af78f8db95d20e44033799f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 20 Feb 2019 11:58:16 -0500 Subject: [PATCH 132/270] feat: lower search timeout --- public/src/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/app.js b/public/src/app.js index df199eeab9..5f7bf0561c 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -599,7 +599,7 @@ app.cacheBuster = null; }); }); }); - }, 400); + }, 250); }); }; From deff7b3483d1a0bcafaac8856000372b7ec36065 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 20 Feb 2019 12:06:27 -0500 Subject: [PATCH 133/270] fix(deps): update dependency nodebb-theme-persona to v9.1.19 (#7392) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 32c38685e9..8b2796aa43 100644 --- a/install/package.json +++ b/install/package.json @@ -90,7 +90,7 @@ "nodebb-plugin-spam-be-gone": "0.6.1", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.9", - "nodebb-theme-persona": "9.1.18", + "nodebb-theme-persona": "9.1.19", "nodebb-theme-slick": "1.2.20", "nodebb-theme-vanilla": "10.1.19", "nodebb-widget-essentials": "4.0.13", From f32a992237ff805585f5d39ecde46abf11632942 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 20 Feb 2019 16:13:26 -0500 Subject: [PATCH 134/270] feat: revamp email templates to be more style agnostic (#7375) * feat: re-designed digest Updated design, logic no longer shows "no topics", just doesn't show the list at all, teaser re-retrieved for digest so that it always grabs a teaser even if there is no reply, changed default email background to a light grey. * fix: minor padding issues in digest * fix: banned template * fix: invitation.tpl * fix: removed unused notif_chat and notif_post email templates * fix: notification template * fix: registration_accepted template * fix: reset tpl * fix: test tpl * fix: email verify tpl * fix: reset notify tpl * fix: welcome tpl * fix: additional minor font fixes * fix: removed unused email header image assets * fix: internationalised 'your daily digest' string * fix: broken url in digest :facepalm: * feat: added RTL support for emailer /cc @PostMidnight --- public/images/emails/banneduser.png | Bin 6250 -> 0 bytes public/images/emails/digestheader.jpg | Bin 11833 -> 0 bytes public/images/emails/emailconfirm.png | Bin 4667 -> 0 bytes public/images/emails/invitation.png | Bin 4764 -> 0 bytes public/images/emails/newtopic.png | Bin 5060 -> 0 bytes public/images/emails/nodebb.png | Bin 4205 -> 0 bytes public/images/emails/notification.png | Bin 4673 -> 0 bytes public/images/emails/password.png | Bin 5044 -> 0 bytes public/images/emails/triangularbackground.png | Bin 47961 -> 0 bytes public/images/emails/unreadpost.png | Bin 3733 -> 0 bytes public/language/en-GB/email.json | 2 +- public/src/modules/helpers.js | 8 +- src/emailer.js | 4 +- src/socket.io/admin.js | 3 +- src/topics/teaser.js | 5 +- src/user/digest.js | 20 +++- src/views/emails/banned.tpl | 41 ++++---- src/views/emails/digest.tpl | 97 ++++++++++-------- src/views/emails/invitation.tpl | 37 +++---- src/views/emails/notif_chat.tpl | 58 ----------- src/views/emails/notif_post.tpl | 57 ---------- src/views/emails/notification.tpl | 93 ++++++++--------- src/views/emails/partials/footer.tpl | 2 +- src/views/emails/partials/header.tpl | 8 +- src/views/emails/registration_accepted.tpl | 36 ++++--- src/views/emails/reset.tpl | 38 +++---- src/views/emails/reset_notify.tpl | 43 +++++--- src/views/emails/test.tpl | 35 ++++--- src/views/emails/verify_email.tpl | 37 +++---- src/views/emails/welcome.tpl | 24 +---- 30 files changed, 272 insertions(+), 376 deletions(-) delete mode 100644 public/images/emails/banneduser.png delete mode 100644 public/images/emails/digestheader.jpg delete mode 100644 public/images/emails/emailconfirm.png delete mode 100644 public/images/emails/invitation.png delete mode 100644 public/images/emails/newtopic.png delete mode 100644 public/images/emails/nodebb.png delete mode 100644 public/images/emails/notification.png delete mode 100644 public/images/emails/password.png delete mode 100644 public/images/emails/triangularbackground.png delete mode 100644 public/images/emails/unreadpost.png delete mode 100644 src/views/emails/notif_chat.tpl delete mode 100644 src/views/emails/notif_post.tpl diff --git a/public/images/emails/banneduser.png b/public/images/emails/banneduser.png deleted file mode 100644 index 4eac70d8ca1c0d7cf0cee894925be7b08dc85bf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6250 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 diff --git a/public/images/emails/digestheader.jpg b/public/images/emails/digestheader.jpg deleted file mode 100644 index 4c873e41b7aff65f4e713b465ede131bfd5c245d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11833 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 diff --git a/public/images/emails/emailconfirm.png b/public/images/emails/emailconfirm.png deleted file mode 100644 index d066015bd9dd61bc3316b2e32d161caa0eb11a51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4667 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 diff --git a/public/images/emails/nodebb.png b/public/images/emails/nodebb.png deleted file mode 100644 index 9cfe1cd70cdfab7ffbf1d5dde0ba6d9a3601b4ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4205 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%0D2gF11j#@ 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 diff --git a/public/language/en-GB/email.json b/public/language/en-GB/email.json index 813ba0f2ce..1e083e0651 100644 --- a/public/language/en-GB/email.json +++ b/public/language/en-GB/email.json @@ -32,11 +32,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.no_topics": "There have been no active topics in the past %1", "digest.day": "day", "digest.week": "week", "digest.month": "month", "digest.subject": "Digest for %1", + "digest.title": "Your Daily Digest", "notif.chat.subject": "New chat message received from %1", "notif.chat.cta": "Click here to continue the conversation", diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index b6d56c9e7b..d1b7a989d0 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -205,14 +205,14 @@ function renderDigestAvatar(block) { if (block.teaser) { if (block.teaser.user.picture) { - return ''; + return ''; } - return '
      ' + block.teaser.user['icon:text'] + '
      '; + return '
      ' + block.teaser.user['icon:text'] + '
      '; } if (block.user.picture) { - return ''; + return ''; } - return '
      ' + block.user['icon:text'] + '
      '; + return '
      ' + block.user['icon:text'] + '
      '; } function userAgentIcons(data) { diff --git a/src/emailer.js b/src/emailer.js index 33048b7ed1..d0f859efd7 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -195,12 +195,13 @@ Emailer.send = function (template, uid, params, callback) { settings: async.apply(User.getSettings, uid), }, next); }, - function (results, next) { + async function (results, next) { if (!results.email) { winston.warn('uid : ' + uid + ' has no email, not sending.'); return next(); } params.uid = uid; + params.rtl = await translator.translate('[[language:dir]]', results.settings.userLang) === 'rtl'; Emailer.sendToEmail(template, results.email, results.settings.userLang, params, next); }, ], callback); @@ -282,6 +283,7 @@ Emailer.sendToEmail = function (template, email, language, params, callback) { pid: params.pid, fromUid: params.fromUid, headers: params.headers, + rtl: params.rtl, }; Plugins.fireHook('filter:email.modify', data, next); }, diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 76a3ca6476..a8c0537e20 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -296,8 +296,9 @@ SocketAdmin.email.test = function (socket, data, callback) { showUnsubscribe: true, }, next); }, - ]); + ], callback); break; + default: emailer.send(data.template, socket.uid, payload, callback); break; diff --git a/src/topics/teaser.js b/src/topics/teaser.js index b2e2f57758..2ff12db4a8 100644 --- a/src/topics/teaser.js +++ b/src/topics/teaser.js @@ -23,6 +23,7 @@ module.exports = function (Topics) { var teaserPids = []; var postData; var tidToPost = {}; + const teaserPost = this ? this.teaserPost : meta.config.teaserPost; topics.forEach(function (topic) { counts.push(topic && topic.postcount); @@ -30,9 +31,9 @@ module.exports = function (Topics) { if (topic.teaserPid === 'null') { delete topic.teaserPid; } - if (meta.config.teaserPost === 'first') { + if (teaserPost === 'first') { teaserPids.push(topic.mainPid); - } else if (meta.config.teaserPost === 'last-post') { + } else if (teaserPost === 'last-post') { teaserPids.push(topic.teaserPid || topic.mainPid); } else { // last-reply and everything else uses teaserPid like `last` that was used before teaserPids.push(topic.teaserPid); diff --git a/src/user/digest.js b/src/user/digest.js index 8feb9b4dd9..2ece74489a 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -180,8 +180,24 @@ Digest.send = function (data, callback) { next(null, data); } }, - function (data, next) { - next(null, data.topics); + (data, next) => { + // Re-generate teasers with different teaserPost option + topics.getTeasers.bind({ teaserPost: 'last-post' })(data.topics, uid, function (err, teasers) { + if (err) { + return next(err); + } + + data.topics.map(function (topicObj, i) { + if (teasers[i].content.length > 255) { + teasers[i].content = teasers[i].content.slice(0, 255) + '...'; + } + + topicObj.teaser = teasers[i]; + return topicObj; + }); + + next(null, data.topics); + }); }, ], callback); } diff --git a/src/views/emails/banned.tpl b/src/views/emails/banned.tpl index db8975c993..20d82ba8df 100644 --- a/src/views/emails/banned.tpl +++ b/src/views/emails/banned.tpl @@ -2,46 +2,41 @@ - - - - - - - - diff --git a/src/views/emails/digest.tpl b/src/views/emails/digest.tpl index 38fba1232f..c24c7fbd9a 100644 --- a/src/views/emails/digest.tpl +++ b/src/views/emails/digest.tpl @@ -3,71 +3,88 @@
      - -
      + - + + + - + - +
      -

      [[email:greeting_with_name, {username}]]

      -

      - [[email:banned.text1, {username}, {site_title}]] - -

      [[email:banned.text2, {until}]] - -

      +
      +

      [[email:greeting_with_name, {username}]]

      +
      +

      [[email:banned.text1, {username}, {site_title}]]

      +

      [[email:banned.text3]] -

      {reason} +

      +

      + {reason}

      -

      [[email:closing]]

      -

      {site_title}

      +
      +

      + [[email:banned.text2, {until}]] +

      - - - - - - -
      - -
      + - + + + - + - + - diff --git a/src/views/emails/invitation.tpl b/src/views/emails/invitation.tpl index 799ac67fd5..64d60307d1 100644 --- a/src/views/emails/invitation.tpl +++ b/src/views/emails/invitation.tpl @@ -2,33 +2,32 @@
      -

      [[email:greeting_with_name, {username}]],

      +
      +

      [[email:greeting_with_name, {username}]]

      +
      +

      [[email:digest.title]]

      -

      [[email:digest.notifications, {site_title}]]

      -
        +
      +
      -

      [[email:digest.latest_topics, {site_title}]]

      -
        - +
      +

      [[email:digest.latest_topics, {site_title}]]

      +
      + @@ -76,12 +93,6 @@ - - -
      - +     [[email:digest.cta, {site_title}]]    
      -

      [[email:closing]]

      -

      {site_title}

      -
      - - - - - - - -
      - -
      + - - + + + + + + diff --git a/src/views/emails/notif_chat.tpl b/src/views/emails/notif_chat.tpl deleted file mode 100644 index 275a5bf782..0000000000 --- a/src/views/emails/notif_chat.tpl +++ /dev/null @@ -1,58 +0,0 @@ - - - -
      -

      [[email:greeting_no_name]]

      -

      [[email:invitation.text1, {username}, {site_title}]]

      -

      [[email:invitation.text2, {expireDays}]]

      +
      +

      [[email:greeting_no_name]]

      + +

      [[email:invitation.text1, {username}, {site_title}]]

      +
      +

      [[email:invitation.text2, {expireDays}]]

      +
      @@ -37,12 +36,6 @@ - - -
      - +     [[email:invitation.ctr]]    
      -

      [[email:closing]]

      -

      {site_title}

      -
      - - - - - - - - - - - - - -
      - -
      - - - - - - - - - - - - - -
      -

      [[email:greeting_with_name, {username}]],

      -

      {summary}:

      -
      - {message.content} -
      - - - - - -
      - -     [[email:notif.chat.cta]]     - -
      - -
      -

      [[email:closing]]

      -

      {site_title}

      -
      -
      - - - diff --git a/src/views/emails/notif_post.tpl b/src/views/emails/notif_post.tpl deleted file mode 100644 index 168743512f..0000000000 --- a/src/views/emails/notif_post.tpl +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - -
      - -
      - - - - - - - - - - - - - -
      -

      {intro}

      -
      - {postBody} -
      - - - - - -
      - -     [[email:notif.post.cta]]     - -
      - -
      -

      [[email:closing]]

      -

      {site_title}

      -
      -
      - - - diff --git a/src/views/emails/notification.tpl b/src/views/emails/notification.tpl index d1301ace7a..60579d48cd 100644 --- a/src/views/emails/notification.tpl +++ b/src/views/emails/notification.tpl @@ -3,55 +3,48 @@ - - - - - + + + + + - - - - - +
      - -
      + + + + + + + + + + + + + +
      +

      [[email:greeting_with_name, {username}]]

      +
      +

      {intro}

      +
      +

      + {body} +

      +
      + + + + + +
      + +     [[email:notif.cta]]     + +
      + +
      +
      - - - - - - - - - - - - - -
      -

      {intro}

      -
      - {body} -
      - - - - - -
      - -     [[email:notif.cta]]     - -
      - -
      -

      [[email:closing]]

      -

      {site_title}

      -
      -
      + -
      - - - + diff --git a/src/views/emails/partials/footer.tpl b/src/views/emails/partials/footer.tpl index 20bbd90f0e..13b350ef23 100644 --- a/src/views/emails/partials/footer.tpl +++ b/src/views/emails/partials/footer.tpl @@ -1,7 +1,7 @@ -
      +

      [[email:notif.post.unsub.info]] [[email:unsub.cta]]. diff --git a/src/views/emails/partials/header.tpl b/src/views/emails/partials/header.tpl index 61867b16d3..c3185b8175 100644 --- a/src/views/emails/partials/header.tpl +++ b/src/views/emails/partials/header.tpl @@ -153,15 +153,15 @@ - -
      + +
      -