{posts.content}
- + + {../user.username} + +{posts.content}
+ +You are up-to-date
'); } else if (semver.gt(latestVersion, version)) { checkEl.removeClass('alert-info').addClass('alert-danger'); - checkEl.append('A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.
'); - } else if (semver.gt(version, latestVersion)) { - checkEl.removeClass('alert-info').addClass('alert-warning'); - checkEl.append('You are running a development version! Unintended bugs may occur.
'); + checkEl.append('A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.
'); } }); diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index a7943784af..b76ccf1708 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -40,7 +40,7 @@ define('admin/manage/flags', [ return app.alertError(err.message); } - $('.post-container').empty().text('No flagged posts!'); + ajaxify.refresh(); }); }); } diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index ece62335cc..b59543d035 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -1,11 +1,9 @@ "use strict"; -/* global config, socket, define, templates, bootbox, app, ajaxify, */ +/* global config, socket, define, templates, bootbox, app, ajaxify */ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) { var Users = {}; Users.init = function() { - var yourid = ajaxify.data.yourid; - selectable.enable('#users-container', '.user-selectable'); function getSelectedUids() { @@ -94,7 +92,7 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) return; } - if (uids.indexOf(yourid) !== -1) { + if (uids.indexOf(app.user.uid.toString()) !== -1) { app.alertError('You can\'t remove yourself as Administrator!'); } else { socket.emit('admin.user.makeAdmins', uids, done('User(s) are now administrators.', '.administrator', true)); @@ -108,7 +106,7 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) return; } - if (uids.indexOf(yourid.toString()) !== -1) { + if (uids.indexOf(app.user.uid.toString()) !== -1) { app.alertError('You can\'t remove yourself as Administrator!'); } else { bootbox.confirm('Do you really want to remove admins?', function(confirm) { diff --git a/public/src/client/chats.js b/public/src/client/chats.js index f32ec38378..437c97978c 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -38,13 +38,8 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll', }; Chats.addEventListeners = function() { - $('.chats-list').on('click', 'li', function(e) { - var env = utils.findBootstrapEnvironment(); - if (env === 'xs' || env === 'sm') { - app.openChat($(this).attr('data-username'), $(this).attr('data-uid')); - } else { - Chats.switchChat(parseInt($(this).attr('data-uid'), 10), $(this).attr('data-username')); - } + components.get('chat/recent').on('click', 'li', function(e) { + Chats.switchChat(parseInt($(this).attr('data-uid'), 10), $(this).attr('data-username')); }); Chats.addSendHandlers(Chats.getRecipientUid(), $('.chat-input'), $('.expanded-chat button[data-action="send"]')); diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 423637e21e..c971a7e5fc 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -255,10 +255,12 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator pid: post.attr('data-pid'), room_id: app.currentRoom }, function(err) { - if (err.message === 'self-vote') { - showVotes(post.attr('data-pid')); - } else { - app.alertError(err.message); + if (err) { + if (err.message === 'self-vote') { + showVotes(post.attr('data-pid')); + } else { + app.alertError(err.message); + } } }); @@ -268,6 +270,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator function showVotes(pid) { socket.emit('posts.getVoters', {pid: pid, cid: ajaxify.data.cid}, function(err, data) { if (err) { + if (err.message === '[[error:no-privileges]]') { + return; + } + + // Only show error if it's an unexpected error. return app.alertError(err.message); } diff --git a/public/src/installer/install.js b/public/src/installer/install.js index 56645f0301..f7224fb71a 100644 --- a/public/src/installer/install.js +++ b/public/src/installer/install.js @@ -115,7 +115,7 @@ $('document').ready(function() { $.post('/launch', function() { setInterval(function() { - $.get('/admin', function(data) { + $.get('/admin').done(function(data) { window.location = 'admin'; }); }, 750); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index b2613a2e8a..a9363836cd 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -19,11 +19,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra }); socket.on('event:chats.receive', function(data) { - if (ajaxify.currentPage.slice(0, 6) === 'chats/') { - // User is on the chats page, so do nothing (src/forum/chats.js will handle it) - return; - } - var username = data.message.fromUser.username; var isSelf = parseInt(data.message.fromUser.uid, 10) === parseInt(app.user.uid, 10); data.message.self = data.self; diff --git a/src/controllers/api.js b/src/controllers/api.js index 5ccedc5aeb..74c4d008c8 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -179,21 +179,68 @@ apiController.getObject = function(req, res, next) { apiController.getUserByUID = function(req, res, next) { var uid = req.params.uid ? req.params.uid : 0; + getUserByUID(uid, function(err, userData) { + if (err || !userData) { + return next(err); + } + res.json(userData); + }); +}; + + +apiController.getUserByUsername = function(req, res, next) { + var username = req.params.username ? req.params.username : 0; + + async.waterfall([ + function(next) { + user.getUidByUsername(username, next); + }, + function(uid, next) { + getUserByUID(uid, next); + } + ], function(err, userData) { + if (err || !userData) { + return next(err); + } + res.json(userData); + }); +}; + + +apiController.getUserByEmail = function(req, res, next) { + var email = req.params.email ? req.params.email : 0; + + async.waterfall([ + function(next) { + user.getUidByEmail(email, next); + }, + function(uid, next) { + getUserByUID(uid, next); + } + ], function(err, userData) { + if (err || !userData) { + return next(err); + } + res.json(userData); + }); +}; + + +function getUserByUID(uid, callback) { async.parallel({ userData: async.apply(user.getUserData, uid), settings: async.apply(user.getSettings, uid) }, function(err, results) { if (err || !results.userData) { - return next(err); + return callback(err, null); } results.userData.email = results.settings.showemail ? results.userData.email : undefined; results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined; - res.json(results.userData); + callback(null, results.userData); }); -}; - +} apiController.getModerators = function(req, res, next) { categories.getModerators(req.params.cid, function(err, moderators) { diff --git a/src/controllers/index.js b/src/controllers/index.js index f418c16572..fa414e1db5 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -50,7 +50,16 @@ Controllers.home = function(req, res, next) { } else if (route === 'popular') { Controllers.popular.get(req, res, next); } else { - res.redirect(route); + var match = /^category\/(\d+)\/(.*)$/.exec(route); + + if (match) { + req.params.topic_index = "1"; + req.params.category_id = match[1]; + req.params.slug = match[2]; + Controllers.categories.get(req, res, next); + } else { + res.redirect(route); + } } } }); diff --git a/src/emailer.js b/src/emailer.js index d63aa1bb7e..449bdf9e9b 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -14,13 +14,26 @@ var async = require('async'), translator = require('../public/src/modules/translator'), transports = { - direct: nodemailer.createTransport('direct') + direct: nodemailer.createTransport('direct'), + gmail: undefined }, app; (function(Emailer) { Emailer.registerApp = function(expressApp) { app = expressApp; + + // Enable Gmail transport if enabled in ACP + if (parseInt(meta.config['email:GmailTransport:enabled'], 10) === 1) { + transports.gmail = nodemailer.createTransport('SMTP', { + service: 'Gmail', + auth: { + user: meta.config['email:GmailTransport:user'], + pass: meta.config['email:GmailTransport:pass'] + } + }); + } + return Emailer; }; @@ -101,7 +114,8 @@ var async = require('async'), data.text = data.plaintext; delete data.plaintext; - transports.direct.sendMail(data, callback); + winston.verbose('[emailer] Sending email to uid ' + data.uid); + transports[transports.gmail ? 'gmail' : 'direct'].sendMail(data, callback); }; function render(tpl, params, next) { diff --git a/src/favourites.js b/src/favourites.js index c5d71f6de2..5af0a7f051 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -133,19 +133,9 @@ var async = require('async'), putVoteInProgress(pid, uid); - user.getUserField(uid, 'reputation', function(err, reputation) { - if (err) { - return callback(err); - } - - if (reputation < parseInt(meta.config['privileges:downvote'], 10)) { - return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); - } - - toggleVote('downvote', pid, uid, function(err, data) { - clearVoteProgress(pid, uid); - callback(err, data); - }); + toggleVote('downvote', pid, uid, function(err, data) { + clearVoteProgress(pid, uid); + callback(err, data); }); }; @@ -197,6 +187,9 @@ var async = require('async'), }, voteStatus: function(next) { Favourites.hasVoted(pid, uid, next); + }, + reputation: function(next) { + user.getUserField(uid, 'reputation', next); } }, function(err, results) { if (err) { @@ -207,6 +200,10 @@ var async = require('async'), return callback(new Error('self-vote')); } + if (command === 'downvote' && parseInt(results.reputation) < parseInt(meta.config['privileges:downvote'], 10)) { + return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); + } + var voteStatus = results.voteStatus, hook, current = voteStatus.upvoted ? 'upvote' : 'downvote'; diff --git a/src/install.js b/src/install.js index 80da3a52c1..70de52c164 100644 --- a/src/install.js +++ b/src/install.js @@ -10,7 +10,7 @@ var async = require('async'), DATABASES = { "redis": { - "dependencies": ["redis@~0.10.1", "connect-redis@~2.0.0"] + "dependencies": ["redis@~2.4.2", "connect-redis@~2.0.0"] }, "mongo": { "dependencies": ["mongodb@~2.0.0", "connect-mongo@~0.8.2"] diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index e2c5747ab6..059e2229d0 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -222,7 +222,7 @@ middleware.privateUploads = function(req, res, next) { middleware.busyCheck = function(req, res, next) { if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) { - res.type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); + res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); } else { next(); } diff --git a/src/reset.js b/src/reset.js index 55559696bf..ce1b585a01 100644 --- a/src/reset.js +++ b/src/reset.js @@ -2,6 +2,7 @@ var winston = require('winston'); var nconf = require('nconf'); +var async = require('async'); var db = require('./database'); var Reset = {}; @@ -81,11 +82,29 @@ function resetThemes(callback) { } function resetPlugin(pluginId) { - db.sortedSetRemove('plugins:active', pluginId, function(err) { + var active = false; + + async.waterfall([ + async.apply(db.isSortedSetMember, 'plugins:active', pluginId), + function(isMember, next) { + active = isMember; + + if (isMember) { + db.sortedSetRemove('plugins:active', pluginId, next); + } else { + next(); + } + } + ], function(err) { if (err) { winston.error('[reset] Could not disable plugin: %s encountered error %s', pluginId, err.message); } else { - winston.info('[reset] Plugin `%s` disabled', pluginId); + if (active) { + winston.info('[reset] Plugin `%s` disabled', pluginId); + } else { + winston.warn('[reset] Plugin `%s` was not active on this forum', pluginId); + winston.info('[reset] No action taken.'); + } } process.exit(); diff --git a/src/routes/api.js b/src/routes/api.js index c80d9507c6..34cf142b37 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -13,6 +13,9 @@ module.exports = function(app, middleware, controllers) { router.get('/widgets/render', controllers.api.renderWidgets); router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUID); + router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUsername); + router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.api.getUserByEmail); + router.get('/:type/pid/:id', controllers.api.getObject); router.get('/:type/tid/:id', controllers.api.getObject); router.get('/:type/cid/:id', controllers.api.getObject); @@ -28,7 +31,7 @@ module.exports = function(app, middleware, controllers) { router.post('/post/upload', middlewares, uploadsController.uploadPost); router.post('/topic/thumb/upload', middlewares, uploadsController.uploadThumb); router.post('/user/:userslug/uploadpicture', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadPicture); - + router.post('/user/:userslug/uploadcover', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadCoverPicture); router.post('/groups/uploadpicture', middlewares.concat([middleware.authenticate]), controllers.groups.uploadCover); }; diff --git a/src/routes/index.js b/src/routes/index.js index 0dfa0303ba..a35c5ea61e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -6,6 +6,7 @@ var nconf = require('nconf'), controllers = require('../controllers'), plugins = require('../plugins'), express = require('express'), + validator = require('validator'), accountRoutes = require('./accounts'), @@ -195,7 +196,7 @@ function handleErrors(app, middleware) { res.json({path: req.path, error: err.message}); } else { middleware.buildHeader(req, res, function() { - res.render('500', {path: req.path, error: err.message}); + res.render('500', {path: req.path, error: validator.escape(err.message)}); }); } }); diff --git a/src/user/email.js b/src/user/email.js index 9a1dfde805..4f2361c0f7 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -32,7 +32,7 @@ var async = require('async'), var confirm_code = utils.generateUUID(), confirm_link = nconf.get('url') + '/confirm/' + confirm_code; - var emailInterval = 10; + var emailInterval = meta.config.hasOwnProperty('emailConfirmInterval') ? parseInt(meta.config.emailConfirmInterval, 10) : 10; async.waterfall([ function(next) { diff --git a/src/views/admin/advanced/events.tpl b/src/views/admin/advanced/events.tpl index 747f9e03db..eb43eaa824 100644 --- a/src/views/admin/advanced/events.tpl +++ b/src/views/admin/advanced/events.tpl @@ -10,9 +10,16 @@{events.jsonString}
{posts.content}
- + + {../user.username} + +{posts.content}
+ ++++ +
+- + + +
+ ++ + + + {../user.username} + : "{../reason}" +