From 14b067a838b95d9de664b0fcd7c956d0f01370e1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 28 Jun 2016 12:34:09 +0300 Subject: [PATCH 01/21] closes #4800 --- public/language/en_GB/error.json | 7 +++ public/src/client/topic/postTools.js | 80 ++++++++++++++++------------ src/controllers/topics.js | 3 +- src/posts/tools.js | 6 +-- src/privileges/posts.js | 32 +++++++++++ src/views/admin/settings/post.tpl | 4 ++ 6 files changed, 94 insertions(+), 38 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 25e3afa69f..288ac0f4f2 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -57,6 +57,13 @@ "post-edit-duration-expired-days": "You are only allowed to edit posts for %1 day(s) after posting", "post-edit-duration-expired-days-hours": "You are only allowed to edit posts for %1 day(s) %2 hour(s) after posting", + "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", + "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", + "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", + "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", + "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", + "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", + "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 7136c32c27..c954dd2402 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -171,43 +171,55 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator var timestamp = parseInt(getData(btn, 'data-timestamp'), 10); var postEditDuration = parseInt(ajaxify.data.postEditDuration, 10); - if (!ajaxify.data.privileges.isAdminOrMod && postEditDuration && Date.now() - timestamp > postEditDuration * 1000) { - var numDays = Math.floor(postEditDuration / 86400); - var numHours = Math.floor((postEditDuration % 86400) / 3600); - var numMinutes = Math.floor(((postEditDuration % 86400) % 3600) / 60); - var numSeconds = ((postEditDuration % 86400) % 3600) % 60; - var msg = '[[error:post-edit-duration-expired, ' + postEditDuration + ']]'; - if (numDays) { - if (numHours) { - msg = '[[error:post-edit-duration-expired-days-hours, ' + numDays + ', ' + numHours + ']]'; - } else { - msg = '[[error:post-edit-duration-expired-days, ' + numDays + ']]'; - } - } else if (numHours) { - if (numMinutes) { - msg = '[[error:post-edit-duration-expired-hours-minutes, ' + numHours + ', ' + numMinutes + ']]'; - } else { - msg = '[[error:post-edit-duration-expired-hours, ' + numHours + ']]'; - } - } else if (numMinutes) { - if (numSeconds) { - msg = '[[error:post-edit-duration-expired-minutes-seconds, ' + numMinutes + ', ' + numSeconds + ']]'; - } else { - msg = '[[error:post-edit-duration-expired-minutes, ' + numMinutes + ']]'; - } - } - return app.alertError(msg); - } - $(window).trigger('action:composer.post.edit', { - pid: getData(btn, 'data-pid') - }); + if (checkDuration(postEditDuration, timestamp, 'post-edit-duration-expired')) { + $(window).trigger('action:composer.post.edit', { + pid: getData(btn, 'data-pid') + }); + } }); postContainer.on('click', '[component="post/delete"]', function() { - togglePostDelete($(this), tid); + var btn = $(this); + var timestamp = parseInt(getData(btn, 'data-timestamp'), 10); + var postDeleteDuration = parseInt(ajaxify.data.postDeleteDuration, 10); + if (checkDuration(postDeleteDuration, timestamp, 'post-delete-duration-expired')) { + togglePostDelete($(this), tid); + } }); + function checkDuration(duration, postTimestamp, languageKey) { + if (!ajaxify.data.privileges.isAdminOrMod && duration && Date.now() - postTimestamp > duration * 1000) { + var numDays = Math.floor(duration / 86400); + var numHours = Math.floor((duration % 86400) / 3600); + var numMinutes = Math.floor(((duration % 86400) % 3600) / 60); + var numSeconds = ((duration % 86400) % 3600) % 60; + var msg = '[[error:' + languageKey + ', ' + duration + ']]'; + if (numDays) { + if (numHours) { + msg = '[[error:' + languageKey + '-days-hours, ' + numDays + ', ' + numHours + ']]'; + } else { + msg = '[[error:' + languageKey + '-days, ' + numDays + ']]'; + } + } else if (numHours) { + if (numMinutes) { + msg = '[[error:' + languageKey + '-hours-minutes, ' + numHours + ', ' + numMinutes + ']]'; + } else { + msg = '[[error:' + languageKey + '-hours, ' + numHours + ']]'; + } + } else if (numMinutes) { + if (numSeconds) { + msg = '[[error:' + languageKey + '-minutes-seconds, ' + numMinutes + ', ' + numSeconds + ']]'; + } else { + msg = '[[error:' + languageKey + '-minutes, ' + numMinutes + ']]'; + } + } + app.alertError(msg); + return false; + } + return true; + } + postContainer.on('click', '[component="post/restore"]', function() { togglePostDelete($(this), tid); }); @@ -394,9 +406,9 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator } function togglePostDelete(button, tid) { - var pid = getData(button, 'data-pid'), - postEl = components.get('post', 'pid', pid), - action = !postEl.hasClass('deleted') ? 'delete' : 'restore'; + var pid = getData(button, 'data-pid'); + var postEl = components.get('post', 'pid', pid); + var action = !postEl.hasClass('deleted') ? 'delete' : 'restore'; postAction(action, pid, tid); } diff --git a/src/controllers/topics.js b/src/controllers/topics.js index f36c1d5ed0..1e41cfe981 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -264,7 +264,8 @@ topicsController.get = function(req, res, callback) { data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5; - data.postEditDuration = parseInt(meta.config.postEditDuration, 10); + data.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0; + data.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0; data.scrollToMyPost = settings.scrollToMyPost; data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; data.pagination = pagination.create(currentPage, pageCount); diff --git a/src/posts/tools.js b/src/posts/tools.js index 64ddf155d7..40d150d049 100644 --- a/src/posts/tools.js +++ b/src/posts/tools.js @@ -34,10 +34,10 @@ module.exports = function(Posts) { return next(new Error('[[error:post-already-restored]]')); } - privileges.posts.canEdit(pid, uid, next); + privileges.posts.canDelete(pid, uid, next); }, - function (canEdit, next) { - if (!canEdit) { + function (canDelete, next) { + if (!canDelete) { return next(new Error('[[error:no-privileges]]')); } diff --git a/src/privileges/posts.js b/src/privileges/posts.js index 6270b6e99e..17fcb1fc74 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -150,6 +150,38 @@ module.exports = function(privileges) { }); }; + privileges.posts.canDelete = function(pid, uid, callback) { + var postData; + async.waterfall([ + function(next) { + posts.getPostFields(pid, ['tid', 'timestamp'], next); + }, + function(_postData, next) { + postData = _postData; + async.parallel({ + isAdminOrMod: async.apply(isAdminOrMod, pid, uid), + isLocked: async.apply(topics.isLocked, postData.tid), + isOwner: async.apply(posts.isOwner, pid, uid) + }, next); + } + ], function(err, results) { + if (err) { + return callback(err); + } + if (results.isAdminOrMod) { + return callback(null, true); + } + if (results.isLocked) { + return callback(new Error('[[error:topic-locked]]')); + } + var postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10); + if (postDeleteDuration && (Date.now() - parseInt(postData.timestamp, 10) > postDeleteDuration * 1000)) { + return callback(new Error('[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]')); + } + callback(null, results.isOwner); + }); + }; + privileges.posts.canMove = function(pid, uid, callback) { posts.isMain(pid, function(err, isMain) { if (err || isMain) { diff --git a/src/views/admin/settings/post.tpl b/src/views/admin/settings/post.tpl index 0855a34333..4ac9aa3d2a 100644 --- a/src/views/admin/settings/post.tpl +++ b/src/views/admin/settings/post.tpl @@ -48,6 +48,10 @@ +
+ + +
From 2bf1be82d616a8df3205367f06df2b57c7116f2c Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Tue, 28 Jun 2016 09:02:30 -0400 Subject: [PATCH 02/21] Latest translations and fallbacks --- public/language/zh_CN/error.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index 8d93db600e..7d35bb52b6 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -63,7 +63,7 @@ "already-unfavourited": "您已取消了此贴的书签", "cant-ban-other-admins": "您不能封禁其他管理员!", "cant-remove-last-admin": "您是唯一的管理员。在删除您的管理员权限前,请添加另一个管理员。", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "在删除之前请你先把管理员的权限从这个账号移除。", "invalid-image-type": "无效的图像类型。允许的类型有:%1", "invalid-image-extension": "无效的图像扩展", "invalid-file-type": "无效文件格式,允许的格式有:%1", From 688028c63b309fbfd1001c6d7c43bf9a2ccc59f8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 28 Jun 2016 18:07:19 +0300 Subject: [PATCH 03/21] closes #4802 --- public/src/client/topic/events.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 07a73809ed..a036a732ec 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -96,12 +96,14 @@ define('forum/topic/events', [ if (!data || !data.post) { return; } - var editedPostEl = components.get('post/content', data.post.pid), - editorEl = $('[data-pid="' + data.post.pid + '"] [component="post/editor"]'), - topicTitle = components.get('topic/title'), - breadCrumb = components.get('breadcrumb/current'); + var editedPostEl = components.get('post/content', data.post.pid); + var editorEl = $('[data-pid="' + data.post.pid + '"] [component="post/editor"]'); + var topicTitle = components.get('topic/title'); + var navbarTitle = components.get('navbar/title').find('span'); + var breadCrumb = components.get('breadcrumb/current'); if (topicTitle.length && data.topic.title && topicTitle.html() !== data.topic.title) { + ajaxify.data.title = data.topic.title; var newUrl = 'topic/' + data.topic.slug + (window.location.search ? window.location.search : ''); history.replaceState({url: newUrl}, null, window.location.protocol + '//' + window.location.host + config.relative_path + '/' + newUrl); @@ -111,6 +113,9 @@ define('forum/topic/events', [ breadCrumb.fadeOut(250, function() { breadCrumb.html(data.topic.title).fadeIn(250); }); + navbarTitle.fadeOut(250, function() { + navbarTitle.html(data.topic.title).fadeIn(250); + }); } editedPostEl.fadeOut(250, function() { From 7da71976cfb55719d3d964c3af1315bd9b585a48 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 28 Jun 2016 16:30:39 -0400 Subject: [PATCH 04/21] WIP temporary bans --- public/language/en_GB/error.json | 1 + src/socket.io/user/ban.js | 11 ++++++--- src/user/admin.js | 40 ++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 288ac0f4f2..113337098f 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -37,6 +37,7 @@ "user-banned": "User banned", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", + "ban-expiry-missing": "Please provide an end date for this ban", "no-category": "Category does not exist", "no-topic": "Topic does not exist", diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index 574490b74d..a8a67b17b0 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -7,7 +7,12 @@ var events = require('../../events'); module.exports = function(SocketUser) { - SocketUser.banUsers = function(socket, uids, callback) { + SocketUser.banUsers = function(socket, uids, until, callback) { + if (!callback && typeof until === 'function') { + callback = until; + until = 0; + } + toggleBan(socket.uid, uids, SocketUser.banUser, function(err) { if (err) { return callback(err); @@ -45,7 +50,7 @@ module.exports = function(SocketUser) { ], callback); } - SocketUser.banUser = function(uid, callback) { + SocketUser.banUser = function(uid, until, callback) { async.waterfall([ function (next) { user.isAdministrator(uid, next); @@ -54,7 +59,7 @@ module.exports = function(SocketUser) { if (isAdmin) { return next(new Error('[[error:cant-ban-other-admins]]')); } - user.ban(uid, next); + user.ban(uid, until, next); }, function (next) { websockets.in('uid_' + uid).emit('event:banned'); diff --git a/src/user/admin.js b/src/user/admin.js index 004e048c93..402c8c57a9 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -52,19 +52,35 @@ module.exports = function(User) { ], callback); }; - User.ban = function(uid, callback) { - async.waterfall([ - function (next) { - User.setUserField(uid, 'banned', 1, next); - }, - function (next) { - db.sortedSetAdd('users:banned', Date.now(), uid, next); - }, - function (next) { - plugins.fireHook('action:user.banned', {uid: uid}); - next(); + User.ban = function(uid, until, callback) { + // "until" (optional) is unix timestamp in milliseconds + if (!callback && typeof until === 'function') { + callback = until; + until = 0; + } + + until = parseInt(until, 10); + if (isNaN(until)) { + return callback(new Error('[[error:ban-expiry-missing]]')); + } + + var tasks = [ + async.apply(User.setUserField, uid, 'banned', 1), + async.apply(db.sortedSetAdd, 'users:banned', Date.now(), uid), + ]; + + if (until > 0 && Date.now() < until) { + tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); + } + + async.series(tasks, function (err) { + if (err) { + return callback(err); } - ], callback); + + plugins.fireHook('action:user.banned', {uid: uid}); + callback(); + }); }; User.unban = function(uid, callback) { From 982746cedf9442ee4f8caa495a372284f77f0458 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Wed, 29 Jun 2016 09:45:43 -0400 Subject: [PATCH 05/21] nodebb-plugin-spam-be-gone@0.4.9 (#4805) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d7b19b4ba..48c4d979b6 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "nodebb-plugin-markdown": "6.0.0", "nodebb-plugin-mentions": "1.1.2", "nodebb-plugin-soundpack-default": "0.1.6", - "nodebb-plugin-spam-be-gone": "0.4.8", + "nodebb-plugin-spam-be-gone": "0.4.9", "nodebb-rewards-essentials": "0.0.8", "nodebb-theme-lavender": "3.0.13", "nodebb-theme-persona": "4.1.3", From 34cafbc0a80d5ca4e66a1720607c4fba2b02a576 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Jun 2016 11:01:43 -0400 Subject: [PATCH 06/21] reloading plugins after main template compilation, during reload --- src/meta.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meta.js b/src/meta.js index 3806c69556..1cbd1af0ae 100644 --- a/src/meta.js +++ b/src/meta.js @@ -61,14 +61,14 @@ var async = require('async'), plugins.fireHook('static:app.reload', {}, next); }, async.apply(plugins.clearRequireCache), - async.apply(plugins.reload), - async.apply(plugins.reloadRoutes), async.apply(Meta.css.minify), async.apply(Meta.js.minify, 'nodebb.min.js'), async.apply(Meta.js.minify, 'acp.min.js'), async.apply(Meta.sounds.init), async.apply(languages.init), async.apply(Meta.templates.compile), + async.apply(plugins.reload), + async.apply(plugins.reloadRoutes), async.apply(auth.reloadRoutes), function(next) { Meta.config['cache-buster'] = utils.generateUUID(); From 04d4fc2eca70be0080b4fb790c187e7738a513ed Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 29 Jun 2016 12:07:23 -0400 Subject: [PATCH 07/21] temporary ban support --- src/controllers/authentication.js | 9 ++++++--- src/socket.io/user/ban.js | 17 ++++++++++------- src/user.js | 29 +++++++++++++++++++++++++++++ src/user/admin.js | 7 ++++++- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index c34b0acd0a..bb575a3c76 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -334,10 +334,13 @@ authenticationController.localLogin = function(req, username, password, next) { function (next) { async.parallel({ userData: function(next) { - db.getObjectFields('user:' + uid, ['password', 'banned', 'passwordExpiry'], next); + db.getObjectFields('user:' + uid, ['password', 'passwordExpiry'], next); }, isAdmin: function(next) { user.isAdministrator(uid, next); + }, + banned: function(next) { + user.isBanned(uid, next); } }, next); }, @@ -349,13 +352,13 @@ authenticationController.localLogin = function(req, username, password, next) { if (!result.isAdmin && parseInt(meta.config.allowLocalLogin, 10) === 0) { return next(new Error('[[error:local-login-disabled]]')); } - if (!userData || !userData.password) { return next(new Error('[[error:invalid-user-data]]')); } - if (userData.banned && parseInt(userData.banned, 10) === 1) { + if (result.banned) { return next(new Error('[[error:user-banned]]')); } + Password.compare(password, userData.password, next); }, function (passwordMatch, next) { diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index a8a67b17b0..d52251d76d 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -7,17 +7,20 @@ var events = require('../../events'); module.exports = function(SocketUser) { - SocketUser.banUsers = function(socket, uids, until, callback) { - if (!callback && typeof until === 'function') { - callback = until; - until = 0; + SocketUser.banUsers = function(socket, data, callback) { + // Backwards compatibility + if (Array.isArray(data)) { + data = { + uids: data, + until: 0 + } } - toggleBan(socket.uid, uids, SocketUser.banUser, function(err) { + toggleBan(socket.uid, data.uids, banUser.bind(null, data.until || 0), function(err) { if (err) { return callback(err); } - async.each(uids, function(uid, next) { + async.each(data.uids, function(uid, next) { events.log({ type: 'user-ban', uid: socket.uid, @@ -50,7 +53,7 @@ module.exports = function(SocketUser) { ], callback); } - SocketUser.banUser = function(uid, until, callback) { + function banUser(until, uid, callback) { async.waterfall([ function (next) { user.isAdministrator(uid, next); diff --git a/src/user.js b/src/user.js index 50df737bc7..3303d04e79 100644 --- a/src/user.js +++ b/src/user.js @@ -256,6 +256,35 @@ var utils = require('../public/src/utils'); }); }; + User.isBanned = function(uid, callback) { + async.waterfall([ + async.apply(User.getUserField, uid, 'banned'), + function(banned, next) { + banned = parseInt(banned, 10) === 1; + if (!banned) { + return next(null, banned); + } else { + // If they are banned, see if the ban has expired + db.sortedSetScore('users:banned:expire', uid, function(err, score) { + var stillBanned = Date.now() < score; + + if (!stillBanned) { + async.parallel([ + async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid), + async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid), + async.apply(User.setUserField, uid, 'banned', 0) + ], function(err) { + next(err, false); + }); + } else { + next(err, true); + } + }); + } + } + ], callback); + }; + User.addInterstitials = function(callback) { plugins.registerHook('core', { hook: 'filter:register.interstitial', diff --git a/src/user/admin.js b/src/user/admin.js index 402c8c57a9..c402eb7e7a 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -71,6 +71,8 @@ module.exports = function(User) { if (until > 0 && Date.now() < until) { tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); + } else { + until = 0; } async.series(tasks, function (err) { @@ -78,7 +80,10 @@ module.exports = function(User) { return callback(err); } - plugins.fireHook('action:user.banned', {uid: uid}); + plugins.fireHook('action:user.banned', { + uid: uid, + until: until > 0 ? until : undefined + }); callback(); }); }; From f20efda91134b75e52ba93e69fba485bfdedc473 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 29 Jun 2016 21:09:05 +0300 Subject: [PATCH 08/21] call posts.relativeToAbsolute when needed --- src/posts/parse.js | 5 ++--- src/topics/follow.js | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index 087146e77d..19e6430681 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -31,7 +31,6 @@ module.exports = function(Posts) { } data.postData.content = translator.escape(data.postData.content); - data.postData.content = Posts.relativeToAbsolute(data.postData.content); if (global.env === 'production' && data.postData.pid) { cache.set(data.postData.pid, data.postData.content); @@ -67,8 +66,8 @@ module.exports = function(Posts) { content = content.slice(0, current.index + 6) + absolute + content.slice(current.index + 6 + current[1].length); } } catch(err) { - winston.verbose(err.messsage); - } + winston.verbose(err.messsage); + } } } diff --git a/src/topics/follow.js b/src/topics/follow.js index c0cb715c9d..e74d85d23c 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -8,6 +8,7 @@ var winston = require('winston'); var db = require('../database'); var user = require('../user'); +var posts = require('../posts'); var notifications = require('../notifications'); var privileges = require('../privileges'); var meta = require('../meta'); @@ -194,6 +195,8 @@ module.exports = function(Topics) { titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); } + postData.content = posts.relativeToAbsolute(postData.content); + notifications.create({ bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]', bodyLong: postData.content, @@ -223,6 +226,7 @@ module.exports = function(Topics) { if (err) { return next(err); } + if (data.userSettings.sendPostNotifications) { emailer.send('notif_post', toUid, { pid: postData.pid, From ca0bfc777e856b5ffc40ce389efa4eff47c4054e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 29 Jun 2016 22:49:32 +0300 Subject: [PATCH 09/21] closes #4808 --- src/groups/membership.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups/membership.js b/src/groups/membership.js index 96962695a1..65b9743537 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -82,7 +82,7 @@ module.exports = function(Groups) { }; function setGroupTitleIfNotSet(groupName, uid, callback) { - if (groupName === 'registered-users') { + if (groupName === 'registered-users' || Groups.isPrivilegeGroup(groupName)) { return callback(); } From ea6d783c319656bdf8a9c0b5bf9237fbb0cf990f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 29 Jun 2016 22:58:05 +0300 Subject: [PATCH 10/21] #4808 --- src/user/profile.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/user/profile.js b/src/user/profile.js index e24d5f08d0..0cadfd6c1e 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -7,6 +7,7 @@ var S = require('string'); var utils = require('../../public/src/utils'); var meta = require('../meta'); var db = require('../database'); +var groups = require('../groups'); var plugins = require('../plugins'); module.exports = function(User) { @@ -100,7 +101,21 @@ module.exports = function(User) { }); } - async.series([isAboutMeValid, isSignatureValid, isEmailAvailable, isUsernameAvailable], function(err) { + function isGroupTitleValid(next) { + if (data.groupTitle === 'registered-users' || groups.isPrivilegeGroup(data.groupTitle)) { + next(new Error('[[error:invali-group-title]]')); + } else { + next(); + } + } + + async.series([ + isAboutMeValid, + isSignatureValid, + isEmailAvailable, + isUsernameAvailable, + isGroupTitleValid + ], function(err) { if (err) { return callback(err); } From b6234cbd2b60bbabbe44cf7440b39cb6d0c7c227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 29 Jun 2016 23:17:14 +0300 Subject: [PATCH 11/21] fix error msg typo --- src/user/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/profile.js b/src/user/profile.js index 0cadfd6c1e..631d8d8f09 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -103,7 +103,7 @@ module.exports = function(User) { function isGroupTitleValid(next) { if (data.groupTitle === 'registered-users' || groups.isPrivilegeGroup(data.groupTitle)) { - next(new Error('[[error:invali-group-title]]')); + next(new Error('[[error:invalid-group-title]]')); } else { next(); } From 376e0319280a986eedadc0127c4580c6f6610e41 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 30 Jun 2016 11:38:33 +0300 Subject: [PATCH 12/21] closes #4809 --- public/src/client/topic/postTools.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index c954dd2402..6c2cebc8bd 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -6,7 +6,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator var PostTools = {}; + var staleReplyAnyway = false; + PostTools.init = function(tid) { + staleReplyAnyway = false; + renderMenu(); addPostHandlers(tid); @@ -238,9 +242,9 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator } function onReplyClicked(button, tid) { - showStaleWarning(function() { - var selectedText = getSelectedText(button); + var selectedText = getSelectedText(button); + showStaleWarning(function() { var username = getUserName(button); if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) { username = ''; @@ -270,6 +274,8 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator } function onQuoteClicked(button, tid) { + var selectedText = getSelectedText(button); + showStaleWarning(function() { function quote(text) { @@ -286,7 +292,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator var username = getUserName(button); var pid = getData(button, 'data-pid'); - var selectedText = getSelectedText(button); + if (selectedText) { return quote(selectedText); } @@ -506,7 +512,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator } function showStaleWarning(callback) { - if (ajaxify.data.lastposttime >= (Date.now() - (1000 * 60 * 60 * 24 * ajaxify.data.topicStaleDays))) { + if (staleReplyAnyway || ajaxify.data.lastposttime >= (Date.now() - (1000 * 60 * 60 * 24 * ajaxify.data.topicStaleDays))) { return callback(); } @@ -519,6 +525,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator label: '[[topic:stale.reply_anyway]]', className: 'btn-link', callback: function() { + staleReplyAnyway = true; callback(); } }, From 795b00a3af311f8d97a652947801c67b6226358c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 1 Jul 2016 13:01:09 +0300 Subject: [PATCH 13/21] closes #4810 --- src/controllers/api.js | 110 +++++++++++++++++++++++++----------- src/privileges/posts.js | 30 ++++++---- src/socket.io/categories.js | 2 +- src/socket.io/posts.js | 12 +--- src/socket.io/topics.js | 12 +--- 5 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/controllers/api.js b/src/controllers/api.js index 0139b9a3d3..4b84d44ef8 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -129,47 +129,93 @@ apiController.renderWidgets = function(req, res, next) { }); }; -apiController.getObject = function(req, res, next) { - apiController.getObjectByType(req.uid, req.params.type, req.params.id, function(err, results) { - if (err) { - return next(err); +apiController.getPostData = function(pid, uid, callback) { + async.parallel({ + privileges: function(next) { + privileges.posts.get([pid], uid, next); + }, + post: function(next) { + posts.getPostData(pid, next); + } + }, function(err, results) { + if (err || !results.post) { + return callback(err); } - res.json(results); + var post = results.post; + var privileges = results.privileges[0]; + + if (!privileges.read || !privileges['topics:read']) { + return callback(); + } + + post.ip = privileges.isAdminOrMod ? post.ip : undefined; + var selfPost = uid && uid === parseInt(post.uid, 10); + if (post.deleted && !(privileges.isAdminOrMod || selfPost)) { + post.content = '[[topic:post_is_deleted]]'; + } + callback(null, post); }); }; -apiController.getObjectByType = function(uid, type, id, callback) { +apiController.getTopicData = function(tid, uid, callback) { + async.parallel({ + privileges: function(next) { + privileges.topics.get(tid, uid, next); + }, + topic: function(next) { + topics.getTopicData(tid, next); + } + }, function(err, results) { + if (err || !results.topic) { + return callback(err); + } + + if (!results.privileges.read || !results.privileges['topics:read'] || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) { + return callback(); + } + callback(null, results.topic); + }); +}; + +apiController.getCategoryData = function(cid, uid, callback) { + async.parallel({ + privileges: function(next) { + privileges.categories.get(cid, uid, next); + }, + category: function(next) { + categories.getCategoryData(cid, next); + } + }, function(err, results) { + if (err || !results.category) { + return callback(err); + } + + if (!results.privileges.read) { + return callback(); + } + callback(null, results.category); + }); +}; + + +apiController.getObject = function(req, res, next) { var methods = { - post: { - canRead: privileges.posts.can, - data: posts.getPostData - }, - topic: { - canRead: privileges.topics.can, - data: topics.getTopicData - }, - category: { - canRead: privileges.categories.can, - data: categories.getCategoryData - } + post: apiController.getPostData, + topic: apiController.getTopicData, + category: apiController.getCategoryData }; - - if (!methods[type]) { - return callback(); + var method = methods[req.params.type]; + if (!method) { + return next(); } - - async.waterfall([ - function (next) { - methods[type].canRead('read', id, uid, next); - }, - function (canRead, next) { - if (!canRead) { - return next(new Error('[[error:no-privileges]]')); - } - methods[type].data(id, next); + method(req.params.id, req.uid, function(err, result) { + if (err || !result) { + return next(err); } - ], callback); + + res.json(result); + }); }; apiController.getUserByUID = function(req, res, next) { diff --git a/src/privileges/posts.js b/src/privileges/posts.js index 17fcb1fc74..a0d06f3600 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -19,17 +19,20 @@ module.exports = function(privileges) { return callback(null, []); } - async.parallel({ - isAdmin: function(next){ - user.isAdministrator(uid, next); + async.waterfall([ + function(next) { + posts.getCidsByPids(pids, next); }, - isModerator: function(next) { - posts.isModerator(pids, uid, next); - }, - isOwner: function(next) { - posts.isOwner(pids, uid, next); + function(cids, next) { + async.parallel({ + isAdmin: async.apply(user.isAdministrator, uid), + isModerator: async.apply(posts.isModerator, pids, uid), + isOwner: async.apply(posts.isOwner, pids, uid), + 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, cids), + read: async.apply(helpers.isUserAllowedTo, 'read', uid, cids), + }, next); } - }, function(err, results) { + ], function(err, results) { if (err) { return callback(err); } @@ -37,11 +40,16 @@ module.exports = function(privileges) { var privileges = []; for (var i=0; i Date: Fri, 1 Jul 2016 19:45:21 +0300 Subject: [PATCH 14/21] uid not used --- src/posts/flags.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/posts/flags.js b/src/posts/flags.js index 2590d68811..cf2b37bf0c 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -2,9 +2,9 @@ 'use strict'; -var async = require('async'), - db = require('../database'), - user = require('../user'); +var async = require('async'); +var db = require('../database'); +var user = require('../user'); module.exports = function(Posts) { @@ -67,17 +67,13 @@ module.exports = function(Posts) { } Posts.dismissFlag = function(pid, callback) { - var uid; - async.parallel([ function(next) { - db.getObjectField('post:' + pid, 'uid', function(err, _uid) { + db.getObjectField('post:' + pid, 'uid', function(err, uid) { if (err) { return next(err); } - uid = _uid; - db.sortedSetsRemove([ 'posts:flagged', 'posts:flags:count', From 7f44041e014583d5406f0d5c2a8b92bb8e444bc8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 1 Jul 2016 19:50:24 +0300 Subject: [PATCH 15/21] simpler --- src/socket.io/posts/flag.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index dbb3f7f551..2ad5dcd2b8 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -23,8 +23,8 @@ module.exports = function(SocketPosts) { return callback(new Error('[[error:invalid-data]]')); } - var flaggingUser = {}, - post; + var flaggingUser = {}; + var post; async.waterfall([ function (next) { @@ -40,9 +40,7 @@ module.exports = function(SocketPosts) { }, function (topicData, next) { post.topic = topicData; - next(); - }, - function (next) { + async.parallel({ isAdminOrMod: function(next) { privileges.categories.isAdminOrMod(post.topic.cid, socket.uid, next); From 73a226e2edf320c0c3d75204e9ded0be22d2170c Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sat, 2 Jul 2016 09:03:12 -0400 Subject: [PATCH 16/21] Latest translations and fallbacks --- public/language/cs/email.json | 2 +- public/language/cs/modules.json | 44 ++++++++++++------------ public/language/cs/notifications.json | 28 +++++++-------- public/language/cs/pages.json | 14 ++++---- public/language/cs/reset_password.json | 6 ++-- public/language/cs/topic.json | 22 ++++++------ public/language/cs/unread.json | 2 +- public/language/cs/user.json | 4 +-- public/language/cs/users.json | 2 +- public/language/fa_IR/global.json | 4 +-- public/language/fa_IR/notifications.json | 2 +- public/language/fa_IR/pages.json | 10 +++--- public/language/fa_IR/topic.json | 26 +++++++------- public/language/fa_IR/user.json | 4 +-- public/language/fa_IR/users.json | 2 +- 15 files changed, 86 insertions(+), 86 deletions(-) diff --git a/public/language/cs/email.json b/public/language/cs/email.json index cec1b4ac52..9c456dd30e 100644 --- a/public/language/cs/email.json +++ b/public/language/cs/email.json @@ -4,7 +4,7 @@ "invite": "Pozvánka od %1", "greeting_no_name": "Dobrý den", "greeting_with_name": "Dobrý den %1", - "welcome.text1": "Děkujeme vám za registraci s %1!", + "welcome.text1": "Děkujeme vám za registraci na %1!", "welcome.text2": "Pro úplnou aktivaci vašeho účtu potřebujeme ověřit vaší emailovou adresu.", "welcome.text3": "Administrátor právě potvrdil vaší registraci. Nyní se můžete přihlásit jménem a heslem.", "welcome.cta": "Klikněte zde pro potvrzení vaší emailové adresy", diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 61bae78b68..a6d59b761f 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -4,41 +4,41 @@ "chat.send": "Odeslat", "chat.no_active": "Nemáte žádné aktivní konverzace.", "chat.user_typing": "%1 píše ...", - "chat.user_has_messaged_you": "%1 has messaged you.", - "chat.see_all": "See all chats", - "chat.mark_all_read": "Mark all chats read", - "chat.no-messages": "Please select a recipient to view chat message history", - "chat.no-users-in-room": "No users in this room", - "chat.recent-chats": "Recent Chats", + "chat.user_has_messaged_you": "%1 Vám napsal.", + "chat.see_all": "Prohlédnout všechny chaty", + "chat.mark_all_read": "Označit vše jako přečtené", + "chat.no-messages": "Prosím vyberte příjemce k prohlédnutí historie zpráv.", + "chat.no-users-in-room": "Žádní uživatelé v místnosti.", + "chat.recent-chats": "Aktuální chaty", "chat.contacts": "Kontakty", "chat.message-history": "Historie zpráv", - "chat.pop-out": "Pop out chat", + "chat.pop-out": "Skrýt chat", "chat.maximize": "Maximalizovat", "chat.seven_days": "7 dní", "chat.thirty_days": "30 dní", "chat.three_months": "3 měsíce", "chat.delete_message_confirm": "Jste si jisti že chcete odstranit tuto zprávu?", - "chat.roomname": "Chat Room %1", - "chat.add-users-to-room": "Add users to room", - "composer.compose": "Compose", - "composer.show_preview": "Show Preview", - "composer.hide_preview": "Hide Preview", - "composer.user_said_in": "%1 said in %2:", - "composer.user_said": "%1 said:", - "composer.discard": "Are you sure you wish to discard this post?", - "composer.submit_and_lock": "Submit and Lock", + "chat.roomname": "Místnost %1", + "chat.add-users-to-room": "Přidat uživatele do místnosti", + "composer.compose": "Napsat", + "composer.show_preview": "Ukázat náhled", + "composer.hide_preview": "Skrýt náhled", + "composer.user_said_in": "%1 řekl v %2:", + "composer.user_said": "%1 řekl:", + "composer.discard": "Jste si jisti, že chcete zrušit tento příspěvek?", + "composer.submit_and_lock": "Potvrdit a uzamknout", "composer.toggle_dropdown": "Toggle Dropdown", - "composer.uploading": "Uploading %1", - "composer.formatting.bold": "Bold", - "composer.formatting.italic": "Italic", - "composer.formatting.list": "List", - "composer.formatting.strikethrough": "Strikethrough", + "composer.uploading": "Odesílám %1", + "composer.formatting.bold": "Tučné", + "composer.formatting.italic": "Kurzíva", + "composer.formatting.list": "Seznam", + "composer.formatting.strikethrough": "Přeškrtnutí", "composer.formatting.link": "Odkaz", "composer.formatting.picture": "Obrázek", "composer.upload-picture": "Nahrát obrázek", "composer.upload-file": "Nahrát soubor", "bootbox.ok": "OK", - "bootbox.cancel": "Cancel", + "bootbox.cancel": "Zrušit", "bootbox.confirm": "Potvrdit", "cover.dragging_title": "Cover Photo Positioning", "cover.dragging_message": "Drag the cover photo to the desired position and click \"Save\"", diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json index 0137a973e3..8c6e418638 100644 --- a/public/language/cs/notifications.json +++ b/public/language/cs/notifications.json @@ -1,16 +1,16 @@ { "title": "Upozornění", - "no_notifs": "You have no new notifications", - "see_all": "See all notifications", - "mark_all_read": "Mark all notifications read", - "back_to_home": "Back to %1", + "no_notifs": "Nemáte žádná nová upozornění.", + "see_all": "Zobrazit všechna upozornění", + "mark_all_read": "Označit všechna upozornění jako přečtená", + "back_to_home": "Zpět na %1", "outgoing_link": "Odkaz mimo fórum", - "outgoing_link_message": "You are now leaving %1", - "continue_to": "Continue to %1", - "return_to": "Return to %1", - "new_notification": "New Notification", - "you_have_unread_notifications": "You have unread notifications.", - "new_message_from": "New message from %1", + "outgoing_link_message": "Opouštíte %1", + "continue_to": "Pokračovat na %1", + "return_to": "Vrátit na %1", + "new_notification": "Nové upozornění", + "you_have_unread_notifications": "Máte nepřečtená upozornění.", + "new_message_from": "Nová zpráva od %1", "upvoted_your_post_in": "%1 has upvoted your post in %2.", "upvoted_your_post_in_dual": "%1 and %2 have upvoted your post in %3.", "upvoted_your_post_in_multiple": "%1 and %2 others have upvoted your post in %3.", @@ -31,8 +31,8 @@ "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", "new_register_multiple": "There are %1 registration requests awaiting review.", - "email-confirmed": "Email Confirmed", - "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", - "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.", - "email-confirm-sent": "Confirmation email sent." + "email-confirmed": "Email potvrzen", + "email-confirmed-message": "Děkujeme za ověření Vaší emailové adresy. Váš účet je nyní aktivován.", + "email-confirm-error-message": "Nastal problém s ověřením Vaší emailové adresy. Pravděpodobně neplatný nebo expirovaný kód.", + "email-confirm-sent": "Ověřovací email odeslán." } \ No newline at end of file diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json index b2dad21ab9..4de8a00f5c 100644 --- a/public/language/cs/pages.json +++ b/public/language/cs/pages.json @@ -1,19 +1,19 @@ { - "home": "Home", - "unread": "Unread Topics", + "home": "Domů", + "unread": "Nepřečtená témata", "popular-day": "Dnešní oblíbená témata", "popular-week": "Oblíbená témata pro tento týden", "popular-month": "Oblíbená témata pro tento měsíc", "popular-alltime": "Oblíbená témata za celou dobu", - "recent": "Recent Topics", + "recent": "Aktuální témata", "flagged-posts": "Označené příspěvky", "users/online": "Uživatelé online", "users/latest": "Nejnovější uživatelé", "users/sort-posts": "Uživatelé s nejvíce příspěvky", "users/sort-reputation": "Uživatelé s nejlepší reputací", - "users/banned": "Banned Users", + "users/banned": "Zabanovaní uživatelé", "users/search": "Hledání uživatele", - "notifications": "Notifications", + "notifications": "Oznámení", "tags": "Tagy", "tag": "Téma označeno pod \"%1\"", "register": "Zaregistrovat účet", @@ -22,8 +22,8 @@ "categories": "Kategorie", "groups": "Skupiny", "group": "%1 skupina", - "chats": "Chats", - "chat": "Chatting with %1", + "chats": "Chaty", + "chat": "Chatovat s %1", "account/edit": "Editing \"%1\"", "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", diff --git a/public/language/cs/reset_password.json b/public/language/cs/reset_password.json index 566b746435..87e85611d1 100644 --- a/public/language/cs/reset_password.json +++ b/public/language/cs/reset_password.json @@ -11,7 +11,7 @@ "enter_email_address": "Zadejte emailovou adresu", "password_reset_sent": "Obnova hesla odeslána", "invalid_email": "Špatný email / Email neexistuje!", - "password_too_short": "The password entered is too short, please pick a different password.", - "passwords_do_not_match": "The two passwords you've entered do not match.", - "password_expired": "Your password has expired, please choose a new password" + "password_too_short": "Zadané heslo je příliš krátké, zvolte si prosím jiné.", + "passwords_do_not_match": "Vámi zadaná hesla se neshodují.", + "password_expired": "Platnost Vašeho hesla vypršela, zvolte si prosím nové." } \ No newline at end of file diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index 1f62f62a3f..53d9fec054 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -74,7 +74,7 @@ "confirm_fork": "Rozdělit", "favourite": "Záložka", "favourites": "Záložky", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourites.has_no_favourites": "Zatím jste do záložek nepřidal žádné příspěvky.", "loading_more_posts": "Načítání více příspěvků", "move_topic": "Přesunout téma", "move_topics": "Přesunout témata", @@ -93,11 +93,11 @@ "composer.replying_to": "Replying to %1", "composer.new_topic": "Nové téma", "composer.uploading": "nahrávání...", - "composer.thumb_url_label": "Paste a topic thumbnail URL", - "composer.thumb_title": "Add a thumbnail to this topic", + "composer.thumb_url_label": "Vložit URL náhled tématu", + "composer.thumb_title": "Přidat k tématu náhled", "composer.thumb_url_placeholder": "http://example.com/thumb.png", "composer.thumb_file_label": "Nebo nahrajte soubor", - "composer.thumb_remove": "Clear fields", + "composer.thumb_remove": "Vymazat pole", "composer.drag_and_drop_images": "Drag and Drop Images Here", "more_users_and_guests": "%1 more user(s) and %2 guest(s)", "more_users": "%1 more user(s)", @@ -107,13 +107,13 @@ "oldest_to_newest": "Od nejstarších po nejnovější", "newest_to_oldest": "Od nejnovějších po nejstarší", "most_votes": "Nejvíce hlasů", - "most_posts": "Most posts", - "stale.title": "Create new topic instead?", - "stale.warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?", - "stale.create": "Create a new topic", - "stale.reply_anyway": "Reply to this topic anyway", + "most_posts": "Nejvíce příspěvků", + "stale.title": "Přesto vytvořit nové téma", + "stale.warning": "Reagujete na starší téma. Nechcete raději vytvořit téma nové a na původní v něm odkázat?", + "stale.create": "Vytvořit nové téma", + "stale.reply_anyway": "Přesto reagovat na toto téma", "link_back": "Re: [%1](%2)", "spam": "Spam", - "offensive": "Offensive", - "custom-flag-reason": "Enter a flagging reason" + "offensive": "Urážlivé", + "custom-flag-reason": "Vložte důvod oznámení" } \ No newline at end of file diff --git a/public/language/cs/unread.json b/public/language/cs/unread.json index 6380aebc5d..8943eefdeb 100644 --- a/public/language/cs/unread.json +++ b/public/language/cs/unread.json @@ -5,7 +5,7 @@ "mark_as_read": "Označit jako přeštené", "selected": "Vybrané", "all": "Vše", - "all_categories": "All categories", + "all_categories": "Všechny kategorie", "topics_marked_as_read.success": "Téma bylo označeno jako přečtené!", "all-topics": "Všechna témata", "new-topics": "Nová témata", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index fb65c1ee4b..c31281719a 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -30,7 +30,7 @@ "signature": "Podpis", "birthday": "Datum narození", "chat": "Chat", - "chat_with": "Chat with %1", + "chat_with": "Chatovat s %1", "follow": "Sledovat", "unfollow": "Nesledovat", "more": "Více", @@ -64,7 +64,7 @@ "settings": "Nastavení", "show_email": "Zobrazovat můj email v profilu", "show_fullname": "Zobrazovat celé jméno", - "restrict_chats": "Only allow chat messages from users I follow", + "restrict_chats": "Povolit chatovací zprávy pouze od uživatelů, které sleduji.", "digest_label": "Odebírat přehled", "digest_description": "Subscribe to email updates for this forum (new notifications and topics) according to a set schedule", "digest_off": "Vypnuto", diff --git a/public/language/cs/users.json b/public/language/cs/users.json index d1a3af5416..ea338090ae 100644 --- a/public/language/cs/users.json +++ b/public/language/cs/users.json @@ -5,7 +5,7 @@ "search": "Vyhledávat", "enter_username": "Zadej uživatelské jméno k hledání", "load_more": "Načíst další", - "users-found-search-took": "%1 user(s) found! Search took %2 seconds.", + "users-found-search-took": "Nalezeno %1 uživatel(ů) za %2 vteřiny.", "filter-by": "Filtrovat dle", "online-only": "Pouze online", "invite": "Pozvat", diff --git a/public/language/fa_IR/global.json b/public/language/fa_IR/global.json index f1926e1bd8..7681ad9bea 100644 --- a/public/language/fa_IR/global.json +++ b/public/language/fa_IR/global.json @@ -50,8 +50,8 @@ "topics": "موضوع ها", "posts": "دیدگاه‌ها", "best": "بهترین", - "upvoted": "Upvoted", - "downvoted": "Downvoted", + "upvoted": "رای مثبت", + "downvoted": "رای منفی", "views": "بازدیدها", "reputation": "اعتبار", "read_more": "بیشتر بخوانید", diff --git a/public/language/fa_IR/notifications.json b/public/language/fa_IR/notifications.json index 9729f68850..e11d75682b 100644 --- a/public/language/fa_IR/notifications.json +++ b/public/language/fa_IR/notifications.json @@ -12,7 +12,7 @@ "you_have_unread_notifications": "شما اطلاعیه های نخوانده دارید.", "new_message_from": "پیام تازه از %1", "upvoted_your_post_in": "%1 امتیاز مثبت به پست شما در %2 داده", - "upvoted_your_post_in_dual": "%1 and %2 have upvoted your post in %3.", + "upvoted_your_post_in_dual": "%1 و %2 رای مثبت به پست شما در\n %3.", "upvoted_your_post_in_multiple": "%1 and %2 others have upvoted your post in %3.", "moved_your_post": "%1 پست شما را به %2 انتقال داده است", "moved_your_topic": "%2 %1 را منتقل کرده است", diff --git a/public/language/fa_IR/pages.json b/public/language/fa_IR/pages.json index ffc6768d62..82258650e7 100644 --- a/public/language/fa_IR/pages.json +++ b/public/language/fa_IR/pages.json @@ -6,7 +6,7 @@ "popular-month": "موضوعات پربازدید این ماه", "popular-alltime": "پربازدیدترین موضوعات", "recent": "جستارهای تازه", - "flagged-posts": "Flagged Posts", + "flagged-posts": "پست نشانه گذاری شده", "users/online": "کاربران آنلاین", "users/latest": "آخرین کاربران", "users/sort-posts": "کاربران با بیش‌ترین پست", @@ -33,12 +33,12 @@ "account/posts": "پست‌های %1", "account/topics": "موضوع های %1", "account/groups": "گروه‌های %1", - "account/favourites": "%1's Bookmarked Posts", + "account/favourites": "%1 پست های به علاقمندی اضافه شدن", "account/settings": "تنظیمات کاربر", "account/watched": "موضوع های دیده شده توسط \"%1\"", - "account/upvoted": "Posts upvoted by %1", - "account/downvoted": "Posts downvoted by %1", - "account/best": "Best posts made by %1", + "account/upvoted": "رای مثبت داده شده به پست ها توسط %1", + "account/downvoted": "رای منفی داده شده به پست ها توسط %1", + "account/best": "بهترین پست های ارسال شده توسط %1", "confirm": "ایمیل تایید شد", "maintenance.text": "%1 در حال حاضر تحت تعمیر و نگهدارییست. لطفا زمان دیگری مراجعه کنید.", "maintenance.messageIntro": "علاوه بر این، مدیر این پیام را گذاشته است:", diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json index 9383c3403d..a4c0951b5a 100644 --- a/public/language/fa_IR/topic.json +++ b/public/language/fa_IR/topic.json @@ -26,28 +26,28 @@ "tools": "ابزارها", "flag": "پرچم", "locked": "قفل شده است", - "bookmark_instructions": "Click here to return to the last read post in this thread.", + "bookmark_instructions": "برای بازگشت به آخرین پست در این موضوع اینجا را کلیک کنید.", "flag_title": "پرچم‌گذاری این موضوع برای بررسی ناظران", "flag_success": "این موضوع برای بررسی ناظران پرچم گذاشته شد.", "deleted_message": "این موضوع پاک شده است. تنها کاربرانِ با حق مدیریت موضوع می‌توانند آن را ببینند.", "following_topic.message": "از این پس اگر کسی در این موضوع پست بگذارد، شما آگاه خواهید شد.", "not_following_topic.message": "شما دیگر اطلاعیه های این موضوع را دریافت نخواهید کرد.", - "ignoring_topic.message": "You will no longer see this topic in the unread topics list. You will be notified when you are mentioned or your post is up voted.", + "ignoring_topic.message": "شما دیگر نمی خواهید این تاپیک را در فهرست عنوان های خوانده نشده ببینید. به شما اطلاع داده خواهد شد زمانی که به پست شما کسی رای بدهد.", "login_to_subscribe": "برای دنبال کردن این موضوع، لطفا ثبت نام کنید و یا با نام کاربری خود وارد شوید", "markAsUnreadForAll.success": "موضوع برای همگان نخوانده در نظر گرفته شد.", - "mark_unread": "Mark unread", - "mark_unread.success": "Topic marked as unread.", + "mark_unread": "علامت بزن به عنوان خوانده نشده", + "mark_unread.success": "تاپیک را علامت بزن به عنوان خوانده نشده", "watch": "پیگیری", "unwatch": "عدم پیگیری", "watch.title": "از پاسخ‌های تازه به این موضوع آگاه شوید.", "unwatch.title": "توقف پیگیری این موضوع", "share_this_post": "به اشتراک‌گذاری این موضوع", - "watching": "Watching", - "not-watching": "Not Watching", - "ignoring": "Ignoring", - "watching.description": "Notify me of new replies.
Show topic in unread.", - "not-watching.description": "Do not notify me of new replies.
Show topic in unread if category is not ignored.", - "ignoring.description": "Do not notify me of new replies.
Do not show topic in unread.", + "watching": "درحال پیگیری", + "not-watching": "درحال پیگیری نیستید", + "ignoring": "نادیده گرفتن", + "watching.description": "به من اطلاع بده برای پاسخ های جدید.
نشان بده تاپیک های خوانده نشده را.", + "not-watching.description": "به من پس از ارسال هر پاسخی جدیدی اطلاع نده.
تاپیک به صورت خوانده نشده قرار بگیرد ولی نادیده گرفته نشود.", + "ignoring.description": "به من پس از ارسال هر پاسخی جدیدی اطلاع نده.
دیگر تاپیک را به صورت خوانده نشده نشان نده.", "thread_tools.title": "ابزارهای موضوع", "thread_tools.markAsUnreadForAll": "نخوانده بگیر", "thread_tools.pin": "سنجاق زدن موضوع", @@ -72,9 +72,9 @@ "disabled_categories_note": "دسته‌های از کار افتاده به رنگ خاکستری در می‌آیند", "confirm_move": "جابه‌جا کردن", "confirm_fork": "شاخه ساختن", - "favourite": "Bookmark", + "favourite": "علاقمندی", "favourites": "علاقمندی ها", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourites.has_no_favourites": "شما هیچ پستی را به صورت علاقمندی ندارید.", "loading_more_posts": "بارگذاری پست‌های بیش‌تر", "move_topic": "جابه‌جایی موضوع", "move_topics": "انتقال موضوع", @@ -85,7 +85,7 @@ "fork_topic_instruction": "پست‌هایی را که می‌خواهید به موضوع تازه ببرید، انتخاب کنید", "fork_no_pids": "هیچ پستی انتخاب نشده!", "fork_success": "موضوع با موفقیت منشعب شد! برای رفتن به موضوع انشعابی اینجا را کلیک کنید.", - "delete_posts_instruction": "Click the posts you want to delete/purge", + "delete_posts_instruction": "با کلیک بر روی پست شما می خواهید به حذف/پاکسازی", "composer.title_placeholder": "عنوان موضوعتان را اینجا بنویسید...", "composer.handle_placeholder": "نام", "composer.discard": "دور بیانداز", diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index d25b6195b4..b309fc453f 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -80,7 +80,7 @@ "has_no_topics": "این کاربر تا به حال هیچ موضوعی ارسال نکرده است", "has_no_watched_topics": "این کاربر تا به حال هیچ موضوعی را پیگیری نکرده است", "has_no_upvoted_posts": "این کاربر به هیچ پستی امتیاز نداده است.", - "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.", + "has_no_downvoted_posts": "این کاربر به هیچ پستی رای منفی نداده است.", "has_no_voted_posts": "این کاربر به پست رای نداده است", "email_hidden": "ایمیل پنهان شده", "hidden": "پنهان", @@ -93,7 +93,7 @@ "enable_topic_searching": "فعال کردن جستجوی داخل-موضوع", "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen", "delay_image_loading": "تاخیر در حال بارگذاری عکس", - "image_load_delay_help": "If enabled, images in topics will not load until they are scrolled into view", + "image_load_delay_help": "اگر فعال باشد، تصاویر در تاپیک ها بارگذاری نمی شود تا زمانی که از طریق اسکرول روی آن بروید قابل مشاهده است", "scroll_to_my_post": "پس از ارسال پست، اولین پست جدید نشان بده", "follow_topics_you_reply_to": "تاپیک هایی که پاسخ داده ای را دنبال کن", "follow_topics_you_create": "موضوع هایی که ایجاد کرده ای را دنبال کن", diff --git a/public/language/fa_IR/users.json b/public/language/fa_IR/users.json index 3cd0087dcf..89a9f19cd6 100644 --- a/public/language/fa_IR/users.json +++ b/public/language/fa_IR/users.json @@ -16,5 +16,5 @@ "unread_topics": "موضوع های خوانده نشده", "categories": "دسته ها", "tags": "برچسب‌ها", - "no-users-found": "No users found!" + "no-users-found": "کاربری پیدا نشد!" } \ No newline at end of file From 6332f47808655bbb5f8ec9abff0277b02e7c78e3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 3 Jul 2016 10:53:50 +0300 Subject: [PATCH 17/21] closes #4814 --- src/groups/delete.js | 4 +- src/groups/membership.js | 180 +++++++++++++++++++++------------------ 2 files changed, 100 insertions(+), 84 deletions(-) diff --git a/src/groups/delete.js b/src/groups/delete.js index ca062b364a..8c7b194d28 100644 --- a/src/groups/delete.js +++ b/src/groups/delete.js @@ -39,7 +39,9 @@ module.exports = function(Groups) { }, next); }); } - ], callback); + ], function(err) { + callback(err); + }); }); }; }; diff --git a/src/groups/membership.js b/src/groups/membership.js index 65b9743537..b570d1fbe7 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -13,72 +13,72 @@ var db = require('./../database'); module.exports = function(Groups) { Groups.join = function(groupName, uid, callback) { - function join() { - var tasks = [ - async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid), - async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount') - ]; - - async.waterfall([ - function(next) { - async.parallel({ - isAdmin: function(next) { - user.isAdministrator(uid, next); - }, - isHidden: function(next) { - Groups.isHidden(groupName, next); - } - }, next); - }, - function(results, next) { - if (results.isAdmin) { - tasks.push(async.apply(db.setAdd, 'group:' + groupName + ':owners', uid)); - } - if (!results.isHidden) { - tasks.push(async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', 1, groupName)); - } - async.parallel(tasks, next); - }, - function(results, next) { - setGroupTitleIfNotSet(groupName, uid, next); - }, - function(next) { - plugins.fireHook('action:group.join', { - groupName: groupName, - uid: uid - }); - next(); - } - ], callback); - } - callback = callback || function() {}; if (!groupName) { return callback(new Error('[[error:invalid-data]]')); } - Groups.exists(groupName, function(err, exists) { - if (err) { - return callback(err); - } - - if (exists) { - return join(); - } - - Groups.create({ - name: groupName, - description: '', - hidden: 1 - }, function(err) { - if (err && err.message !== '[[error:group-already-exists]]') { - winston.error('[groups.join] Could not create new hidden group: ' + err.message); - return callback(err); + async.waterfall([ + function(next) { + Groups.isMember(uid, groupName, next); + }, + function(isMember, next) { + if (isMember) { + return callback(); } - join(); - }); - }); + Groups.exists(groupName, next); + }, + function(exists, next) { + if (exists) { + return next(); + } + Groups.create({ + name: groupName, + description: '', + hidden: 1 + }, function(err) { + if (err && err.message !== '[[error:group-already-exists]]') { + winston.error('[groups.join] Could not create new hidden group: ' + err.message); + return callback(err); + } + next(); + }); + }, + function(next) { + async.parallel({ + isAdmin: function(next) { + user.isAdministrator(uid, next); + }, + isHidden: function(next) { + Groups.isHidden(groupName, next); + } + }, next); + }, + function(results, next) { + var tasks = [ + async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid), + async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount') + ]; + if (results.isAdmin) { + tasks.push(async.apply(db.setAdd, 'group:' + groupName + ':owners', uid)); + } + if (!results.isHidden) { + tasks.push(async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', 1, groupName)); + } + async.parallel(tasks, next); + }, + function(results, next) { + setGroupTitleIfNotSet(groupName, uid, next); + }, + function(next) { + plugins.fireHook('action:group.join', { + groupName: groupName, + uid: uid + }); + next(); + } + ], callback); }; function setGroupTitleIfNotSet(groupName, uid, callback) { @@ -204,38 +204,52 @@ module.exports = function(Groups) { Groups.leave = function(groupName, uid, callback) { callback = callback || function() {}; - var tasks = [ - async.apply(db.sortedSetRemove, 'group:' + groupName + ':members', uid), - async.apply(db.setRemove, 'group:' + groupName + ':owners', uid), - async.apply(db.decrObjectField, 'group:' + groupName, 'memberCount') - ]; - - async.parallel(tasks, function(err) { - if (err) { - return callback(err); - } - - plugins.fireHook('action:group.leave', { - groupName: groupName, - uid: uid - }); - - Groups.getGroupFields(groupName, ['hidden', 'memberCount'], function(err, groupData) { - if (err || !groupData) { - return callback(err); + async.waterfall([ + function(next) { + Groups.isMember(uid, groupName, next); + }, + function(isMember, next) { + if (!isMember) { + return callback(); } + Groups.exists(groupName, next); + }, + function(exists, next) { + if (!exists) { + return callback(); + } + async.parallel([ + async.apply(db.sortedSetRemove, 'group:' + groupName + ':members', uid), + async.apply(db.setRemove, 'group:' + groupName + ':owners', uid), + async.apply(db.decrObjectField, 'group:' + groupName, 'memberCount') + ], next); + }, + function(results, next) { + Groups.getGroupFields(groupName, ['hidden', 'memberCount'], next); + }, + function(groupData, next) { + if (!groupData) { + return callback(); + } if (parseInt(groupData.hidden, 10) === 1 && parseInt(groupData.memberCount, 10) === 0) { - Groups.destroy(groupName, callback); + Groups.destroy(groupName, next); } else { if (parseInt(groupData.hidden, 10) !== 1) { - db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, callback); + db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, next); } else { - callback(); + next(); } } - }); - }); + }, + function(next) { + plugins.fireHook('action:group.leave', { + groupName: groupName, + uid: uid + }); + next(); + } + ], callback); }; Groups.leaveAllGroups = function(uid, callback) { From 497d9455da9bfea439bc523abd993052be21e946 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sun, 3 Jul 2016 09:03:18 -0400 Subject: [PATCH 18/21] Latest translations and fallbacks --- public/language/cs/global.json | 4 ++-- public/language/cs/login.json | 4 ++-- public/language/cs/tags.json | 2 +- public/language/cs/topic.json | 18 +++++++++--------- public/language/cs/unread.json | 2 +- public/language/fa_IR/error.json | 4 ++-- public/language/fa_IR/notifications.json | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/public/language/cs/global.json b/public/language/cs/global.json index 273eaffe68..20163c921d 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -29,13 +29,13 @@ "header.popular": "Populární", "header.users": "Uživatelé", "header.groups": "Skupiny", - "header.chats": "Chats", + "header.chats": "Chaty", "header.notifications": "Oznámení", "header.search": "Hledat", "header.profile": "Můj profil", "header.navigation": "Navigace", "notifications.loading": "Načítání upozornění", - "chats.loading": "Načítání grafů", + "chats.loading": "Načítání chatů", "motd.welcome": "Vítejte na NodeBB, diskusní platforma buducnosti.", "previouspage": "Předchozí stránka", "nextpage": "Další stránka", diff --git a/public/language/cs/login.json b/public/language/cs/login.json index c6699ab6ae..746c3364a9 100644 --- a/public/language/cs/login.json +++ b/public/language/cs/login.json @@ -1,6 +1,6 @@ { - "username-email": "Username / Email", - "username": "Username", + "username-email": "Uživatel / Email", + "username": "Uživatel", "email": "Email", "remember_me": "Zapamatovat si mě?", "forgot_password": "Zapomněli jste heslo?", diff --git a/public/language/cs/tags.json b/public/language/cs/tags.json index 8fc07c7da2..59b7f40c43 100644 --- a/public/language/cs/tags.json +++ b/public/language/cs/tags.json @@ -1,7 +1,7 @@ { "no_tag_topics": "Není zde žádné téma s tímto tagem.", "tags": "Tagy", - "enter_tags_here": "Enter tags here, between %1 and %2 characters each.", + "enter_tags_here": "Zde vložte tagy, každý o délce %1 až %2 znaků.", "enter_tags_here_short": "Vložte tagy ...", "no_tags": "Zatím tu není žádný tag." } \ No newline at end of file diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index 53d9fec054..6d30b3ea98 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -35,8 +35,8 @@ "ignoring_topic.message": "You will no longer see this topic in the unread topics list. You will be notified when you are mentioned or your post is up voted.", "login_to_subscribe": "Please register or log in in order to subscribe to this topic.", "markAsUnreadForAll.success": "Topic marked as unread for all.", - "mark_unread": "Mark unread", - "mark_unread.success": "Topic marked as unread.", + "mark_unread": "Označ za nepřečtené", + "mark_unread.success": "Téma označeno jako nepřečtené", "watch": "Sledovat", "unwatch": "Unwatch", "watch.title": "Be notified of new replies in this topic", @@ -48,18 +48,18 @@ "watching.description": "Notify me of new replies.
Show topic in unread.", "not-watching.description": "Do not notify me of new replies.
Show topic in unread if category is not ignored.", "ignoring.description": "Do not notify me of new replies.
Do not show topic in unread.", - "thread_tools.title": "Topic Tools", + "thread_tools.title": "Správa tématu", "thread_tools.markAsUnreadForAll": "Označit jako nepřečtené", - "thread_tools.pin": "Pin Topic", - "thread_tools.unpin": "Unpin Topic", - "thread_tools.lock": "Lock Topic", + "thread_tools.pin": "Připnout téma", + "thread_tools.unpin": "Odepnout téma", + "thread_tools.lock": "Zamknout téma", "thread_tools.unlock": "Odemknout téma", "thread_tools.move": "Přesunout téma", "thread_tools.move_all": "Přesunout vše", - "thread_tools.fork": "Fork Topic", + "thread_tools.fork": "Větvit téma", "thread_tools.delete": "Odstranit téma", "thread_tools.delete-posts": "Odstranit přispěvky", - "thread_tools.delete_confirm": "Are you sure you want to delete this topic?", + "thread_tools.delete_confirm": "Opravdu chcete smazat toto téma.", "thread_tools.restore": "Obnovit téma", "thread_tools.restore_confirm": "Are you sure you want to restore this topic?", "thread_tools.purge": "Purge Topic", @@ -88,7 +88,7 @@ "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Zadejte název tématu...", "composer.handle_placeholder": "Jméno", - "composer.discard": "Discard", + "composer.discard": "Zrušit", "composer.submit": "Odeslat", "composer.replying_to": "Replying to %1", "composer.new_topic": "Nové téma", diff --git a/public/language/cs/unread.json b/public/language/cs/unread.json index 8943eefdeb..0c6ee0319f 100644 --- a/public/language/cs/unread.json +++ b/public/language/cs/unread.json @@ -2,7 +2,7 @@ "title": "Nepřečtené", "no_unread_topics": "Nejsou zde žádné nepřečtené témata.", "load_more": "Načíst další", - "mark_as_read": "Označit jako přeštené", + "mark_as_read": "Označit jako přečtené", "selected": "Vybrané", "all": "Vše", "all_categories": "Všechny kategorie", diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index a5afb7d656..7a68ce7a2f 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -92,7 +92,7 @@ "invalid-chat-message": "پیام نامعتبر", "chat-message-too-long": "پیام طولانی تر از حد مجاز است", "cant-edit-chat-message": "شما اجازه ی ویرایش این پیام را ندارید", - "cant-remove-last-user": "You can't remove the last user", + "cant-remove-last-user": "شما نمی توانید آخرین کاربر را حذف کنید", "cant-delete-chat-message": "شما اجازه حذف این پیام را ندارید.", "already-voting-for-this-post": "شما قبلا به این پست رای داده اید.", "reputation-system-disabled": "سیستم اعتبار غیر فعال شده است", @@ -106,7 +106,7 @@ "wrong-login-type-email": "لطفا از ایمیل خود برای ورود استفاده کنید", "wrong-login-type-username": "لطفا از نام کاربری خود برای ورود استفاده کنید", "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).", - "no-session-found": "No login session found!", + "no-session-found": "هیچ سشن ورودی یافت نشد!", "not-in-room": "هیچ کاربری در این گفتگو نیست", "no-users-in-room": "هیچ کاربری در این گفتگو نیست", "cant-kick-self": "شما نمی توانید خودتان را از گروه کیک کنید" diff --git a/public/language/fa_IR/notifications.json b/public/language/fa_IR/notifications.json index e11d75682b..cfcf1c270d 100644 --- a/public/language/fa_IR/notifications.json +++ b/public/language/fa_IR/notifications.json @@ -13,7 +13,7 @@ "new_message_from": "پیام تازه از %1", "upvoted_your_post_in": "%1 امتیاز مثبت به پست شما در %2 داده", "upvoted_your_post_in_dual": "%1 و %2 رای مثبت به پست شما در\n %3.", - "upvoted_your_post_in_multiple": "%1 and %2 others have upvoted your post in %3.", + "upvoted_your_post_in_multiple": "%1و %2 دیگران به پست شما رای مثبت دادن در %3.", "moved_your_post": "%1 پست شما را به %2 انتقال داده است", "moved_your_topic": "%2 %1 را منتقل کرده است", "favourited_your_post_in": "%1 has bookmarked your post in %2.", From aadd8835945d4088a6472526082b3fdbe8a82249 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 4 Jul 2016 14:37:30 +0300 Subject: [PATCH 19/21] fix numbers on users infinite load --- public/src/client/users.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/client/users.js b/public/src/client/users.js index 4091390068..d90d3ca6ac 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -67,6 +67,7 @@ define('forum/users', ['translator'], function(translator) { translated = $(translated); $('#users-container').append(translated); translated.find('span.timeago').timeago(); + utils.addCommasToNumbers(translated.find('.formatted-number')); $('#users-container .anon-user').appendTo($('#users-container')); }); }); From 789a3736af9b4b1608a8f0dc42a85545f28f927c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 4 Jul 2016 17:49:02 +0300 Subject: [PATCH 20/21] closes #4820 --- public/language/en_GB/pages.json | 1 + public/language/en_GB/users.json | 1 + public/src/admin/manage/flags.js | 45 +++++++- public/src/client/users.js | 11 +- src/controllers/admin/flags.js | 27 +++-- src/controllers/admin/users.js | 4 + src/controllers/users.js | 41 ++++--- src/posts/flags.js | 179 ++++++++++++++++++------------- src/routes/admin.js | 1 + src/routes/index.js | 1 + src/socket.io/admin/user.js | 18 +--- src/socket.io/user/search.js | 1 + src/user.js | 3 +- src/user/admin.js | 10 +- src/user/search.js | 20 ++-- src/views/admin/manage/flags.tpl | 82 +++++++------- src/views/admin/manage/users.tpl | 3 +- 17 files changed, 275 insertions(+), 173 deletions(-) diff --git a/public/language/en_GB/pages.json b/public/language/en_GB/pages.json index bbdf3cdc47..2fe5b5dbac 100644 --- a/public/language/en_GB/pages.json +++ b/public/language/en_GB/pages.json @@ -13,6 +13,7 @@ "users/sort-posts": "Users with the most posts", "users/sort-reputation": "Users with the most reputation", "users/banned": "Banned Users", + "users/most-flags": "Most flagged users", "users/search": "User Search", "notifications": "Notifications", diff --git a/public/language/en_GB/users.json b/public/language/en_GB/users.json index e693bf6333..5c9d8b93a4 100644 --- a/public/language/en_GB/users.json +++ b/public/language/en_GB/users.json @@ -2,6 +2,7 @@ "latest_users": "Latest Users", "top_posters": "Top Posters", "most_reputation": "Most Reputation", + "most_flags": "Most Flags", "search": "Search", "enter_username": "Enter a username to search", "load_more": "Load More", diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index eaa839522a..e1ccde9c00 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -1,11 +1,12 @@ "use strict"; -/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/ +/*global define, socket, app, utils, bootbox, ajaxify*/ define('admin/manage/flags', [ 'forum/infinitescroll', 'admin/modules/selectable', - 'autocomplete' -], function(infinitescroll, selectable, autocomplete) { + 'autocomplete', + 'Chart' +], function(infinitescroll, selectable, autocomplete, Chart) { var Flags = {}; @@ -20,6 +21,7 @@ define('admin/manage/flags', [ handleDismissAll(); handleDelete(); handleInfiniteScroll(); + handleGraphs(); }; function handleDismiss() { @@ -101,5 +103,42 @@ define('admin/manage/flags', [ }); } + function handleGraphs() { + var dailyCanvas = document.getElementById('flags:daily'); + var dailyLabels = utils.getDaysArray().map(function(text, idx) { + return idx % 3 ? '' : text; + }); + + if (utils.isMobile()) { + Chart.defaults.global.showTooltips = false; + } + var data = { + 'flags:daily': { + labels: dailyLabels, + datasets: [ + { + label: "", + fillColor: "rgba(151,187,205,0.2)", + strokeColor: "rgba(151,187,205,1)", + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + data: ajaxify.data.analytics + } + ] + } + }; + + + + dailyCanvas.width = $(dailyCanvas).parent().width(); + new Chart(dailyCanvas.getContext('2d')).Line(data['flags:daily'], { + responsive: true, + animation: false + }); + + } + return Flags; }); \ No newline at end of file diff --git a/public/src/client/users.js b/public/src/client/users.js index d90d3ca6ac..fe083a13e2 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -102,14 +102,15 @@ define('forum/users', ['translator'], function(translator) { if (!username) { return loadPage(page); } - + var activeSection = getActiveSection(); socket.emit('user.search', { query: username, page: page, searchBy: 'username', sortBy: $('.search select').val() || getSortBy(), - onlineOnly: $('.search .online-only').is(':checked') || (getActiveSection() === 'online'), - bannedOnly: getActiveSection() === 'banned' + onlineOnly: $('.search .online-only').is(':checked') || (activeSection === 'online'), + bannedOnly: activeSection === 'banned', + flaggedOnly: activeSection === 'flagged' }, function(err, data) { if (err) { return app.alertError(err.message); @@ -168,8 +169,8 @@ define('forum/users', ['translator'], function(translator) { } function getActiveSection() { - var url = window.location.href, - parts = url.split('/'); + var url = window.location.href; + var parts = url.split('/'); return parts[parts.length - 1]; } diff --git a/src/controllers/admin/flags.js b/src/controllers/admin/flags.js index cb2f830365..340eda0a9d 100644 --- a/src/controllers/admin/flags.js +++ b/src/controllers/admin/flags.js @@ -2,6 +2,7 @@ var async = require('async'); var posts = require('../../posts'); +var analytics = require('../../analytics'); var flagsController = {}; @@ -13,20 +14,28 @@ flagsController.get = function(req, res, next) { async.waterfall([ function (next) { - if (byUsername) { - posts.getUserFlags(byUsername, sortBy, req.uid, start, stop, next); - } else { - var set = sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged'; - posts.getFlags(set, req.uid, start, stop, next); - } + async.parallel({ + posts: function(next) { + if (byUsername) { + posts.getUserFlags(byUsername, sortBy, req.uid, start, stop, next); + } else { + var set = sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged'; + posts.getFlags(set, req.uid, start, stop, next); + } + }, + analytics: function(next) { + analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30, next); + } + }, next); } - ], function (err, posts) { + ], function (err, results) { if (err) { return next(err); } var data = { - posts: posts, - next: stop + 1, + posts: results.posts, + analytics: results.analytics, + next: stop + 1, byUsername: byUsername, title: '[[pages:flagged-posts]]' }; diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index 1b833c2709..e682045fce 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -28,6 +28,10 @@ usersController.noPosts = function(req, res, next) { getUsersByScore('users:postcount', 'noposts', 0, 0, req, res, next); }; +usersController.flagged = function(req, res, next) { + getUsersByScore('users:flags', 'mostflags', 1, '+inf', req, res, next); +}; + usersController.inactive = function(req, res, next) { var timeRange = 1000 * 60 * 60 * 24 * 30 * (parseInt(req.query.months, 10) || 3); var cutoff = Date.now() - timeRange; diff --git a/src/controllers/users.js b/src/controllers/users.js index c84de75b5b..4ee7e5a9ec 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -69,6 +69,20 @@ usersController.getBannedUsers = function(req, res, next) { }); }; +usersController.getFlaggedUsers = function(req, res, next) { + usersController.getUsers('users:flags', req.uid, req.query.page, function(err, userData) { + if (err) { + return next(err); + } + + if (!userData.isAdminOrGlobalMod) { + return next(); + } + + render(req, res, userData, next); + }); +}; + usersController.renderUsersPage = function(set, req, res, next) { usersController.getUsers(set, req.uid, req.query.page, function(err, userData) { if (err) { @@ -79,23 +93,16 @@ usersController.renderUsersPage = function(set, req, res, next) { }; usersController.getUsers = function(set, uid, page, callback) { - var setToTitles = { - 'users:postcount': '[[pages:users/sort-posts]]', - 'users:reputation': '[[pages:users/sort-reputation]]', - 'users:joindate': '[[pages:users/latest]]', - 'users:online': '[[pages:users/online]]', - 'users:banned': '[[pages:users/banned]]' + var setToData = { + 'users:postcount': {title: '[[pages:users/sort-posts]]', crumb: '[[users:top_posters]]'}, + 'users:reputation': {title: '[[pages:users/sort-reputation]]', crumb: '[[users:most_reputation]]'}, + 'users:joindate': {title: '[[pages:users/latest]]', crumb: '[[global:users]]'}, + 'users:online': {title: '[[pages:users/online]]', crumb: '[[global:online]]'}, + 'users:banned': {title: '[[pages:users/banned]]', crumb: '[[user:banned]]'}, + 'users:flags': {title: '[[pages:users/most-flags]]', crumb: '[[users:most_flags]]'}, }; - var setToCrumbs = { - 'users:postcount': '[[users:top_posters]]', - 'users:reputation': '[[users:most_reputation]]', - 'users:joindate': '[[global:users]]', - 'users:online': '[[global:online]]', - 'users:banned': '[[user:banned]]' - }; - - var breadcrumbs = [{text: setToCrumbs[set]}]; + var breadcrumbs = [{text: setToData[set].crumb}]; if (set !== 'users:joindate') { breadcrumbs.unshift({text: '[[global:users]]', url: '/users'}); @@ -127,7 +134,7 @@ usersController.getUsers = function(set, uid, page, callback) { users: results.usersData.users, pagination: pagination.create(page, pageCount), userCount: results.usersData.count, - title: setToTitles[set] || '[[pages:users/latest]]', + title: setToData[set].title || '[[pages:users/latest]]', breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), setName: set, isAdminOrGlobalMod: results.isAdministrator || results.isGlobalMod @@ -148,6 +155,8 @@ usersController.getUsersAndCount = function(set, uid, start, stop, callback) { db.sortedSetCount('users:online', now - 300000, '+inf', next); } else if (set === 'users:banned') { db.sortedSetCard('users:banned', next); + } else if (set === 'users:flags') { + db.sortedSetCard('users:flags', next); } else { db.getObjectField('global', 'userCount', next); } diff --git a/src/posts/flags.js b/src/posts/flags.js index cf2b37bf0c..9eae966cda 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -5,7 +5,7 @@ var async = require('async'); var db = require('../database'); var user = require('../user'); - +var analytics = require('../analytics'); module.exports = function(Posts) { @@ -13,52 +13,59 @@ module.exports = function(Posts) { if (!parseInt(uid, 10) || !reason) { return callback(); } - async.parallel({ - hasFlagged: async.apply(hasFlagged, post.pid, uid), - exists: async.apply(Posts.exists, post.pid) - }, function(err, results) { - if (err || !results.exists) { - return callback(err || new Error('[[error:no-post]]')); - } - if (results.hasFlagged) { - return callback(new Error('[[error:already-flagged]]')); - } - var now = Date.now(); - - async.parallel([ - function(next) { - db.sortedSetAdd('posts:flagged', now, post.pid, next); - }, - function(next) { - db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next); - }, - function(next) { - db.incrObjectField('post:' + post.pid, 'flags', next); - }, - function(next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); - }, - function(next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); - }, - function(next) { - if (parseInt(post.uid, 10)) { - db.sortedSetAdd('uid:' + post.uid + ':flag:pids', now, post.pid, next); - } else { - next(); - } - }, - function(next) { - if (parseInt(post.uid, 10)) { - db.setAdd('uid:' + post.uid + ':flagged_by', uid, next); - } else { - next(); - } + async.waterfall([ + function(next) { + async.parallel({ + hasFlagged: async.apply(hasFlagged, post.pid, uid), + exists: async.apply(Posts.exists, post.pid) + }, next); + }, + function(results, next) { + if (!results.exists) { + return next(new Error('[[error:no-post]]')); } - ], function(err) { - callback(err); - }); + + if (results.hasFlagged) { + return next(new Error('[[error:already-flagged]]')); + } + + var now = Date.now(); + async.parallel([ + function(next) { + db.sortedSetAdd('posts:flagged', now, post.pid, next); + }, + function(next) { + db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next); + }, + function(next) { + db.incrObjectField('post:' + post.pid, 'flags', next); + }, + function(next) { + db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); + }, + function(next) { + db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); + }, + function(next) { + if (parseInt(post.uid, 10)) { + async.parallel([ + async.apply(db.sortedSetIncrBy, 'users:flags', 1, post.uid), + async.apply(db.incrObjectField, 'user:' + post.uid, 'flags'), + async.apply(db.sortedSetAdd, 'uid:' + post.uid + ':flag:pids', now, post.pid) + ], next); + } else { + next(); + } + } + ], next); + } + ], function(err) { + if (err) { + return callback(err); + } + analytics.increment('flags'); + callback(); }); }; @@ -67,41 +74,58 @@ module.exports = function(Posts) { } Posts.dismissFlag = function(pid, callback) { - async.parallel([ + async.waterfall([ function(next) { - db.getObjectField('post:' + pid, 'uid', function(err, uid) { - if (err) { - return next(err); - } - - db.sortedSetsRemove([ - 'posts:flagged', - 'posts:flags:count', - 'uid:' + uid + ':flag:pids' - ], pid, next); - }); + db.getObjectFields('post:' + pid, ['pid', 'uid', 'flags'], next); }, - function(next) { - async.series([ + function(postData, next) { + if (!postData.pid) { + return callback(); + } + async.parallel([ function(next) { - db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function(err, uids) { - async.each(uids, function(uid, next) { - var nid = 'post_flag:' + pid + ':uid:' + uid; + if (parseInt(postData.uid, 10)) { + if (parseInt(postData.flags, 10) > 0) { async.parallel([ - async.apply(db.delete, 'notifications:' + nid), - async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid) + async.apply(db.sortedSetIncrBy, 'users:flags', -postData.flags, postData.uid), + async.apply(db.incrObjectFieldBy, 'user:' + postData.uid, 'flags', -postData.flags) ], next); - }, next); - }); + } else { + next(); + } + } }, - async.apply(db.delete, 'pid:' + pid + ':flag:uids') + function(next) { + db.sortedSetsRemove([ + 'posts:flagged', + 'posts:flags:count', + 'uid:' + postData.uid + ':flag:pids' + ], pid, next); + }, + function(next) { + async.series([ + function(next) { + db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function(err, uids) { + async.each(uids, function(uid, next) { + var nid = 'post_flag:' + pid + ':uid:' + uid; + async.parallel([ + async.apply(db.delete, 'notifications:' + nid), + async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid) + ], next); + }, next); + }); + }, + async.apply(db.delete, 'pid:' + pid + ':flag:uids') + ], next); + }, + async.apply(db.deleteObjectField, 'post:' + pid, 'flags'), + async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason') ], next); }, - async.apply(db.deleteObjectField, 'post:' + pid, 'flags'), - async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason') - ], function(err) { - callback(err); - }); + function(results, next) { + db.sortedSetsRemoveRangeByScore(['users:flags'], '-inf', 0, next); + } + ], callback); }; Posts.dismissAllFlags = function(callback) { @@ -109,7 +133,16 @@ module.exports = function(Posts) { if (err) { return callback(err); } - async.eachLimit(pids, 50, Posts.dismissFlag, callback); + async.eachSeries(pids, Posts.dismissFlag, callback); + }); + }; + + Posts.dismissUserFlags = function(uid, callback) { + db.getSortedSetRange('uid:' + uid + ':flag:pids', 0, -1, function(err, pids) { + if (err) { + return callback(err); + } + async.eachSeries(pids, Posts.dismissFlag, callback); }); }; diff --git a/src/routes/admin.js b/src/routes/admin.js index 521f0194bf..543b9e8489 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -65,6 +65,7 @@ function addRoutes(router, middleware, controllers) { router.get('/manage/users/not-validated', middlewares, controllers.admin.users.notValidated); router.get('/manage/users/no-posts', middlewares, controllers.admin.users.noPosts); router.get('/manage/users/inactive', middlewares, controllers.admin.users.inactive); + router.get('/manage/users/flagged', middlewares, controllers.admin.users.flagged); router.get('/manage/users/banned', middlewares, controllers.admin.users.banned); router.get('/manage/registration', middlewares, controllers.admin.users.registrationQueue); diff --git a/src/routes/index.js b/src/routes/index.js index 06c9706856..1e51f5d59e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -75,6 +75,7 @@ function userRoutes(app, middleware, controllers) { setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); setupPageRoute(app, '/users/banned', middleware, middlewares, controllers.users.getBannedUsers); + setupPageRoute(app, '/users/flagged', middleware, middlewares, controllers.users.getFlaggedUsers); } function groupRoutes(app, middleware, controllers) { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 2633425dc4..ed7b4b378d 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -185,25 +185,15 @@ User.search = function(socket, data, callback) { return user && user.uid; }); - async.parallel({ - users: function(next) { - user.getUsersFields(uids, ['email'], next); - }, - flagCounts: function(next) { - var sets = uids.map(function(uid) { - return 'uid:' + uid + ':flagged_by'; - }); - db.setsCount(sets, next); - } - }, function(err, results) { + user.getUsersFields(uids, ['email', 'flags'], function(err, userInfo) { if (err) { return callback(err); } userData.forEach(function(user, index) { - if (user) { - user.email = (results.users[index] && results.users[index].email) || ''; - user.flags = results.flagCounts[index] || 0; + if (user && userInfo[index]) { + user.email = userInfo[index].email || ''; + user.flags = userInfo[index].flags || 0; } }); diff --git a/src/socket.io/user/search.js b/src/socket.io/user/search.js index ecef1127c2..76b1daa3a7 100644 --- a/src/socket.io/user/search.js +++ b/src/socket.io/user/search.js @@ -20,6 +20,7 @@ module.exports = function(SocketUser) { sortBy: data.sortBy, onlineOnly: data.onlineOnly, bannedOnly: data.bannedOnly, + flaggedOnly: data.flaggedOnly, uid: socket.uid }, function(err, result) { if (err) { diff --git a/src/user.js b/src/user.js index 3303d04e79..b4557abfb0 100644 --- a/src/user.js +++ b/src/user.js @@ -89,7 +89,8 @@ var utils = require('../public/src/utils'); }; User.getUsers = function(uids, uid, callback) { - var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'banned', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; + var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', + 'banned', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; async.waterfall([ function (next) { diff --git a/src/user/admin.js b/src/user/admin.js index c402eb7e7a..59ec0e0979 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -3,6 +3,7 @@ var async = require('async'); var db = require('../database'); +var posts = require('../posts'); var plugins = require('../plugins'); module.exports = function(User) { @@ -89,7 +90,6 @@ module.exports = function(User) { }; User.unban = function(uid, callback) { - db.delete('uid:' + uid + ':flagged_by'); async.waterfall([ function (next) { User.setUserField(uid, 'banned', 0, next); @@ -108,9 +108,9 @@ module.exports = function(User) { if (!Array.isArray(uids) || !uids.length) { return callback(); } - var keys = uids.map(function(uid) { - return 'uid:' + uid + ':flagged_by'; - }); - db.deleteAll(keys, callback); + + async.eachSeries(uids, function(uid, next) { + posts.dismissUserFlags(uid, next); + }, callback); }; }; diff --git a/src/user/search.js b/src/user/search.js index b2a1e83a80..6bb7084b1f 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -1,10 +1,10 @@ 'use strict'; -var async = require('async'), - meta = require('../meta'), - plugins = require('../plugins'), - db = require('../database'); +var async = require('async'); +var meta = require('../meta'); +var plugins = require('../plugins'); +var db = require('../database'); module.exports = function(User) { @@ -84,7 +84,7 @@ module.exports = function(User) { function filterAndSortUids(uids, data, callback) { var sortBy = data.sortBy || 'joindate'; - var fields = ['uid', 'status', 'lastonline', 'banned', sortBy]; + var fields = ['uid', 'status', 'lastonline', 'banned', 'flags', sortBy]; User.getUsersFields(uids, fields, function(err, userData) { if (err) { @@ -96,13 +96,19 @@ module.exports = function(User) { return user && user.status !== 'offline' && (Date.now() - parseInt(user.lastonline, 10) < 300000); }); } - - if(data.bannedOnly) { + + if (data.bannedOnly) { userData = userData.filter(function(user) { return user && user.banned; }); } + if (data.flaggedOnly) { + userData = userData.filter(function(user) { + return user && parseInt(user.flags, 10) > 0; + }); + } + sortUsers(userData, sortBy); uids = userData.map(function(user) { diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index a7a9253163..1bd1726b97 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -1,32 +1,48 @@
-
+ +
+ +
+
+
+
+

+ +

+
+ +
+
+ + + +
+
- -
-
@@ -53,7 +69,6 @@

{posts.content}

-

@@ -92,15 +107,4 @@
- -
-
-
Flags Control Panel
-
-
- -
-
-
-
diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index 644bde38f6..6af10174bb 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -8,6 +8,7 @@
  • Not validated
  • No Posts
  • Inactive
  • +
  • Most Flags
  • Banned
  • User Search
  • @@ -84,7 +85,7 @@ posts {users.postcount} -
    {users.flags}
    +
    From f70d54a397b4eeb7a64abfb70cf2c0769734de07 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 4 Jul 2016 17:53:13 +0300 Subject: [PATCH 21/21] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 48c4d979b6..f7cf719173 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.9", "nodebb-rewards-essentials": "0.0.8", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.3", - "nodebb-theme-vanilla": "5.1.0", + "nodebb-theme-persona": "4.1.4", + "nodebb-theme-vanilla": "5.1.1", "nodebb-widget-essentials": "2.0.9", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0",