From 8296dc56304e294966f34b8c777c6010d7f4e376 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 24 Jul 2014 17:30:37 -0400 Subject: [PATCH 001/372] beginning topic searching + new hook --- src/socket.io/topics.js | 4 ++++ src/topics.js | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 274950d455..c00745d2ae 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -417,4 +417,8 @@ SocketTopics.searchTags = function(socket, data, callback) { topics.searchTags(data, callback); }; +SocketTopics.search = function(socket, data, callback) { + topics.search(data.tid, data.term, callback); +}; + module.exports = SocketTopics; diff --git a/src/topics.js b/src/topics.js index addde2cdda..c4f784b811 100644 --- a/src/topics.js +++ b/src/topics.js @@ -446,4 +446,15 @@ var async = require('async'), }); }; + Topics.search = function(tid, term, callback) { + if (plugins.hasListeners('filter:topic.search')) { + plugins.fireHook('filter:topic.search', { + tid: tid, + term: term + }, callback); + } else { + callback(undefined, []); + } + }; + }(exports)); From a49aee9d3dbada3f0a8f32b5945678b7e456f18e Mon Sep 17 00:00:00 2001 From: chronize Date: Thu, 31 Jul 2014 15:20:41 -0400 Subject: [PATCH 002/372] Merge master --- package.json | 45 +++++++++++++++++++++++--------- public/src/forum/admin/groups.js | 27 ++++++++++++++++--- src/groups.js | 8 +++++- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 1f29c41593..ecc7ae2750 100644 --- a/package.json +++ b/package.json @@ -16,24 +16,33 @@ }, "dependencies": { "async": "~0.9.0", + "base64-url": "^1.0.0", "bcryptjs": "~2.0.1", + "body-parser": "^1.0.1", + "colors": "^0.6.2", + "compression": "^1.0.1", "connect-flash": "^0.1.1", + "connect-multiparty": "^1.0.1", + "cookie": "^0.1.2", + "cookie-parser": "^1.0.1", + "cookie-signature": "^1.0.4", "cron": "~1.0.4", + "csurf": "^1.1.0", + "cycle": "^1.0.3", "daemon": "~1.1.0", + "debug": "^1.0.4", + "depd": "^0.4.4", "express": "4.6.1", - "cookie-parser": "^1.0.1", - "body-parser": "^1.0.1", - "serve-favicon": "^2.0.1", "express-session": "^1.0.2", - "csurf": "^1.1.0", - "compression": "^1.0.1", - "connect-multiparty": "^1.0.1", - "morgan": "^1.0.0", + "finalhandler": "^0.1.0", "gm": "1.16.0", "gravatar": "1.0.6", "less": "~1.7.3", "mkdirp": "~0.5.0", - "nconf": "~0.6.7", + "mocha": "^1.21.3", + "morgan": "^1.0.0", + "ms": "^0.6.2", + "nconf": "^0.6.9", "nodebb-plugin-dbsearch": "0.0.13", "nodebb-plugin-markdown": "~0.5.0", "nodebb-plugin-mentions": "~0.5.0", @@ -41,24 +50,34 @@ "nodebb-theme-lavender": "~0.0.74", "nodebb-theme-vanilla": "~0.0.111", "nodebb-widget-essentials": "~0.1.0", - "npm": "^1.4.6", + "npm": "^1.4.21", "passport": "~0.2.0", "passport-local": "1.0.0", + "path-to-regexp": "^0.2.3", + "pkginfo": "^0.3.0", "prompt": "~0.2.11", + "read": "^1.0.5", + "reds": "^0.2.4", "request": "~2.38.0", + "revalidator": "^0.2.0", "rimraf": "~2.2.6", "rss": "~0.3.2", "semver": "~2.3.1", + "serve-favicon": "^2.0.1", "sitemap": "~0.7.3", "socket.io": "~0.9.16", + "socket.io-client": "^1.0.6", "socket.io-wildcard": "~0.1.1", + "stack-trace": "0.0.9", "string": "~1.9.0", - "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", + "templates.js": "0.0.8", + "uglify-js": "^2.4.15", "underscore": "~1.6.0", + "utile": "^0.2.1", + "utils-merge": "^1.0.0", "validator": "~3.16.1", - "winston": "~0.7.2", - "xregexp": "~2.0.0", - "templates.js": "0.0.8" + "winston": "^0.7.3", + "xregexp": "~2.0.0" }, "devDependencies": { "mocha": "~1.13.0" diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js index 5e4f0c34d1..34a1b59ed0 100644 --- a/public/src/forum/admin/groups.js +++ b/public/src/forum/admin/groups.js @@ -78,14 +78,33 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { break; case 'members': socket.emit('admin.groups.get', groupName, function(err, groupObj) { - var formEl = detailsModal.find('form'); + var formEl = detailsModal.find('form').keypress(function(e) { + switch(e.keyCode) { + case 13: + detailsModalSave.click(); + break; + default: + break; + } + }), + groupLabelPreview = formEl.find('#group-label-preview'), + changeGroupUserTitle = formEl.find('#change-group-user-title'), + changeGroupLabelColor = formEl.find('#change-group-label-color'); formEl.find('#change-group-name').val(groupObj.name).prop('readonly', groupObj.system); formEl.find('#change-group-desc').val(groupObj.description); - formEl.find('#change-group-user-title').val(groupObj.userTitle); + changeGroupUserTitle.val(groupObj.userTitle).keydown(function() { + setTimeout(function() { + groupLabelPreview.text(changeGroupUserTitle.val()); + }, 0); + }); formEl.find('#group-icon').attr('class', 'fa fa-2x ' + groupObj.icon).attr('value', groupObj.icon); - formEl.find('#change-group-label-color').val(groupObj.labelColor); - formEl.find('#group-label-preview').css('background', groupObj.labelColor || '#000000').text(groupObj.userTitle); + changeGroupLabelColor.val(groupObj.labelColor).keydown(function() { + setTimeout(function() { + groupLabelPreview.css('background', changeGroupLabelColor.val() || '#000000'); + }, 0); + }); + groupLabelPreview.css('background', groupObj.labelColor || '#000000').text(groupObj.userTitle); groupMembersEl.empty(); if (groupObj.members.length > 0) { diff --git a/src/groups.js b/src/groups.js index 8164ae457c..5af818c5ec 100644 --- a/src/groups.js +++ b/src/groups.js @@ -295,7 +295,13 @@ db.rename('group:' + oldName, 'group:' + newName, next); }, function(next) { - db.rename('group:' + oldName + ':members', 'group:' + newName + ':members', next); + Groups.exists('group:' + oldName + ':members', function(err, exists) { + if (exists) { + db.rename('group:' + oldName + ':members', 'group:' + newName + ':members', next); + } else { + next(); + } + }); }, function(next) { renameGroupMember('groups', oldName, newName, next); From 4e4187417e388312b8b5a39386665707fffce435 Mon Sep 17 00:00:00 2001 From: chronize Date: Thu, 31 Jul 2014 16:25:48 -0400 Subject: [PATCH 003/372] optimize ACP groups screen, jQuery caching --- public/src/forum/admin/groups.js | 167 +++++++++++++++++-------------- src/groups.js | 5 - 2 files changed, 94 insertions(+), 78 deletions(-) diff --git a/public/src/forum/admin/groups.js b/public/src/forum/admin/groups.js index 34a1b59ed0..ee39ccf975 100644 --- a/public/src/forum/admin/groups.js +++ b/public/src/forum/admin/groups.js @@ -7,28 +7,53 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { Groups.init = function() { var yourid = ajaxify.variables.get('yourid'), createModal = $('#create-modal'), - createNameEl = $('#create-group-name'), - detailsModal = $('#group-details-modal'), - detailsSearch = detailsModal.find('#group-details-search'), - searchResults = detailsModal.find('#group-details-search-results'), - groupMembersEl = detailsModal.find('ul.current_members'), - detailsModalSave = detailsModal.find('.btn-primary'), - searchDelay, - listEl = $('#groups-list'); - - $('#create').on('click', function() { + createGroupName = $('#create-group-name'), + create = $('#create'), + createModalGo = $('#create-modal-go'), + createGroupDesc = $('#create-group-desc'), + createModalError = $('#create-modal-error'), + groupDetailsModal = $('#group-details-modal'), + groupDetailsSearch = $('#group-details-search'), + groupDetailsSearchResults = $('#group-details-search-results'), + groupMembersEl = $('ul.current_members'), + formEl = groupDetailsModal.find('form'), + detailsModalSave = $('#details-modal-save'), + groupsList = $('#groups-list'), + groupIcon = $('#group-icon'), + changeGroupIcon = $('#change-group-icon'), + changeGroupName = $('#change-group-name'), + changeGroupDesc = $('#change-group-desc'), + changeGroupUserTitle = $('#change-group-user-title'), + changeGroupLabelColor = $('#change-group-label-color'), + groupIcon = $('#group-icon'), + groupLabelPreview = $('#group-label-preview'), + searchDelay; + + // Tooltips + $('#groups-list .members li').tooltip(); + + createModal.on('keypress', function(e) { + switch(e.keyCode) { + case 13: + createModalGo.click(); + break; + default: + break; + } + }); + + create.on('click', function() { createModal.modal('show'); setTimeout(function() { - createNameEl.focus(); + createGroupName.focus(); }, 250); }); - $('#create-modal-go').on('click', function() { + createModalGo.on('click', function() { var submitObj = { - name: createNameEl.val(), - description: $('#create-group-desc').val() + name: createGroupName.val(), + description: createGroupDesc.val() }, - errorEl = $('#create-modal-error'), errorText; socket.emit('admin.groups.create', submitObj, function(err, data) { @@ -45,10 +70,10 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { break; } - errorEl.html(errorText).removeClass('hide'); + createModalError.html(errorText).removeClass('hide'); } else { - errorEl.addClass('hide'); - createNameEl.val(''); + createModalError.addClass('hide'); + createGroupName.val(''); createModal.on('hidden.bs.modal', function() { ajaxify.go('admin/groups'); }); @@ -57,7 +82,29 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { }); }); - listEl.on('click', 'button[data-action]', function() { + formEl.keypress(function(e) { + switch(e.keyCode) { + case 13: + detailsModalSave.click(); + break; + default: + break; + } + }); + + changeGroupUserTitle.keydown(function() { + setTimeout(function() { + groupLabelPreview.text(changeGroupUserTitle.val()); + }, 0); + }); + + changeGroupLabelColor.keydown(function() { + setTimeout(function() { + groupLabelPreview.css('background', changeGroupLabelColor.val() || '#000000'); + }, 0); + }); + + groupsList.on('click', 'button[data-action]', function() { var el = $(this), action = el.attr('data-action'), groupName = el.parents('li[data-groupname]').attr('data-groupname'); @@ -78,32 +125,12 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { break; case 'members': socket.emit('admin.groups.get', groupName, function(err, groupObj) { - var formEl = detailsModal.find('form').keypress(function(e) { - switch(e.keyCode) { - case 13: - detailsModalSave.click(); - break; - default: - break; - } - }), - groupLabelPreview = formEl.find('#group-label-preview'), - changeGroupUserTitle = formEl.find('#change-group-user-title'), - changeGroupLabelColor = formEl.find('#change-group-label-color'); - - formEl.find('#change-group-name').val(groupObj.name).prop('readonly', groupObj.system); - formEl.find('#change-group-desc').val(groupObj.description); - changeGroupUserTitle.val(groupObj.userTitle).keydown(function() { - setTimeout(function() { - groupLabelPreview.text(changeGroupUserTitle.val()); - }, 0); - }); - formEl.find('#group-icon').attr('class', 'fa fa-2x ' + groupObj.icon).attr('value', groupObj.icon); - changeGroupLabelColor.val(groupObj.labelColor).keydown(function() { - setTimeout(function() { - groupLabelPreview.css('background', changeGroupLabelColor.val() || '#000000'); - }, 0); - }); + + changeGroupName.val(groupObj.name).prop('readonly', groupObj.system); + changeGroupDesc.val(groupObj.description); + changeGroupUserTitle.val(groupObj.userTitle); + groupIcon.attr('class', 'fa fa-2x ' + groupObj.icon).attr('value', groupObj.icon); + changeGroupLabelColor.val(groupObj.labelColor); groupLabelPreview.css('background', groupObj.labelColor || '#000000').text(groupObj.userTitle); groupMembersEl.empty(); @@ -117,23 +144,21 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { } } - detailsModal.attr('data-groupname', groupObj.name); - detailsModal.modal('show'); + groupDetailsModal.attr('data-groupname', groupObj.name); + groupDetailsModal.modal('show'); }); break; } }); - detailsSearch.on('keyup', function() { - var searchEl = this; + groupDetailsSearch.on('keyup', function() { if (searchDelay) { clearTimeout(searchDelay); } searchDelay = setTimeout(function() { - var searchText = searchEl.value, - resultsEl = $('#group-details-search-results'), + var searchText = groupDetailsSearch.val(), foundUser; socket.emit('admin.user.search', searchText, function(err, results) { @@ -143,7 +168,7 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { numResults = 4; } - resultsEl.empty(); + groupDetailsSearchResults.empty(); for (x = 0; x < numResults; x++) { foundUser = $('
  • '); foundUser @@ -151,19 +176,19 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { .append($('').attr('src', results.users[x].picture)) .append($('').html(results.users[x].username)); - resultsEl.append(foundUser); + groupDetailsSearchResults.append(foundUser); } } else { - resultsEl.html('
  • No Users Found
  • '); + groupDetailsSearchResults.html('
  • No Users Found
  • '); } }); }, 200); }); - searchResults.on('click', 'li[data-uid]', function() { + groupDetailsSearchResults.on('click', 'li[data-uid]', function() { var userLabel = $(this), uid = parseInt(userLabel.attr('data-uid'), 10), - groupName = detailsModal.attr('data-groupname'), + groupName = groupDetailsModal.attr('data-groupname'), members = []; groupMembersEl.find('li[data-uid]').each(function() { @@ -184,7 +209,7 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { groupMembersEl.on('click', 'li[data-uid]', function() { var uid = $(this).attr('data-uid'), - groupName = detailsModal.attr('data-groupname'); + groupName = groupDetailsModal.attr('data-groupname'); socket.emit('admin.groups.get', groupName, function(err, groupObj){ if (!err){ @@ -204,38 +229,34 @@ define('forum/admin/groups', ['forum/admin/iconSelect'], function(iconSelect) { }); }); - $('#change-group-icon').on('click', function() { - iconSelect.init($('#group-icon')); + changeGroupIcon.on('click', function() { + iconSelect.init(groupIcon); }); - admin.enableColorPicker($('#change-group-label-color'), function(hsb, hex) { - $('#group-label-preview').css('background-color', '#' + hex); + admin.enableColorPicker(changeGroupLabelColor, function(hsb, hex) { + groupLabelPreview.css('background-color', '#' + hex); }); detailsModalSave.on('click', function() { - var formEl = detailsModal.find('form'); - socket.emit('admin.groups.update', { - groupName: detailsModal.attr('data-groupname'), + groupName: groupDetailsModal.attr('data-groupname'), values: { - name: formEl.find('#change-group-name').val(), - userTitle: formEl.find('#change-group-user-title').val(), - description: formEl.find('#change-group-desc').val(), - icon: formEl.find('#group-icon').attr('value'), - labelColor: formEl.find('#change-group-label-color').val() + name: changeGroupName.val(), + userTitle: changeGroupUserTitle.val(), + description: changeGroupDesc.val(), + icon: groupIcon.attr('value'), + labelColor: changeGroupLabelColor.val() } }, function(err) { if (!err) { - detailsModal.on('hidden.bs.modal', function() { + groupDetailsModal.on('hidden.bs.modal', function() { ajaxify.go('admin/groups'); }); - detailsModal.modal('hide'); + groupDetailsModal.modal('hide'); } }); }); - // Tooltips - $('#groups-list .members li').tooltip(); }; return Groups; diff --git a/src/groups.js b/src/groups.js index 35ca5a6ccf..af60b9b161 100644 --- a/src/groups.js +++ b/src/groups.js @@ -302,16 +302,11 @@ }, function(next) { Groups.exists('group:' + oldName + ':members', function(err, exists) { -<<<<<<< HEAD - if (exists) { - db.rename('group:' + oldName + ':members', 'group:' + newName + ':members', next); -======= if (err) { return next(err); } if (exists) { db.rename('group:' + oldName + ':members', 'group:' + newName + ':members', next); ->>>>>>> aa4089e6d78b6412bae1274bfa0c20bbb754c054 } else { next(); } From c4567144408dd04883b21634b0b16ed8ce96b253 Mon Sep 17 00:00:00 2001 From: chronize Date: Thu, 31 Jul 2014 18:16:27 -0400 Subject: [PATCH 004/372] revert package.json --- package.json | 45 +++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index ecc7ae2750..1f29c41593 100644 --- a/package.json +++ b/package.json @@ -16,33 +16,24 @@ }, "dependencies": { "async": "~0.9.0", - "base64-url": "^1.0.0", "bcryptjs": "~2.0.1", - "body-parser": "^1.0.1", - "colors": "^0.6.2", - "compression": "^1.0.1", "connect-flash": "^0.1.1", - "connect-multiparty": "^1.0.1", - "cookie": "^0.1.2", - "cookie-parser": "^1.0.1", - "cookie-signature": "^1.0.4", "cron": "~1.0.4", - "csurf": "^1.1.0", - "cycle": "^1.0.3", "daemon": "~1.1.0", - "debug": "^1.0.4", - "depd": "^0.4.4", "express": "4.6.1", + "cookie-parser": "^1.0.1", + "body-parser": "^1.0.1", + "serve-favicon": "^2.0.1", "express-session": "^1.0.2", - "finalhandler": "^0.1.0", + "csurf": "^1.1.0", + "compression": "^1.0.1", + "connect-multiparty": "^1.0.1", + "morgan": "^1.0.0", "gm": "1.16.0", "gravatar": "1.0.6", "less": "~1.7.3", "mkdirp": "~0.5.0", - "mocha": "^1.21.3", - "morgan": "^1.0.0", - "ms": "^0.6.2", - "nconf": "^0.6.9", + "nconf": "~0.6.7", "nodebb-plugin-dbsearch": "0.0.13", "nodebb-plugin-markdown": "~0.5.0", "nodebb-plugin-mentions": "~0.5.0", @@ -50,34 +41,24 @@ "nodebb-theme-lavender": "~0.0.74", "nodebb-theme-vanilla": "~0.0.111", "nodebb-widget-essentials": "~0.1.0", - "npm": "^1.4.21", + "npm": "^1.4.6", "passport": "~0.2.0", "passport-local": "1.0.0", - "path-to-regexp": "^0.2.3", - "pkginfo": "^0.3.0", "prompt": "~0.2.11", - "read": "^1.0.5", - "reds": "^0.2.4", "request": "~2.38.0", - "revalidator": "^0.2.0", "rimraf": "~2.2.6", "rss": "~0.3.2", "semver": "~2.3.1", - "serve-favicon": "^2.0.1", "sitemap": "~0.7.3", "socket.io": "~0.9.16", - "socket.io-client": "^1.0.6", "socket.io-wildcard": "~0.1.1", - "stack-trace": "0.0.9", "string": "~1.9.0", - "templates.js": "0.0.8", - "uglify-js": "^2.4.15", + "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", "underscore": "~1.6.0", - "utile": "^0.2.1", - "utils-merge": "^1.0.0", "validator": "~3.16.1", - "winston": "^0.7.3", - "xregexp": "~2.0.0" + "winston": "~0.7.2", + "xregexp": "~2.0.0", + "templates.js": "0.0.8" }, "devDependencies": { "mocha": "~1.13.0" From efe938b392b3115f48f624a13c44e0ac16e77290 Mon Sep 17 00:00:00 2001 From: chronize Date: Sat, 2 Aug 2014 15:03:15 -0400 Subject: [PATCH 005/372] fix upgrade.js according to CodeClimate recommendation, remove BOM --- src/upgrade.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/upgrade.js b/src/upgrade.js index db91530ada..815baab82f 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -1,4 +1,4 @@ -"use strict"; +"use strict"; var db = require('./database'), async = require('async'), @@ -75,7 +75,7 @@ Upgrade.upgrade = function(callback) { { "widget": "html", "data": { - "html": Meta.config['motd'] || "Welcome to NodeBB, if you are an administrator of this forum visit the Themes ACP to modify and add widgets." + "html": Meta.config.motd || "Welcome to NodeBB, if you are an administrator of this forum visit the Themes ACP to modify and add widgets." } } ]), function(err) { @@ -171,7 +171,7 @@ Upgrade.upgrade = function(callback) { { "widget": "html", "data": { - "html": Meta.config['motd'] || "Welcome to NodeBB, if you are an administrator of this forum visit the Themes ACP to modify and add widgets.", + "html": Meta.config.motd || "Welcome to NodeBB, if you are an administrator of this forum visit the Themes ACP to modify and add widgets.", "container": container, "title": "MOTD" } From ceaedf9e2febb21be6672e01a889fbd0d94cbf7e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 12 Aug 2014 13:02:07 -0400 Subject: [PATCH 006/372] closes #1941 --- src/topics/follow.js | 77 ++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/src/topics/follow.js b/src/topics/follow.js index e16cf99b9a..76fb14cf6a 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -22,49 +22,50 @@ module.exports = function(Topics) { }; Topics.notifyFollowers = function(tid, pid, exceptUid) { - async.parallel({ - nid: function(next) { - async.parallel({ - topicData: async.apply(Topics.getTopicFields, tid, ['title', 'slug']), - username: async.apply(user.getUserField, exceptUid, 'username'), - postIndex: async.apply(posts.getPidIndex, pid), - postContent: function(next) { - async.waterfall([ - async.apply(posts.getPostField, pid, 'content'), - function(content, next) { - postTools.parse(content, next); - } - ], next); - } - }, function(err, results) { - if (err) { - return next(err); - } + Topics.getFollowers(tid, function(err, followers) { + if (err || !Array.isArray(followers) || !followers.length) { + return; + } - notifications.create({ - bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topicData.title + ']]', - bodyLong: results.postContent, - path: nconf.get('relative_path') + '/topic/' + results.topicData.slug + '/' + results.postIndex, - uniqueId: 'topic:' + tid + ':uid:' + exceptUid, - tid: tid, - from: exceptUid - }, next); - }); - }, - followers: function(next) { - Topics.getFollowers(tid, next); + var index = followers.indexOf(exceptUid.toString()); + if (index !== -1) { + followers.splice(index, 1); + } + + if (!followers.length) { + return; } - }, function(err, results) { - if (!err && results.followers.length) { - var index = results.followers.indexOf(exceptUid.toString()); - if (index !== -1) { - results.followers.splice(index, 1); + async.parallel({ + topicData: async.apply(Topics.getTopicFields, tid, ['title', 'slug']), + username: async.apply(user.getUserField, exceptUid, 'username'), + postIndex: async.apply(posts.getPidIndex, pid), + postContent: function(next) { + async.waterfall([ + async.apply(posts.getPostField, pid, 'content'), + function(content, next) { + postTools.parse(content, next); + } + ], next); + } + }, function(err, results) { + if (err) { + return; } - notifications.push(results.nid, results.followers); - } + notifications.create({ + bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topicData.title + ']]', + bodyLong: results.postContent, + path: nconf.get('relative_path') + '/topic/' + results.topicData.slug + '/' + results.postIndex, + uniqueId: 'topic:' + tid + ':uid:' + exceptUid, + tid: tid, + from: exceptUid + }, function(err, nid) { + if (!err) { + notifications.push(nid, followers); + } + }); + }); }); }; - }; \ No newline at end of file From ac43ff3c3d070855b3720de27250da00bab8def1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 12 Aug 2014 13:45:18 -0400 Subject: [PATCH 007/372] closes #1970 --- public/language/en_GB/notifications.json | 2 + src/socket.io/posts.js | 84 +++++++++++++----------- src/socket.io/topics.js | 54 +++++++++++++-- 3 files changed, 94 insertions(+), 46 deletions(-) diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json index b45129b467..5225cffbe6 100644 --- a/public/language/en_GB/notifications.json +++ b/public/language/en_GB/notifications.json @@ -13,6 +13,8 @@ "new_message_from": "New message from %1", "upvoted_your_post": "%1 has upvoted your post.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", "favourited_your_post": "%1 has favourited your post.", "user_flagged_post": "%1 flagged a post.", "user_posted_to" : "%1 has posted a reply to: %2", diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 328eda21f2..6b3b51547c 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -51,8 +51,11 @@ SocketPosts.reply = function(socket, data, callback) { }; SocketPosts.upvote = function(socket, data, callback) { + if (!data || !data.pid) { + return callback(new Error('[[error:invalid-data]]')); + } favouriteCommand('upvote', 'voted', socket, data, callback); - sendNotificationToPostOwner(data, socket.uid, 'notifications:upvoted_your_post'); + SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:upvoted_your_post'); }; SocketPosts.downvote = function(socket, data, callback) { @@ -64,8 +67,11 @@ SocketPosts.unvote = function(socket, data, callback) { }; SocketPosts.favourite = function(socket, data, callback) { + if (!data || !data.pid) { + return callback(new Error('[[error:invalid-data]]')); + } favouriteCommand('favourite', 'favourited', socket, data, callback); - sendNotificationToPostOwner(data, socket.uid, 'notifications:favourited_your_post'); + SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:favourited_your_post'); }; SocketPosts.unfavourite = function(socket, data, callback) { @@ -73,7 +79,6 @@ SocketPosts.unfavourite = function(socket, data, callback) { }; function favouriteCommand(command, eventName, socket, data, callback) { - if(data && data.pid && data.room_id) { favourites[command](data.pid, socket.uid, function(err, result) { if (err) { @@ -90,49 +95,50 @@ function favouriteCommand(command, eventName, socket, data, callback) { } } -function sendNotificationToPostOwner(data, fromuid, notification) { - if(data && data.pid && fromuid) { - posts.getPostFields(data.pid, ['tid', 'uid'], function(err, postData) { - if (err) { - return; - } +SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { + if(!pid || !fromuid) { + return; + } + posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) { + if (err) { + return; + } - if (fromuid === parseInt(postData.uid, 10)) { + if (fromuid === parseInt(postData.uid, 10)) { + return; + } + + async.parallel({ + username: async.apply(user.getUserField, fromuid, 'username'), + slug: async.apply(topics.getTopicField, postData.tid, 'slug'), + index: async.apply(posts.getPidIndex, pid), + postContent: function(next) { + async.waterfall([ + async.apply(posts.getPostField, pid, 'content'), + function(content, next) { + postTools.parse(content, next); + } + ], next); + } + }, function(err, results) { + if (err) { return; } - async.parallel({ - username: async.apply(user.getUserField, fromuid, 'username'), - slug: async.apply(topics.getTopicField, postData.tid, 'slug'), - index: async.apply(posts.getPidIndex, data.pid), - postContent: function(next) { - async.waterfall([ - async.apply(posts.getPostField, data.pid, 'content'), - function(content, next) { - postTools.parse(content, next); - } - ], next); - } - }, function(err, results) { - if (err) { - return; + notifications.create({ + bodyShort: '[[' + notification + ', ' + results.username + ']]', + bodyLong: results.postContent, + path: nconf.get('relative_path') + '/topic/' + results.slug + '/' + results.index, + uniqueId: 'post:' + pid + ':uid:' + fromuid, + from: fromuid + }, function(err, nid) { + if (!err) { + notifications.push(nid, [postData.uid]); } - - notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ']]', - bodyLong: results.postContent, - path: nconf.get('relative_path') + '/topic/' + results.slug + '/' + results.index, - uniqueId: 'post:' + data.pid + ':uid:' + fromuid, - from: fromuid - }, function(err, nid) { - if (!err) { - notifications.push(nid, [postData.uid]); - } - }); }); }); - } -} + }); +}; SocketPosts.getRawPost = function(socket, pid, callback) { async.waterfall([ diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 4c66db8cb1..20d780923b 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -1,17 +1,20 @@ 'use strict'; -var topics = require('../topics'), +var nconf = require('nconf'), + async = require('async'), + + topics = require('../topics'), categories = require('../categories'), privileges = require('../privileges'), + notifications = require('../notifications'), threadTools = require('../threadTools'), websockets = require('./index'), user = require('../user'), - db = require('./../database'), - meta = require('./../meta'), + db = require('../database'), + meta = require('../meta'), utils = require('../../public/src/utils'), - - async = require('async'), + SocketPosts = require('./posts'), SocketTopics = {}; @@ -290,7 +293,14 @@ SocketTopics.movePost = function(socket, data, callback) { return callback(err || new Error('[[error:no-privileges]]')); } - topics.movePostToTopic(data.pid, data.tid, callback); + topics.movePostToTopic(data.pid, data.tid, function(err) { + if (err) { + return callback(err); + } + + SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:moved_your_post'); + callback(); + }); }); }; @@ -299,7 +309,7 @@ SocketTopics.move = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - async.each(data.tids, function(tid, next) { + async.eachLimit(data.tids, 10, function(tid, next) { var oldCid; async.waterfall([ function(next) { @@ -331,11 +341,41 @@ SocketTopics.move = function(socket, data, callback) { tid: tid }); + SocketTopics.sendNotificationToTopicOwner(tid, socket.uid, 'notifications:moved_your_topic'); + next(); }); }, callback); }; + +SocketTopics.sendNotificationToTopicOwner = function(tid, fromuid, notification) { + if(!tid || !fromuid) { + return; + } + + async.parallel({ + username: async.apply(user.getUserField, fromuid, 'username'), + topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug']), + }, function(err, results) { + if (err || fromuid === parseInt(results.topicData.uid, 10)) { + return; + } + + notifications.create({ + bodyShort: '[[' + notification + ', ' + results.username + ']]', + path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, + uniqueId: 'topic:' + tid + ':uid:' + fromuid, + from: fromuid + }, function(err, nid) { + if (!err) { + notifications.push(nid, [results.topicData.uid]); + } + }); + }); +}; + + SocketTopics.moveAll = function(socket, data, callback) { if(!data || !data.cid || !data.currentCid) { return callback(new Error('[[error:invalid-data]]')); From 005405b16c5e98dfeebe7cb5bcadae9d9a67c022 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 12 Aug 2014 21:41:23 -0400 Subject: [PATCH 008/372] closes #1976 --- bcrypt.js | 30 +++++++++++++++++++++ src/password.js | 51 ++++++++++++++++++++++++++++++++++++ src/routes/authentication.js | 12 ++++----- src/user.js | 12 +++------ src/user/profile.js | 14 +++++----- 5 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 bcrypt.js create mode 100644 src/password.js diff --git a/bcrypt.js b/bcrypt.js new file mode 100644 index 0000000000..a926310923 --- /dev/null +++ b/bcrypt.js @@ -0,0 +1,30 @@ + +'use strict'; + + +var bcrypt = require('bcryptjs'); + +process.on('message', function(m) { + if (m.type === 'hash') { + hash(m.rounds, m.password); + } else if (m.type === 'compare') { + compare(m.password, m.hash); + } +}); + +function hash(rounds, password) { + bcrypt.genSalt(rounds, function(err, salt) { + if (err) { + return process.send({type:'hash', err: {message: err.message}}); + } + bcrypt.hash(password, salt, function(err, hash) { + process.send({type:'hash', err: err ? {message: err.message} : null, hash: hash, password: password}); + }); + }); +} + +function compare(password, hash) { + bcrypt.compare(password, hash, function(err, res) { + process.send({type:'compare', err: err ? {message: err.message} : null, hash: hash, password: password, result: res}); + }); +} \ No newline at end of file diff --git a/src/password.js b/src/password.js new file mode 100644 index 0000000000..c4a7febca8 --- /dev/null +++ b/src/password.js @@ -0,0 +1,51 @@ + + +'use strict'; +var fork = require('child_process').fork; + +(function(module) { + + var child = fork('./bcrypt', process.argv.slice(2), { + env: process.env + }); + + var callbacks = { + 'hash': {}, + 'compare': {} + }; + + module.hash = function(rounds, password, callback) { + sendCommand({type: 'hash', password: password, rounds: rounds}, callback); + }; + + module.compare = function(password, hash, callback) { + sendCommand({type: 'compare', password: password, hash: hash}, callback); + }; + + function sendCommand(data, callback) { + callbacks[data.type][data.password] = callbacks[data.type][data.password] || []; + callbacks[data.type][data.password].push(callback); + child.send(data); + } + + child.on('message', function(msg) { + var cbs = callbacks[msg.type] ? callbacks[msg.type][msg.password] : null; + + if (Array.isArray(cbs)) { + if (msg.err) { + var err = new Error(msg.err.message); + cbs.forEach(function(callback) { + callback(err); + }); + cbs.length = 0; + return; + } + + cbs.forEach(function(callback) { + callback(null, msg.type === 'hash' ? msg.hash : msg.result); + }); + cbs.length = 0; + } + }); + +}(exports)); diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 42139e87c4..19d13aeae6 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -4,15 +4,15 @@ var passport = require('passport'), passportLocal = require('passport-local').Strategy, nconf = require('nconf'), - bcrypt = require('bcryptjs'), + Password = require('../password'), winston = require('winston'), async = require('async'), - meta = require('./../meta'), - user = require('./../user'), - plugins = require('./../plugins'), + meta = require('../meta'), + user = require('../user'), + plugins = require('../plugins'), db = require('../database'), - utils = require('./../../public/src/utils'), + utils = require('../../public/src/utils'), login_strategies = []; @@ -226,7 +226,7 @@ return next(null, false, '[[error:user-banned]]'); } - bcrypt.compare(password, userData.password, function(err, res) { + Password.compare(password, userData.password, function(err, res) { if (err) { return next(new Error('bcrypt compare error')); } diff --git a/src/user.js b/src/user.js index c0e96960c6..951b47e8d9 100644 --- a/src/user.js +++ b/src/user.js @@ -1,6 +1,6 @@ 'use strict'; -var bcrypt = require('bcryptjs'), +var async = require('async'), nconf = require('nconf'), gravatar = require('gravatar'), @@ -9,7 +9,8 @@ var bcrypt = require('bcryptjs'), db = require('./database'), meta = require('./meta'), groups = require('./groups'), - emitter = require('./emitter'); + emitter = require('./emitter'), + Password = require('./password'); (function(User) { @@ -295,12 +296,7 @@ var bcrypt = require('bcryptjs'), return callback(null, password); } - bcrypt.genSalt(nconf.get('bcrypt_rounds'), function(err, salt) { - if (err) { - return callback(err); - } - bcrypt.hash(password, salt, callback); - }); + Password.hash(nconf.get('bcrypt_rounds'), password, callback); }; User.onNewPostMade = function(postData) { diff --git a/src/user/profile.js b/src/user/profile.js index ea332dee21..d76ac2bf65 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -1,15 +1,15 @@ 'use strict'; -var bcrypt = require('bcryptjs'), - async = require('async'), +var async = require('async'), validator = require('validator'), S = require('string'), - utils = require('./../../public/src/utils'), - meta = require('./../meta'), - events = require('./../events'), - db = require('./../database'); + utils = require('../../public/src/utils'), + meta = require('../meta'), + events = require('../events'), + db = require('../database'), + Password = require('../password'); module.exports = function(User) { @@ -248,7 +248,7 @@ module.exports = function(User) { return hashAndSetPassword(callback); } - bcrypt.compare(data.currentPassword, currentPassword, function(err, res) { + Password.compare(data.currentPassword, currentPassword, function(err, res) { if (err || !res) { return callback(err || new Error('[[user:change_password_error_wrong_current]]')); } From dae3ef49beef36f217768f04769ab41fbe1467ea Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 13 Aug 2014 13:35:55 -0400 Subject: [PATCH 009/372] show user names who upvoted a post on mouse over --- public/src/forum/topic/postTools.js | 54 ++++++++++++++++++----------- src/favourites.js | 8 +++++ src/socket.io/posts.js | 17 +++++---- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/public/src/forum/topic/postTools.js b/public/src/forum/topic/postTools.js index 5fe564c3db..3b63650942 100644 --- a/public/src/forum/topic/postTools.js +++ b/public/src/forum/topic/postTools.js @@ -15,6 +15,8 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com share.addShareHandlers(topicName); addFavouriteHandler(); + + addVoteHandler(); }; PostTools.toggle = function(pid, isDeleted) { @@ -37,30 +39,42 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com function addFavouriteHandler() { $('#post-container').on('mouseenter', '.favourite-tooltip', function() { - if (!$(this).data('users-loaded')) { - $(this).data('users-loaded', 'true'); - var pid = $(this).parents('.post-row').attr('data-pid'); - var el = $(this).attr('title', "Loading..."); - socket.emit('posts.getFavouritedUsers', pid, function(err, usernames) { - if (err) { - return; - } - if (usernames.length > 6) { - var otherCount = usernames.length - 5; - usernames = usernames.slice(0, 5).join(', ').replace(/,/g, '|'); - translator.translate('[[topic:users_and_others, ' + usernames + ', ' + otherCount + ']]', function(translated) { - translated = translated.replace(/\|/g, ','); - el.attr('title', translated).tooltip('show'); - }); - } else { - usernames = usernames.join(', '); - el.attr('title', usernames).tooltip('show'); - } - }); + loadDataAndCreateTooltip($(this), 'posts.getFavouritedUsers'); + }); + } + + function addVoteHandler() { + $('#post-container').on('mouseenter', '.post-row .votes', function() { + loadDataAndCreateTooltip($(this), 'posts.getUpvoters'); + }); + } + + function loadDataAndCreateTooltip(el, method) { + var pid = el.parents('.post-row').attr('data-pid'); + socket.emit(method, pid, function(err, usernames) { + if (!err) { + createTooltip(el, usernames); } }); } + function createTooltip(el, usernames) { + if (!usernames.length) { + return; + } + if (usernames.length > 6) { + var otherCount = usernames.length - 5; + usernames = usernames.slice(0, 5).join(', ').replace(/,/g, '|'); + translator.translate('[[topic:users_and_others, ' + usernames + ', ' + otherCount + ']]', function(translated) { + translated = translated.replace(/\|/g, ','); + el.attr('title', translated).tooltip('destroy').tooltip('show'); + }); + } else { + usernames = usernames.join(', '); + el.attr('title', usernames).tooltip('destroy').tooltip('show'); + } + } + function addPostHandlers(tid, threadState) { $('.topic').on('click', '.post_reply', function() { if (!threadState.locked) { diff --git a/src/favourites.js b/src/favourites.js index c887c5c7e7..64de4fa32f 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -253,4 +253,12 @@ var async = require('async'), db.getSetsMembers(sets, callback); }; + Favourites.getUpvotedUidsByPids = function(pids, callback) { + var sets = pids.map(function(pid) { + return 'pid:' + pid + ':upvote'; + }); + db.getSetsMembers(sets, callback); + }; + + }(exports)); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 6b3b51547c..d08903a779 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -250,20 +250,25 @@ SocketPosts.getPrivileges = function(socket, pid, callback) { SocketPosts.getFavouritedUsers = function(socket, pid, callback) { favourites.getFavouritedUidsByPids([pid], function(err, data) { - if(err) { + if (err || !Array.isArray(data) || !data.length) { return callback(err); } - if(!Array.isArray(data) || !data.length) { - callback(null, []); - } + user.getUsernamesByUids(data[0], callback); + }); +}; - var pid_uids = data[0]; +SocketPosts.getUpvoters = function(socket, pid, callback) { + favourites.getUpvotedUidsByPids([pid], function(err, data) { + if (err || !Array.isArray(data) || !data.length) { + return callback(err, []); + } - user.getUsernamesByUids(pid_uids, callback); + user.getUsernamesByUids(data[0], callback); }); }; + SocketPosts.getPidPage = function(socket, pid, callback) { posts.getPidPage(pid, socket.uid, callback); }; From f562caaa61acaac61cbbc1d99b4f62a4aeb224a2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 13 Aug 2014 17:11:17 -0400 Subject: [PATCH 010/372] callback --- src/user.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/user.js b/src/user.js index 951b47e8d9..6cd1589a45 100644 --- a/src/user.js +++ b/src/user.js @@ -310,12 +310,10 @@ var emitter.on('event:newpost', User.onNewPostMade); User.incrementUserPostCountBy = function(uid, value, callback) { + callback = callback || function() {}; User.incrementUserFieldBy(uid, 'postcount', value, function(err, newpostcount) { if (err) { - if(typeof callback === 'function') { - callback(err); - } - return; + return callback(err); } db.sortedSetAdd('users:postcount', newpostcount, uid, callback); }); From 5c84a3adb50077fe85dc785da4cb2614c6d4db4d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 13 Aug 2014 18:25:26 -0400 Subject: [PATCH 011/372] closes #1974 --- install/data/defaults.json | 4 ++++ src/controllers/accounts.js | 3 ++- src/user.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index fc5369de82..0ecd7b5fd1 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -63,6 +63,10 @@ "field": "maximumProfileImageSize", "value": 256 }, + { + "field": "profileImageDimension", + "value": 128 + }, { "field": "chatMessagesToDisplay", "value": 50 diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index f2be462147..15bfd0a17b 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -398,10 +398,11 @@ accountsController.uploadPicture = function (req, res, next) { } var updateUid = req.user.uid; + var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; async.waterfall([ function(next) { - image.resizeImage(req.files.userPhoto.path, extension, 128, 128, next); + image.resizeImage(req.files.userPhoto.path, extension, imageDimension, imageDimension, next); }, function(next) { if (parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1) { diff --git a/src/user.js b/src/user.js index 6cd1589a45..0919406089 100644 --- a/src/user.js +++ b/src/user.js @@ -279,7 +279,7 @@ var } var options = { - size: '128', + size: parseInt(meta.config.profileImageDimension, 10) || 128, default: customGravatarDefaultImage || meta.config.defaultGravatarImage || 'identicon', rating: 'pg' }; From e909e46786c8f17322a331727821b5288110fb5b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 13 Aug 2014 19:25:58 -0400 Subject: [PATCH 012/372] closes #1989 --- public/language/en_GB/notifications.json | 1 + src/socket.io/user.js | 28 ++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json index 5225cffbe6..5bb153ccbc 100644 --- a/public/language/en_GB/notifications.json +++ b/public/language/en_GB/notifications.json @@ -19,6 +19,7 @@ "user_flagged_post": "%1 flagged a post.", "user_posted_to" : "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 7501c9651b..b3c7bf71dc 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -1,11 +1,13 @@ 'use strict'; var async = require('async'), + nconf = require('nconf'), user = require('../user'), groups = require('../groups'), topics = require('../topics'), + notifications = require('../notifications'), messaging = require('../messaging'), - utils = require('./../../public/src/utils'), + utils = require('../../public/src/utils'), meta = require('../meta'), SocketUser = {}; @@ -159,7 +161,29 @@ SocketUser.changePicture = function(socket, data, callback) { SocketUser.follow = function(socket, data, callback) { if (socket.uid && data) { - user.follow(socket.uid, data.uid, callback); + user.follow(socket.uid, data.uid, function(err) { + if (err) { + return callback(err); + } + + user.getUserFields(socket.uid, ['username', 'userslug'], function(err, userData) { + if (err) { + return callback(err); + } + + notifications.create({ + bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', + path: nconf.get('relative_path') + '/user/' + userData.userslug, + uniqueId: 'follow:uid:' + socket.uid, + from: socket.uid + }, function(err, nid) { + if (!err) { + notifications.push(nid, [data.uid]); + } + callback(err); + }); + }); + }); } }; From d12a526e82a90c324d29dc767f0faa76ea6a8112 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 13 Aug 2014 19:44:59 -0400 Subject: [PATCH 013/372] check against 0 first --- src/socket.io/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 64b1ae8f39..befcb0cc0b 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -321,7 +321,7 @@ Sockets.getUidsInRoom = function(roomName) { var uids = []; var clients = io.sockets.clients(roomName); for(var i=0; i Date: Wed, 13 Aug 2014 21:42:04 -0400 Subject: [PATCH 014/372] closes #1369 --- public/language/en_GB/user.json | 5 ++- src/threadTools.js | 15 ++++++++ src/topics/create.js | 63 +++++++++++++++++++-------------- src/user/settings.js | 6 +++- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index ab8c1cdae6..29c6dc983b 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -71,5 +71,8 @@ "notification_sounds" : "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } diff --git a/src/threadTools.js b/src/threadTools.js index 5e52d043c7..4bff68bdf1 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -240,4 +240,19 @@ var winston = require('winston'), ], callback); }; + ThreadTools.follow = function(tid, uid, callback) { + callback = callback || function() {}; + async.waterfall([ + function (next) { + ThreadTools.exists(tid, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-topic]]')); + } + db.setAdd('tid:' + tid + ':followers', uid, next); + } + ], callback); + }; + }(exports)); diff --git a/src/topics/create.js b/src/topics/create.js index d7a847d94a..955d42d182 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -105,9 +105,6 @@ module.exports = function(Topics) { if(!canCreate) { return next(new Error('[[error:no-privileges]]')); } - next(); - }, - function(next) { user.isReadyToPost(uid, next); }, function(next) { @@ -115,35 +112,46 @@ module.exports = function(Topics) { }, function(filteredData, next) { content = filteredData.content || data.content; - next(); - }, - function(next) { Topics.create({uid: uid, title: title, cid: cid, thumb: data.thumb, tags: data.tags}, next); }, function(tid, next) { Topics.reply({uid:uid, tid:tid, content:content, req: data.req}, next); }, function(postData, next) { - threadTools.toggleFollow(postData.tid, uid); - next(null, postData); - }, - function(postData, next) { - Topics.getTopicsByTids([postData.tid], 0, function(err, topicData) { - if(err) { - return next(err); + async.parallel({ + postData: function(next) { + next(null, postData); + }, + settings: function(next) { + user.getSettings(uid, function(err, settings) { + if (err) { + return next(err); + } + if (settings.followTopicsOnCreate) { + threadTools.follow(postData.tid, uid, next); + } else { + next(); + } + }); + }, + topicData: function(next) { + Topics.getTopicsByTids([postData.tid], 0, next); } - if(!topicData || !topicData.length) { - return next(new Error('[[error:no-topic]]')); - } - topicData = topicData[0]; - topicData.unreplied = 1; + }, next); + }, + function(data, next) { + if(!Array.isArray(data.topicData) || !data.topicData.length) { + return next(new Error('[[error:no-topic]]')); + } + + data.topicData = data.topicData[0]; + data.topicData.unreplied = 1; - plugins.fireHook('action:topic.post', topicData); + plugins.fireHook('action:topic.post', data.topicData); - next(null, { - topicData: topicData, - postData: postData - }); + next(null, { + topicData: data.topicData, + postData: data.postData }); } ], callback); @@ -178,9 +186,6 @@ module.exports = function(Topics) { if (!canReply) { return next(new Error('[[error:no-privileges]]')); } - next(); - }, - function(next) { user.isReadyToPost(uid, next); }, function(next) { @@ -215,6 +220,12 @@ module.exports = function(Topics) { function(topicData, next) { topicData.title = validator.escape(topicData.title); postData.topic = topicData; + user.getSettings(uid, next); + }, + function(settings, next) { + if (settings.followTopicsOnReply) { + threadTools.follow(postData.tid, uid); + } posts.getPidIndex(postData.pid, next); }, function(index, next) { diff --git a/src/user/settings.js b/src/user/settings.js index e023eda5eb..2cf9fba084 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -33,6 +33,8 @@ module.exports = function(User) { settings.notificationSounds = settings.notificationSounds ? parseInt(settings.notificationSounds, 10) === 1 : true; settings.language = settings.language || meta.config.defaultLang || 'en_GB'; settings.topicPostSort = settings.topicPostSort || meta.config.topicPostSort || 'oldest_to_newest'; + settings.followTopicsOnCreate = settings.followTopicsOnCreate ? parseInt(settings.followTopicsOnCreate, 10) === 1 : true; + settings.followTopicsOnReply = settings.followTopicsOnReply ? parseInt(settings.followTopicsOnReply, 10) === 1 : false; callback(null, settings); }); }); @@ -80,7 +82,9 @@ module.exports = function(User) { topicsPerPage: data.topicsPerPage, postsPerPage: data.postsPerPage, notificationSounds: data.notificationSounds, - language: data.language || meta.config.defaultLang + language: data.language || meta.config.defaultLang, + followTopicsOnCreate: data.followTopicsOnCreate, + followTopicsOnReply: data.followTopicsOnReply }, callback); }; From c58712e2a974fd8d21d6fc339b32b2778deb9325 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 14 Aug 2014 08:34:38 -0400 Subject: [PATCH 015/372] closes #1971 --- public/src/forum/admin/users.js | 16 ++++++++++++++++ src/socket.io/admin/user.js | 8 ++++++++ src/user/auth.js | 10 +++++++++- src/user/reset.js | 4 +--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/public/src/forum/admin/users.js b/public/src/forum/admin/users.js index 4bd33c8657..554f78660b 100644 --- a/public/src/forum/admin/users.js +++ b/public/src/forum/admin/users.js @@ -73,6 +73,22 @@ define('forum/admin/users', function() { unselectAll(); }); + $('.reset-lockout').on('click', function() { + var uids = getSelectedUids(); + if (!uids.length) { + return; + } + + socket.emit('admin.user.resetLockouts', uids, function(err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('Lockout(s) reset!'); + }); + + unselectAll(); + }); + $('.admin-user').on('click', function() { var uids = getSelectedUids(); if (!uids.length) { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index bdd3d2566d..dd8cf8de04 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -88,6 +88,14 @@ User.banUser = function(uid, callback) { }); }; +User.resetLockouts = function(socket, uids, callback) { + if (!Array.isArray(uids)) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.each(uids, user.auth.resetLockout, callback); +}; + User.deleteUsers = function(socket, uids, callback) { if(!Array.isArray(uids)) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/user/auth.js b/src/user/auth.js index b9c7b348ce..0aaa6f658a 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -1,6 +1,7 @@ 'use strict'; -var db = require('../database'), +var async = require('async'), + db = require('../database'), meta = require('../meta'); module.exports = function(User) { @@ -42,4 +43,11 @@ module.exports = function(User) { User.auth.clearLoginAttempts = function(uid) { db.delete('loginAttempts:' + uid); }; + + User.auth.resetLockout = function(uid, callback) { + async.parallel([ + async.apply(db.delete, 'loginAttemps:' + uid), + async.apply(db.delete, 'lockout:' + uid) + ], callback); + } }; \ No newline at end of file diff --git a/src/user/reset.js b/src/user/reset.js index ee80679bcd..2b101e8c27 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -88,10 +88,8 @@ var async = require('async'), db.deleteObjectField('reset:uid', code); db.deleteObjectField('reset:expiry', code); - db.delete('lockout:' + uid); - user.auth.clearLoginAttempts(uid); - callback(); + user.auth.resetLockout(uid, callback); }); }); }); From 5c91bc43354e2e7e1192c076c94c48a5504f8c36 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 14 Aug 2014 09:59:40 -0400 Subject: [PATCH 016/372] optimized home --- src/categories/recentreplies.js | 55 +++++++++++++++++++++++---------- src/controllers/index.js | 12 +------ 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index a240e68fad..81a59162d7 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -3,6 +3,7 @@ var async = require('async'), winston = require('winston'), + _ = require('underscore'), db = require('../database'), posts = require('../posts'), @@ -23,19 +24,51 @@ module.exports = function(Categories) { }); }; - Categories.getRecentTopicReplies = function(cid, uid, count, callback) { + Categories.getRecentTopicReplies = function(categoryData, callback) { + async.map(categoryData, function(category, next) { + getRecentTopicPids(category.cid, parseInt(category.numRecentReplies), next); + }, function(err, results) { + var pids = _.flatten(results); + + pids = pids.filter(function(pid, index, array) { + return !!pid && array.indexOf(pid) === index; + }); + + posts.getPostSummaryByPids(pids, {stripTags: true}, function(err, posts) { + if (err) { + return callback(err); + } + + async.each(categoryData, function(category, next) { + assignPostsToCategory(category, posts, next); + }, callback); + }); + }); + }; + + function assignPostsToCategory(category, posts, next) { + category.posts = posts.filter(function(post) { + return parseInt(post.category.cid, 10) === parseInt(category.cid); + }).sort(function(a, b) { + return parseInt(b.timestamp, 10) - parseInt(a.timestamp, 10); + }).slice(0, parseInt(category.numRecentReplies, 10)); + + next(); + } + + function getRecentTopicPids(cid, count, callback) { count = parseInt(count, 10); if (!count) { return callback(null, []); } db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, 0, function(err, pids) { - if (err || !pids || !pids.length) { + if (err || !Array.isArray(pids) || !pids.length) { return callback(err, []); } if (count === 1) { - return posts.getPostSummaryByPids(pids, {stripTags: true}, callback); + return callback(null, pids); } async.parallel({ @@ -60,22 +93,12 @@ module.exports = function(Categories) { pids = pids.concat(topicPids).filter(function(pid, index, array) { return !!pid && array.indexOf(pid) === index; }); - - posts.getPostSummaryByPids(pids, {stripTags: true}, function(err, posts) { - if (err) { - return callback(err); - } - - posts = posts.sort(function(a, b) { - return parseInt(b.timestamp, 10) - parseInt(a.timestamp, 10); - }).slice(0, count); - - callback(err, posts); - }); + callback(null, pids); }); }); }); - }; + } + Categories.moveRecentReplies = function(tid, oldCid, cid) { function movePost(postData, next) { diff --git a/src/controllers/index.js b/src/controllers/index.js index ccea529f5b..31c8348fb5 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -69,17 +69,7 @@ Controllers.home = function(req, res, next) { return next(err); } - function getRecentReplies(category, callback) { - categories.getRecentTopicReplies(category.cid, uid, parseInt(category.numRecentReplies, 10), function (err, posts) { - if (err) { - return callback(err); - } - category.posts = posts; - callback(); - }); - } - - async.each(categoryData, getRecentReplies, function (err) { + categories.getRecentTopicReplies(categoryData, function(err) { next(err, categoryData); }); }); From 60624eedec01a505f18bbc0022ee151fc51acd01 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 14 Aug 2014 10:16:43 -0400 Subject: [PATCH 017/372] small tweak --- src/categories/recentreplies.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 81a59162d7..efd84a7e19 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -25,9 +25,7 @@ module.exports = function(Categories) { }; Categories.getRecentTopicReplies = function(categoryData, callback) { - async.map(categoryData, function(category, next) { - getRecentTopicPids(category.cid, parseInt(category.numRecentReplies), next); - }, function(err, results) { + async.map(categoryData, getRecentTopicPids, function(err, results) { var pids = _.flatten(results); pids = pids.filter(function(pid, index, array) { @@ -56,13 +54,13 @@ module.exports = function(Categories) { next(); } - function getRecentTopicPids(cid, count, callback) { - count = parseInt(count, 10); + function getRecentTopicPids(category, callback) { + var count = parseInt(category.numRecentReplies, 10); if (!count) { return callback(null, []); } - db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, 0, function(err, pids) { + db.getSortedSetRevRange('categories:recent_posts:cid:' + category.cid, 0, 0, function(err, pids) { if (err || !Array.isArray(pids) || !pids.length) { return callback(err, []); } @@ -73,10 +71,10 @@ module.exports = function(Categories) { async.parallel({ pinnedTids: function(next) { - db.getSortedSetRevRangeByScore('categories:' + cid + ':tid', 0, -1, Infinity, Date.now(), next); + db.getSortedSetRevRangeByScore('categories:' + category.cid + ':tid', 0, -1, Infinity, Date.now(), next); }, tids: function(next) { - db.getSortedSetRevRangeByScore('categories:' + cid + ':tid', 0, Math.max(0, count), Date.now(), 0, next); + db.getSortedSetRevRangeByScore('categories:' + category.cid + ':tid', 0, Math.max(0, count), Date.now(), 0, next); } }, function(err, results) { if (err) { From d91fa5747d64f8605048941392e965afd43e4248 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 14 Aug 2014 15:19:57 -0400 Subject: [PATCH 018/372] closes #1964 --- public/language/en_GB/recent.json | 1 + src/controllers/categories.js | 4 ++-- src/topics/popular.js | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/public/language/en_GB/recent.json b/public/language/en_GB/recent.json index 32b31d2666..ff6400f27a 100644 --- a/public/language/en_GB/recent.json +++ b/public/language/en_GB/recent.json @@ -4,5 +4,6 @@ "week": "Week", "month": "Month", "year": "Year", + "alltime": "All Time", "no_recent_topics": "There are no recent topics." } \ No newline at end of file diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 506747f741..a0fb8db212 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -17,7 +17,7 @@ categoriesController.recent = function(req, res, next) { return next(err); } - data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false; + data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; res.render('recent', data); }); @@ -33,7 +33,7 @@ categoriesController.popular = function(req, res, next) { return next(err); } - data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false; + data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; res.render('popular', {topics: data}); }); diff --git a/src/topics/popular.js b/src/topics/popular.js index dd9a8d4240..895849eae8 100644 --- a/src/topics/popular.js +++ b/src/topics/popular.js @@ -16,6 +16,10 @@ module.exports = function(Topics) { yearly: 'year' }; + if (term === 'alltime') { + return getAllTimePopular(uid, callback); + } + var since = terms[term] || 'day'; async.waterfall([ @@ -45,6 +49,12 @@ module.exports = function(Topics) { ], callback); }; + function getAllTimePopular(uid, callback) { + Topics.getTopicsFromSet(uid, 'topics:posts', 0, 19, function(err, data) { + callback(err, data ? data.topics : null); + }); + } + function getTopics(tids, uid, callback) { var keys = tids.map(function(tid) { return 'topic:' + tid; From e10eaf0d50ed9591c1aed5221932d58501d7d3e3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 14 Aug 2014 21:12:12 -0400 Subject: [PATCH 019/372] added tag counts to api return --- src/database/level/sorted.js | 4 ++++ src/database/mongo/sorted.js | 4 ++++ src/database/redis/sorted.js | 11 +++++++++++ src/topics/tags.js | 32 +++++++++++++++++++++++--------- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/database/level/sorted.js b/src/database/level/sorted.js index 623774aac2..46305c56d1 100644 --- a/src/database/level/sorted.js +++ b/src/database/level/sorted.js @@ -113,6 +113,10 @@ module.exports = function(db, module) { }); }; + module.sortedSetsCard = function(keys, callback) { + async.map(keys, module.sortedSetCard, callback); + }; + module.sortedSetRank = function(key, value, callback) { module.getListRange(key, 0, -1, function(err, list) { for (var i = 0, ii=list.length; i< ii; i++) { diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 267446ae7e..4ba3033932 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -101,6 +101,10 @@ module.exports = function(db, module) { }); }; + module.sortedSetsCard = function(keys, callback) { + async.map(keys, module.sortedSetCard, callback); + }; + module.sortedSetRank = function(key, value, callback) { getSortedSetRank(module.getSortedSetRange, key, value, callback); }; diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 59bffa685a..9f1b124711 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -51,6 +51,17 @@ module.exports = function(redisClient, module) { redisClient.zcard(key, callback); }; + module.sortedSetsCard = function(keys, callback) { + if (Array.isArray(keys) && !keys.length) { + return callback(null, []); + } + var multi = redisClient.multi(); + for(var i=0; i Date: Fri, 15 Aug 2014 11:48:01 -0400 Subject: [PATCH 020/372] sortedSetScores for tag counts --- src/database/level/sorted.js | 29 ++++++++++++++++ src/database/mongo/sorted.js | 64 +++++++++++++++++++++++++----------- src/database/redis/sorted.js | 16 ++++++--- src/topics/tags.js | 6 ++-- 4 files changed, 87 insertions(+), 28 deletions(-) diff --git a/src/database/level/sorted.js b/src/database/level/sorted.js index 46305c56d1..369c820d53 100644 --- a/src/database/level/sorted.js +++ b/src/database/level/sorted.js @@ -153,6 +153,35 @@ module.exports = function(db, module) { }); }; + module.sortedSetScores = function(key, values, callback) { + values = values.map(function(value) { + return value ? value.toString() : value; + }); + + module.getListRange(key, 0, -1, function(err, list) { + if (err) { + return callback(err); + } + + var map = {}; + list = list.filter(function(item) { + return values.indexOf(item.value) !== -1; + }).forEach(function(item) { + map[item.value] = item.score; + }); + + var returnData = new Array(values.length), + score; + + for(var i=0; i Date: Fri, 15 Aug 2014 15:45:01 -0400 Subject: [PATCH 021/372] closes #1948 --- public/src/modules/composer.js | 129 +++++++++++--------- public/src/modules/composer/categoryList.js | 37 ++++++ public/src/modules/composer/resize.js | 4 +- src/categories.js | 4 +- src/controllers/index.js | 2 +- src/sitemap.js | 2 +- src/socket.io/categories.js | 4 + 7 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 public/src/modules/composer/categoryList.js diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 1ca81906de..337938b4dd 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -9,11 +9,12 @@ var dependencies = [ 'composer/formatting', 'composer/drafts', 'composer/tags', + 'composer/categoryList', 'composer/preview', 'composer/resize' ]; -define('composer', dependencies, function(taskbar, controls, uploads, formatting, drafts, tags, preview, resize) { +define('composer', dependencies, function(taskbar, controls, uploads, formatting, drafts, tags, categoryList, preview, resize) { var composer = { active: undefined, posts: {}, @@ -200,84 +201,96 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting var template = (composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm') ? 'composer-mobile' : 'composer'; - templates.parse(template, {allowTopicsThumbnail: allowTopicsThumbnail, showTags: isTopic || isMain}, function(composerTemplate) { - translator.translate(composerTemplate, function(composerTemplate) { - composerTemplate = $(composerTemplate); + var data = { + allowTopicsThumbnail: allowTopicsThumbnail, + showTags: isTopic || isMain, + isTopic: isTopic + }; - composerTemplate.attr('id', 'cmp-uuid-' + post_uuid); + parseAndTranslate(template, data, function(composerTemplate) { - $(document.body).append(composerTemplate); + composerTemplate = $(composerTemplate); - var postContainer = $(composerTemplate[0]), - postData = composer.posts[post_uuid], - bodyEl = postContainer.find('textarea'), - draft = drafts.getDraft(postData.save_id); + composerTemplate.attr('id', 'cmp-uuid-' + post_uuid); - tags.init(postContainer, composer.posts[post_uuid]); - updateTitle(postData, postContainer); + $(document.body).append(composerTemplate); - activate(post_uuid); - resize.reposition(postContainer); + var postContainer = $(composerTemplate[0]), + postData = composer.posts[post_uuid], + bodyEl = postContainer.find('textarea'), + draft = drafts.getDraft(postData.save_id); - if (config.allowFileUploads || config.hasImageUploadPlugin) { - uploads.initialize(post_uuid); - } + tags.init(postContainer, composer.posts[post_uuid]); + categoryList.init(postContainer, composer.posts[post_uuid]); + updateTitle(postData, postContainer); - formatting.addHandler(postContainer); + activate(post_uuid); + resize.reposition(postContainer); - if (allowTopicsThumbnail) { - uploads.toggleThumbEls(postContainer, composer.posts[post_uuid].topic_thumb || ''); - } + if (config.allowFileUploads || config.hasImageUploadPlugin) { + uploads.initialize(post_uuid); + } - postContainer.on('change', 'input, textarea', function() { - composer.posts[post_uuid].modified = true; - }); + formatting.addHandler(postContainer); - postContainer.on('click', '.action-bar button[data-action="post"]', function() { - $(this).attr('disabled', true); - post(post_uuid); - }); + if (allowTopicsThumbnail) { + uploads.toggleThumbEls(postContainer, composer.posts[post_uuid].topic_thumb || ''); + } - postContainer.on('click', '.action-bar button[data-action="discard"]', function() { - if (!composer.posts[post_uuid].modified) { - discard(post_uuid); - return; - } + postContainer.on('change', 'input, textarea', function() { + composer.posts[post_uuid].modified = true; + }); - translator.translate('[[modules:composer.discard]]', function(translated) { - bootbox.confirm(translated, function(confirm) { - if (confirm) { - discard(post_uuid); - } - }); - }); - }); + postContainer.on('click', '.action-bar button[data-action="post"]', function() { + $(this).attr('disabled', true); + post(post_uuid); + }); - bodyEl.on('input propertychange', function() { - preview.render(postContainer); - }); + postContainer.on('click', '.action-bar button[data-action="discard"]', function() { + if (!composer.posts[post_uuid].modified) { + discard(post_uuid); + return; + } - bodyEl.on('scroll', function() { - preview.matchScroll(postContainer); + translator.translate('[[modules:composer.discard]]', function(translated) { + bootbox.confirm(translated, function(confirm) { + if (confirm) { + discard(post_uuid); + } + }); }); + }); - bodyEl.val(draft ? draft : postData.body); - preview.render(postContainer, function() { - preview.matchScroll(postContainer); - }); - drafts.init(postContainer, postData); + bodyEl.on('input propertychange', function() { + preview.render(postContainer); + }); + + bodyEl.on('scroll', function() { + preview.matchScroll(postContainer); + }); - resize.handleResize(postContainer); + bodyEl.val(draft ? draft : postData.body); + preview.render(postContainer, function() { + preview.matchScroll(postContainer); + }); + drafts.init(postContainer, postData); - handleHelp(postContainer); + resize.handleResize(postContainer); - $(window).trigger('action:composer.loaded', { - post_uuid: post_uuid - }); + handleHelp(postContainer); - formatting.addComposerButtons(); - focusElements(postContainer); + $(window).trigger('action:composer.loaded', { + post_uuid: post_uuid }); + + formatting.addComposerButtons(); + focusElements(postContainer); + }); + } + + function parseAndTranslate(template, data, callback) { + templates.parse(template, data, function(composerTemplate) { + translator.translate(composerTemplate, callback); }); } diff --git a/public/src/modules/composer/categoryList.js b/public/src/modules/composer/categoryList.js new file mode 100644 index 0000000000..776e435bc9 --- /dev/null +++ b/public/src/modules/composer/categoryList.js @@ -0,0 +1,37 @@ + +'use strict'; + +/*globals define, config, socket, app*/ + +define('composer/categoryList', function() { + var categoryList = {}; + + categoryList.init = function(postContainer, postData) { + var listEl = postContainer.find('.category-list'); + if (!listEl.length) { + return; + } + + socket.emit('categories.getCategoriesByPrivilege', 'topics:create', function(err, categories) { + if (err) { + return app.alertError(err.message); + } + + categories.forEach(function(category) { + $('').appendTo(listEl); + }); + + if (postData.cid) { + listEl.find('option[value="' + postData.cid + '"]').prop('selected', true); + } + }); + + listEl.on('change', function() { + if (postData.cid) { + postData.cid = this.value; + } + }); + }; + + return categoryList; +}); diff --git a/public/src/modules/composer/resize.js b/public/src/modules/composer/resize.js index a544aaf9b6..75382b2b6e 100644 --- a/public/src/modules/composer/resize.js +++ b/public/src/modules/composer/resize.js @@ -137,8 +137,8 @@ define('composer/resize', function() { function resizeWritePreview(postContainer) { - var h1 = postContainer.find('.title').outerHeight(true); - var h2 = postContainer.find('.tags-container').outerHeight(true); + var h1 = postContainer.find('.title-container').outerHeight(true); + var h2 = postContainer.find('.category-tag-row').outerHeight(true); var h3 = postContainer.find('.formatting-bar').outerHeight(true); var h4 = postContainer.find('.topic-thumb-container').outerHeight(true); var h5 = $('.taskbar').height(); diff --git a/src/categories.js b/src/categories.js index bf42392596..eae19b4595 100644 --- a/src/categories.js +++ b/src/categories.js @@ -188,7 +188,7 @@ var db = require('./database'), }); }; - Categories.getVisibleCategories = function(uid, callback) { + Categories.getCategoriesByPrivilege = function(uid, privilege, callback) { db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { if (err) { return callback(err); @@ -198,7 +198,7 @@ var db = require('./database'), return callback(null, []); } - privileges.categories.filter('find', cids, uid, function(err, cids) { + privileges.categories.filter(privilege, cids, uid, function(err, cids) { if (err) { return callback(err); } diff --git a/src/controllers/index.js b/src/controllers/index.js index 31c8348fb5..8a6ca21459 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -64,7 +64,7 @@ Controllers.home = function(req, res, next) { }, categories: function (next) { var uid = req.user ? req.user.uid : 0; - categories.getVisibleCategories(uid, function (err, categoryData) { + categories.getCategoriesByPrivilege(uid, 'find', function (err, categoryData) { if (err) { return next(err); } diff --git a/src/sitemap.js b/src/sitemap.js index e01a2cde3e..5db793a128 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -31,7 +31,7 @@ var path = require('path'), async.parallel([ function(next) { var categoryUrls = []; - categories.getVisibleCategories(0, function(err, categoriesData) { + categories.getCategoriesByPrivilege(0, 'find', function(err, categoriesData) { if (err) { return next(err); } diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index c594361589..4cac4beb56 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -79,4 +79,8 @@ SocketCategories.getUsersInCategory = function(socket, cid, callback) { user.getMultipleUserFields(uids, ['uid', 'userslug', 'username', 'picture'], callback); }; +SocketCategories.getCategoriesByPrivilege = function(socket, privilege, callback) { + categories.getCategoriesByPrivilege(socket.uid, privilege, callback); +}; + module.exports = SocketCategories; From 522fda2e580cf5de35f48a204ec64333fb48f543 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 15 Aug 2014 18:11:57 -0400 Subject: [PATCH 022/372] closes #1936 --- src/controllers/categories.js | 25 +++++++++--------- src/controllers/tags.js | 7 ++--- src/postTools.js | 49 +++++++++++++++++++++-------------- src/topics/popular.js | 15 ++++++----- src/topics/tags.js | 11 +++++--- src/topics/unread.js | 36 +++++++++++++++---------- 6 files changed, 84 insertions(+), 59 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index a0fb8db212..398ec070aa 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -5,15 +5,16 @@ var categoriesController = {}, qs = require('querystring'), nconf = require('nconf'), privileges = require('../privileges'), - user = require('./../user'), - categories = require('./../categories'), - topics = require('./../topics'), - meta = require('./../meta'); + user = require('../user'), + categories = require('../categories'), + topics = require('../topics'), + meta = require('../meta'); categoriesController.recent = function(req, res, next) { var uid = req.user ? req.user.uid : 0; - topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) { - if(err) { + var end = (parseInt(meta.config.topicsPerList, 10) || 20) - 1; + topics.getLatestTopics(uid, 0, end, req.params.term, function (err, data) { + if (err) { return next(err); } @@ -28,8 +29,8 @@ categoriesController.popular = function(req, res, next) { var term = req.params.term || 'daily'; - topics.getPopular(term, uid, function(err, data) { - if(err) { + topics.getPopular(term, uid, meta.config.topicsPerList, function(err, data) { + if (err) { return next(err); } @@ -41,12 +42,12 @@ categoriesController.popular = function(req, res, next) { categoriesController.unread = function(req, res, next) { var uid = req.user ? req.user.uid : 0; - - topics.getUnreadTopics(uid, 0, 20, function (err, data) { - if(err) { + var end = (parseInt(meta.config.topicsPerList, 10) || 20) - 1; + topics.getUnreadTopics(uid, 0, end, function (err, data) { + if (err) { return next(err); } - + data.topics = data.topics.slice(0, parseInt(meta.config.topicsPerList, 10) || 20); res.render('unread', data); }); }; diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 74c21a17fa..258a28b6c9 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -3,13 +3,14 @@ var tagsController = {}, async = require('async'), nconf = require('nconf'), - topics = require('./../topics'); + meta = require('../meta'), + topics = require('../topics'); tagsController.getTag = function(req, res, next) { var tag = req.params.tag; var uid = req.user ? req.user.uid : 0; - - topics.getTagTids(tag, 0, 19, function(err, tids) { + var end = (parseInt(meta.config.topicsPerList, 10) || 20) - 1; + topics.getTagTids(tag, 0, end, function(err, tids) { if (err) { return next(err); } diff --git a/src/postTools.js b/src/postTools.js index 97d3e71928..a162561740 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -54,33 +54,44 @@ var winston = require('winston'), if (err) { return next(err); } + options.tags = options.tags || []; - if (isMainPost) { - title = title.trim(); - - var topicData = { - title: title, - slug: tid + '/' + utils.slugify(title) - }; - if (options.topic_thumb) { - topicData.thumb = options.topic_thumb; - } - db.setObject('topic:' + tid, topicData, function(err) { - plugins.fireHook('action:topic.edit', tid); + if (!isMainPost) { + return next(null, { + tid: tid, + isMainPost: false }); + } - topics.updateTags(tid, options.tags); + title = title.trim(); + + var topicData = { + title: title, + slug: tid + '/' + utils.slugify(title) + }; + if (options.topic_thumb) { + topicData.thumb = options.topic_thumb; } - next(null, { - tid: tid, - title: validator.escape(title), - isMainPost: isMainPost, - tags: options.tags.map(function(tag) { return {name:tag}; }) + db.setObject('topic:' + tid, topicData, function(err) { + plugins.fireHook('action:topic.edit', tid); }); - }); + topics.updateTags(tid, options.tags, function(err) { + if (err) { + return next(err); + } + topics.getTopicTagsObjects(tid, function(err, tags) { + next(err, { + tid: tid, + title: validator.escape(title), + isMainPost: isMainPost, + tags: tags + }); + }); + }); + }); }, content: function(next) { PostTools.parse(postData.content, next); diff --git a/src/topics/popular.js b/src/topics/popular.js index 895849eae8..167bf1cb83 100644 --- a/src/topics/popular.js +++ b/src/topics/popular.js @@ -8,7 +8,8 @@ var async = require('async'), module.exports = function(Topics) { - Topics.getPopular = function(term, uid, callback) { + Topics.getPopular = function(term, uid, count, callback) { + count = parseInt(count, 10) || 20; var terms = { daily: 'day', weekly: 'week', @@ -17,7 +18,7 @@ module.exports = function(Topics) { }; if (term === 'alltime') { - return getAllTimePopular(uid, callback); + return getAllTimePopular(uid, count, callback); } var since = terms[term] || 'day'; @@ -27,7 +28,7 @@ module.exports = function(Topics) { Topics.getLatestTids(0, -1, since, next); }, function(tids, next) { - getTopics(tids, uid, next); + getTopics(tids, uid, count, next); }, function(topics, next) { var tids = topics.map(function(topic) { @@ -49,13 +50,13 @@ module.exports = function(Topics) { ], callback); }; - function getAllTimePopular(uid, callback) { - Topics.getTopicsFromSet(uid, 'topics:posts', 0, 19, function(err, data) { + function getAllTimePopular(uid, count, callback) { + Topics.getTopicsFromSet(uid, 'topics:posts', 0, count - 1, function(err, data) { callback(err, data ? data.topics : null); }); } - function getTopics(tids, uid, callback) { + function getTopics(tids, uid, count, callback) { var keys = tids.map(function(tid) { return 'topic:' + tid; }); @@ -69,7 +70,7 @@ module.exports = function(Topics) { return parseInt(b.postcount, 10) - parseInt(a.postcount, 10); }); - topics = topics.slice(0, 20).map(function(topic) { + topics = topics.slice(0, count).map(function(topic) { return topic.tid; }); diff --git a/src/topics/tags.js b/src/topics/tags.js index 7a909bd35b..9feb917677 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -132,16 +132,19 @@ module.exports = function(Topics) { }); } - Topics.updateTags = function(tid, tags) { + Topics.updateTags = function(tid, tags, callback) { + callback = callback || function() {}; Topics.getTopicField(tid, 'timestamp', function(err, timestamp) { if (err) { - return winston.error(err.message); + return callback(err); } Topics.deleteTopicTags(tid, function(err) { - if (!err) { - Topics.createTags(tags, tid, timestamp); + if (err) { + return callback(err); } + + Topics.createTags(tags, tid, timestamp, callback); }); }); }; diff --git a/src/topics/unread.js b/src/topics/unread.js index 2754fa9a88..fce061cd07 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -4,10 +4,11 @@ var async = require('async'), winston = require('winston'), - db = require('./../database'), - user = require('./../user'), - notifications = require('./../notifications'), - categories = require('./../categories'), + db = require('../database'), + user = require('../user'), + meta = require('../meta'), + notifications = require('../notifications'), + categories = require('../categories'), privileges = require('../privileges'); module.exports = function(Topics) { @@ -27,8 +28,14 @@ module.exports = function(Topics) { return callback(null, unreadTids); } + var count = 0; + if (stop === -1) { + count = Infinity; + } else { + count = stop - start + 1; + } async.whilst(function() { - return unreadTids.length < 21 && !done; + return unreadTids.length < count && !done; }, function(next) { Topics.getLatestTids(start, stop, 'month', function(err, tids) { if (err) { @@ -49,19 +56,21 @@ module.exports = function(Topics) { return !read[index]; }); - unreadTids.push.apply(unreadTids, newtids); + privileges.topics.filter('read', newtids, uid, function(err, newtids) { + if (err) { + return next(err); + } + unreadTids.push.apply(unreadTids, newtids); - start = stop + 1; - stop = start + 19; + start = stop + 1; + stop = start + 19; - next(); + next(); + }); }); }); }, function(err) { - if (err) { - return callback(err); - } - privileges.topics.filter('read', unreadTids, uid, callback); + callback(err, unreadTids.slice(0, count)); }); }; @@ -162,7 +171,6 @@ module.exports = function(Topics) { }; Topics.markAsRead = function(tid, uid, callback) { - db.setAdd('tid:' + tid + ':read_by_uid', uid, function(err) { if (err) { return callback(err); From b2f8a65bc87724c9cb6beb90d3bce21034ed2801 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 15 Aug 2014 18:12:43 -0400 Subject: [PATCH 023/372] removed slice --- src/controllers/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 398ec070aa..3f059b80af 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -47,7 +47,7 @@ categoriesController.unread = function(req, res, next) { if (err) { return next(err); } - data.topics = data.topics.slice(0, parseInt(meta.config.topicsPerList, 10) || 20); + res.render('unread', data); }); }; From ca90afd54478f8cb17df40ec46dfac02fb11163c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 16 Aug 2014 21:33:42 -0400 Subject: [PATCH 024/372] #1992 (drunk) not even done yet --- public/src/forum/category.js | 9 +++-- public/src/forum/infinitescroll.js | 7 ++-- public/src/forum/topic.js | 46 ++++++++++++++++++------- public/src/modules/navigator.js | 30 +++++++++++------ src/categories/recentreplies.js | 6 ++-- src/controllers/groups.js | 3 +- src/controllers/index.js | 2 +- src/controllers/topics.js | 45 +++++++++++++++++-------- src/groups.js | 4 +-- src/posts.js | 54 ++++++++++++++++++++++-------- src/search.js | 2 +- src/socket.io/topics.js | 19 ++++++----- src/topics.js | 50 ++++++++++----------------- src/topics/posts.js | 9 +++-- 14 files changed, 178 insertions(+), 108 deletions(-) diff --git a/public/src/forum/category.js b/public/src/forum/category.js index 81c082ba85..d62719fb9d 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -35,7 +35,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' enableInfiniteLoadingOrPagination(); if (!config.usePagination) { - navigator.init('#topics-container > .category-item', ajaxify.variables.get('topic_count'), undefined, Category.toTop, Category.toBottom); + navigator.init('#topics-container > .category-item', ajaxify.variables.get('topic_count'), Category.toTop, Category.toBottom, Category.navigatorCallback); } $('#topics-container').on('click', '.topic-title', function() { @@ -60,6 +60,10 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' }); }; + Category.navigatorCallback = function(element, elementCount) { + return parseInt(element.attr('data-index'), 10) + 1; + } + $(window).on('action:popstate', function(ev, data) { if(data.url.indexOf('category/') === 0) { var bookmark = localStorage.getItem('category:bookmark'); @@ -285,8 +289,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' return; } - infinitescroll.calculateAfter(direction, '#topics-container .category-item[data-tid]', config.topicsPerPage, function(after, offset, el) { - + infinitescroll.calculateAfter(direction, '#topics-container .category-item[data-tid]', config.topicsPerPage, false, function(after, offset, el) { loadTopicsAfter(after, function() { if (direction < 0 && el) { Category.scrollToTopic(el.attr('data-tid'), null, 0, offset); diff --git a/public/src/forum/infinitescroll.js b/public/src/forum/infinitescroll.js index 053635f312..870f555a8c 100644 --- a/public/src/forum/infinitescroll.js +++ b/public/src/forum/infinitescroll.js @@ -55,13 +55,16 @@ define('forum/infinitescroll', function() { }); }; - scroll.calculateAfter = function(direction, selector, count, callback) { + scroll.calculateAfter = function(direction, selector, count, reverse, callback) { var after = 0, offset = 0, el = direction > 0 ? $(selector).last() : $(selector).first(); + var count = reverse ? -count : count; + var increment = reverse ? -1 : 1; + if (direction > 0) { - after = parseInt(el.attr('data-index'), 10) + 1; + after = parseInt(el.attr('data-index'), 10) + increment; } else { after = parseInt(el.attr('data-index'), 10); if (isNaN(after)) { diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 0300d58121..61ad6f4837 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -63,7 +63,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT handleBookmark(tid); - navigator.init('.posts > .post-row', postCount, Topic.navigatorCallback, Topic.toTop, Topic.toBottom); + navigator.init('.posts > .post-row', postCount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex); socket.on('event:new_post', onNewPost); socket.on('event:new_notification', onNewNotification); @@ -80,8 +80,11 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT }; Topic.toBottom = function() { - socket.emit('topics.lastPostIndex', ajaxify.variables.get('topic_id'), function(err, index) { - navigator.scrollBottom(index); + socket.emit('topics.postcount', ajaxify.variables.get('topic_id'), function(err, postCount) { + if (config.topicPostSort !== 'oldest_to_newest') { + postCount = 1; + } + navigator.scrollBottom(postCount); }); }; @@ -201,8 +204,23 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT } } - Topic.navigatorCallback = function(element) { + Topic.calculateIndex = function(index, elementCount) { + if (index !== 1 && config.topicPostSort !== 'oldest_to_newest') { + return elementCount - index + 2; + } + return index; + }; + + Topic.navigatorCallback = function(element, elementCount) { var postIndex = parseInt(element.attr('data-index'), 10); + var index = postIndex + 1; + if (config.topicPostSort !== 'oldest_to_newest') { + if (postIndex === 0) { + index = 1; + } else { + index = Math.max(elementCount - postIndex + 1, 1); + } + } var currentBookmark = localStorage.getItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark'); @@ -230,6 +248,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT currentUrl = newUrl; } } + return index; }; function onNewPostPagination(data) { @@ -247,8 +266,9 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT } function createNewPosts(data, callback) { + callback = callback || function() {}; if(!data || (data.posts && !data.posts.length)) { - return; + return callback(false); } function removeAlreadyAddedPosts() { @@ -297,7 +317,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT removeAlreadyAddedPosts(); if(!data.posts.length) { - return; + return callback(false); } findInsertionPoint(); @@ -320,9 +340,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT $(window).trigger('action:posts.loaded'); onNewPostsLoaded(html, data.posts); - if (typeof callback === 'function') { - callback(); - } + callback(true); }); } @@ -373,7 +391,9 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT return; } - infinitescroll.calculateAfter(direction, '#post-container .post-row[data-index!="0"]', config.postsPerPage, function(after, offset, el) { + var reverse = config.topicPostSort === 'newest_to_oldest' || config.topicPostSort === 'most_votes'; + + infinitescroll.calculateAfter(direction, '#post-container .post-row[data-index!="0"]', config.postsPerPage, reverse, function(after, offset, el) { loadPostsAfter(after, function() { if (direction < 0 && el) { navigator.scrollToPost(el.attr('data-index'), false, 0, offset); @@ -401,9 +421,11 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT indicatorEl.fadeOut(); if (data && data.posts && data.posts.length) { - createNewPosts(data, function() { + createNewPosts(data, function(postsCreated) { done(); - callback(); + if (postsCreated) { + callback(); + } }); hidePostToolsForDeletedPosts(); } else { diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 88e3934128..3392a7bd83 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -11,8 +11,8 @@ define('navigator', ['forum/pagination'], function(pagination) { var count = 0; navigator.scrollActive = false; - navigator.init = function(selector, count, callback, toTop, toBottom) { - + navigator.init = function(selector, count, toTop, toBottom, callback, calculateIndex) { + index = 1; navigator.selector = selector; navigator.callback = callback; toTop = toTop || function() {}; @@ -36,7 +36,13 @@ define('navigator', ['forum/pagination'], function(pagination) { input.val(''); return; } - var url = generateUrl(input.val()); + + var index = parseInt(input.val(), 10); + if (typeof calculateIndex === 'function') { + index = calculateIndex(index, count); + } + + var url = generateUrl(index); input.val(''); $('.pagination-block .dropdown-toggle').trigger('click'); ajaxify.go(url); @@ -76,12 +82,9 @@ define('navigator', ['forum/pagination'], function(pagination) { var el = $(this); if (elementInView(el)) { - index = parseInt(el.attr('data-index'), 10) + 1; - - navigator.updateTextAndProgressBar(); - if (typeof navigator.callback === 'function') { - navigator.callback(el); + index = navigator.callback(el, count); + navigator.updateTextAndProgressBar(); } return false; @@ -147,7 +150,7 @@ define('navigator', ['forum/pagination'], function(pagination) { return scrollToPid(postIndex, highlight, duration, offset); } - if(config.usePagination) { + if (config.usePagination) { if (window.location.search.indexOf('page') !== -1) { navigator.update(); return; @@ -162,12 +165,19 @@ define('navigator', ['forum/pagination'], function(pagination) { } else { scrollToPid(postIndex, highlight, duration, offset); } + } else { + navigator.scrollActive = false; } }; function scrollToPid(postIndex, highlight, duration, offset) { var scrollTo = $('#post_anchor_' + postIndex); + if (!scrollTo) { + navigator.scrollActive = false; + return; + } + var done = false; function animateScroll() { $('html, body').animate({ @@ -195,7 +205,7 @@ define('navigator', ['forum/pagination'], function(pagination) { } } - if ($('#post-container').length && scrollTo.length) { + if ($('#post-container').length) { animateScroll(); } } diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index efd84a7e19..2fdd5306bf 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -20,11 +20,11 @@ module.exports = function(Categories) { return callback(err, []); } - posts.getPostSummaryByPids(pids, {stripTags: true}, callback); + posts.getPostSummaryByPids(pids, uid, {stripTags: true}, callback); }); }; - Categories.getRecentTopicReplies = function(categoryData, callback) { + Categories.getRecentTopicReplies = function(categoryData, uid, callback) { async.map(categoryData, getRecentTopicPids, function(err, results) { var pids = _.flatten(results); @@ -32,7 +32,7 @@ module.exports = function(Categories) { return !!pid && array.indexOf(pid) === index; }); - posts.getPostSummaryByPids(pids, {stripTags: true}, function(err, posts) { + posts.getPostSummaryByPids(pids, uid, {stripTags: true}, function(err, posts) { if (err) { return callback(err); } diff --git a/src/controllers/groups.js b/src/controllers/groups.js index a7295bdd7e..9185e6e1aa 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -17,6 +17,7 @@ groupsController.list = function(req, res) { }; groupsController.details = function(req, res) { + var uid = req.user ? parseInt(req.user.uid, 10) : 0; async.parallel({ group: function(next) { groups.get(req.params.name, { @@ -24,7 +25,7 @@ groupsController.details = function(req, res) { }, next); }, posts: function(next) { - groups.getLatestMemberPosts(req.params.name, 10, next); + groups.getLatestMemberPosts(req.params.name, 10, uid, next); } }, function(err, results) { if (!err) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 8a6ca21459..7a03171ef2 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -69,7 +69,7 @@ Controllers.home = function(req, res, next) { return next(err); } - categories.getRecentTopicReplies(categoryData, function(err) { + categories.getRecentTopicReplies(categoryData, uid, function(err) { next(err, categoryData); }); }); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 0b20eb7af6..4bd80e7032 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -30,20 +30,18 @@ topicsController.get = function(req, res, next) { userPrivileges = privileges; - user.getSettings(uid, next); + async.parallel({ + postCount: function(next) { + topics.getPostCount(tid, next); + }, + settings: function(next) { + user.getSettings(uid, next); + } + }, next); }, - function (settings, next) { - var postIndex = 0; - if (!settings.usePagination) { - postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage - 1), 0); - } else if (!req.query.page) { - var index = Math.max(parseInt((req.params.post_index || 0), 10), 0); - page = Math.ceil((index + 1) / settings.postsPerPage); - } - - var start = (page - 1) * settings.postsPerPage + postIndex, - end = start + settings.postsPerPage - 1; - + function (results, next) { + var settings = results.settings; + var postCount = parseInt(results.postCount, 10) + 1; var set = 'tid:' + tid + ':posts', reverse = false; @@ -54,6 +52,24 @@ topicsController.get = function(req, res, next) { set = 'tid:' + tid + ':posts:votes'; } + var postIndex = 0; + if (!settings.usePagination) { + if (reverse) { + if (!req.params.post_index || parseInt(req.params.post_index, 10) === 1) { + req.params.post_index = 0; + } + postIndex = Math.max(postCount - (req.params.post_index || postCount) - (settings.postsPerPage - 1), 0); + } else { + postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage + 1), 0); + } + } else if (!req.query.page) { + var index = Math.max(parseInt(req.params.post_index, 10), 0); + page = Math.ceil((index + 1) / settings.postsPerPage); + } + + var start = (page - 1) * settings.postsPerPage + postIndex, + end = start + settings.postsPerPage - 1; + topics.getTopicWithPosts(tid, set, uid, start, end, reverse, function (err, topicData) { if (topicData) { if (topicData.deleted && !userPrivileges.view_deleted) { @@ -191,6 +207,7 @@ topicsController.get = function(req, res, next) { topicsController.teaser = function(req, res, next) { var tid = req.params.topic_id; + var uid = req.user ? parseInt(req.user.uid, 10) : 0; topics.getLatestUndeletedPid(tid, function(err, pid) { if (err) { return next(err); @@ -200,7 +217,7 @@ topicsController.teaser = function(req, res, next) { return res.json(404, 'not-found'); } - posts.getPostSummaryByPids([pid], {stripTags: false}, function(err, posts) { + posts.getPostSummaryByPids([pid], uid, {stripTags: false}, function(err, posts) { if (err) { return next(err); } diff --git a/src/groups.js b/src/groups.js index b47b069b8a..219fae8e45 100644 --- a/src/groups.js +++ b/src/groups.js @@ -418,7 +418,7 @@ }); }; - Groups.getLatestMemberPosts = function(groupName, max, callback) { + Groups.getLatestMemberPosts = function(groupName, max, uid, callback) { Groups.get(groupName, {}, function(err, groupObj) { if (err || parseInt(groupObj.memberCount, 10) === 0) { return callback(null, []); @@ -433,7 +433,7 @@ return callback(err); } - posts.getPostSummaryByPids(pids, {stripTags: false}, callback); + posts.getPostSummaryByPids(pids, uid, {stripTags: false}, callback); }); }); }; diff --git a/src/posts.js b/src/posts.js index 1392ac0372..419b7bd9ab 100644 --- a/src/posts.js +++ b/src/posts.js @@ -186,7 +186,7 @@ var async = require('async'), if (err) { return callback(err); } - Posts.getPostSummaryByPids(pids, {stripTags: true}, callback); + Posts.getPostSummaryByPids(pids, uid, {stripTags: true}, callback); }); }); }; @@ -270,7 +270,7 @@ var async = require('async'), }); }; - Posts.getPostSummaryByPids = function(pids, options, callback) { + Posts.getPostSummaryByPids = function(pids, uid, options, callback) { options.stripTags = options.hasOwnProperty('stripTags') ? options.stripTags : false; options.parse = options.hasOwnProperty('parse') ? options.parse : true; @@ -319,13 +319,7 @@ var async = require('async'), }); }, indices: function(next) { - var pids = [], keys = []; - for (var i=0; i Date: Sat, 16 Aug 2014 21:40:41 -0400 Subject: [PATCH 025/372] removed unused functions --- src/posts.js | 23 ----------------------- src/socket.io/posts.js | 9 --------- 2 files changed, 32 deletions(-) diff --git a/src/posts.js b/src/posts.js index 419b7bd9ab..15ab9c38ff 100644 --- a/src/posts.js +++ b/src/posts.js @@ -522,29 +522,6 @@ var async = require('async'), }); } - Posts.getPidPage = function(pid, uid, callback) { - if(!pid) { - return callback(new Error('[[error:invalid-pid]]')); - } - - var index = 0; - async.waterfall([ - function(next) { - Posts.getPidIndex(pid, next); - }, - function(result, next) { - index = result; - if (index === 1) { - return callback(null, 1); - } - user.getSettings(uid, next); - }, - function(settings, next) { - next(null, Math.ceil((index - 1) / settings.postsPerPage)); - } - ], callback); - }; - Posts.getPidIndex = function(pid, callback) { Posts.getPostField(pid, 'tid', function(err, tid) { if(err) { diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index d08903a779..0a1f3def44 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -268,15 +268,6 @@ SocketPosts.getUpvoters = function(socket, pid, callback) { }); }; - -SocketPosts.getPidPage = function(socket, pid, callback) { - posts.getPidPage(pid, socket.uid, callback); -}; - -SocketPosts.getPidIndex = function(socket, pid, callback) { - posts.getPidIndex(pid, callback); -}; - SocketPosts.flag = function(socket, pid, callback) { if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); From 5ae7c92d55040494fcff9f8905a6da5d3deb050b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 16 Aug 2014 23:25:30 -0400 Subject: [PATCH 026/372] notifications refactor added getMultiple which works with an array of nids --- src/controllers/accounts.js | 2 +- src/notifications.js | 72 +++++++------ src/user/notifications.js | 199 +++++++++++++++++------------------- 3 files changed, 134 insertions(+), 139 deletions(-) diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 15bfd0a17b..fac96c99a5 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -478,7 +478,7 @@ accountsController.uploadPicture = function (req, res, next) { }; accountsController.getNotifications = function(req, res, next) { - user.notifications.getAll(req.user.uid, null, null, function(err, notifications) { + user.notifications.getAll(req.user.uid, 25, function(err, notifications) { res.render('notifications', { notifications: notifications }); diff --git a/src/notifications.js b/src/notifications.js index b5d61895e5..ecd73e22ed 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -24,47 +24,57 @@ var async = require('async'), }; Notifications.get = function(nid, callback) { - db.getObject('notifications:' + nid, function(err, notification) { + Notifications.getMultiple([nid], function(err, notifications) { + callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null); + }); + }; + + Notifications.getMultiple = function(nids, callback) { + var keys = nids.map(function(nid) { + return 'notifications:' + nid; + }); + + db.getObjects(keys, function(err, notifications) { if (err) { return callback(err); } - if (!notification) { - winston.info('[notifications.get] Could not retrieve nid ' + nid); - return callback(null, null); - } + async.map(notifications, function(notification, next) { + if (!notification) { + return next(null, null); + } - // Backwards compatibility for old notification schema - // Remove this block when NodeBB v0.6.0 is released. - if (notification.hasOwnProperty('text')) { - notification.bodyShort = notification.text; - notification.bodyLong = ''; - notification.text = S(notification.text).escapeHTML().s; - } + // Backwards compatibility for old notification schema + // Remove this block when NodeBB v0.6.0 is released. + if (notification.hasOwnProperty('text')) { + notification.bodyShort = notification.text; + notification.bodyLong = ''; + notification.text = S(notification.text).escapeHTML().s; + } - notification.bodyShort = S(notification.bodyShort).escapeHTML().s; - notification.bodyLong = S(notification.bodyLong).escapeHTML().s; + notification.bodyShort = S(notification.bodyShort).escapeHTML().s; + notification.bodyLong = S(notification.bodyLong).escapeHTML().s; - if (notification.from && !notification.image) { - User.getUserField(notification.from, 'picture', function(err, picture) { - if (err) { - return callback(err); + if (notification.from && !notification.image) { + User.getUserField(notification.from, 'picture', function(err, picture) { + if (err) { + return next(err); + } + notification.image = picture; + next(null, notification); + }); + return; + } else if (notification.image) { + switch(notification.image) { + case 'brand:logo': + notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'; + break; } - notification.image = picture; - callback(null, notification); - }); - return; - } else if (notification.image) { - switch(notification.image) { - case 'brand:logo': - notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'; - break; - } - return callback(null, notification); - } + return next(null, notification); + } - callback(null, notification); + }, callback); }); }; diff --git a/src/user/notifications.js b/src/user/notifications.js index f6e85aa05f..38ece77940 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -17,86 +17,22 @@ var async = require('async'), (function(UserNotifications) { UserNotifications.get = function(uid, callback) { - function getNotifications(set, start, stop, iterator, done) { - db.getSortedSetRevRange(set, start, stop, function(err, uniqueIds) { - if(err) { - return done(err); - } - - if(!Array.isArray(uniqueIds) || !uniqueIds.length) { - return done(null, []); - } - - if (uniqueIds.length > maxNotifs) { - uniqueIds.length = maxNotifs; - } - - db.getObjectFields('uid:' + uid + ':notifications:uniqueId:nid', uniqueIds, function(err, uniqueIdToNids) { - if (err) { - return done(err); - } - - var nidsToUniqueIds = {}; - var nids = []; - uniqueIds.forEach(function(uniqueId) { - nidsToUniqueIds[uniqueIdToNids[uniqueId]] = uniqueId; - nids.push(uniqueIdToNids[uniqueId]); - }); - - async.map(nids, function(nid, next) { - notifications.get(nid, function(err, notif_data) { - if (err) { - return next(err); - } - - if (!notif_data) { - if (process.env.NODE_ENV === 'development') { - winston.info('[notifications.get] nid ' + nid + ' not found. Removing.'); - } - - db.sortedSetRemove(set, nidsToUniqueIds[nid]); - db.deleteObjectField('uid:' + uid + ':notifications:uniqueId:nid', nidsToUniqueIds[nid]); - return next(); - } - - if (typeof iterator === 'function') { - iterator(notif_data, next); - } else { - next(null, notif_data); - } - }); - }, done); - }); - }); - } - var maxNotifs = 15; async.parallel({ unread: function(next) { - getNotifications('uid:' + uid + ':notifications:unread', 0, 9, function(notif_data, next) { - notif_data.read = false; - notif_data.readClass = !notif_data.read ? 'label-warning' : ''; - next(null, notif_data); - }, next); + getNotificationsFromSet('uid:' + uid + ':notifications:unread', uid, 0, 9, maxNotifs, next); }, read: function(next) { - getNotifications('uid:' + uid + ':notifications:read', 0, 9, function(notif_data, next) { - notif_data.read = true; - next(null, notif_data); - }, next); + getNotificationsFromSet('uid:' + uid + ':notifications:read', uid, 0, 9, maxNotifs, next); } }, function(err, notifications) { - function filterDeleted(notifObj) { - return !!notifObj; - } - if (err) { return callback(err); } - notifications.read = notifications.read.filter(filterDeleted); - notifications.unread = notifications.unread.filter(filterDeleted); + notifications.read = notifications.read.filter(Boolean); + notifications.unread = notifications.unread.filter(Boolean); // Limit the number of notifications to `maxNotifs`, prioritising unread notifications if (notifications.read.length + notifications.unread.length > maxNotifs) { @@ -107,48 +43,71 @@ var async = require('async'), }); }; - UserNotifications.getAll = function(uid, limit, before, callback) { - var now = new Date(); + function getNotificationsFromSet(set, uid, start, stop, max, callback) { + db.getSortedSetRevRange(set, start, stop, function(err, uniqueIds) { + if (err) { + return callback(err); + } + + if(!Array.isArray(uniqueIds) || !uniqueIds.length) { + return callback(null, []); + } + + if (uniqueIds.length > max) { + uniqueIds.length = max; + } + + db.getObjectFields('uid:' + uid + ':notifications:uniqueId:nid', uniqueIds, function(err, uniqueIdToNids) { + if (err) { + return callback(err); + } + + var nidsToUniqueIds = {}; + var nids = []; + uniqueIds.forEach(function(uniqueId) { + nidsToUniqueIds[uniqueIdToNids[uniqueId]] = uniqueId; + nids.push(uniqueIdToNids[uniqueId]); + }); + + UserNotifications.getNotifications(nids, uid, function(err, notifications) { + if (err) { + return callback(err); + } + + notifications.forEach(function(notification, index) { + if (!notification) { + if (process.env.NODE_ENV === 'development') { + winston.info('[notifications.get] nid ' + nids[index] + ' not found. Removing.'); + } + db.sortedSetRemove(set, nidsToUniqueIds[nids[index]]); + db.deleteObjectField('uid:' + uid + ':notifications:uniqueId:nid', nidsToUniqueIds[nids[index]]); + } + }); + + callback(null, notifications); + }); + }); + }); + } + + UserNotifications.getAll = function(uid, limit, callback) { if (!limit || parseInt(limit, 10) <= 0) { limit = 25; } - if (before) { - before = new Date(parseInt(before, 10)); - } db.getObjectValues('uid:' + uid + ':notifications:uniqueId:nid', function(err, nids) { if (err) { return callback(err); } - async.map(nids, function(nid, next) { - notifications.get(nid, function(err, notif_data) { - if (err || !notif_data) { - return next(err); - } - UserNotifications.isNotificationRead(notif_data.nid, uid, function(err, isRead) { - if (err) { - return next(err); - } - - notif_data.read = isRead; - next(null, notif_data); - }); - }); - }, function(err, notifs) { + UserNotifications.getNotifications(nids, uid, function(err, notifs) { if (err) { return callback(err); } - notifs = notifs.filter(function(notif) { - return !!notif; - }).sort(function(a, b) { + notifs = notifs.filter(Boolean).sort(function(a, b) { return parseInt(b.datetime, 10) - parseInt(a.datetime, 10); - }).map(function(notif) { - notif.datetimeISO = utils.toISOString(notif.datetime); - notif.readClass = !notif.read ? 'label-warning' : ''; - return notif; }); callback(null, notifs); @@ -156,8 +115,34 @@ var async = require('async'), }); }; - UserNotifications.isNotificationRead = function(nid, uid, callback) { - db.isSortedSetMember('uid:' + uid + ':notifications:read', nid, callback); + UserNotifications.getNotifications = function(nids, uid, callback) { + notifications.getMultiple(nids, function(err, notifications) { + if (err) { + return callback(err); + } + + var uniqueIds = notifications.map(function(notification) { + return notification ? notification.uniqueId : null; + }); + + db.isSortedSetMembers('uid:' + uid + ':notifications:read', uniqueIds, function(err, hasRead) { + if (err) { + return callback(err); + } + + notifications = notifications.map(function(notification, index) { + if (!notification) { + return null; + } + notification.read = hasRead[index]; + notification.datetimeISO = utils.toISOString(notification.datetime); + notification.readClass = !notification.read ? 'label-warning' : ''; + return notification; + }); + + callback(null, notifications); + }); + }); }; UserNotifications.getDailyUnread = function(uid, callback) { @@ -182,9 +167,7 @@ var async = require('async'), return uniqueIdToNids[uniqueId]; }); - async.map(nids, function(nid, next) { - notifications.get(nid, next); - }, callback); + UserNotifications.getNotifications(nids, uid, callback); }); }); }; @@ -212,15 +195,17 @@ var async = require('async'), return uniqueIdsToNids[uniqueId]; }); - async.filter(nids, function(nid, next) { - notifications.get(nid, function(err, notifObj) { - if (err || !notifObj) { - return next(false); - } + UserNotifications.getNotifications(nids, uid, function(err, notifications) { + if (err) { + return callback(err); + } - next(notifObj[field] === value.toString()); + notifications = notifications.filter(function(notification) { + return !!notification || notification[field] !== value.toString(); + }).map(function(notification) { + return notification.nid; }); - }, function(nids) { + callback(null, nids); }); }); From 05fdc945f309574e46f4ce3488ad139e6cfc202e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 17 Aug 2014 00:14:45 -0400 Subject: [PATCH 027/372] closes #1993 --- src/posts.js | 15 +++++--- src/socket.io/posts.js | 15 ++------ src/socket.io/user.js | 1 + src/topics.js | 4 +-- src/topics/create.js | 2 +- src/topics/follow.js | 7 ++-- src/user/notifications.js | 76 ++++++++++++++++++++++++++++++++------- 7 files changed, 84 insertions(+), 36 deletions(-) diff --git a/src/posts.js b/src/posts.js index 15ab9c38ff..4f33a78f47 100644 --- a/src/posts.js +++ b/src/posts.js @@ -522,13 +522,20 @@ var async = require('async'), }); } - Posts.getPidIndex = function(pid, callback) { - Posts.getPostField(pid, 'tid', function(err, tid) { + Posts.getPidIndex = function(pid, uid, callback) { + async.parallel({ + settings: function(next) { + user.getSettings(uid, next); + }, + tid: function(next) { + Posts.getPostField(pid, 'tid', next); + } + }, function(err, results) { if(err) { return callback(err); } - - db.sortedSetRank('tid:' + tid + ':posts', pid, function(err, index) { + var set = results.settings.topicPostSort === 'most_votes' ? 'tid:' + results.tid + ':posts:votes' : 'tid:' + results.tid + ':posts'; + db.sortedSetRank(set, pid, function(err, index) { if (!utils.isNumber(index)) { return callback(err, 1); } diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 0a1f3def44..3315bf2f5a 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -110,8 +110,6 @@ SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { async.parallel({ username: async.apply(user.getUserField, fromuid, 'username'), - slug: async.apply(topics.getTopicField, postData.tid, 'slug'), - index: async.apply(posts.getPidIndex, pid), postContent: function(next) { async.waterfall([ async.apply(posts.getPostField, pid, 'content'), @@ -128,7 +126,7 @@ SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { notifications.create({ bodyShort: '[[' + notification + ', ' + results.username + ']]', bodyLong: results.postContent, - path: nconf.get('relative_path') + '/topic/' + results.slug + '/' + results.index, + pid: pid, uniqueId: 'post:' + pid + ':uid:' + fromuid, from: fromuid }, function(err, nid) { @@ -274,7 +272,6 @@ SocketPosts.flag = function(socket, pid, callback) { } var message = '', - path = '', post; async.waterfall([ @@ -293,21 +290,13 @@ SocketPosts.flag = function(socket, pid, callback) { }, function(postData, next) { post = postData; - topics.getTopicField(postData.tid, 'slug', next); - }, - function(topicSlug, next) { - path = nconf.get('relative_path') + '/topic/' + topicSlug; - posts.getPidIndex(pid, next); - }, - function(postIndex, next) { - path += '/' + postIndex; groups.get('administrators', {}, next); }, function(adminGroup, next) { notifications.create({ bodyShort: message, bodyLong: post.content, - path: path, + pid: pid, uniqueId: 'post_flag:' + pid, from: socket.uid }, function(err, nid) { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 6b9766fea0..42adbb5981 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -206,6 +206,7 @@ function toggleFollow(method, uid, theiruid, callback) { fromUid: uid, toUid: theiruid }); + callback(); }); } diff --git a/src/topics.js b/src/topics.js index 8ef34f8975..5eeeab2a3c 100644 --- a/src/topics.js +++ b/src/topics.js @@ -386,7 +386,7 @@ var async = require('async'), }); }; - Topics.getTeaser = function(tid, callback) { + Topics.getTeaser = function(tid, uid, callback) { Topics.getLatestUndeletedPid(tid, function(err, pid) { if (err || !pid) { return callback(err); @@ -411,7 +411,7 @@ var async = require('async'), }); }, postIndex: function(next) { - posts.getPidIndex(pid, next); + posts.getPidIndex(pid, uid, next); } }, function(err, results) { if (err) { diff --git a/src/topics/create.js b/src/topics/create.js index 955d42d182..60c41880b1 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -226,7 +226,7 @@ module.exports = function(Topics) { if (settings.followTopicsOnReply) { threadTools.follow(postData.tid, uid); } - posts.getPidIndex(postData.pid, next); + posts.getPidIndex(postData.pid, uid, next); }, function(index, next) { postData.index = index - 1; diff --git a/src/topics/follow.js b/src/topics/follow.js index 76fb14cf6a..0ea48620be 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -37,9 +37,8 @@ module.exports = function(Topics) { } async.parallel({ - topicData: async.apply(Topics.getTopicFields, tid, ['title', 'slug']), + title: async.apply(Topics.getTopicField, tid, 'title'), username: async.apply(user.getUserField, exceptUid, 'username'), - postIndex: async.apply(posts.getPidIndex, pid), postContent: function(next) { async.waterfall([ async.apply(posts.getPostField, pid, 'content'), @@ -54,9 +53,9 @@ module.exports = function(Topics) { } notifications.create({ - bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topicData.title + ']]', + bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.title + ']]', bodyLong: results.postContent, - path: nconf.get('relative_path') + '/topic/' + results.topicData.slug + '/' + results.postIndex, + pid: pid, uniqueId: 'topic:' + tid + ':uid:' + exceptUid, tid: tid, from: exceptUid diff --git a/src/user/notifications.js b/src/user/notifications.js index 38ece77940..7f4a833350 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -130,21 +130,74 @@ var async = require('async'), return callback(err); } - notifications = notifications.map(function(notification, index) { - if (!notification) { - return null; - } - notification.read = hasRead[index]; - notification.datetimeISO = utils.toISOString(notification.datetime); - notification.readClass = !notification.read ? 'label-warning' : ''; - return notification; + var pids = notifications.map(function(notification) { + return notification ? notification.pid : null; }); - callback(null, notifications); + generatePostPaths(pids, uid, function(err, paths) { + if (err) { + return callback(err); + } + + notifications = notifications.map(function(notification, index) { + if (!notification) { + return null; + } + + notification.read = hasRead[index]; + notification.path = paths[index] || notification.path || ''; + notification.datetimeISO = utils.toISOString(notification.datetime); + notification.readClass = !notification.read ? 'label-warning' : ''; + return notification; + }); + + callback(null, notifications); + }); }); }); }; + function generatePostPaths(pids, uid, callback) { + var postKeys = pids.map(function(pid) { + return 'post:' + pid; + }); + + db.getObjectsFields(postKeys, ['pid', 'tid'], function(err, postData) { + if (err) { + return callback(err); + } + + var topicKeys = postData.map(function(post) { + return post ? 'topic:' + post.tid : null; + }); + + async.parallel({ + indices: function(next) { + posts.getPostIndices(postData, uid, next); + }, + topics: function(next) { + db.getObjectsFields(topicKeys, ['slug'], next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + var paths = []; + pids.forEach(function(pid, index) { + var slug = results.topics[index] ? results.topics[index].slug : null; + var postIndex = results.indices[index] ? parseInt(results.indices[index], 10) + 1 : null; + if (slug && postIndex) { + paths.push(nconf.get('relative_path') + '/topic/' + slug + '/' + postIndex); + } else { + paths.push(null); + } + }); + callback(null, paths); + }); + }); + } + UserNotifications.getDailyUnread = function(uid, callback) { var now = Date.now(), yesterday = now - (1000*60*60*24); // Approximate, can be more or less depending on time changes, makes no difference really. @@ -221,8 +274,7 @@ var async = require('async'), async.parallel({ username: async.apply(user.getUserField, uid, 'username'), - topic: async.apply(topics.getTopicFields, tid, ['slug', 'cid', 'title']), - postIndex: async.apply(posts.getPidIndex, pid), + topic: async.apply(topics.getTopicFields, tid, ['cid', 'title']), postContent: function(next) { async.waterfall([ async.apply(posts.getPostField, pid, 'content'), @@ -246,7 +298,7 @@ var async = require('async'), notifications.create({ bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topic.title + ']]', bodyLong: results.postContent, - path: nconf.get('relative_path') + '/topic/' + results.topic.slug + '/' + results.postIndex, + pid: pid, uniqueId: 'topic:' + tid + ':uid:' + uid, tid: tid, from: uid From d411ed5c28490f6d24372e2c5d0d04f711a19ec5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 17 Aug 2014 19:26:24 -0400 Subject: [PATCH 028/372] closes #1995 --- public/src/forum/admin/tags.js | 60 +++++++++++++++++++++++++++++ src/controllers/admin.js | 12 +++++- src/routes/admin.js | 3 ++ src/socket.io/admin.js | 1 + src/socket.io/admin/tags.js | 15 ++++++++ src/topics/tags.js | 70 +++++++++++++++++++++++++--------- 6 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 public/src/forum/admin/tags.js create mode 100644 src/socket.io/admin/tags.js diff --git a/public/src/forum/admin/tags.js b/public/src/forum/admin/tags.js new file mode 100644 index 0000000000..e6468c7f21 --- /dev/null +++ b/public/src/forum/admin/tags.js @@ -0,0 +1,60 @@ +"use strict"; +/*global define, socket, app, admin*/ + +define('forum/admin/tags', [], function() { + var Tags = {}; + + Tags.init = function() { + handleColorPickers(); + + $('.tag-list').on('click', '.save', function() { + save($(this)); + }); + + $('#tag-search').on('input propertychange', function() { + $('.tag-list').children().each(function() { + var $this = $(this); + $this.toggleClass('hide', $this.attr('data-tag').indexOf($('#tag-search').val()) === -1); + }); + }); + }; + + function handleColorPickers() { + function enableColorPicker(idx, inputEl) { + var $inputEl = $(inputEl), + previewEl = $inputEl.parents('.tag-row').find('.tag-item'); + + admin.enableColorPicker($inputEl, function(hsb, hex) { + if ($inputEl.attr('data-name') === 'bgColor') { + previewEl.css('background-color', '#' + hex); + } else if ($inputEl.attr('data-name') === 'color') { + previewEl.css('color', '#' + hex); + } + }); + } + + + $('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker); + } + + function save(saveBtn) { + var tagRow = saveBtn.parents('.tag-row'); + + var data = { + tag: tagRow.attr('data-tag'), + bgColor : tagRow.find('[data-name="bgColor"]').val(), + color : tagRow.find('[data-name="color"]').val() + }; + + socket.emit('admin.tags.update', data, function(err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('Tag Updated!'); + }); + } + + + + return Tags; +}); \ No newline at end of file diff --git a/src/controllers/admin.js b/src/controllers/admin.js index cb81fab3d3..afbadadcc8 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -16,9 +16,9 @@ var async = require('async'), validator = require('validator'); - var adminController = { categories: {}, + tags: {}, topics: {}, groups: {}, themes: {}, @@ -126,6 +126,16 @@ function filterAndRenderCategories(req, res, next, active) { }); } +adminController.tags.get = function(req, res, next) { + topics.getTags(0, -1, function(err, tags) { + if (err) { + return next(err); + } + + res.render('admin/tags', {tags: tags}); + }); +}; + adminController.database.get = function(req, res, next) { db.info(function (err, data) { res.render('admin/database', data); diff --git a/src/routes/admin.js b/src/routes/admin.js index 8b6b06468c..77dc3cc31a 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -48,6 +48,9 @@ function forumRoutes(app, middleware, controllers) { app.get('/admin/categories/disabled', middleware.admin.buildHeader, controllers.admin.categories.disabled); app.get('/api/admin/categories/disabled', controllers.admin.categories.disabled); + + app.get('/admin/tags', middleware.admin.buildHeader, controllers.admin.tags.get); + app.get('/api/admin/tags', controllers.admin.tags.get); } function apiRoutes(app, middleware, controllers) { diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 44bb228ade..bc8140a2a1 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -19,6 +19,7 @@ var groups = require('../groups'), user: require('./admin/user'), categories: require('./admin/categories'), groups: require('./admin/groups'), + tags: require('./admin/tags'), themes: {}, plugins: {}, widgets: {}, diff --git a/src/socket.io/admin/tags.js b/src/socket.io/admin/tags.js new file mode 100644 index 0000000000..08ceca1a9f --- /dev/null +++ b/src/socket.io/admin/tags.js @@ -0,0 +1,15 @@ +"use strict"; + +var topics = require('../../topics'), + Tags = {}; + +Tags.update = function(socket, data, callback) { + if (!data) { + return callback(new Error('[[error:invalid-data]]')); + } + + topics.updateTag(data.tag, data, callback); +}; + + +module.exports = Tags; \ No newline at end of file diff --git a/src/topics/tags.js b/src/topics/tags.js index 9feb917677..861a8974a6 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -57,6 +57,10 @@ module.exports = function(Topics) { return tag; }; + Topics.updateTag = function(tag, data, callback) { + db.setObject('tag:' + tag, data, callback); + }; + function updateTagCount(tag, callback) { callback = callback || function() {}; Topics.getTagTopicCount(tag, function(err, count) { @@ -80,9 +84,33 @@ module.exports = function(Topics) { }; Topics.getTags = function(start, end, callback) { - db.getSortedSetRevRangeWithScores('tags:topic:count', start, end, callback); + db.getSortedSetRevRangeWithScores('tags:topic:count', start, end, function(err, tags) { + if (err) { + return callback(err); + } + + addTagData(tags, callback); + }); }; + function addTagData(tags, callback) { + var keys = tags.map(function(tag) { + return 'tag:' + tag.value; + }); + + db.getObjects(keys, function(err, tagData) { + if (err) { + return callback(err); + } + + tags.forEach(function(tag, index) { + tag.color = tagData[index] ? tagData[index].color : ''; + tag.bgColor = tagData[index] ? tagData[index].bgColor : ''; + }); + callback(null, tags); + }); + } + Topics.getTopicTags = function(tid, callback) { db.getSetMembers('topic:' + tid + ':tags', callback); }; @@ -98,40 +126,46 @@ module.exports = function(Topics) { return 'topic:' + tid + ':tags'; }); - db.getSetsMembers(sets, function(err, members) { + db.getSetsMembers(sets, function(err, topicTags) { if (err) { return callback(err); } - var uniqueTags = _.uniq(_.flatten(members)); + var uniqueTopicTags = _.uniq(_.flatten(topicTags)); + + var tags = uniqueTopicTags.map(function(tag) { + return {value: tag}; + }); - db.sortedSetScores('tags:topic:count', uniqueTags, function(err, data) { + async.parallel({ + tagData: function(next) { + addTagData(tags, next); + }, + counts: function(next) { + db.sortedSetScores('tags:topic:count', uniqueTopicTags, next); + } + }, function(err, results) { if (err) { return callback(err); } - var tagCounts = _.object(uniqueTags, data); + results.tagData.forEach(function(tag, index) { + tag.score = results.counts[index] ? results.counts[index] : 0; + }); + + var tagData = _.object(uniqueTopicTags, results.tagData); - members.forEach(function(tags, index) { + topicTags.forEach(function(tags, index) { if (Array.isArray(tags)) { - members[index] = mapToObject(tags, tagCounts); + topicTags[index] = tags.map(function(tag) {return tagData[tag];}); } }); - callback(null, members); + + callback(null, topicTags); }); }); }; - function mapToObject(tags, tagCounts) { - if (!tags) { - return tags; - } - - return tags.map(function(tag) { - return {name: tag, score: tagCounts ? tagCounts[tag] : 0}; - }); - } - Topics.updateTags = function(tid, tags, callback) { callback = callback || function() {}; Topics.getTopicField(tid, 'timestamp', function(err, timestamp) { From 24ea74dc410a1f9bc3f58792a46eec16ddbc7a3a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 17 Aug 2014 22:10:16 -0400 Subject: [PATCH 029/372] lol O(log(n)) to O(1) and away --- src/user/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index 7f4a833350..b6e82e6d23 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -226,7 +226,7 @@ var async = require('async'), }; UserNotifications.getUnreadCount = function(uid, callback) { - db.sortedSetCount('uid:' + uid + ':notifications:unread', -Infinity, Infinity, callback); + db.sortedSetCard('uid:' + uid + ':notifications:unread', callback); }; UserNotifications.getUnreadByField = function(uid, field, value, callback) { From 77a7c5caefa46ec026eaa00d305e734fd326d56c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 17 Aug 2014 22:12:43 -0400 Subject: [PATCH 030/372] eachLimit notifications.push --- src/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications.js b/src/notifications.js index ecd73e22ed..f0e36a5d76 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -128,7 +128,7 @@ var async = require('async'), return callback(err); } - async.each(uids, function(uid, next) { + async.eachLimit(uids, 10, function(uid, next) { if (!parseInt(uid, 10)) { return next(); } From b858b120da3003e4313d7884ed73f3b2cc6d6d0f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 18 Aug 2014 16:03:25 -0400 Subject: [PATCH 031/372] closes #1998 --- src/posts.js | 6 +++--- src/topics.js | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/posts.js b/src/posts.js index 4f33a78f47..ab87cbeb2a 100644 --- a/src/posts.js +++ b/src/posts.js @@ -102,7 +102,7 @@ var async = require('async'), return callback(null, []); } - Posts.getPostsByPids(pids, tid, callback); + Posts.getPostsByPids(pids, callback); }); }; @@ -110,7 +110,7 @@ var async = require('async'), db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, callback); }; - Posts.getPostsByPids = function(pids, tid, callback) { + Posts.getPostsByPids = function(pids, callback) { var keys = []; for(var x=0, numPids=pids.length; x Date: Mon, 18 Aug 2014 16:18:51 -0400 Subject: [PATCH 032/372] getMainPost uses getMainPosts --- src/topics.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/topics.js b/src/topics.js index a2d3130ae3..596aab1929 100644 --- a/src/topics.js +++ b/src/topics.js @@ -308,23 +308,8 @@ var async = require('async'), }; Topics.getMainPost = function(tid, uid, callback) { - Topics.getTopicField(tid, 'mainPid', function(err, mainPid) { - if (err) { - return callback(err); - } - if (!parseInt(mainPid, 10)) { - return callback(null, []); - } - posts.getPostsByPids([mainPid], function(err, postData) { - if (err) { - return callback(err); - } - if (!Array.isArray(postData) || !postData[0]) { - return callback(null, []); - } - postData[0].index = 0; - Topics.addPostData(postData, uid, callback); - }); + Topics.getMainPosts([tid], uid, function(err, mainPosts) { + calllback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null); }); }; From 83de5ba5a43c15157ac2f879f44f857638325981 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 18 Aug 2014 19:04:49 -0400 Subject: [PATCH 033/372] added topic owner uid --- src/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index ab87cbeb2a..5293b5ac46 100644 --- a/src/posts.js +++ b/src/posts.js @@ -302,7 +302,7 @@ var async = require('async'), user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next); }, topicsAndCategories: function(next) { - db.getObjectsFields(tids, ['tid', 'title', 'cid', 'slug', 'deleted'], function(err, topics) { + db.getObjectsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted'], function(err, topics) { if (err) { return next(err); } From b7c7dd98c34a932282b6acef526cb9d83b085294 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 21 Aug 2014 00:11:39 -0400 Subject: [PATCH 034/372] ACP setting and API return for subcats --- public/src/forum/admin/categories.js | 24 ++++++++++++++++++++++++ src/categories.js | 21 +++++++++++++++++++-- src/categories/update.js | 1 + src/controllers/categories.js | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index cc1627fdf6..c7851c1e7e 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -231,6 +231,30 @@ define('forum/admin/categories', ['uploader', 'forum/admin/iconSelect'], functio }); setupEditTargets(); + + $('button[data-action="setParent"]').on('click', function() { + var cid = $(this).parents('[data-cid]').attr('data-cid'), + modal = $('#setParent'); + + modal.find('select').val($(this).attr('data-parentCid')); + modal.attr('data-cid', cid).modal(); + }); + $('#setParent [data-cid]').on('click', function() { + var modalEl = $('#setParent'), + parentCid = $(this).attr('data-cid'), + payload = {}; + + payload[modalEl.attr('data-cid')] = { + parentCid: parentCid + }; + + socket.emit('admin.categories.update', payload, function(err) { + modalEl.one('hidden.bs.modal', function() { + ajaxify.go('admin/categories/active'); + }); + modalEl.modal('hide'); + }); + }); }); }; diff --git a/src/categories.js b/src/categories.js index eae19b4595..e3f17e96e5 100644 --- a/src/categories.js +++ b/src/categories.js @@ -263,7 +263,13 @@ var db = require('./database'), }); }; - Categories.getCategoriesData = function(cids, callback) { + Categories.getCategoriesData = function(cids, expandParent, callback) { + // expandParent is optional, default: true + if (typeof expandParent === 'function' && !callback) { + callback = expandParent; + expandParent = undefined; + } + var keys = cids.map(function(cid) { return 'category:' + cid; }); @@ -285,7 +291,18 @@ var db = require('./database'), category.description = validator.escape(category.description); category.backgroundImage = category.image ? nconf.get('relative_path') + category.image : ''; category.disabled = category.disabled ? parseInt(category.disabled, 10) !== 0 : false; - next(null, category); + + if (expandParent !== false && category.parentCid) { + Categories.getCategoriesData([category.parentCid], false, function(err, categories) { + if (!err && categories.length) { + category.parent = categories[0]; + } + + next(null, category); + }); + } else { + next(null, category); + } }, callback); }); }; diff --git a/src/categories/update.js b/src/categories/update.js index 002628d723..fa04d99deb 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -13,6 +13,7 @@ module.exports = function(Categories) { function updateCategory(cid, next) { var category = modified[cid]; var fields = Object.keys(category); + console.log('updating', cid, 'fields:', fields); async.each(fields, function(key, next) { updateCategoryField(cid, key, category[key], next); diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 25a4bf3255..f7557210fb 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -8,7 +8,7 @@ var categoriesController = {}, user = require('../user'), categories = require('../categories'), topics = require('../topics'), - meta = require('../meta'); + meta = require('../meta'), plugins = require('../plugins'); categoriesController.recent = function(req, res, next) { From 69b9e57dafec5b2fec83f87418608eb36e063948 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 21 Aug 2014 09:52:21 -0400 Subject: [PATCH 035/372] completing subcategory integration in backend, closed #1299 --- src/categories.js | 51 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/categories.js b/src/categories.js index e3f17e96e5..4f78fa652f 100644 --- a/src/categories.js +++ b/src/categories.js @@ -257,17 +257,23 @@ var db = require('./database'), db.isSetMember('cid:' + cid + ':read_by_uid', uid, callback); }; - Categories.getCategoryData = function(cid, callback) { - Categories.getCategoriesData([cid], function(err, categories) { + Categories.getCategoryData = function(cid, expandFamily, callback) { + // expandFamily is optional, default: true + if (typeof expandFamily === 'function' && !callback) { + callback = expandFamily; + expandFamily = true; + } + + Categories.getCategoriesData([cid], expandFamily, function(err, categories) { callback(err, categories ? categories[0] : null); }); }; - Categories.getCategoriesData = function(cids, expandParent, callback) { - // expandParent is optional, default: true - if (typeof expandParent === 'function' && !callback) { - callback = expandParent; - expandParent = undefined; + Categories.getCategoriesData = function(cids, expandFamily, callback) { + // expandFamily is optional, default: true + if (typeof expandFamily === 'function' && !callback) { + callback = expandFamily; + expandFamily = true; } var keys = cids.map(function(cid) { @@ -292,10 +298,14 @@ var db = require('./database'), category.backgroundImage = category.image ? nconf.get('relative_path') + category.image : ''; category.disabled = category.disabled ? parseInt(category.disabled, 10) !== 0 : false; - if (expandParent !== false && category.parentCid) { - Categories.getCategoriesData([category.parentCid], false, function(err, categories) { - if (!err && categories.length) { - category.parent = categories[0]; + if (expandFamily !== false) { + async.parallel({ + children: async.apply(Categories.getChildren, category.cid), + parent: async.apply(Categories.getCategoryData, category.parentCid, false) + }, function(err, data) { + if (!err) { + category.parent = data.parent; + category.children = data.children; } next(null, category); @@ -362,6 +372,25 @@ var db = require('./database'), }); }; + Categories.getChildren = function(cid, callback) { + async.waterfall([ + async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), + function (cids, next) { + Categories.getMultipleCategoryFields(cids, ['cid', 'parentCid'], next); + }, + function (data, next) { + next(null, data.filter(function(category) { + if (parseInt(category.parentCid, 10) === parseInt(cid, 10)) return true; + }).map(function(category) { + return category.cid; + })); + }, + function (cids, next) { + Categories.getCategoriesData(cids, false, next); + } + ], callback); + }; + Categories.onNewPostMade = function(postData) { topics.getTopicFields(postData.tid, ['cid', 'pinned'], function(err, topicData) { if (err) { From 5a42b6a7ea15a275a98aa1d75e07cfb2d4cda194 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 22 Aug 2014 18:05:50 -0400 Subject: [PATCH 036/372] closes #2009 --- src/socket.io/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 4cac4beb56..554ea486a9 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -24,7 +24,7 @@ SocketCategories.getRecentReplies = function(socket, cid, callback) { }; SocketCategories.get = function(socket, data, callback) { - categories.getAllCategories(callback); + categories.getCategoriesByPrivilege(socket.uid, 'find', callback); }; SocketCategories.loadMore = function(socket, data, callback) { From 95c839579e1d7feb4805c21ca1f47172da974c19 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 22 Aug 2014 19:10:26 -0400 Subject: [PATCH 037/372] closes #2007 --- src/categories.js | 93 ++++++++++++++++++++-------------------- src/controllers/admin.js | 7 ++- src/install.js | 2 +- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/categories.js b/src/categories.js index 4f78fa652f..2245517d5b 100644 --- a/src/categories.js +++ b/src/categories.js @@ -67,12 +67,13 @@ var db = require('./database'), }; Categories.getCategoryById = function(cid, start, end, uid, callback) { - Categories.getCategoryData(cid, function(err, category) { - if(err || !category) { + Categories.getCategories([cid], uid, function(err, categories) { + if (err || !Array.isArray(categories) || !categories[0]) { return callback(err || new Error('[[error:invalid-cid]]')); } + var category = categories[0]; - if(parseInt(uid, 10)) { + if (parseInt(uid, 10)) { Categories.markAsRead(cid, uid); } @@ -174,7 +175,7 @@ var db = require('./database'), }); }; - Categories.getAllCategories = function(callback) { + Categories.getAllCategories = function(uid, callback) { db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { if (err) { return callback(err); @@ -184,7 +185,7 @@ var db = require('./database'), return callback(null, []); } - Categories.getCategoriesData(cids, callback); + Categories.getCategories(cids, uid, callback); }); }; @@ -257,25 +258,13 @@ var db = require('./database'), db.isSetMember('cid:' + cid + ':read_by_uid', uid, callback); }; - Categories.getCategoryData = function(cid, expandFamily, callback) { - // expandFamily is optional, default: true - if (typeof expandFamily === 'function' && !callback) { - callback = expandFamily; - expandFamily = true; - } - - Categories.getCategoriesData([cid], expandFamily, function(err, categories) { + Categories.getCategoryData = function(cid, callback) { + Categories.getCategoriesData([cid], function(err, categories) { callback(err, categories ? categories[0] : null); }); }; - Categories.getCategoriesData = function(cids, expandFamily, callback) { - // expandFamily is optional, default: true - if (typeof expandFamily === 'function' && !callback) { - callback = expandFamily; - expandFamily = true; - } - + Categories.getCategoriesData = function(cids, callback) { var keys = cids.map(function(cid) { return 'category:' + cid; }); @@ -298,21 +287,7 @@ var db = require('./database'), category.backgroundImage = category.image ? nconf.get('relative_path') + category.image : ''; category.disabled = category.disabled ? parseInt(category.disabled, 10) !== 0 : false; - if (expandFamily !== false) { - async.parallel({ - children: async.apply(Categories.getChildren, category.cid), - parent: async.apply(Categories.getCategoryData, category.parentCid, false) - }, function(err, data) { - if (!err) { - category.parent = data.parent; - category.children = data.children; - } - - next(null, category); - }); - } else { - next(null, category); - } + next(null, category); }, callback); }); }; @@ -353,6 +328,12 @@ var db = require('./database'), categories: function(next) { Categories.getCategoriesData(cids, next); }, + children: function(next) { + Categories.getChildren(cids, uid, next); + }, + parents: function(next) { + Categories.getParents(cids, next); + }, hasRead: function(next) { Categories.hasReadCategories(cids, uid, next); } @@ -366,27 +347,47 @@ var db = require('./database'), uid = parseInt(uid, 10); for(var i=0; i Date: Fri, 22 Aug 2014 19:21:20 -0400 Subject: [PATCH 038/372] closes #2005 --- public/src/forum/topic/move.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/public/src/forum/topic/move.js b/public/src/forum/topic/move.js index c94dfb40f2..11f82c85f3 100644 --- a/public/src/forum/topic/move.js +++ b/public/src/forum/topic/move.js @@ -90,24 +90,10 @@ define('forum/topic/move', function() { } function renderCategories(categories) { - var categoriesEl = modal.find('.category-list'), - info; - - for (var x = 0; x < categories.length; ++x) { - info = categories[x]; - if(parseInt(info.cid, 10) === parseInt(Move.currentCid, 10)) { - continue; - } - - $('
  • ') - .css({background: info.bgColor, color: info.color || '#fff'}) - .toggleClass('disabled', info.disabled) - .attr('data-cid', info.cid) - .html(' ' + info.name) - .appendTo(categoriesEl); - } - - $('#categories-loading').remove(); + templates.parse('partials/category_list', {categories: categories}, function(html) { + modal.find('.modal-body').prepend(html); + $('#categories-loading').remove(); + }); } return Move; From 62dd056faa5e8b6cbeb30fbaa5c5138d4f9eec9c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 23 Aug 2014 21:53:16 -0400 Subject: [PATCH 039/372] tag search --- src/topics/tags.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/topics/tags.js b/src/topics/tags.js index 861a8974a6..ecca55eb2f 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -214,19 +214,8 @@ module.exports = function(Topics) { return callback(null, []); } - if (plugins.hasListeners('filter:tags.category')) { - plugins.fireHook('filter:tags.category', {tags: [], cid: data.cid}, function(err, result) { - if (data.query.length === 1) { - callback(err, result.tags); - } else { - doSearch(err, result ? result.tags : null); - } - }); - } else { - db.getSortedSetRevRange('tags:topic:count', 0, -1, doSearch); - } - function doSearch(err, tags) { + db.getSortedSetRevRange('tags:topic:count', 0, -1, function(err, tags) { if (err) { return callback(null, []); } @@ -244,7 +233,7 @@ module.exports = function(Topics) { }); callback(null, matches); - } + }); }; }; \ No newline at end of file From 7019618863fdf08a19233859378caf0582d4cbcf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 23 Aug 2014 22:34:39 -0400 Subject: [PATCH 040/372] closes #2003 --- src/posts.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/posts.js b/src/posts.js index 5293b5ac46..aa2345db0c 100644 --- a/src/posts.js +++ b/src/posts.js @@ -330,6 +330,14 @@ var async = require('async'), return obj; } + function stripTags(content) { + if (options.stripTags && content) { + var s = S(content); + return s.stripTags.apply(s, utils.stripTags).s; + } + return content; + } + if (err) { return callback(err); } @@ -355,6 +363,7 @@ var async = require('async'), post.relativeTime = utils.toISOString(post.timestamp); if (!post.content || !options.parse) { + post.content = stripTags(post.content); return next(null, post); } @@ -363,12 +372,7 @@ var async = require('async'), return next(err); } - if (options.stripTags && content) { - var s = S(content); - post.content = s.stripTags.apply(s, utils.stripTags).s; - } else { - post.content = content; - } + post.content = stripTags(content); next(null, post); }); From 943874805d31651a8735e9af0985795567ac2965 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 Aug 2014 12:30:49 -0400 Subject: [PATCH 041/372] proof-of-concept for hot-swapping of routes --- src/hotswap.js | 33 +++++++++++++++ src/plugins.js | 34 ++++++++++++++- src/routes/debug.js | 6 ++- src/routes/index.js | 100 +++++++++++++++++++++++--------------------- src/webserver.js | 1 + 5 files changed, 123 insertions(+), 51 deletions(-) create mode 100644 src/hotswap.js diff --git a/src/hotswap.js b/src/hotswap.js new file mode 100644 index 0000000000..717c099052 --- /dev/null +++ b/src/hotswap.js @@ -0,0 +1,33 @@ +var HotSwap = {}, + winston = require('winston'), + stack; + +HotSwap.prepare = function(app) { + stack = app._router.stack; +}; + +HotSwap.find = function(id) { + if (stack) { + for(var x=0,numEntries=stack.length;x Date: Sun, 24 Aug 2014 14:25:26 -0400 Subject: [PATCH 042/372] framework for reloading --- src/meta.js | 5 +++++ src/routes/debug.js | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/meta.js b/src/meta.js index 9c0dc8adad..fb4e5cac52 100644 --- a/src/meta.js +++ b/src/meta.js @@ -28,6 +28,11 @@ var async = require('async'), }); }; + Meta.reload = function(step) { + // 1. Reload plugins and associated routes + // 2. Minify scripts and css, update cache buster + }; + Meta.restart = function() { if (process.send) { process.send({ diff --git a/src/routes/debug.js b/src/routes/debug.js index 30f6eaf8b8..584a70a6b6 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -57,9 +57,5 @@ module.exports = function(app, middleware, controllers) { router.get('/test', function(req, res) { res.redirect(404); - var plugins = require('../plugins'); - plugins.reloadRoutes(function() { - res.send(200, 'routes replaced'); - }); }); }; From 841c755bb75a735a8b9b67f603387373dd1b242a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 Aug 2014 17:46:22 -0400 Subject: [PATCH 043/372] on-demand reloading of client-side assets --- src/meta/js.js | 187 ++++++++++++++++++++++++--------------------- src/routes/meta.js | 4 +- 2 files changed, 102 insertions(+), 89 deletions(-) diff --git a/src/meta/js.js b/src/meta/js.js index 29c6f77880..0625738db9 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -19,35 +19,37 @@ module.exports = function(Meta) { hash: +new Date(), prepared: false, minFile: 'nodebb.min.js', - scripts: [ - 'vendor/jquery/js/jquery.js', - 'vendor/jquery/js/jquery-ui-1.10.4.custom.js', - 'vendor/jquery/timeago/jquery.timeago.min.js', - 'vendor/jquery/js/jquery.form.min.js', - 'vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js', - 'vendor/jquery/deserialize/jquery.deserialize.min.js', - 'vendor/bootstrap/js/bootstrap.min.js', - 'vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', - 'vendor/requirejs/require.js', - 'vendor/bootbox/bootbox.min.js', - 'vendor/tinycon/tinycon.js', - 'vendor/xregexp/xregexp.js', - 'vendor/xregexp/unicode/unicode-base.js', - 'vendor/buzz/buzz.min.js', - '../node_modules/templates.js/lib/templates.js', - 'src/utils.js', - 'src/app.js', - 'src/ajaxify.js', - 'src/variables.js', - 'src/widgets.js', - 'src/translator.js', - 'src/helpers.js', - 'src/overrides.js' - ] + scripts: { + base: [ + 'public/vendor/jquery/js/jquery.js', + 'public/vendor/jquery/js/jquery-ui-1.10.4.custom.js', + 'public/vendor/jquery/timeago/jquery.timeago.min.js', + 'public/vendor/jquery/js/jquery.form.min.js', + 'public/vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js', + 'public/vendor/jquery/deserialize/jquery.deserialize.min.js', + 'public/vendor/bootstrap/js/bootstrap.min.js', + 'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', + 'public/vendor/requirejs/require.js', + 'public/vendor/bootbox/bootbox.min.js', + 'public/vendor/tinycon/tinycon.js', + 'public/vendor/xregexp/xregexp.js', + 'public/vendor/xregexp/unicode/unicode-base.js', + 'public/vendor/buzz/buzz.min.js', + './node_modules/templates.js/lib/templates.js', + 'public/src/utils.js', + 'public/src/app.js', + 'public/src/ajaxify.js', + 'public/src/variables.js', + 'public/src/widgets.js', + 'public/src/translator.js', + 'public/src/helpers.js', + 'public/src/overrides.js' + ] + } }; Meta.js.loadRJS = function(callback) { - var rjsPath = path.join(__dirname, '../..', '/public/src'); + var rjsPath = path.join(__dirname, '../../public/src'); async.parallel({ forum: function(next) { @@ -65,75 +67,53 @@ module.exports = function(Meta) { rjsFiles = rjsFiles.filter(function(file) { return file.match('admin') === null; }).map(function(file) { - return path.join('src', file.replace(rjsPath, '')); + return path.join('public/src', file.replace(rjsPath, '')); }); - Meta.js.scripts = Meta.js.scripts.concat(rjsFiles); + Meta.js.scripts.rjs = rjsFiles; callback(); }); }; Meta.js.prepare = function (callback) { - plugins.fireHook('filter:scripts.get', Meta.js.scripts, function(err, scripts) { - var jsPaths = scripts.map(function (jsPath) { - jsPath = path.normalize(jsPath); - - if (jsPath.substring(0, 7) === 'plugins') { - var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { - if (jsPath.match(mappedPath)) { - return mappedPath; - } else { - return null; - } - }).filter(function(a) { return a; }); - - if (matches.length) { - var relPath = jsPath.slice(('plugins/' + matches[0]).length), - pluginId = matches[0].split(path.sep)[0]; - - return plugins.staticDirs[matches[0]] + relPath; - } else { - winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); - return null; - } + async.parallel([ + async.apply(Meta.js.loadRJS), // Require.js scripts + async.apply(getPluginScripts), // plugin scripts via filter:scripts.get + function(next) { // client scripts via "scripts" config in plugin.json + var pluginsScripts = [], + pluginDirectories = [], + clientScripts = []; + + pluginsScripts = plugins.clientScripts.filter(function(path) { + if (path.indexOf('.js') !== -1) { + return true; } else { - return path.join(__dirname, '../..', '/public', jsPath); + pluginDirectories.push(path); + return false; } }); - Meta.js.scripts = jsPaths.filter(function(path) { - return path !== null; - }); + // Add plugin scripts + Meta.js.scripts.client = pluginsScripts; - var pluginDirectories = []; - - plugins.clientScripts = plugins.clientScripts.filter(function(path) { - if (path.indexOf('.js') !== -1) { - return true; - } else { - pluginDirectories.push(path); - return false; - } - }); - - // Add plugin scripts - Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts); - - async.each(pluginDirectories, function(directory, next) { - utils.walk(directory, function(err, scripts) { - Meta.js.scripts = Meta.js.scripts.concat(scripts); - next(err); - }); - }, function(err) { - // Translate into relative paths - Meta.js.scripts = Meta.js.scripts.map(function(script) { - return path.relative(path.resolve(__dirname, '../..'), script).replace(/\\/g, '/'); - }); + // Add plugin script directories + async.each(pluginDirectories, function(directory, next) { + utils.walk(directory, function(err, scripts) { + Meta.js.scripts.client = Meta.js.scripts.client.concat(scripts); + next(err); + }); + }, next); + } + ], function(err) { + if (err) return callback(err); - Meta.js.prepared = true; - callback(err); + // Convert all scripts to paths relative to the NodeBB base directory + var basePath = path.resolve(__dirname, '../..'); + Meta.js.scripts.all = Meta.js.scripts.base.concat(Meta.js.scripts.rjs, Meta.js.scripts.plugin, Meta.js.scripts.client).map(function(script) { + return path.relative(basePath, script).replace(/\\/g, '/'); }); + callback(); }); }; @@ -183,13 +163,11 @@ module.exports = function(Meta) { } }); - Meta.js.loadRJS(function() { - Meta.js.prepare(function() { - minifier.send({ - action: 'js', - minify: minify, - scripts: Meta.js.scripts - }); + Meta.js.prepare(function() { + minifier.send({ + action: 'js', + minify: minify, + scripts: Meta.js.scripts.all }); }); }; @@ -199,4 +177,39 @@ module.exports = function(Meta) { Meta.js.minifierProc.kill('SIGTERM'); } }; + + function getPluginScripts(callback) { + plugins.fireHook('filter:scripts.get', [], function(err, scripts) { + if (err) callback(err, []); + + var jsPaths = scripts.map(function (jsPath) { + jsPath = path.normalize(jsPath); + + // if (jsPath.substring(0, 7) === 'plugins') { + var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { + if (jsPath.match(mappedPath)) { + return mappedPath; + } else { + return null; + } + }).filter(function(a) { return a; }); + + if (matches.length) { + var relPath = jsPath.slice(('plugins/' + matches[0]).length), + pluginId = matches[0].split(path.sep)[0]; + + return plugins.staticDirs[matches[0]] + relPath; + } else { + winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); + return null; + } + // } else { + // return path.join(__dirname, '../..', jsPath); + // } + }); + + Meta.js.scripts.plugin = jsPaths.filter(Boolean); + callback(); + }); + }; }; \ No newline at end of file diff --git a/src/routes/meta.js b/src/routes/meta.js index f0d9aa85b2..76cefadf0e 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -35,8 +35,8 @@ function setupPluginSourceMapping(app) { development mode (`./nodebb dev`) */ var routes = plugins.clientScripts, - mapping, - prefix = __dirname.split(path.sep).length - 1; + prefix = __dirname.split(path.sep).length - 1, + mapping; routes.forEach(function(route) { mapping = '/' + route.split(path.sep).slice(prefix).join('/'); From 013dfd0cebf3222c9c29a0db2bc0efd05cd888c0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 10:13:01 -0400 Subject: [PATCH 044/372] added callbacks to css and js reloading, issue #2010 --- src/meta/css.js | 11 ++++++++++- src/meta/js.js | 23 +++++++++++++++-------- src/plugins.js | 8 +++++--- src/routes/plugins.js | 3 +-- src/webserver.js | 1 + 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/meta/css.js b/src/meta/css.js index f60e320703..737f203ee5 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -20,7 +20,7 @@ module.exports = function(Meta) { Meta.css.branding = {}; Meta.css.defaultBranding = {}; - Meta.css.minify = function() { + Meta.css.minify = function(callback) { winston.info('[meta/css] Minifying LESS/CSS'); db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) { var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'), @@ -54,6 +54,9 @@ module.exports = function(Meta) { parser.parse(source, function(err, tree) { if (err) { winston.error('[meta/css] Could not minify LESS/CSS: ' + err.message); + if (typeof callback === 'function') { + callback(err); + } return; } @@ -63,6 +66,9 @@ module.exports = function(Meta) { }); } catch (err) { winston.error('[meta/css] Syntax Error: ' + err.message + ' - ' + path.basename(err.filename) + ' on line ' + err.line); + if (typeof callback === 'function') { + callback(err); + } return; } @@ -89,6 +95,9 @@ module.exports = function(Meta) { winston.info('[meta/css] Done.'); emitter.emit('meta:css.compiled'); + if (typeof callback === 'function') { + callback(); + } }); }); }; diff --git a/src/meta/js.js b/src/meta/js.js index 0625738db9..14e3112f19 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -117,28 +117,35 @@ module.exports = function(Meta) { }); }; - Meta.js.minify = function(minify) { + Meta.js.minify = function(minify, callback) { var minifier = Meta.js.minifierProc = fork('minifier.js', { silent: true }), minifiedStream = minifier.stdio[1], + minifiedString = '', mapStream = minifier.stdio[2], + mapString = '', step = 0, onComplete = function() { if (step === 0) { return step++; } + Meta.js.cache = minifiedString; + Meta.js.map = mapString; winston.info('[meta/js] Compilation complete'); emitter.emit('meta:js.compiled'); minifier.kill(); + if (typeof callback === 'function') { + callback(); + } }; minifiedStream.on('data', function(buffer) { - Meta.js.cache += buffer.toString(); + minifiedString += buffer.toString(); }); mapStream.on('data', function(buffer) { - Meta.js.map += buffer.toString(); + mapString += buffer.toString(); }); minifier.on('message', function(message) { @@ -158,7 +165,11 @@ module.exports = function(Meta) { case 'error': winston.error('[meta/js] Could not compile client-side scripts! ' + message.payload.message); minifier.kill(); - process.exit(); + if (typeof callback === 'function') { + callback(err); + } else { + process.exit(0); + } break; } }); @@ -185,7 +196,6 @@ module.exports = function(Meta) { var jsPaths = scripts.map(function (jsPath) { jsPath = path.normalize(jsPath); - // if (jsPath.substring(0, 7) === 'plugins') { var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { if (jsPath.match(mappedPath)) { return mappedPath; @@ -203,9 +213,6 @@ module.exports = function(Meta) { winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); return null; } - // } else { - // return path.join(__dirname, '../..', jsPath); - // } }); Meta.js.scripts.plugin = jsPaths.filter(Boolean); diff --git a/src/plugins.js b/src/plugins.js index b2578ed51e..427816c7bc 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -129,15 +129,18 @@ var fs = require('fs'), } else { var router = express.Router(); router.hotswapId = 'plugins'; + router.render = function() { + app.render.apply(app, arguments); + }; // Deprecated as of v0.5.0, remove this hook call for NodeBB v0.6.0-1 Plugins.fireHook('action:app.load', router, middleware, controllers); Plugins.fireHook('static:app.load', router, middleware, controllers, function() { hotswap.replace('plugins', router); + winston.info('[plugins] All plugins reloaded and rerouted'); + callback(); }); - - callback(); } }; @@ -527,7 +530,6 @@ var fs = require('fs'), // Reload meta data Plugins.reload(function() { - if(!active) { Plugins.fireHook('action:plugin.activate', id); } diff --git a/src/routes/plugins.js b/src/routes/plugins.js index fd195ca3f2..17efdb8c53 100644 --- a/src/routes/plugins.js +++ b/src/routes/plugins.js @@ -8,8 +8,7 @@ var _ = require('underscore'), async = require('async'), winston = require('winston'), - plugins = require('../plugins'), - pluginRoutes = []; + plugins = require('../plugins'); module.exports = function(app, middleware, controllers) { diff --git a/src/webserver.js b/src/webserver.js index f2595ddabc..5c6f571372 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -110,6 +110,7 @@ if(nconf.get('ssl')) { emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() { winston.info('NodeBB Ready'); emitter.emit('nodebb:ready'); + emitter.removeAllListeners('templates:compiled').removeAllListeners('meta:js.compiled').removeAllListeners('meta:css.compiled'); }); emitter.on('templates:compiled', function() { From 4e55707652b7dfa9fb18891c5a9d1366a6baf2bd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 10:46:48 -0400 Subject: [PATCH 045/372] closed #2011 --- public/src/forum/admin/index.js | 41 +++++++++++++++++++++++++++------ src/meta.js | 13 +++++++---- src/socket.io/admin.js | 4 ++++ 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index c10706e257..58eb0d8eef 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -37,18 +37,45 @@ define('forum/admin/index', function() { }); $('.restart').on('click', function() { + bootbox.confirm('Are you sure you wish to restart NodeBB?', function(confirm) { + if (confirm) { + app.alert({ + timeout: 5000, + title: 'Restarting... ', + message: 'NodeBB is restarting.', + type: 'info' + }); + + $(window).one('action:reconnected', function() { + app.alertSuccess('NodeBB has successfully restarted.'); + }); + + socket.emit('admin.restart'); + } + }); + }); + + $('.reload').on('click', function() { app.alert({ - timeout: 5000, - title: 'Restarting...', + alert_id: 'instance_reload', + title: 'Reloading... ', message: 'NodeBB is restarting.', - type: 'info' + type: 'info', + timeout: 5000 }); - $(window).one('action:reconnected', function() { - app.alertSuccess('NodeBB has successfully restarted.'); + socket.emit('admin.reload', function(err) { + if (!err) { + app.alertSuccess('NodeBB has successfully reloaded.'); + } else { + app.alert({ + alert_id: 'instance_reload', + title: '[[global:alert.error]]', + message: err.message, + type: 'danger' + }); + } }); - - socket.emit('admin.restart'); }); }; diff --git a/src/meta.js b/src/meta.js index fb4e5cac52..9a616cc957 100644 --- a/src/meta.js +++ b/src/meta.js @@ -3,7 +3,8 @@ var async = require('async'), winston = require('winston'), user = require('./user'), - groups = require('./groups'); + groups = require('./groups'), + plugins = require('./plugins'); (function (Meta) { @@ -28,9 +29,13 @@ var async = require('async'), }); }; - Meta.reload = function(step) { - // 1. Reload plugins and associated routes - // 2. Minify scripts and css, update cache buster + Meta.reload = function(callback) { + plugins.reload(function() { + async.parallel([ + async.apply(Meta.js.minify, false), + async.apply(Meta.css.minify) + ], callback); + }); }; Meta.restart = function() { diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index bc8140a2a1..f51b0dd2a7 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -38,6 +38,10 @@ SocketAdmin.before = function(socket, method, next) { }); }; +SocketAdmin.reload = function(socket, data, callback) { + meta.reload(callback); +}; + SocketAdmin.restart = function(socket, data, callback) { meta.restart(); }; From c9e80b6f64b03587687900471529ef7a2f234ee9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 11:56:48 -0400 Subject: [PATCH 046/372] closed #2012 --- app.js | 10 +++++++++- loader.js | 29 ++++++++++++++++++++--------- src/webserver.js | 34 +++++++++++++++++++++------------- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 26fe077f87..bd9cb02e0a 100644 --- a/app.js +++ b/app.js @@ -141,7 +141,13 @@ function start() { nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path')); plugins.ready(function() { - webserver.init(); + webserver.init(function() { + // If this callback is called, this means that loader.js is used + process.on('SIGCONT', webserver.listen); + process.send({ + action: 'ready' + }); + }); }); process.on('SIGTERM', shutdown); @@ -313,6 +319,8 @@ function shutdown(code) { winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.'); require('./src/database').close(); winston.info('[app] Database connection closed.'); + require('./src/webserver').server.close(); + winston.info('[app] Web server closed to connections.'); winston.info('[app] Shutdown complete.'); process.exit(code || 0); diff --git a/loader.js b/loader.js index 6448615348..a3ce275704 100644 --- a/loader.js +++ b/loader.js @@ -5,8 +5,7 @@ var nconf = require('nconf'), pidFilePath = __dirname + '/pidfile', output = fs.openSync(__dirname + '/logs/output.log', 'a'), start = function() { - var fork = require('child_process').fork, - nbb_start = function() { + var nbb_start = function(callback) { if (timesStarted > 3) { console.log('\n[loader] Experienced three start attempts in 10 seconds, most likely an error on startup. Halting.'); return nbb_stop(); @@ -18,14 +17,24 @@ var nconf = require('nconf'), } startTimer = setTimeout(resetTimer, 1000*10); - nbb = fork('./app', process.argv.slice(2), { + if (nbb) { + nbbOld = nbb; + } + + nbb = require('child_process').fork('./app', process.argv.slice(2), { env: process.env }); nbb.on('message', function(message) { if (message && typeof message === 'object' && message.action) { - if (message.action === 'restart') { - nbb_restart(); + switch (message.action) { + case 'ready': + if (!callback) return nbb.kill('SIGCONT'); + callback(); + break; + case 'restart': + nbb_restart(); + break; } } }); @@ -52,10 +61,12 @@ var nconf = require('nconf'), } }, nbb_restart = function() { - nbb.removeAllListeners('exit').on('exit', function() { - nbb_start(); + nbb_start(function() { + nbbOld.removeAllListeners('exit').on('exit', function() { + nbb.kill('SIGCONT'); + }); + nbbOld.kill(); }); - nbb.kill(); }, resetTimer = function() { clearTimeout(startTimer); @@ -70,7 +81,7 @@ var nconf = require('nconf'), nbb_start(); }, - nbb; + nbb, nbbOld; nconf.argv(); diff --git a/src/webserver.js b/src/webserver.js index 5c6f571372..3278713dd1 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -97,7 +97,7 @@ if(nconf.get('ssl')) { } module.exports.server = server; - module.exports.init = function () { + module.exports.init = function(callback) { server.on("error", function(err){ if (err.code === 'EADDRINUSE') { winston.error('NodeBB address in use, exiting...'); @@ -114,18 +114,26 @@ if(nconf.get('ssl')) { }); emitter.on('templates:compiled', function() { - var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port; - winston.info('NodeBB attempting to listen on: ' + bind_address); - - server.listen(port, nconf.get('bind_address'), function(){ - winston.info('NodeBB is now listening on: ' + bind_address); - if (process.send) { - process.send({ - action: 'ready', - bind_address: bind_address - }); - } - }); + if (process.send) { + callback(); + } else { + module.exports.listen(); + } + }); + }; + + module.exports.listen = function() { + var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port; + winston.info('NodeBB attempting to listen on: ' + bind_address); + + server.listen(port, nconf.get('bind_address'), function(){ + winston.info('NodeBB is now listening on: ' + bind_address); + if (process.send) { + process.send({ + action: 'listening', + bind_address: bind_address + }); + } }); }; From ba91d7aba677c0ea32232206daece5dd6a8ae7dd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 Aug 2014 12:30:49 -0400 Subject: [PATCH 047/372] proof-of-concept for hot-swapping of routes --- src/hotswap.js | 33 +++++++++++++++ src/plugins.js | 34 ++++++++++++++- src/routes/debug.js | 6 ++- src/routes/index.js | 100 +++++++++++++++++++++++--------------------- src/webserver.js | 1 + 5 files changed, 123 insertions(+), 51 deletions(-) create mode 100644 src/hotswap.js diff --git a/src/hotswap.js b/src/hotswap.js new file mode 100644 index 0000000000..717c099052 --- /dev/null +++ b/src/hotswap.js @@ -0,0 +1,33 @@ +var HotSwap = {}, + winston = require('winston'), + stack; + +HotSwap.prepare = function(app) { + stack = app._router.stack; +}; + +HotSwap.find = function(id) { + if (stack) { + for(var x=0,numEntries=stack.length;x Date: Sun, 24 Aug 2014 14:25:26 -0400 Subject: [PATCH 048/372] framework for reloading --- src/meta.js | 5 +++++ src/routes/debug.js | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/meta.js b/src/meta.js index 9c0dc8adad..fb4e5cac52 100644 --- a/src/meta.js +++ b/src/meta.js @@ -28,6 +28,11 @@ var async = require('async'), }); }; + Meta.reload = function(step) { + // 1. Reload plugins and associated routes + // 2. Minify scripts and css, update cache buster + }; + Meta.restart = function() { if (process.send) { process.send({ diff --git a/src/routes/debug.js b/src/routes/debug.js index 30f6eaf8b8..584a70a6b6 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -57,9 +57,5 @@ module.exports = function(app, middleware, controllers) { router.get('/test', function(req, res) { res.redirect(404); - var plugins = require('../plugins'); - plugins.reloadRoutes(function() { - res.send(200, 'routes replaced'); - }); }); }; From 3e033043f3afe1fd7ef00e671920d5b23f34ab44 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 Aug 2014 17:46:22 -0400 Subject: [PATCH 049/372] on-demand reloading of client-side assets --- src/meta/js.js | 187 ++++++++++++++++++++++++--------------------- src/routes/meta.js | 4 +- 2 files changed, 102 insertions(+), 89 deletions(-) diff --git a/src/meta/js.js b/src/meta/js.js index 29c6f77880..0625738db9 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -19,35 +19,37 @@ module.exports = function(Meta) { hash: +new Date(), prepared: false, minFile: 'nodebb.min.js', - scripts: [ - 'vendor/jquery/js/jquery.js', - 'vendor/jquery/js/jquery-ui-1.10.4.custom.js', - 'vendor/jquery/timeago/jquery.timeago.min.js', - 'vendor/jquery/js/jquery.form.min.js', - 'vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js', - 'vendor/jquery/deserialize/jquery.deserialize.min.js', - 'vendor/bootstrap/js/bootstrap.min.js', - 'vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', - 'vendor/requirejs/require.js', - 'vendor/bootbox/bootbox.min.js', - 'vendor/tinycon/tinycon.js', - 'vendor/xregexp/xregexp.js', - 'vendor/xregexp/unicode/unicode-base.js', - 'vendor/buzz/buzz.min.js', - '../node_modules/templates.js/lib/templates.js', - 'src/utils.js', - 'src/app.js', - 'src/ajaxify.js', - 'src/variables.js', - 'src/widgets.js', - 'src/translator.js', - 'src/helpers.js', - 'src/overrides.js' - ] + scripts: { + base: [ + 'public/vendor/jquery/js/jquery.js', + 'public/vendor/jquery/js/jquery-ui-1.10.4.custom.js', + 'public/vendor/jquery/timeago/jquery.timeago.min.js', + 'public/vendor/jquery/js/jquery.form.min.js', + 'public/vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js', + 'public/vendor/jquery/deserialize/jquery.deserialize.min.js', + 'public/vendor/bootstrap/js/bootstrap.min.js', + 'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js', + 'public/vendor/requirejs/require.js', + 'public/vendor/bootbox/bootbox.min.js', + 'public/vendor/tinycon/tinycon.js', + 'public/vendor/xregexp/xregexp.js', + 'public/vendor/xregexp/unicode/unicode-base.js', + 'public/vendor/buzz/buzz.min.js', + './node_modules/templates.js/lib/templates.js', + 'public/src/utils.js', + 'public/src/app.js', + 'public/src/ajaxify.js', + 'public/src/variables.js', + 'public/src/widgets.js', + 'public/src/translator.js', + 'public/src/helpers.js', + 'public/src/overrides.js' + ] + } }; Meta.js.loadRJS = function(callback) { - var rjsPath = path.join(__dirname, '../..', '/public/src'); + var rjsPath = path.join(__dirname, '../../public/src'); async.parallel({ forum: function(next) { @@ -65,75 +67,53 @@ module.exports = function(Meta) { rjsFiles = rjsFiles.filter(function(file) { return file.match('admin') === null; }).map(function(file) { - return path.join('src', file.replace(rjsPath, '')); + return path.join('public/src', file.replace(rjsPath, '')); }); - Meta.js.scripts = Meta.js.scripts.concat(rjsFiles); + Meta.js.scripts.rjs = rjsFiles; callback(); }); }; Meta.js.prepare = function (callback) { - plugins.fireHook('filter:scripts.get', Meta.js.scripts, function(err, scripts) { - var jsPaths = scripts.map(function (jsPath) { - jsPath = path.normalize(jsPath); - - if (jsPath.substring(0, 7) === 'plugins') { - var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { - if (jsPath.match(mappedPath)) { - return mappedPath; - } else { - return null; - } - }).filter(function(a) { return a; }); - - if (matches.length) { - var relPath = jsPath.slice(('plugins/' + matches[0]).length), - pluginId = matches[0].split(path.sep)[0]; - - return plugins.staticDirs[matches[0]] + relPath; - } else { - winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); - return null; - } + async.parallel([ + async.apply(Meta.js.loadRJS), // Require.js scripts + async.apply(getPluginScripts), // plugin scripts via filter:scripts.get + function(next) { // client scripts via "scripts" config in plugin.json + var pluginsScripts = [], + pluginDirectories = [], + clientScripts = []; + + pluginsScripts = plugins.clientScripts.filter(function(path) { + if (path.indexOf('.js') !== -1) { + return true; } else { - return path.join(__dirname, '../..', '/public', jsPath); + pluginDirectories.push(path); + return false; } }); - Meta.js.scripts = jsPaths.filter(function(path) { - return path !== null; - }); + // Add plugin scripts + Meta.js.scripts.client = pluginsScripts; - var pluginDirectories = []; - - plugins.clientScripts = plugins.clientScripts.filter(function(path) { - if (path.indexOf('.js') !== -1) { - return true; - } else { - pluginDirectories.push(path); - return false; - } - }); - - // Add plugin scripts - Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts); - - async.each(pluginDirectories, function(directory, next) { - utils.walk(directory, function(err, scripts) { - Meta.js.scripts = Meta.js.scripts.concat(scripts); - next(err); - }); - }, function(err) { - // Translate into relative paths - Meta.js.scripts = Meta.js.scripts.map(function(script) { - return path.relative(path.resolve(__dirname, '../..'), script).replace(/\\/g, '/'); - }); + // Add plugin script directories + async.each(pluginDirectories, function(directory, next) { + utils.walk(directory, function(err, scripts) { + Meta.js.scripts.client = Meta.js.scripts.client.concat(scripts); + next(err); + }); + }, next); + } + ], function(err) { + if (err) return callback(err); - Meta.js.prepared = true; - callback(err); + // Convert all scripts to paths relative to the NodeBB base directory + var basePath = path.resolve(__dirname, '../..'); + Meta.js.scripts.all = Meta.js.scripts.base.concat(Meta.js.scripts.rjs, Meta.js.scripts.plugin, Meta.js.scripts.client).map(function(script) { + return path.relative(basePath, script).replace(/\\/g, '/'); }); + callback(); }); }; @@ -183,13 +163,11 @@ module.exports = function(Meta) { } }); - Meta.js.loadRJS(function() { - Meta.js.prepare(function() { - minifier.send({ - action: 'js', - minify: minify, - scripts: Meta.js.scripts - }); + Meta.js.prepare(function() { + minifier.send({ + action: 'js', + minify: minify, + scripts: Meta.js.scripts.all }); }); }; @@ -199,4 +177,39 @@ module.exports = function(Meta) { Meta.js.minifierProc.kill('SIGTERM'); } }; + + function getPluginScripts(callback) { + plugins.fireHook('filter:scripts.get', [], function(err, scripts) { + if (err) callback(err, []); + + var jsPaths = scripts.map(function (jsPath) { + jsPath = path.normalize(jsPath); + + // if (jsPath.substring(0, 7) === 'plugins') { + var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { + if (jsPath.match(mappedPath)) { + return mappedPath; + } else { + return null; + } + }).filter(function(a) { return a; }); + + if (matches.length) { + var relPath = jsPath.slice(('plugins/' + matches[0]).length), + pluginId = matches[0].split(path.sep)[0]; + + return plugins.staticDirs[matches[0]] + relPath; + } else { + winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); + return null; + } + // } else { + // return path.join(__dirname, '../..', jsPath); + // } + }); + + Meta.js.scripts.plugin = jsPaths.filter(Boolean); + callback(); + }); + }; }; \ No newline at end of file diff --git a/src/routes/meta.js b/src/routes/meta.js index f0d9aa85b2..76cefadf0e 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -35,8 +35,8 @@ function setupPluginSourceMapping(app) { development mode (`./nodebb dev`) */ var routes = plugins.clientScripts, - mapping, - prefix = __dirname.split(path.sep).length - 1; + prefix = __dirname.split(path.sep).length - 1, + mapping; routes.forEach(function(route) { mapping = '/' + route.split(path.sep).slice(prefix).join('/'); From 0c4a788698940190ca336167891791273d5abac7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 10:13:01 -0400 Subject: [PATCH 050/372] added callbacks to css and js reloading, issue #2010 --- src/meta/css.js | 11 ++++++++++- src/meta/js.js | 23 +++++++++++++++-------- src/plugins.js | 8 +++++--- src/routes/plugins.js | 3 +-- src/webserver.js | 1 + 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/meta/css.js b/src/meta/css.js index f60e320703..737f203ee5 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -20,7 +20,7 @@ module.exports = function(Meta) { Meta.css.branding = {}; Meta.css.defaultBranding = {}; - Meta.css.minify = function() { + Meta.css.minify = function(callback) { winston.info('[meta/css] Minifying LESS/CSS'); db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) { var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'), @@ -54,6 +54,9 @@ module.exports = function(Meta) { parser.parse(source, function(err, tree) { if (err) { winston.error('[meta/css] Could not minify LESS/CSS: ' + err.message); + if (typeof callback === 'function') { + callback(err); + } return; } @@ -63,6 +66,9 @@ module.exports = function(Meta) { }); } catch (err) { winston.error('[meta/css] Syntax Error: ' + err.message + ' - ' + path.basename(err.filename) + ' on line ' + err.line); + if (typeof callback === 'function') { + callback(err); + } return; } @@ -89,6 +95,9 @@ module.exports = function(Meta) { winston.info('[meta/css] Done.'); emitter.emit('meta:css.compiled'); + if (typeof callback === 'function') { + callback(); + } }); }); }; diff --git a/src/meta/js.js b/src/meta/js.js index 0625738db9..14e3112f19 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -117,28 +117,35 @@ module.exports = function(Meta) { }); }; - Meta.js.minify = function(minify) { + Meta.js.minify = function(minify, callback) { var minifier = Meta.js.minifierProc = fork('minifier.js', { silent: true }), minifiedStream = minifier.stdio[1], + minifiedString = '', mapStream = minifier.stdio[2], + mapString = '', step = 0, onComplete = function() { if (step === 0) { return step++; } + Meta.js.cache = minifiedString; + Meta.js.map = mapString; winston.info('[meta/js] Compilation complete'); emitter.emit('meta:js.compiled'); minifier.kill(); + if (typeof callback === 'function') { + callback(); + } }; minifiedStream.on('data', function(buffer) { - Meta.js.cache += buffer.toString(); + minifiedString += buffer.toString(); }); mapStream.on('data', function(buffer) { - Meta.js.map += buffer.toString(); + mapString += buffer.toString(); }); minifier.on('message', function(message) { @@ -158,7 +165,11 @@ module.exports = function(Meta) { case 'error': winston.error('[meta/js] Could not compile client-side scripts! ' + message.payload.message); minifier.kill(); - process.exit(); + if (typeof callback === 'function') { + callback(err); + } else { + process.exit(0); + } break; } }); @@ -185,7 +196,6 @@ module.exports = function(Meta) { var jsPaths = scripts.map(function (jsPath) { jsPath = path.normalize(jsPath); - // if (jsPath.substring(0, 7) === 'plugins') { var matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { if (jsPath.match(mappedPath)) { return mappedPath; @@ -203,9 +213,6 @@ module.exports = function(Meta) { winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?'); return null; } - // } else { - // return path.join(__dirname, '../..', jsPath); - // } }); Meta.js.scripts.plugin = jsPaths.filter(Boolean); diff --git a/src/plugins.js b/src/plugins.js index b2578ed51e..427816c7bc 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -129,15 +129,18 @@ var fs = require('fs'), } else { var router = express.Router(); router.hotswapId = 'plugins'; + router.render = function() { + app.render.apply(app, arguments); + }; // Deprecated as of v0.5.0, remove this hook call for NodeBB v0.6.0-1 Plugins.fireHook('action:app.load', router, middleware, controllers); Plugins.fireHook('static:app.load', router, middleware, controllers, function() { hotswap.replace('plugins', router); + winston.info('[plugins] All plugins reloaded and rerouted'); + callback(); }); - - callback(); } }; @@ -527,7 +530,6 @@ var fs = require('fs'), // Reload meta data Plugins.reload(function() { - if(!active) { Plugins.fireHook('action:plugin.activate', id); } diff --git a/src/routes/plugins.js b/src/routes/plugins.js index fd195ca3f2..17efdb8c53 100644 --- a/src/routes/plugins.js +++ b/src/routes/plugins.js @@ -8,8 +8,7 @@ var _ = require('underscore'), async = require('async'), winston = require('winston'), - plugins = require('../plugins'), - pluginRoutes = []; + plugins = require('../plugins'); module.exports = function(app, middleware, controllers) { diff --git a/src/webserver.js b/src/webserver.js index f2595ddabc..5c6f571372 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -110,6 +110,7 @@ if(nconf.get('ssl')) { emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() { winston.info('NodeBB Ready'); emitter.emit('nodebb:ready'); + emitter.removeAllListeners('templates:compiled').removeAllListeners('meta:js.compiled').removeAllListeners('meta:css.compiled'); }); emitter.on('templates:compiled', function() { From ab77e6767ec799787faf79d806f4a66a9d68ef2f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 10:46:48 -0400 Subject: [PATCH 051/372] closed #2011 --- public/src/forum/admin/index.js | 41 +++++++++++++++++++++++++++------ src/meta.js | 13 +++++++---- src/socket.io/admin.js | 4 ++++ 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index c10706e257..58eb0d8eef 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -37,18 +37,45 @@ define('forum/admin/index', function() { }); $('.restart').on('click', function() { + bootbox.confirm('Are you sure you wish to restart NodeBB?', function(confirm) { + if (confirm) { + app.alert({ + timeout: 5000, + title: 'Restarting... ', + message: 'NodeBB is restarting.', + type: 'info' + }); + + $(window).one('action:reconnected', function() { + app.alertSuccess('NodeBB has successfully restarted.'); + }); + + socket.emit('admin.restart'); + } + }); + }); + + $('.reload').on('click', function() { app.alert({ - timeout: 5000, - title: 'Restarting...', + alert_id: 'instance_reload', + title: 'Reloading... ', message: 'NodeBB is restarting.', - type: 'info' + type: 'info', + timeout: 5000 }); - $(window).one('action:reconnected', function() { - app.alertSuccess('NodeBB has successfully restarted.'); + socket.emit('admin.reload', function(err) { + if (!err) { + app.alertSuccess('NodeBB has successfully reloaded.'); + } else { + app.alert({ + alert_id: 'instance_reload', + title: '[[global:alert.error]]', + message: err.message, + type: 'danger' + }); + } }); - - socket.emit('admin.restart'); }); }; diff --git a/src/meta.js b/src/meta.js index fb4e5cac52..9a616cc957 100644 --- a/src/meta.js +++ b/src/meta.js @@ -3,7 +3,8 @@ var async = require('async'), winston = require('winston'), user = require('./user'), - groups = require('./groups'); + groups = require('./groups'), + plugins = require('./plugins'); (function (Meta) { @@ -28,9 +29,13 @@ var async = require('async'), }); }; - Meta.reload = function(step) { - // 1. Reload plugins and associated routes - // 2. Minify scripts and css, update cache buster + Meta.reload = function(callback) { + plugins.reload(function() { + async.parallel([ + async.apply(Meta.js.minify, false), + async.apply(Meta.css.minify) + ], callback); + }); }; Meta.restart = function() { diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index bc8140a2a1..f51b0dd2a7 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -38,6 +38,10 @@ SocketAdmin.before = function(socket, method, next) { }); }; +SocketAdmin.reload = function(socket, data, callback) { + meta.reload(callback); +}; + SocketAdmin.restart = function(socket, data, callback) { meta.restart(); }; From 2bfa7d5b97d29211871f9e1545b4ac5044215198 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 Aug 2014 11:56:48 -0400 Subject: [PATCH 052/372] closed #2012 --- app.js | 10 +++++++++- loader.js | 29 ++++++++++++++++++++--------- src/webserver.js | 34 +++++++++++++++++++++------------- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 26fe077f87..bd9cb02e0a 100644 --- a/app.js +++ b/app.js @@ -141,7 +141,13 @@ function start() { nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path')); plugins.ready(function() { - webserver.init(); + webserver.init(function() { + // If this callback is called, this means that loader.js is used + process.on('SIGCONT', webserver.listen); + process.send({ + action: 'ready' + }); + }); }); process.on('SIGTERM', shutdown); @@ -313,6 +319,8 @@ function shutdown(code) { winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.'); require('./src/database').close(); winston.info('[app] Database connection closed.'); + require('./src/webserver').server.close(); + winston.info('[app] Web server closed to connections.'); winston.info('[app] Shutdown complete.'); process.exit(code || 0); diff --git a/loader.js b/loader.js index 6448615348..a3ce275704 100644 --- a/loader.js +++ b/loader.js @@ -5,8 +5,7 @@ var nconf = require('nconf'), pidFilePath = __dirname + '/pidfile', output = fs.openSync(__dirname + '/logs/output.log', 'a'), start = function() { - var fork = require('child_process').fork, - nbb_start = function() { + var nbb_start = function(callback) { if (timesStarted > 3) { console.log('\n[loader] Experienced three start attempts in 10 seconds, most likely an error on startup. Halting.'); return nbb_stop(); @@ -18,14 +17,24 @@ var nconf = require('nconf'), } startTimer = setTimeout(resetTimer, 1000*10); - nbb = fork('./app', process.argv.slice(2), { + if (nbb) { + nbbOld = nbb; + } + + nbb = require('child_process').fork('./app', process.argv.slice(2), { env: process.env }); nbb.on('message', function(message) { if (message && typeof message === 'object' && message.action) { - if (message.action === 'restart') { - nbb_restart(); + switch (message.action) { + case 'ready': + if (!callback) return nbb.kill('SIGCONT'); + callback(); + break; + case 'restart': + nbb_restart(); + break; } } }); @@ -52,10 +61,12 @@ var nconf = require('nconf'), } }, nbb_restart = function() { - nbb.removeAllListeners('exit').on('exit', function() { - nbb_start(); + nbb_start(function() { + nbbOld.removeAllListeners('exit').on('exit', function() { + nbb.kill('SIGCONT'); + }); + nbbOld.kill(); }); - nbb.kill(); }, resetTimer = function() { clearTimeout(startTimer); @@ -70,7 +81,7 @@ var nconf = require('nconf'), nbb_start(); }, - nbb; + nbb, nbbOld; nconf.argv(); diff --git a/src/webserver.js b/src/webserver.js index 5c6f571372..3278713dd1 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -97,7 +97,7 @@ if(nconf.get('ssl')) { } module.exports.server = server; - module.exports.init = function () { + module.exports.init = function(callback) { server.on("error", function(err){ if (err.code === 'EADDRINUSE') { winston.error('NodeBB address in use, exiting...'); @@ -114,18 +114,26 @@ if(nconf.get('ssl')) { }); emitter.on('templates:compiled', function() { - var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port; - winston.info('NodeBB attempting to listen on: ' + bind_address); - - server.listen(port, nconf.get('bind_address'), function(){ - winston.info('NodeBB is now listening on: ' + bind_address); - if (process.send) { - process.send({ - action: 'ready', - bind_address: bind_address - }); - } - }); + if (process.send) { + callback(); + } else { + module.exports.listen(); + } + }); + }; + + module.exports.listen = function() { + var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port; + winston.info('NodeBB attempting to listen on: ' + bind_address); + + server.listen(port, nconf.get('bind_address'), function(){ + winston.info('NodeBB is now listening on: ' + bind_address); + if (process.send) { + process.send({ + action: 'listening', + bind_address: bind_address + }); + } }); }; From c57b0a2199b9e6a299538ce2516696244b7c3160 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 25 Aug 2014 12:36:27 -0400 Subject: [PATCH 053/372] closes #2013 --- public/src/modules/alerts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 9cf301f5af..83e4daa833 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -63,7 +63,9 @@ define('alerts', function() { alert.attr('class', 'alert alert-dismissable alert-' + params.type); clearTimeout(parseInt(alert.attr('timeoutId'), 10)); - startTimeout(alert, params.timeout); + if (params.timeout) { + startTimeout(alert, params.timeout); + } alert.children().fadeOut(100); translator.translate(alert.html(), function(translatedHTML) { From 5f96823f663a41d3adc2d2025bc6b365b7922925 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2014 11:09:54 -0400 Subject: [PATCH 054/372] allowed the "forum updated" message to show up for reloads as well --- public/src/app.js | 39 +++++++++++++++++++-------------- public/src/forum/admin/index.js | 29 +++++++++++++++++------- src/meta.js | 9 +++++--- src/socket.io/meta.js | 6 ++++- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 64e5e3e15c..b84a528764 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -54,23 +54,6 @@ var socket, socket.emit('meta.reconnected'); - socket.removeAllListeners('event:nodebb.ready'); - socket.on('event:nodebb.ready', function(cacheBuster) { - if (app.cacheBuster !== cacheBuster) { - app.cacheBuster = cacheBuster; - - app.alert({ - alert_id: 'forum_updated', - title: '[[global:updated.title]]', - message: '[[global:updated.message]]', - clickfn: function() { - window.location.reload(); - }, - type: 'warning' - }); - } - }); - $(window).trigger('action:reconnected'); setTimeout(function() { @@ -542,6 +525,28 @@ var socket, }); }); }); + + socket.removeAllListeners('event:nodebb.ready'); + socket.on('event:nodebb.ready', function(cacheBusters) { + if ( + !app.cacheBusters || + app.cacheBusters.general !== cacheBusters.general || + app.cacheBusters.css !== cacheBusters.css || + app.cacheBusters.js !== cacheBusters.js + ) { + app.cacheBusters = cacheBusters; + + app.alert({ + alert_id: 'forum_updated', + title: '[[global:updated.title]]', + message: '[[global:updated.message]]', + clickfn: function() { + window.location.reload(); + }, + type: 'warning' + }); + } + }); }); }; diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index 58eb0d8eef..031cfb0049 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -40,14 +40,21 @@ define('forum/admin/index', function() { bootbox.confirm('Are you sure you wish to restart NodeBB?', function(confirm) { if (confirm) { app.alert({ - timeout: 5000, + alert_id: 'instance_restart', + type: 'info', title: 'Restarting... ', message: 'NodeBB is restarting.', - type: 'info' + timeout: 5000 }); $(window).one('action:reconnected', function() { - app.alertSuccess('NodeBB has successfully restarted.'); + app.alert({ + alert_id: 'instance_restart', + type: 'success', + title: ' Success', + message: 'NodeBB has successfully restarted.', + timeout: 5000 + }); }); socket.emit('admin.restart'); @@ -58,21 +65,27 @@ define('forum/admin/index', function() { $('.reload').on('click', function() { app.alert({ alert_id: 'instance_reload', - title: 'Reloading... ', - message: 'NodeBB is restarting.', type: 'info', + title: 'Reloading... ', + message: 'NodeBB is reloading.', timeout: 5000 }); socket.emit('admin.reload', function(err) { if (!err) { - app.alertSuccess('NodeBB has successfully reloaded.'); + app.alert({ + alert_id: 'instance_reload', + type: 'success', + title: ' Success', + message: 'NodeBB has successfully reloaded.', + timeout: 5000 + }); } else { app.alert({ alert_id: 'instance_reload', + type: 'danger', title: '[[global:alert.error]]', - message: err.message, - type: 'danger' + message: err.message }); } }); diff --git a/src/meta.js b/src/meta.js index 9a616cc957..949f81d88f 100644 --- a/src/meta.js +++ b/src/meta.js @@ -4,8 +4,8 @@ var async = require('async'), winston = require('winston'), user = require('./user'), groups = require('./groups'), - plugins = require('./plugins'); - + plugins = require('./plugins'), + emitter = require('./emitter'); (function (Meta) { Meta.restartRequired = false; @@ -34,7 +34,10 @@ var async = require('async'), async.parallel([ async.apply(Meta.js.minify, false), async.apply(Meta.css.minify) - ], callback); + ], function() { + emitter.emit('nodebb:ready'); + callback.apply(null, arguments); + }); }); }; diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 005f32fdfc..107e645569 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -35,7 +35,11 @@ SocketMeta.reconnected = function(socket, data, callback) { }; emitter.on('nodebb:ready', function() { - websockets.server.sockets.emit('event:nodebb.ready', meta.config['cache-buster']); + websockets.server.sockets.emit('event:nodebb.ready', { + general: meta.config['cache-buster'], + css: meta.css.hash, + js: meta.js.hash + }); }); SocketMeta.buildTitle = function(socket, text, callback) { From eef200be10451083965b07032a9ab9d415e1b927 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 26 Aug 2014 13:47:48 -0400 Subject: [PATCH 055/372] closes #1284 --- public/language/en_GB/user.json | 2 ++ public/src/forum/account/edit.js | 21 ++++++++++++++++++++- src/socket.io/user.js | 6 ++++++ src/topics.js | 13 +++---------- src/topics/posts.js | 4 ++-- src/user/delete.js | 6 +++--- 6 files changed, 36 insertions(+), 16 deletions(-) diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 29c6dc983b..a113ac03d8 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -5,6 +5,8 @@ "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Full Name", "website": "Website", diff --git a/public/src/forum/account/edit.js b/public/src/forum/account/edit.js index 0a9237c7af..d93ca01bbd 100644 --- a/public/src/forum/account/edit.js +++ b/public/src/forum/account/edit.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals define, ajaxify, socket, app, config, utils, translator */ +/* globals define, ajaxify, socket, app, config, utils, translator, bootbox */ define('forum/account/edit', ['forum/account/header', 'uploader'], function(header, uploader) { var AccountEdit = {}, @@ -26,6 +26,7 @@ define('forum/account/edit', ['forum/account/header', 'uploader'], function(head currentEmail = $('#inputEmail').val(); handleImageChange(); + handleAccountDelete(); handleImageUpload(); handleEmailConfirm(); handlePasswordChange(); @@ -125,6 +126,24 @@ define('forum/account/edit', ['forum/account/header', 'uploader'], function(head }); } + function handleAccountDelete() { + $('#deleteAccountBtn').on('click', function() { + translator.translate('[[user:delete_account_confirm]]', function(translated) { + bootbox.confirm(translated, function(confirm) { + if (!confirm) { + return; + } + socket.emit('user.deleteAccount', {}, function(err) { + if (!err) { + app.logout(); + } + }); + }); + }); + return false; + }); + } + function handleImageUpload() { $('#upload-picture-modal').on('hide', function() { $('#userPhotoInput').val(''); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 42adbb5981..d1e743d901 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -18,6 +18,12 @@ SocketUser.exists = function(socket, data, callback) { } }; +SocketUser.deleteAccount = function(socket, data, callback) { + if (socket.uid) { + user.deleteAccount(socket.uid, callback); + } +}; + SocketUser.count = function(socket, data, callback) { user.count(callback); }; diff --git a/src/topics.js b/src/topics.js index 596aab1929..7fcc065058 100644 --- a/src/topics.js +++ b/src/topics.js @@ -3,6 +3,7 @@ var async = require('async'), validator = require('validator'), + _ = require('underscore'), db = require('./database'), posts = require('./posts'), utils = require('../public/src/utils'), @@ -205,20 +206,12 @@ var async = require('async'), Topics.getTopicsTagsObjects(tids, next); } }, function(err, results) { - function arrayToObject(array, field) { - var obj = {}; - for (var i=0; i Date: Tue, 26 Aug 2014 14:48:05 -0400 Subject: [PATCH 056/372] added recompilation of templates to NodeBB Reloading - #2010 --- public/src/ajaxify.js | 517 ++++++++++++++++---------------- public/src/modules/templates.js | 15 + src/meta.js | 16 +- src/meta/templates.js | 89 ++++++ src/middleware/index.js | 86 +----- 5 files changed, 377 insertions(+), 346 deletions(-) create mode 100644 public/src/modules/templates.js create mode 100644 src/meta/templates.js diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 06e5731983..cea7b376b4 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -2,339 +2,340 @@ var ajaxify = ajaxify || {}; -(function () { - /*global app, templates, utils, socket, translator, config, RELATIVE_PATH*/ - - var location = document.location || window.location, - rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''), - templatesConfig = null, - availableTemplates = null, - apiXHR = null, - - PRELOADER_RATE_LIMIT = 10000; - - window.onpopstate = function (event) { - if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) { - ajaxify.go(event.state.url, function() { - $(window).trigger('action:popstate', {url: event.state.url}); - }, true); - } - }; - - ajaxify.currentPage = null; - ajaxify.initialLoad = false; - ajaxify.preloader = {}; - - function onAjaxError(err, url) { - var data = err.data, textStatus = err.textStatus; - - $('#content, #footer').removeClass('ajaxifying'); - - if (data) { - if (data.status === 404) { - return ajaxify.go('404'); - } else if (data.status === 403) { - app.alertError('[[global:please_log_in]]'); - app.previousUrl = url; - return ajaxify.go('login'); - } else if (data.status === 302) { - return ajaxify.go(data.responseJSON.slice(1)); +$(document).ready(function() { + require(['templates'], function (templatesModule) { + /*global app, templates, utils, socket, translator, config, RELATIVE_PATH*/ + + var location = document.location || window.location, + rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''), + apiXHR = null, + + PRELOADER_RATE_LIMIT = 10000; + + window.onpopstate = function (event) { + if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) { + ajaxify.go(event.state.url, function() { + $(window).trigger('action:popstate', {url: event.state.url}); + }, true); + } + }; + + ajaxify.currentPage = null; + ajaxify.initialLoad = false; + ajaxify.preloader = {}; + + function onAjaxError(err, url) { + var data = err.data, textStatus = err.textStatus; + + $('#content, #footer').removeClass('ajaxifying'); + + if (data) { + if (data.status === 404) { + return ajaxify.go('404'); + } else if (data.status === 403) { + app.alertError('[[global:please_log_in]]'); + app.previousUrl = url; + return ajaxify.go('login'); + } else if (data.status === 302) { + return ajaxify.go(data.responseJSON.slice(1)); + } + } else if (textStatus !== "abort") { + app.alertError(data.responseJSON.error); } - } else if (textStatus !== "abort") { - app.alertError(data.responseJSON.error); } - } - ajaxify.go = function (url, callback, quiet) { - // "quiet": If set to true, will not call pushState - app.enterRoom('global'); + ajaxify.go = function (url, callback, quiet) { + // "quiet": If set to true, will not call pushState + app.enterRoom('global'); - $(window).off('scroll'); + $(window).off('scroll'); - $(window).trigger('action:ajaxify.start', {url: url}); + $(window).trigger('action:ajaxify.start', {url: url}); - if ($('#content').hasClass('ajaxifying') && apiXHR) { - apiXHR.abort(); - } + if ($('#content').hasClass('ajaxifying') && apiXHR) { + apiXHR.abort(); + } - // Remove trailing slash - url = url.replace(/\/$/, ""); + // Remove trailing slash + url = url.replace(/\/$/, ""); - url = ajaxify.removeRelativePath(url); + url = ajaxify.removeRelativePath(url); - var tpl_url = ajaxify.getTemplateMapping(url); + var tpl_url = ajaxify.getTemplateMapping(url); - var hash = ''; - if(ajaxify.initialLoad) { - hash = window.location.hash ? window.location.hash : ''; - } + var hash = ''; + if(ajaxify.initialLoad) { + hash = window.location.hash ? window.location.hash : ''; + } - if (ajaxify.isTemplateAvailable(tpl_url) && !!!templatesConfig.force_refresh[tpl_url]) { - ajaxify.currentPage = url; + if (ajaxify.isTemplateAvailable(tpl_url) && !!!templatesModule.config.force_refresh[tpl_url]) { + ajaxify.currentPage = url; - if (window.history && window.history.pushState) { - window.history[!quiet ? 'pushState' : 'replaceState']({ - url: url + hash - }, url, RELATIVE_PATH + '/' + url + hash); - } + if (window.history && window.history.pushState) { + window.history[!quiet ? 'pushState' : 'replaceState']({ + url: url + hash + }, url, RELATIVE_PATH + '/' + url + hash); + } - translator.load(config.defaultLang, tpl_url); + translator.load(config.defaultLang, tpl_url); - $('#footer, #content').removeClass('hide').addClass('ajaxifying'); - var animationDuration = parseFloat($('#content').css('transition-duration')) || 0.2, - startTime = (new Date()).getTime(); + $('#footer, #content').removeClass('hide').addClass('ajaxifying'); + var animationDuration = parseFloat($('#content').css('transition-duration')) || 0.2, + startTime = (new Date()).getTime(); - ajaxify.variables.flush(); - ajaxify.loadData(url, function(err, data) { - if (err) { - return onAjaxError(err, url); - } + ajaxify.variables.flush(); + ajaxify.loadData(url, function(err, data) { + if (err) { + return onAjaxError(err, url); + } - $(window).trigger('action:ajaxify.loadingTemplates', {}); + $(window).trigger('action:ajaxify.loadingTemplates', {}); - templates.parse(tpl_url, data, function(template) { - translator.translate(template, function(translatedTemplate) { - setTimeout(function() { - $('#content').html(translatedTemplate); + templates.parse(tpl_url, data, function(template) { + translator.translate(template, function(translatedTemplate) { + setTimeout(function() { + $('#content').html(translatedTemplate); - ajaxify.variables.parse(); + ajaxify.variables.parse(); - ajaxify.widgets.render(tpl_url, url, function() { - $(window).trigger('action:ajaxify.end', {url: url}); - }); + ajaxify.widgets.render(tpl_url, url, function() { + $(window).trigger('action:ajaxify.end', {url: url}); + }); - $(window).trigger('action:ajaxify.contentLoaded', {url: url}); + $(window).trigger('action:ajaxify.contentLoaded', {url: url}); - ajaxify.loadScript(tpl_url); + ajaxify.loadScript(tpl_url); - if (typeof callback === 'function') { - callback(); - } + if (typeof callback === 'function') { + callback(); + } - app.processPage(); + app.processPage(); - $('#content, #footer').removeClass('ajaxifying'); - ajaxify.initialLoad = false; + $('#content, #footer').removeClass('ajaxifying'); + ajaxify.initialLoad = false; - app.refreshTitle(url); - }, animationDuration * 1000 - ((new Date()).getTime() - startTime)) + app.refreshTitle(url); + }, animationDuration * 1000 - ((new Date()).getTime() - startTime)) + }); }); }); - }); - return true; - } + return true; + } - return false; - }; + return false; + }; - ajaxify.removeRelativePath = function(url) { - if (url.indexOf(RELATIVE_PATH.slice(1)) === 0) { - url = url.slice(RELATIVE_PATH.length); - } - return url; - }; + ajaxify.removeRelativePath = function(url) { + if (url.indexOf(RELATIVE_PATH.slice(1)) === 0) { + url = url.slice(RELATIVE_PATH.length); + } + return url; + }; - ajaxify.refresh = function() { - ajaxify.go(ajaxify.currentPage); - }; + ajaxify.refresh = function() { + ajaxify.go(ajaxify.currentPage); + }; - ajaxify.loadScript = function(tpl_url, callback) { - require(['forum/' + tpl_url], function(script) { - if (script && script.init) { - script.init(); - } + ajaxify.loadScript = function(tpl_url, callback) { + require(['forum/' + tpl_url], function(script) { + if (script && script.init) { + script.init(); + } - if (callback) { - callback(); - } - }); - }; + if (callback) { + callback(); + } + }); + }; - ajaxify.isTemplateAvailable = function(tpl) { - return $.inArray(tpl + '.tpl', availableTemplates) !== -1; - }; + ajaxify.isTemplateAvailable = function(tpl) { + return $.inArray(tpl + '.tpl', templatesModule.available) !== -1; + }; - ajaxify.getTemplateMapping = function(url) { - var tpl_url = ajaxify.getCustomTemplateMapping(url.split('?')[0]); + ajaxify.getTemplateMapping = function(url) { + var tpl_url = ajaxify.getCustomTemplateMapping(url.split('?')[0]); - if (tpl_url === false && !templates[url]) { - tpl_url = url.split('/'); + if (tpl_url === false && !templates[url]) { + tpl_url = url.split('/'); - while(tpl_url.length) { - if (ajaxify.isTemplateAvailable(tpl_url.join('/'))) { - tpl_url = tpl_url.join('/'); - break; + while(tpl_url.length) { + if (ajaxify.isTemplateAvailable(tpl_url.join('/'))) { + tpl_url = tpl_url.join('/'); + break; + } + tpl_url.pop(); } - tpl_url.pop(); - } - if (!tpl_url.length) { - tpl_url = url.split('/')[0].split('?')[0]; + if (!tpl_url.length) { + tpl_url = url.split('/')[0].split('?')[0]; + } + } else if (templates[url]) { + tpl_url = url; } - } else if (templates[url]) { - tpl_url = url; - } - return tpl_url; - }; + return tpl_url; + }; - ajaxify.getCustomTemplateMapping = function(tpl) { - if (templatesConfig && templatesConfig.custom_mapping && tpl !== undefined) { - for (var pattern in templatesConfig.custom_mapping) { - if (tpl.match(pattern)) { - return (templatesConfig.custom_mapping[pattern]); + ajaxify.getCustomTemplateMapping = function(tpl) { + if (templatesModule.config && templatesModule.config.custom_mapping && tpl !== undefined) { + for (var pattern in templatesModule.config.custom_mapping) { + if (tpl.match(pattern)) { + return (templatesModule.config.custom_mapping[pattern]); + } } } - } - return false; - }; + return false; + }; - ajaxify.loadData = function(url, callback) { - url = ajaxify.removeRelativePath(url); + ajaxify.loadData = function(url, callback) { + url = ajaxify.removeRelativePath(url); - $(window).trigger('action:ajaxify.loadingData', {url: url}); + $(window).trigger('action:ajaxify.loadingData', {url: url}); - if (ajaxify.preloader && ajaxify.preloader[url] && !ajaxify.preloader[url].loading) { - callback(null, ajaxify.preloader[url].data); - ajaxify.preloader = {}; - return; - } + if (ajaxify.preloader && ajaxify.preloader[url] && !ajaxify.preloader[url].loading) { + callback(null, ajaxify.preloader[url].data); + ajaxify.preloader = {}; + return; + } - var location = document.location || window.location, - tpl_url = ajaxify.getCustomTemplateMapping(url.split('?')[0]); + var location = document.location || window.location, + tpl_url = ajaxify.getCustomTemplateMapping(url.split('?')[0]); - if (!tpl_url) { - tpl_url = ajaxify.getTemplateMapping(url); - } + if (!tpl_url) { + tpl_url = ajaxify.getTemplateMapping(url); + } - apiXHR = $.ajax({ - url: RELATIVE_PATH + '/api/' + url, - cache: false, - success: function(data) { - if (!data) { - ajaxify.go('404'); - return; - } + apiXHR = $.ajax({ + url: RELATIVE_PATH + '/api/' + url, + cache: false, + success: function(data) { + if (!data) { + ajaxify.go('404'); + return; + } - data.relative_path = RELATIVE_PATH; + data.relative_path = RELATIVE_PATH; - if (callback) { - callback(null, data); - } - }, - error: function(data, textStatus) { - callback({ - data: data, - textStatus: textStatus - }); - } - }); - }; - - ajaxify.loadTemplate = function(template, callback) { - if (templates.cache[template]) { - callback(templates.cache[template]); - } else { - $.ajax({ - url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''), - type: 'GET', - success: function(data) { - callback(data.toString()); + if (callback) { + callback(null, data); + } }, - error: function(error) { - throw new Error("Unable to load template: " + template + " (" + error.statusText + ")"); + error: function(data, textStatus) { + callback({ + data: data, + textStatus: textStatus + }); } }); - } - }; - - $('document').ready(function () { - if (!window.history || !window.history.pushState) { - return; // no ajaxification for old browsers - } - - function hrefEmpty(href) { - return href === undefined || href === '' || href === 'javascript:;' || href === window.location.href + "#" || href.slice(0, 1) === "#"; - } + }; + + ajaxify.loadTemplate = function(template, callback) { + if (templates.cache[template]) { + callback(templates.cache[template]); + } else { + $.ajax({ + url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''), + type: 'GET', + success: function(data) { + callback(data.toString()); + }, + error: function(error) { + throw new Error("Unable to load template: " + template + " (" + error.statusText + ")"); + } + }); + } + }; - // Enhancing all anchors to ajaxify... - $(document.body).on('click', 'a', function (e) { - if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false') { - return; + $('document').ready(function () { + if (!window.history || !window.history.pushState) { + return; // no ajaxification for old browsers } - if(!window.location.pathname.match(/\/(403|404)$/g)) { - app.previousUrl = window.location.href; + function hrefEmpty(href) { + return href === undefined || href === '' || href === 'javascript:;' || href === window.location.href + "#" || href.slice(0, 1) === "#"; } - if ((!e.ctrlKey && !e.shiftKey && !e.metaKey) && e.which === 1) { - if (this.host === window.location.host) { - // Internal link - var url = this.href.replace(rootUrl + '/', ''); + // Enhancing all anchors to ajaxify... + $(document.body).on('click', 'a', function (e) { + if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false') { + return; + } - if(window.location.pathname === this.pathname && this.hash) { - if (this.hash !== window.location.hash) { - window.location.hash = this.hash; - } + if(!window.location.pathname.match(/\/(403|404)$/g)) { + app.previousUrl = window.location.href; + } + + if ((!e.ctrlKey && !e.shiftKey && !e.metaKey) && e.which === 1) { + if (this.host === window.location.host) { + // Internal link + var url = this.href.replace(rootUrl + '/', ''); + + if(window.location.pathname === this.pathname && this.hash) { + if (this.hash !== window.location.hash) { + window.location.hash = this.hash; + } - ajaxify.loadScript(ajaxify.getTemplateMapping(url)); - e.preventDefault(); - } else { - if (ajaxify.go(url)) { + ajaxify.loadScript(ajaxify.getTemplateMapping(url)); + e.preventDefault(); + } else { + if (ajaxify.go(url)) { + e.preventDefault(); + } + } + } else if (window.location.pathname !== '/outgoing') { + // External Link + if (config.openOutgoingLinksInNewTab) { + window.open(this.href, '_blank'); + e.preventDefault(); + } else if (config.useOutgoingLinksPage) { + ajaxify.go('outgoing?url=' + encodeURIComponent(this.href)); e.preventDefault(); } - } - } else if (window.location.pathname !== '/outgoing') { - // External Link - if (config.openOutgoingLinksInNewTab) { - window.open(this.href, '_blank'); - e.preventDefault(); - } else if (config.useOutgoingLinksPage) { - ajaxify.go('outgoing?url=' + encodeURIComponent(this.href)); - e.preventDefault(); } } - } - }); + }); - $(document.body).on('mouseover', 'a', function (e) { - if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false') { - return; - } + $(document.body).on('mouseover', 'a', function (e) { + if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false') { + return; + } + + if (this.host === window.location.host) { + // Internal link + var url = this.href.replace(rootUrl + '/', ''), + currentTime = (new Date()).getTime(); - if (this.host === window.location.host) { - // Internal link - var url = this.href.replace(rootUrl + '/', ''), - currentTime = (new Date()).getTime(); - - if (!ajaxify.preloader[url] || (!ajaxify.preloader[url].loading && currentTime - ajaxify.preloader[url].lastFetched > PRELOADER_RATE_LIMIT)) { - ajaxify.preloader[url] = { - loading: true - }; - ajaxify.loadData(url, function(err, data) { - ajaxify.preloader[url] = err ? null : { - url: url, - data: data, - lastFetched: currentTime, - loading: false + if (!ajaxify.preloader[url] || (!ajaxify.preloader[url].loading && currentTime - ajaxify.preloader[url].lastFetched > PRELOADER_RATE_LIMIT)) { + ajaxify.preloader[url] = { + loading: true }; - }); + ajaxify.loadData(url, function(err, data) { + ajaxify.preloader[url] = err ? null : { + url: url, + data: data, + lastFetched: currentTime, + loading: false + }; + }); + } } - } - }); + }); - templates.registerLoader(ajaxify.loadTemplate); + templates.registerLoader(ajaxify.loadTemplate); - $.getJSON(RELATIVE_PATH + '/api/get_templates_listing', function (data) { - templatesConfig = data.templatesConfig; - availableTemplates = data.availableTemplates; + templatesModule.refresh(app.load); + // $.getJSON(RELATIVE_PATH + '/api/get_templates_listing', function (data) { + // templatesModule.config = data.templatesConfig; + // availableTemplates = data.availableTemplates; - app.load(); + // app.load(); + // }); }); - }); -}()); + }); +}); \ No newline at end of file diff --git a/public/src/modules/templates.js b/public/src/modules/templates.js new file mode 100644 index 0000000000..3227a8b039 --- /dev/null +++ b/public/src/modules/templates.js @@ -0,0 +1,15 @@ +define('templates', function() { + var Templates = {}; + + Templates.refresh = function(callback) { + $.getJSON(RELATIVE_PATH + '/api/get_templates_listing', function (data) { + Templates.config = data.templatesConfig; + Templates.available = data.availableTemplates; + + if (callback) callback(); + // app.load(); + }); + }; + + return Templates; +}); \ No newline at end of file diff --git a/src/meta.js b/src/meta.js index 949f81d88f..137a4b238f 100644 --- a/src/meta.js +++ b/src/meta.js @@ -17,7 +17,7 @@ var async = require('async'), require('./meta/css')(Meta); require('./meta/sounds')(Meta); require('./meta/settings')(Meta); - + Meta.templates = require('./meta/templates'); /* Assorted */ Meta.userOrGroupExists = function(slug, callback) { @@ -33,10 +33,16 @@ var async = require('async'), plugins.reload(function() { async.parallel([ async.apply(Meta.js.minify, false), - async.apply(Meta.css.minify) - ], function() { - emitter.emit('nodebb:ready'); - callback.apply(null, arguments); + async.apply(Meta.css.minify), + async.apply(Meta.templates.compile) + ], function(err) { + if (!err) { + emitter.emit('nodebb:ready'); + callback.apply(null, arguments); + } else { + console.log('failed!'); + emitter.emit('nodebb:reload.failed'); + } }); }); }; diff --git a/src/meta/templates.js b/src/meta/templates.js new file mode 100644 index 0000000000..15ebd33a47 --- /dev/null +++ b/src/meta/templates.js @@ -0,0 +1,89 @@ +var mkdirp = require('mkdirp'), + rimraf = require('rimraf'), + winston = require('winston'), + async = require('async'), + path = require('path'), + fs = require('fs'), + nconf = require('nconf'), + + emitter = require('../emitter'), + plugins = require('../plugins'), + utils = require('../../public/src/utils'), + + Templates = {}; + +Templates.compile = function(callback) { + var baseTemplatesPath = nconf.get('base_templates_path'), + viewsPath = nconf.get('views_dir'), + themeTemplatesPath = nconf.get('theme_templates_path'); + + plugins.getTemplates(function(err, pluginTemplates) { + winston.info('[meta/templates] Compiling templates'); + rimraf.sync(viewsPath); + mkdirp.sync(viewsPath); + + async.parallel({ + baseTpls: function(next) { + utils.walk(baseTemplatesPath, next); + }, + themeTpls: function(next) { + utils.walk(themeTemplatesPath, next); + } + }, function(err, data) { + var baseTpls = data.baseTpls, + themeTpls = data.themeTpls, + paths = {}; + + if (!baseTpls || !themeTpls) { + winston.warn('[meta/templates] Could not find base template files at: ' + baseTemplatesPath); + } + + baseTpls = !baseTpls ? [] : baseTpls.map(function(tpl) { return tpl.replace(baseTemplatesPath, ''); }); + themeTpls = !themeTpls ? [] : themeTpls.map(function(tpl) { return tpl.replace(themeTemplatesPath, ''); }); + + baseTpls.forEach(function(el, i) { + paths[baseTpls[i]] = path.join(baseTemplatesPath, baseTpls[i]); + }); + + themeTpls.forEach(function(el, i) { + paths[themeTpls[i]] = path.join(themeTemplatesPath, themeTpls[i]); + }); + + for (var tpl in pluginTemplates) { + if (pluginTemplates.hasOwnProperty(tpl)) { + paths[tpl] = pluginTemplates[tpl]; + } + } + + async.each(Object.keys(paths), function(relativePath, next) { + var file = fs.readFileSync(paths[relativePath]).toString(), + matches = null, + regex = /[ \t]*[ \t]*/; + + while(matches = file.match(regex)) { + var partial = "/" + matches[1]; + + if (paths[partial] && relativePath !== partial) { + file = file.replace(regex, fs.readFileSync(paths[partial]).toString()); + } else { + winston.warn('[themes] Partial not loaded: ' + matches[1]); + file = file.replace(regex, ""); + } + } + + mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/'))); + fs.writeFile(path.join(viewsPath, relativePath), file, next); + }, function(err) { + if (err) { + winston.error(err); + } else { + winston.info('[themes] Successfully compiled templates.'); + emitter.emit('templates:compiled'); + if (callback) callback(); + } + }); + }); + }); +}; + +module.exports = Templates; \ No newline at end of file diff --git a/src/middleware/index.js b/src/middleware/index.js index a90ea6d9a1..5ea711b46c 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -24,10 +24,7 @@ var utils = require('./../../public/src/utils'), session = require('express-session'), relativePath, - viewsPath, - themesPath, - baseTemplatesPath, - themeTemplatesPath; + themesPath; var middleware = {}; @@ -68,91 +65,17 @@ function routeCurrentTheme(app, themeId, themesData) { // Theme's templates path nconf.set('theme_templates_path', themeObj.templates ? path.join(themesPath, themeObj.id, themeObj.templates) : nconf.get('base_templates_path')); - themeTemplatesPath = nconf.get('theme_templates_path'); -} - -function compileTemplates(pluginTemplates) { - var mkdirp = require('mkdirp'), - rimraf = require('rimraf'); - - winston.info('[themes] Compiling templates'); - rimraf.sync(viewsPath); - mkdirp.sync(viewsPath); - - async.parallel({ - baseTpls: function(next) { - utils.walk(baseTemplatesPath, next); - }, - themeTpls: function(next) { - utils.walk(themeTemplatesPath, next); - } - }, function(err, data) { - var baseTpls = data.baseTpls, - themeTpls = data.themeTpls, - paths = {}; - - if (!baseTpls || !themeTpls) { - winston.warn('[themes] Could not find base template files at: ' + baseTemplatesPath); - } - - baseTpls = !baseTpls ? [] : baseTpls.map(function(tpl) { return tpl.replace(baseTemplatesPath, ''); }); - themeTpls = !themeTpls ? [] : themeTpls.map(function(tpl) { return tpl.replace(themeTemplatesPath, ''); }); - - baseTpls.forEach(function(el, i) { - paths[baseTpls[i]] = path.join(baseTemplatesPath, baseTpls[i]); - }); - - themeTpls.forEach(function(el, i) { - paths[themeTpls[i]] = path.join(themeTemplatesPath, themeTpls[i]); - }); - - for (var tpl in pluginTemplates) { - if (pluginTemplates.hasOwnProperty(tpl)) { - paths[tpl] = pluginTemplates[tpl]; - } - } - - async.each(Object.keys(paths), function(relativePath, next) { - var file = fs.readFileSync(paths[relativePath]).toString(), - matches = null, - regex = /[ \t]*[ \t]*/; - - while(matches = file.match(regex)) { - var partial = "/" + matches[1]; - - if (paths[partial] && relativePath !== partial) { - file = file.replace(regex, fs.readFileSync(paths[partial]).toString()); - } else { - winston.warn('[themes] Partial not loaded: ' + matches[1]); - file = file.replace(regex, ""); - } - } - - mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/'))); - fs.writeFile(path.join(viewsPath, relativePath), file, next); - }, function(err) { - if (err) { - winston.error(err); - } else { - winston.info('[themes] Successfully compiled templates.'); - emitter.emit('templates:compiled'); - } - }); - }); } module.exports = function(app, data) { middleware = require('./middleware')(app); relativePath = nconf.get('relative_path'); - viewsPath = nconf.get('views_dir'); themesPath = nconf.get('themes_path'); - baseTemplatesPath = nconf.get('base_templates_path'); - app.engine('tpl', templates.__express); app.set('view engine', 'tpl'); - app.set('views', viewsPath); + app.set('views', nconf.get('views_dir')); app.set('json spaces', process.env.NODE_ENV === 'development' ? 4 : 0); app.use(flash()); @@ -205,10 +128,7 @@ module.exports = function(app, data) { routeCurrentTheme(app, data.currentThemeId, data.themesData); routeThemeScreenshots(app, data.themesData); - plugins.getTemplates(function(err, pluginTemplates) { - compileTemplates(pluginTemplates); - }); - + meta.templates.compile(); return middleware; }; From e497290dbcafdb16da6d062f2c6bdcb01eaf5100 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2014 14:51:22 -0400 Subject: [PATCH 057/372] removed commented-out code --- public/src/ajaxify.js | 7 ------- public/src/modules/templates.js | 1 - 2 files changed, 8 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index cea7b376b4..f1fe33c40d 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -327,14 +327,7 @@ $(document).ready(function() { }); templates.registerLoader(ajaxify.loadTemplate); - templatesModule.refresh(app.load); - // $.getJSON(RELATIVE_PATH + '/api/get_templates_listing', function (data) { - // templatesModule.config = data.templatesConfig; - // availableTemplates = data.availableTemplates; - - // app.load(); - // }); }); }); diff --git a/public/src/modules/templates.js b/public/src/modules/templates.js index 3227a8b039..2c25dbdb64 100644 --- a/public/src/modules/templates.js +++ b/public/src/modules/templates.js @@ -7,7 +7,6 @@ define('templates', function() { Templates.available = data.availableTemplates; if (callback) callback(); - // app.load(); }); }; From 2d9ca83ae024b11644c530a154196e0148e91c13 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 Aug 2014 15:31:27 -0400 Subject: [PATCH 058/372] better error messaging when js fails to compile when reloading --- public/language/en_GB/error.json | 4 +++- public/src/forum/admin/index.js | 2 +- src/meta.js | 6 ++---- src/meta/js.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 2b9a0aec46..75d43fce3f 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -75,5 +75,7 @@ "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index 031cfb0049..e45c661628 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -85,7 +85,7 @@ define('forum/admin/index', function() { alert_id: 'instance_reload', type: 'danger', title: '[[global:alert.error]]', - message: err.message + message: '[[error:reload-failed, ' + err.message + ']]' }); } }); diff --git a/src/meta.js b/src/meta.js index 137a4b238f..8ac432e4ad 100644 --- a/src/meta.js +++ b/src/meta.js @@ -38,11 +38,9 @@ var async = require('async'), ], function(err) { if (!err) { emitter.emit('nodebb:ready'); - callback.apply(null, arguments); - } else { - console.log('failed!'); - emitter.emit('nodebb:reload.failed'); } + + callback.apply(null, arguments); }); }); }; diff --git a/src/meta/js.js b/src/meta/js.js index 14e3112f19..e4d361c74f 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -166,7 +166,7 @@ module.exports = function(Meta) { winston.error('[meta/js] Could not compile client-side scripts! ' + message.payload.message); minifier.kill(); if (typeof callback === 'function') { - callback(err); + callback(new Error(message.payload.message)); } else { process.exit(0); } From 42f87a1db7cb86a7417a12392f4911d0e5de0c79 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 26 Aug 2014 15:55:17 -0400 Subject: [PATCH 059/372] closes #2017 --- src/user.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/user.js b/src/user.js index 0919406089..7abee910cb 100644 --- a/src/user.js +++ b/src/user.js @@ -69,9 +69,8 @@ var if (err) { return callback(err); } - plugins.fireHook('filter:user.removeFields', fieldsToRemove, function(err, fields) { - callback(err, modifyUserData(users, fields)); - }); + + modifyUserData(users, fieldsToRemove, callback); }); }; @@ -96,13 +95,11 @@ var return callback(err); } - plugins.fireHook('filter:user.removeFields', [], function(err, fields) { - callback(err, modifyUserData(users, fields)); - }); + modifyUserData(users, [], callback); }); }; - function modifyUserData(users, fieldsToRemove) { + function modifyUserData(users, fieldsToRemove, callback) { users.forEach(function(user) { if (!user) { return; @@ -132,7 +129,8 @@ var user[fieldsToRemove[i]] = undefined; } }); - return users; + + plugins.fireHook('filter:users.get', users, callback); } User.updateLastOnlineTime = function(uid, callback) { From 69ce425ae5c991babfeae4fe615ef46912035622 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 26 Aug 2014 18:45:03 -0400 Subject: [PATCH 060/372] closes #2021 --- public/src/modules/chat.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index cc4fbb1f42..73b94a0442 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -125,6 +125,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, } }); chatModal.css('zIndex', topZ + 1); + taskbar.updateActive(chatModal.attr('UUID')); }; module.getModal = function(touid) { From 8eecf59c4764a9612ed76e95b274c37cdd187417 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 26 Aug 2014 18:47:52 -0400 Subject: [PATCH 061/372] closes #2020 --- public/src/forum/chats.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index d84dbac203..cf35f8d5f8 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -170,6 +170,7 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { var recipientUid = Chats.getRecipientUid(); if (recipientUid) { socket.emit('modules.chats.markRead', recipientUid); + $('.expanded-chat input').focus(); } $('.chats-list li').removeClass('bg-primary'); $('.chats-list li[data-uid="' + recipientUid + '"]').addClass('bg-primary'); From fdf19f902559967f8181115a680ca4741214ccd9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 27 Aug 2014 01:27:17 -0400 Subject: [PATCH 062/372] closes #2022 --- public/src/modules/settings.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 29bc4a4c4e..78ac0ad044 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -448,17 +448,18 @@ define('settings', function () { helper.persistSettings(hash, Settings.cfg, notify, callback); }, load: function (hash, formEl, callback) { + callback = callback || function() {}; socket.emit('admin.settings.get', { hash: hash }, function (err, values) { - if (!err) { - $(formEl).deserialize(values); - if (typeof callback === 'function') { - callback(); - } - } else { + if (err) { console.log('[settings] Unable to load settings for hash: ', hash); + return callback(err); } + + $(formEl).deserialize(values); + + callback(null, values); }); }, save: function (hash, formEl, callback) { From 347d7de25b7849826c659d93e17f633288191745 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2014 14:04:31 -0400 Subject: [PATCH 063/372] an ajaxify.go 302 will pass callback and quiet arguments along as well now --- public/src/ajaxify.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index f1fe33c40d..f0a7f92d9b 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -24,7 +24,7 @@ $(document).ready(function() { ajaxify.initialLoad = false; ajaxify.preloader = {}; - function onAjaxError(err, url) { + function onAjaxError(err, url, callback, quiet) { var data = err.data, textStatus = err.textStatus; $('#content, #footer').removeClass('ajaxifying'); @@ -37,7 +37,7 @@ $(document).ready(function() { app.previousUrl = url; return ajaxify.go('login'); } else if (data.status === 302) { - return ajaxify.go(data.responseJSON.slice(1)); + return ajaxify.go(data.responseJSON.slice(1), callback, quiet); } } else if (textStatus !== "abort") { app.alertError(data.responseJSON.error); @@ -86,7 +86,7 @@ $(document).ready(function() { ajaxify.variables.flush(); ajaxify.loadData(url, function(err, data) { if (err) { - return onAjaxError(err, url); + return onAjaxError(err, url, callback, quiet); } $(window).trigger('action:ajaxify.loadingTemplates', {}); From d67cd489cddedb7badfbe8b1dbed362137d5aab7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 27 Aug 2014 14:09:15 -0400 Subject: [PATCH 064/372] crash fix --- src/user/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index b6e82e6d23..9d43845b9c 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -254,7 +254,7 @@ var async = require('async'), } notifications = notifications.filter(function(notification) { - return !!notification || notification[field] !== value.toString(); + return notification && notification[field] !== value.toString(); }).map(function(notification) { return notification.nid; }); From 1d3aafc08f102fa4d80d03619a70e41931acf544 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2014 14:42:10 -0400 Subject: [PATCH 065/372] adding sourceRoot to minified mapping --- minifier.js | 5 +++-- src/meta/js.js | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/minifier.js b/minifier.js index 0d4f2b7966..7860792f90 100644 --- a/minifier.js +++ b/minifier.js @@ -13,7 +13,7 @@ var uglifyjs = require('uglify-js'), }; /* Javascript */ -Minifier.js.minify = function (scripts, minify, callback) { +Minifier.js.minify = function (scripts, relativePath, minify, callback) { var options = {}; scripts = scripts.filter(function(file) { @@ -23,6 +23,7 @@ Minifier.js.minify = function (scripts, minify, callback) { if (!minify) { options.sourceMapURL = '/nodebb.min.js.map'; options.outSourceMap = 'nodebb.min.js.map'; + options.sourceRoot = relativePath; options.mangle = false; options.compress = false; options.prefix = 1; @@ -56,7 +57,7 @@ Minifier.js.minify = function (scripts, minify, callback) { process.on('message', function(payload) { switch(payload.action) { case 'js': - Minifier.js.minify(payload.scripts, payload.minify, function(data) { + Minifier.js.minify(payload.scripts, payload.relativePath, payload.minify, function(data) { process.stdout.write(data.js); process.send({ type: 'end', diff --git a/src/meta/js.js b/src/meta/js.js index e4d361c74f..e75902f79d 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -6,6 +6,7 @@ var winston = require('winston'), async = require('async'), _ = require('underscore'), os = require('os'), + nconf = require('nconf'), plugins = require('../plugins'), emitter = require('../emitter'), @@ -177,6 +178,7 @@ module.exports = function(Meta) { Meta.js.prepare(function() { minifier.send({ action: 'js', + relativePath: nconf.get('url') + '/', minify: minify, scripts: Meta.js.scripts.all }); From 347fc05376364e8738d63908321098873bd92c0a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 27 Aug 2014 15:03:36 -0400 Subject: [PATCH 066/372] getPidIndex socket call --- src/socket.io/posts.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 3315bf2f5a..5326fc09de 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -367,4 +367,8 @@ SocketPosts.getCategory = function(socket, pid, callback) { posts.getCidByPid(pid, callback); }; +SocketPosts.getPidIndex = function(socket, pid, callback) { + posts.getPidIndex(pid, socket.uid, callback); +}; + module.exports = SocketPosts; From cd10248226a043d9d259e89e0e830ba1b1832570 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 27 Aug 2014 15:25:02 -0400 Subject: [PATCH 067/372] basic topic searching via search bar working --- public/src/app.js | 15 ++++++---- public/src/forum/search.js | 12 ++++---- public/src/modules/search.js | 54 ++++++++++++++++++++++++++++++++++++ src/topics.js | 2 +- 4 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 public/src/modules/search.js diff --git a/public/src/app.js b/public/src/app.js index b84a528764..4a3e3b6ca2 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -445,11 +445,16 @@ var socket, return false; }); - $('#search-form').on('submit', function () { - var input = $(this).find('input'); - ajaxify.go('search/' + input.val().replace(/^[ ?#]*/, '')); - input.val(''); - return false; + $('#search-form').on('submit', function (e) { + e.preventDefault(); + var input = $(this).find('input'), + term = input.val(); + + require(['search'], function(search) { + search.query(term, function() { + input.val(''); + }); + }); }); } diff --git a/public/src/forum/search.js b/public/src/forum/search.js index 38a57eb414..861d8dd441 100644 --- a/public/src/forum/search.js +++ b/public/src/forum/search.js @@ -1,4 +1,4 @@ -define('forum/search', function() { +define('forum/search', ['search'], function(searchModule) { var Search = {}; Search.init = function() { @@ -21,11 +21,13 @@ define('forum/search', function() { $('#search-form input').val(searchQuery); - $('#mobile-search-form').off('submit').on('submit', function() { + $('#mobile-search-form').off('submit').on('submit', function(e) { + e.preventDefault(); var input = $(this).find('input'); - ajaxify.go('search/' + input.val(), null, 'search'); - input.val(''); - return false; + + searchModule.query(input.val(), function() { + input.val(''); + }); }); }; diff --git a/public/src/modules/search.js b/public/src/modules/search.js new file mode 100644 index 0000000000..a110bf4155 --- /dev/null +++ b/public/src/modules/search.js @@ -0,0 +1,54 @@ +define('search', ['navigator'], function(nav) { + "use strict"; + /* globals socket, ajaxify */ + + var Search = {}; + + Search.query = function(term, callback) { + // Detect if a tid was specified + var topicSearch = term.match(/in:topic-([\d]+)/); + + if (!topicSearch) { + term = term.replace(/^[ ?#]*/, ''); + ajaxify.go('search/' + term); + callback(); + } else { + var cleanedTerm = term.replace(topicSearch[0], ''), + tid = topicSearch[1]; + + Search.queryTopic(tid, cleanedTerm, callback); + } + }; + + Search.queryTopic = function(tid, term, callback) { + socket.emit('topics.search', { + tid: tid, + term: term + }, function(err, pids) { + var args = arguments; + + // Sort pids numerically & store + Search.results = pids.sort(function(a, b) { + return a-b; + }); + + if (!err && !ajaxify.currentPage.match(new RegExp('^topic/' + tid))) { + ajaxify.go('topic/' + tid, function() { + if (callback) callback.apply(null, args); + Search.highlightResult(0); + }); + } else { + if (callback) callback.apply(null, args); + Search.highlightResult(0); + } + }); + }; + + Search.highlightResult = function(index) { + socket.emit('posts.getPidIndex', Search.results[index], function(err, postIndex) { + nav.scrollToPost(postIndex-1, true); // why -1? Ask @barisusakli + }); + }; + + return Search; +}); \ No newline at end of file diff --git a/src/topics.js b/src/topics.js index b10709eb47..f5fe901c59 100644 --- a/src/topics.js +++ b/src/topics.js @@ -508,7 +508,7 @@ var async = require('async'), term: term }, callback); } else { - callback(undefined, []); + callback(new Error('no-plugins-available'), []); } }; From ba4b98de0e3c49db40b6685a5bb525f22f1526b5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 27 Aug 2014 16:06:49 -0400 Subject: [PATCH 068/372] email and username checks on profile update --- public/language/en_GB/error.json | 1 + src/user/profile.js | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 75d43fce3f..c9de71d2f4 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -23,6 +23,7 @@ "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", diff --git a/src/user/profile.js b/src/user/profile.js index d76ac2bf65..e8d42da6d5 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -29,6 +29,10 @@ module.exports = function(User) { return next(); } + if (!utils.isEmailValid(data.email)) { + return next(new Error('[[error:invalid-email]]')); + } + User.getUserField(uid, 'email', function(err, email) { if(email === data.email) { return next(); @@ -53,6 +57,14 @@ module.exports = function(User) { return next(); } + if (data.username.length < meta.config.minimumUsernameLength) { + return next(new Error('[[error:username-too-short]]')); + } + + if (data.username.length > meta.config.maximumUsernameLength) { + return next(new Error('[[error:username-too-long]]')); + } + if(!utils.isUserNameValid(data.username) || !userslug) { return next(new Error('[[error:invalid-username]]')); } From 94c765fa250a2ffdc23698edf0be8dc8979d1ea7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 27 Aug 2014 18:03:53 -0400 Subject: [PATCH 069/372] closes #1996 --- public/src/forum/admin/tags.js | 25 +++++++++++++---- public/src/forum/tags.js | 51 ++++++++++++++++++++++++++++++---- src/controllers/admin.js | 2 +- src/controllers/tags.js | 5 ++-- src/socket.io/topics.js | 49 ++++++++++++++++++++++++++++++++ src/topics/tags.js | 13 +++++---- 6 files changed, 123 insertions(+), 22 deletions(-) diff --git a/public/src/forum/admin/tags.js b/public/src/forum/admin/tags.js index e6468c7f21..965ae0db13 100644 --- a/public/src/forum/admin/tags.js +++ b/public/src/forum/admin/tags.js @@ -1,8 +1,9 @@ "use strict"; -/*global define, socket, app, admin*/ +/*global define, socket, app, admin, utils*/ -define('forum/admin/tags', [], function() { +define('forum/admin/tags', ['forum/infinitescroll'], function(infinitescroll) { var Tags = {}; + var timeoutId = 0; Tags.init = function() { handleColorPickers(); @@ -12,10 +13,22 @@ define('forum/admin/tags', [], function() { }); $('#tag-search').on('input propertychange', function() { - $('.tag-list').children().each(function() { - var $this = $(this); - $this.toggleClass('hide', $this.attr('data-tag').indexOf($('#tag-search').val()) === -1); - }); + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = 0; + } + timeoutId = setTimeout(function() { + socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, tags) { + if (err) { + return app.alertError(err.message); + } + infinitescroll.parseAndTranslate('admin/tags', 'tags', {tags: tags}, function(html) { + $('.tag-list').html(html); + utils.makeNumbersHumanReadable(html.find('.human-readable-number')); + timeoutId = 0; + }); + }); + }, 100); }); }; diff --git a/public/src/forum/tags.js b/public/src/forum/tags.js index c96a921472..bf4395f497 100644 --- a/public/src/forum/tags.js +++ b/public/src/forum/tags.js @@ -1,20 +1,59 @@ 'use strict'; -/* globals define, app */ +/* globals define, app, utils, socket */ -define('forum/tags', function() { +define('forum/tags', ['forum/infinitescroll'], function(infinitescroll) { var Tags = {}; + var timeoutId = 0; Tags.init = function() { app.enterRoom('tags'); $('#tag-search').on('input propertychange', function() { - $('.tag-list').children().each(function() { - var $this = $(this); - $this.toggleClass('hide', $this.find('a').attr('data-value').indexOf($('#tag-search').val()) === -1); - }) + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = 0; + } + timeoutId = setTimeout(function() { + socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, results) { + if (err) { + return app.alertError(err.message); + } + onTagsLoaded(results, true, function() { + timeoutId = 0; + }); + }); + }, 100); }); + + infinitescroll.init(Tags.loadMoreTags); }; + Tags.loadMoreTags = function(direction) { + if(direction < 0 || !$('.tag-list').length) { + return; + } + + infinitescroll.loadMore('topics.loadMoreTags', { + after: $('.tag-list').attr('data-nextstart') + }, function(data, done) { + if (data && data.tags && data.tags.length) { + onTagsLoaded(data.tags, false, done); + $('.tag-list').attr('data-nextstart', data.nextStart); + } else { + done(); + } + }); + }; + + function onTagsLoaded(tags, replace, callback) { + callback = callback || function() {}; + infinitescroll.parseAndTranslate('tags', 'tags', {tags: tags}, function(html) { + $('.tag-list')[replace ? 'html' : 'append'](html); + utils.makeNumbersHumanReadable(html.find('.human-readable-number')); + callback(); + }); + } + return Tags; }); diff --git a/src/controllers/admin.js b/src/controllers/admin.js index f853f16cf7..13b0bfde7b 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -132,7 +132,7 @@ function filterAndRenderCategories(req, res, next, active) { } adminController.tags.get = function(req, res, next) { - topics.getTags(0, -1, function(err, tags) { + topics.getTags(0, 99, function(err, tags) { if (err) { return next(err); } diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 258a28b6c9..6d3d18c1f0 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -47,14 +47,13 @@ tagsController.getTag = function(req, res, next) { }; tagsController.getTags = function(req, res, next) { - topics.getTags(0, -1, function(err, tags) { + topics.getTags(0, 99, function(err, tags) { if (err) { return next(err); } - res.render('tags', {tags: tags}); + res.render('tags', {tags: tags, nextStart: 100}); }); - }; module.exports = tagsController; diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 576c368f34..30d64b7b96 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -518,4 +518,53 @@ SocketTopics.searchTags = function(socket, data, callback) { topics.searchTags(data, callback); }; +SocketTopics.searchAndLoadTags = function(socket, data, callback) { + topics.searchTags(data, function(err, tags) { + if (err) { + return callback(err); + } + async.parallel({ + counts: function(next) { + db.sortedSetScores('tags:topic:count', tags, next); + }, + tagData: function(next) { + tags = tags.map(function(tag) { + return {value: tag}; + }); + + topics.getTagData(tags, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + results.tagData.forEach(function(tag, index) { + tag.score = results.counts[index]; + }); + results.tagData.sort(function(a, b) { + return parseInt(b.score, 10) - parseInt(a.score, 10); + }); + + callback(null, results.tagData); + }); + }); +}; + +SocketTopics.loadMoreTags = function(socket, data, callback) { + if(!data || !data.after) { + return callback(new Error('[[error:invalid-data]]')); + } + + var start = parseInt(data.after, 10), + end = start + 99; + + topics.getTags(start, end, function(err, tags) { + if (err) { + return callback(err); + } + + callback(null, {tags: tags, nextStart: end + 1}); + }); +}; + module.exports = SocketTopics; diff --git a/src/topics/tags.js b/src/topics/tags.js index ecca55eb2f..681d137fc9 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -89,11 +89,11 @@ module.exports = function(Topics) { return callback(err); } - addTagData(tags, callback); + Topics.getTagData(tags, callback); }); }; - function addTagData(tags, callback) { + Topics.getTagData = function(tags, callback) { var keys = tags.map(function(tag) { return 'tag:' + tag.value; }); @@ -109,7 +109,7 @@ module.exports = function(Topics) { }); callback(null, tags); }); - } + }; Topics.getTopicTags = function(tid, callback) { db.getSetMembers('topic:' + tid + ':tags', callback); @@ -139,7 +139,7 @@ module.exports = function(Topics) { async.parallel({ tagData: function(next) { - addTagData(tags, next); + Topics.getTagData(tags, next); }, counts: function(next) { db.sortedSetScores('tags:topic:count', uniqueTopicTags, next); @@ -214,12 +214,13 @@ module.exports = function(Topics) { return callback(null, []); } - db.getSortedSetRevRange('tags:topic:count', 0, -1, function(err, tags) { if (err) { return callback(null, []); } - + if (data.query === '') { + return callback(null, tags); + } data.query = data.query.toLowerCase(); var matches = []; for(var i=0; i Date: Fri, 29 Aug 2014 09:55:49 -0400 Subject: [PATCH 070/372] updated regex to match admin route with no trailing slash --- src/routes/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 2d2cd8730e..01b095a092 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -163,8 +163,7 @@ module.exports = function(app, middleware) { app.all(relativePath + '/api/?*', middleware.updateLastOnlineTime, middleware.prepareAPI); app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI); - app.all(relativePath + '/admin/*', middleware.admin.isAdmin); - app.get(relativePath + '/admin', middleware.admin.isAdmin); + app.all(relativePath + '/admin/?*', middleware.admin.isAdmin); adminRoutes(router, middleware, controllers); metaRoutes(router, middleware, controllers); @@ -202,9 +201,9 @@ module.exports = function(app, middleware) { plugins.reloadRoutes(); // plugins.ready(function() { // plugins.fireHook('static:app.load', pluginRouter, middleware, controllers, function() { - - + + // }); // }); }; From 5d22806ded9f3c6b5874fc2ee2206999522b4e6f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 29 Aug 2014 11:18:02 -0400 Subject: [PATCH 071/372] more topic searching work (working example, needs UX tie-in) --- public/src/app.js | 19 +++++++--- public/src/modules/search.js | 68 +++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 4a3e3b6ca2..f7965a518d 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -445,16 +445,25 @@ var socket, return false; }); - $('#search-form').on('submit', function (e) { - e.preventDefault(); - var input = $(this).find('input'), - term = input.val(); + require(['search'], function(search) { + $('#search-form').on('submit', function (e) { + e.preventDefault(); + var input = $(this).find('input'), + term = input.val(); + - require(['search'], function(search) { search.query(term, function() { input.val(''); }); }); + + $('.topic-search') + .on('click', '.prev', function() { + search.topicDOM.prev(); + }) + .on('click', '.next', function() { + search.topicDOM.next(); + }); }); } diff --git a/public/src/modules/search.js b/public/src/modules/search.js index a110bf4155..7de1f33395 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -2,7 +2,9 @@ define('search', ['navigator'], function(nav) { "use strict"; /* globals socket, ajaxify */ - var Search = {}; + var Search = { + current: {} + }; Search.query = function(term, callback) { // Detect if a tid was specified @@ -25,29 +27,61 @@ define('search', ['navigator'], function(nav) { tid: tid, term: term }, function(err, pids) { - var args = arguments; + callback(err); // Sort pids numerically & store - Search.results = pids.sort(function(a, b) { - return a-b; - }); + Search.current = { + results: pids.sort(function(a, b) { + return a-b; + }), + tid: tid, + term: term + }; + + Search.topicDOM.update(0); + }); + }; + + Search.checkPagePresence = function(tid, callback) { + if (!ajaxify.currentPage.match(new RegExp('^topic/' + tid))) { + ajaxify.go('topic/' + tid, callback); + } else { + callback(); + } + }; - if (!err && !ajaxify.currentPage.match(new RegExp('^topic/' + tid))) { - ajaxify.go('topic/' + tid, function() { - if (callback) callback.apply(null, args); - Search.highlightResult(0); - }); - } else { - if (callback) callback.apply(null, args); - Search.highlightResult(0); - } + Search.topicDOM = {}; + Search.topicDOM.start = function() { + var topicSearchEl = $('.topic-search'); + + topicSearchEl.find('.count').html('1 / ' + Search.current.results.length); + topicSearchEl.removeClass('hidden'); + Search.checkPagePresence(Search.current.tid, function() { + Search.highlightResult(0); }); }; - Search.highlightResult = function(index) { - socket.emit('posts.getPidIndex', Search.results[index], function(err, postIndex) { - nav.scrollToPost(postIndex-1, true); // why -1? Ask @barisusakli + Search.topicDOM.prev = function() { + Search.topicDOM.update((Search.current.index === 0) ? Search.current.results.length-1 : Search.current.index-1); + }; + + Search.topicDOM.next = function() { + Search.topicDOM.update((Search.current.index === Search.current.results.length-1) ? 0 : Search.current.index+1); + }; + + Search.topicDOM.update = function(index) { + var topicSearchEl = $('.topic-search'); + + Search.current.index = index; + + topicSearchEl.find('.count').html((index+1) + ' / ' + Search.current.results.length); + topicSearchEl.removeClass('hidden'); + Search.checkPagePresence(Search.current.tid, function() { + socket.emit('posts.getPidIndex', Search.current.results[index], function(err, postIndex) { + nav.scrollToPost(postIndex-1, true); // why -1? Ask @barisusakli + }); }); + }; return Search; From 063a4e56288f49da3ae6fc96c31f1519566537ac Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 29 Aug 2014 11:48:30 -0400 Subject: [PATCH 072/372] finished up UX integration for in-topic searching, added mousetrap lib to capture ctrl-F only in topic view, closed #1800 --- public/language/en_GB/search.json | 3 ++- public/src/ajaxify.js | 6 ++++- public/src/app.js | 31 +++++++++++++++++++------- public/src/modules/search.js | 33 ++++++++++++++-------------- public/vendor/mousetrap/mousetrap.js | 9 ++++++++ src/meta/js.js | 1 + 6 files changed, 56 insertions(+), 27 deletions(-) create mode 100644 public/vendor/mousetrap/mousetrap.js diff --git a/public/language/en_GB/search.json b/public/language/en_GB/search.json index 20d27c2a46..81c404a591 100644 --- a/public/language/en_GB/search.json +++ b/public/language/en_GB/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index f0a7f92d9b..9fa4b16262 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -116,10 +116,14 @@ $(document).ready(function() { ajaxify.initialLoad = false; app.refreshTitle(url); - }, animationDuration * 1000 - ((new Date()).getTime() - startTime)) + }, animationDuration * 1000 - ((new Date()).getTime() - startTime)); }); }); + + require(['search'], function(search) { + search.topicDOM.end(); + }); }); return true; diff --git a/public/src/app.js b/public/src/app.js index f7965a518d..a3bf1f9d81 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -419,11 +419,20 @@ var socket, searchFields = $("#search-fields"), searchInput = $('#search-fields input'); + $('#search-form').on('submit', dismissSearch); + searchInput.on('blur', dismissSearch); + function dismissSearch(){ searchFields.hide(); searchButton.show(); } + function prepareSearch() { + searchFields.removeClass('hide').show(); + searchButton.hide(); + searchInput.focus(); + } + searchButton.on('click', function(e) { if (!config.loggedIn && !config.allowGuestSearching) { app.alert({ @@ -435,17 +444,11 @@ var socket, } e.stopPropagation(); - searchFields.removeClass('hide').show(); - $(this).hide(); - - searchInput.focus(); - - $('#search-form').on('submit', dismissSearch); - searchInput.on('blur', dismissSearch); + prepareSearch(); return false; }); - require(['search'], function(search) { + require(['search', 'mousetrap'], function(search, Mousetrap) { $('#search-form').on('submit', function (e) { e.preventDefault(); var input = $(this).find('input'), @@ -464,6 +467,18 @@ var socket, .on('click', '.next', function() { search.topicDOM.next(); }); + + Mousetrap.bind('ctrl+f', function(e) { + // If in topic, open search window and populate, otherwise regular behaviour + var match = ajaxify.currentPage.match(/^topic\/([\d]+)/), + tid; + if (match) { + e.preventDefault(); + tid = match[1]; + searchInput.val('in:topic-' + tid + ' '); + prepareSearch(); + } + }); }); } diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 7de1f33395..aa035367a8 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -51,16 +51,6 @@ define('search', ['navigator'], function(nav) { }; Search.topicDOM = {}; - Search.topicDOM.start = function() { - var topicSearchEl = $('.topic-search'); - - topicSearchEl.find('.count').html('1 / ' + Search.current.results.length); - topicSearchEl.removeClass('hidden'); - Search.checkPagePresence(Search.current.tid, function() { - Search.highlightResult(0); - }); - }; - Search.topicDOM.prev = function() { Search.topicDOM.update((Search.current.index === 0) ? Search.current.results.length-1 : Search.current.index-1); }; @@ -71,17 +61,26 @@ define('search', ['navigator'], function(nav) { Search.topicDOM.update = function(index) { var topicSearchEl = $('.topic-search'); - Search.current.index = index; - topicSearchEl.find('.count').html((index+1) + ' / ' + Search.current.results.length); - topicSearchEl.removeClass('hidden'); - Search.checkPagePresence(Search.current.tid, function() { - socket.emit('posts.getPidIndex', Search.current.results[index], function(err, postIndex) { - nav.scrollToPost(postIndex-1, true); // why -1? Ask @barisusakli + if (Search.current.results.length > 0) { + topicSearchEl.find('.count').html((index+1) + ' / ' + Search.current.results.length); + topicSearchEl.removeClass('hidden').find('.prev, .next').removeAttr('disabled'); + Search.checkPagePresence(Search.current.tid, function() { + socket.emit('posts.getPidIndex', Search.current.results[index], function(err, postIndex) { + nav.scrollToPost(postIndex-1, true); // why -1? Ask @barisusakli + }); }); - }); + } else { + translator.translate('[[search:no-matches]]', function(text) { + topicSearchEl.find('.count').html(text); + }); + topicSearchEl.removeClass('hidden').find('.prev, .next').attr('disabled', 'disabled'); + } + }; + Search.topicDOM.end = function() { + $('.topic-search').addClass('hidden'); }; return Search; diff --git a/public/vendor/mousetrap/mousetrap.js b/public/vendor/mousetrap/mousetrap.js new file mode 100644 index 0000000000..01709ffd9a --- /dev/null +++ b/public/vendor/mousetrap/mousetrap.js @@ -0,0 +1,9 @@ +/* mousetrap v1.4.6 craig.is/killing/mice */ +(function(J,r,f){function s(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:B[a.which]?B[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a){a=a||{};var b=!1,d;for(d in n)a[d]?b=!0:n[d]=0;b||(u=!1)}function C(a,b,d,c,e,v){var g,k,f=[],h=d.type;if(!l[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(g=0;gg||h.hasOwnProperty(g)&&(p[h[g]]=g)}e=p[d]?"keydown":"keypress"}"keypress"==e&&f.length&&(e="keydown");return{key:c,modifiers:f,action:e}}function F(a,b,d,c,e){q[a+":"+d]=b;a=a.replace(/\s+/g," ");var f=a.split(" ");1":".","?":"/","|":"\\"},G={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p,l={},q={},n={},D,z=!1,I=!1,u=!1;for(f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;s(r,"keypress",y);s(r,"keydown",y);s(r,"keyup",y);var m={bind:function(a,b,d){a=a instanceof Array?a:[a];for(var c=0;c Date: Fri, 29 Aug 2014 12:29:00 -0400 Subject: [PATCH 073/372] added secret key bindings for admins to reload and restart their NodeBBs :shipit: --- public/src/app.js | 5 +++++ public/src/modules/admin.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 public/src/modules/admin.js diff --git a/public/src/app.js b/public/src/app.js index a3bf1f9d81..1e98a871e3 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -576,6 +576,11 @@ var socket, }); } }); + + // Admin keyboard shortcuts + require(['admin'], function(Admin) { + Admin.init(); + }); }); }; diff --git a/public/src/modules/admin.js b/public/src/modules/admin.js new file mode 100644 index 0000000000..8b447942be --- /dev/null +++ b/public/src/modules/admin.js @@ -0,0 +1,19 @@ +define('admin', ['mousetrap'], function(Mousetrap) { + var Admin= {}; + + Admin.init = function() { + if (app.isAdmin) { + Mousetrap.bind('ctrl+shift+a r', function() { + console.log('[admin] Reloading NodeBB...'); + socket.emit('admin.reload'); + }); + + Mousetrap.bind('ctrl+shift+a R', function() { + console.log('[admin] Restarting NodeBB...'); + socket.emit('admin.restart'); + }); + } + }; + + return Admin; +}); \ No newline at end of file From acef6141475e44acc1f76942924aa29928cad5da Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 29 Aug 2014 14:50:24 -0400 Subject: [PATCH 074/372] removed 2 BS classes --- src/topics/unread.js | 90 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index fce061cd07..0ff51edc21 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -19,6 +19,50 @@ module.exports = function(Topics) { }); }; + Topics.getUnreadTopics = function(uid, start, stop, callback) { + + var unreadTopics = { + showSelect: true, + nextStart : 0, + topics: [] + }; + + function sendUnreadTopics(tids) { + Topics.getTopicsByTids(tids, uid, function(err, topicData) { + if (err) { + return callback(err); + } + + if (!Array.isArray(topicData) || !topicData.length) { + return callback(null, unreadTopics); + } + + db.sortedSetRevRank('topics:recent', topicData[topicData.length - 1].tid, function(err, rank) { + if (err) { + return callback(err); + } + + unreadTopics.topics = topicData; + unreadTopics.nextStart = parseInt(rank, 10) + 1; + + callback(null, unreadTopics); + }); + }); + } + + Topics.getUnreadTids(uid, start, stop, function(err, unreadTids) { + if (err) { + return callback(err); + } + + if (unreadTids.length) { + sendUnreadTopics(unreadTids); + } else { + callback(null, unreadTopics); + } + }); + }; + Topics.getUnreadTids = function(uid, start, stop, callback) { var unreadTids = [], done = false; @@ -74,53 +118,7 @@ module.exports = function(Topics) { }); }; - Topics.getUnreadTopics = function(uid, start, stop, callback) { - - var unreadTopics = { - no_topics_message: '', - show_markread_button: 'hidden', - showSelect: true, - nextStart : 0, - topics: [] - }; - - function sendUnreadTopics(tids) { - Topics.getTopicsByTids(tids, uid, function(err, topicData) { - if (err) { - return callback(err); - } - - if (!Array.isArray(topicData) || !topicData.length) { - return callback(null, unreadTopics); - } - - db.sortedSetRevRank('topics:recent', topicData[topicData.length - 1].tid, function(err, rank) { - if(err) { - return callback(err); - } - - unreadTopics.topics = topicData; - unreadTopics.nextStart = parseInt(rank, 10) + 1; - unreadTopics.no_topics_message = (!topicData || topicData.length === 0) ? '' : 'hidden'; - unreadTopics.show_markread_button = topicData.length === 0 ? 'hidden' : ''; - callback(null, unreadTopics); - }); - }); - } - - Topics.getUnreadTids(uid, start, stop, function(err, unreadTids) { - if (err) { - return callback(err); - } - - if (unreadTids.length) { - sendUnreadTopics(unreadTids); - } else { - callback(null, unreadTopics); - } - }); - }; Topics.pushUnreadCount = function(uids, callback) { var websockets = require('./../socket.io'); From 197226eea61ad75fe9217326feb816e0eb0f7198 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 29 Aug 2014 15:57:20 -0400 Subject: [PATCH 075/372] closes #1742 --- public/language/en_GB/category.json | 3 +- public/src/forum/category.js | 18 ++++++ src/categories.js | 17 ++++++ src/socket.io/categories.js | 19 ++++++ src/topics/unread.js | 94 ++++++++++++++++++++--------- src/user.js | 12 ++++ 6 files changed, 133 insertions(+), 30 deletions(-) diff --git a/public/language/en_GB/category.json b/public/language/en_GB/category.json index db742469f9..22d72c3064 100644 --- a/public/language/en_GB/category.json +++ b/public/language/en_GB/category.json @@ -5,5 +5,6 @@ "browsing": "browsing", "no_replies": "No one has replied", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } diff --git a/public/src/forum/category.js b/public/src/forum/category.js index d62719fb9d..21cceb55aa 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -48,8 +48,26 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' } }); }); + + handleIgnoreWatch(cid); }; + function handleIgnoreWatch(cid) { + $('.watch, .ignore').on('click', function() { + $this = $(this); + var command = $this.hasClass('watch') ? 'watch' : 'ignore'; + + socket.emit('categories.' + command, cid, function(err) { + if (err) { + return app.alertError(err.message); + } + + $('.watch').toggleClass('hidden', command === 'watch'); + $('.ignore').toggleClass('hidden', command === 'ignore'); + }); + }) + } + Category.toTop = function() { navigator.scrollTop(0); }; diff --git a/src/categories.js b/src/categories.js index 2245517d5b..39affb9d1e 100644 --- a/src/categories.js +++ b/src/categories.js @@ -83,6 +83,9 @@ var db = require('./database'), }, pageCount: function(next) { Categories.getPageCount(cid, uid, next); + }, + isIgnored: function(next) { + Categories.isIgnored([cid], uid, next); } }, function(err, results) { if(err) { @@ -92,6 +95,7 @@ var db = require('./database'), category.topics = results.topics.topics; category.nextStart = results.topics.nextStart; category.pageCount = results.pageCount; + category.isIgnored = results.isIgnored[0]; category.topic_row_size = 'col-md-9'; plugins.fireHook('filter:category.get', category, uid, callback); @@ -141,6 +145,19 @@ var db = require('./database'), ], callback); }; + Categories.isIgnored = function(cids, uid, callback) { + user.getIgnoredCategories(uid, function(err, ignoredCids) { + if (err) { + return callback(err); + } + + cids = cids.map(function(cid) { + return ignoredCids.indexOf(cid.toString()) !== -1; + }); + callback(null, cids); + }); + }; + Categories.getTopicIds = function(cid, start, stop, callback) { db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback); }; diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 554ea486a9..d92f18ca19 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -5,6 +5,7 @@ var async = require('async'), categories = require('../categories'), privileges = require('../privileges'), user = require('../user'), + topics = require('../topics'), websockets = require('./index'), SocketCategories = {}; @@ -83,4 +84,22 @@ SocketCategories.getCategoriesByPrivilege = function(socket, privilege, callback categories.getCategoriesByPrivilege(socket.uid, privilege, callback); }; +SocketCategories.watch = function(socket, cid, callback) { + user.watchCategory(socket.uid, cid, function(err) { + if (err) { + return callback(err); + } + topics.pushUnreadCount(socket.uid, callback); + }); +}; + +SocketCategories.ignore = function(socket, cid, callback) { + user.ignoreCategory(socket.uid, cid, function(err) { + if (err) { + return callback(err); + } + topics.pushUnreadCount(socket.uid, callback); + }); +}; + module.exports = SocketCategories; diff --git a/src/topics/unread.js b/src/topics/unread.js index 0ff51edc21..41385318a4 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -27,7 +27,11 @@ module.exports = function(Topics) { topics: [] }; - function sendUnreadTopics(tids) { + function sendUnreadTopics(tids, callback) { + if (!tids.length) { + return callback(null, unreadTopics); + } + Topics.getTopicsByTids(tids, uid, function(err, topicData) { if (err) { return callback(err); @@ -55,11 +59,7 @@ module.exports = function(Topics) { return callback(err); } - if (unreadTids.length) { - sendUnreadTopics(unreadTids); - } else { - callback(null, unreadTopics); - } + sendUnreadTopics(unreadTids, callback); }); }; @@ -75,50 +75,86 @@ module.exports = function(Topics) { var count = 0; if (stop === -1) { count = Infinity; - } else { + } else { count = stop - start + 1; } - async.whilst(function() { - return unreadTids.length < count && !done; - }, function(next) { - Topics.getLatestTids(start, stop, 'month', function(err, tids) { - if (err) { - return next(err); - } - if (tids && !tids.length) { - done = true; - return next(); - } + user.getIgnoredCategories(uid, function(err, ignoredCids) { + if (err) { + return callback(err); + } - Topics.hasReadTopics(tids, uid, function(err, read) { + async.whilst(function() { + return unreadTids.length < count && !done; + }, function(next) { + Topics.getLatestTids(start, stop, 'month', function(err, tids) { if (err) { return next(err); } - var newtids = tids.filter(function(tid, index) { - return !read[index]; - }); + if (tids && !tids.length) { + done = true; + return next(); + } - privileges.topics.filter('read', newtids, uid, function(err, newtids) { + Topics.hasReadTopics(tids, uid, function(err, read) { if (err) { return next(err); } - unreadTids.push.apply(unreadTids, newtids); - start = stop + 1; - stop = start + 19; + var newtids = tids.filter(function(tid, index) { + return !read[index]; + }); + + privileges.topics.filter('read', newtids, uid, function(err, newtids) { + if (err) { + return next(err); + } + + filterTopicsFromIgnoredCategories(newtids, ignoredCids, function(err, newtids) { + if (err) { + return next(err); + } - next(); + unreadTids.push.apply(unreadTids, newtids); + + start = stop + 1; + stop = start + 19; + + next(); + }); + }); }); }); + }, function(err) { + callback(err, unreadTids.slice(0, count)); }); - }, function(err) { - callback(err, unreadTids.slice(0, count)); + }); }; + function filterTopicsFromIgnoredCategories(tids, ignoredCids, callback) { + if (!Array.isArray(ignoredCids) || !ignoredCids.length || !tids.length) { + return callback(null, tids); + } + + var keys = tids.map(function(tid) { + return 'topic:' + tid; + }); + db.getObjectsFields(keys, ['tid', 'cid'], function(err, topics) { + if (err) { + return callback(err); + } + topics = topics.filter(function(topic) { + return topic && ignoredCids.indexOf(topic.cid.toString()) === -1; + }).map(function(topic) { + return topic.tid; + }); + + callback(null, topics); + }); + } Topics.pushUnreadCount = function(uids, callback) { var websockets = require('./../socket.io'); diff --git a/src/user.js b/src/user.js index 7abee910cb..16ce0fe537 100644 --- a/src/user.js +++ b/src/user.js @@ -438,5 +438,17 @@ var }); }; + User.getIgnoredCategories = function(uid, callback) { + db.getSortedSetRange('uid:' + uid + ':ignored:cids', 0, -1, callback); + }; + + User.ignoreCategory = function(uid, cid, callback) { + db.sortedSetAdd('uid:' + uid + ':ignored:cids', Date.now(), cid, callback); + }; + + User.watchCategory = function(uid, cid, callback) { + db.sortedSetRemove('uid:' + uid + ':ignored:cids', cid, callback); + }; + }(exports)); From 576081e25f188d0919a6df2740d2ba0c71268ee7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 29 Aug 2014 16:40:06 -0400 Subject: [PATCH 076/372] delete user ignored cids on account delete --- src/user/delete.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/user/delete.js b/src/user/delete.js index 214511dffe..460a8a1df5 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -104,6 +104,9 @@ module.exports = function(User) { function(next) { db.delete('uid:' + uid + ':downvote', next); }, + function(next) { + db.delete('uid:' + uid + ':ignored:cids', next); + }, function(next) { deleteUserFromFollowers(uid, next); }, From 6ac8d9d3499e7386e5d237574d4a0a664254d703 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 29 Aug 2014 17:55:44 -0400 Subject: [PATCH 077/372] check uid --- src/user.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/user.js b/src/user.js index 16ce0fe537..67a8c81c4d 100644 --- a/src/user.js +++ b/src/user.js @@ -443,10 +443,16 @@ var }; User.ignoreCategory = function(uid, cid, callback) { + if (!uid) { + return callback(); + } db.sortedSetAdd('uid:' + uid + ':ignored:cids', Date.now(), cid, callback); }; User.watchCategory = function(uid, cid, callback) { + if (!uid) { + return callback(); + } db.sortedSetRemove('uid:' + uid + ':ignored:cids', cid, callback); }; From 6b286a8f07233193e12857b94f56785239f08ba0 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 29 Aug 2014 18:18:31 -0400 Subject: [PATCH 078/372] fix, dont display duped topics on category view --- public/src/forum/category.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/public/src/forum/category.js b/public/src/forum/category.js index 21cceb55aa..646582abaf 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -54,7 +54,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' function handleIgnoreWatch(cid) { $('.watch, .ignore').on('click', function() { - $this = $(this); + var $this = $(this); var command = $this.hasClass('watch') ? 'watch' : 'ignore'; socket.emit('categories.' + command, cid, function(err) { @@ -65,7 +65,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' $('.watch').toggleClass('hidden', command === 'watch'); $('.ignore').toggleClass('hidden', command === 'ignore'); }); - }) + }); } Category.toTop = function() { @@ -80,7 +80,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' Category.navigatorCallback = function(element, elementCount) { return parseInt(element.attr('data-index'), 10) + 1; - } + }; $(window).on('action:popstate', function(ev, data) { if(data.url.indexOf('category/') === 0) { @@ -238,10 +238,8 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' return; } - var topics = data.topics; - - function removeAlreadyAddedTopics() { - topics = topics.filter(function(topic) { + function removeAlreadyAddedTopics(topics) { + return topics.filter(function(topic) { return $('#topics-container li[data-tid="' + topic.tid +'"]').length === 0; }); } @@ -255,7 +253,7 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' } var last = $('#topics-container .category-item[data-tid]').last(); var lastIndex = last.attr('data-index'); - var firstIndex = topics[topics.length - 1].index; + var firstIndex = data.topics[data.topics.length - 1].index; if (firstIndex > lastIndex) { after = last; } else { @@ -263,8 +261,8 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' } } - removeAlreadyAddedTopics(); - if(!topics.length) { + data.topics = removeAlreadyAddedTopics(data.topics); + if(!data.topics.length) { return; } From 603cddc687d2f9340781402ffe09429247f40cd1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 30 Aug 2014 15:19:18 -0400 Subject: [PATCH 079/372] closes #1934 --- public/language/en_GB/error.json | 2 ++ src/controllers/topics.js | 5 +++-- src/favourites.js | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index c9de71d2f4..b0ed8eeb5d 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -76,6 +76,8 @@ "cant-chat-with-yourself": "You can't chat with yourself!", + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 0c7aef7e38..e482efd412 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -184,8 +184,9 @@ topicsController.get = function(req, res, next) { } data.privileges = userPrivileges; - data['reputation:disabled'] = meta.config['reputation:disabled'] === '1' ? true : false; - data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false; + data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; + data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; + data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; var topic_url = tid + (req.params.slug ? '/' + req.params.slug : ''); var queryString = qs.stringify(req.query); diff --git a/src/favourites.js b/src/favourites.js index 64de4fa32f..50957c323f 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -98,20 +98,28 @@ var async = require('async'), } Favourites.upvote = function(pid, uid, callback) { - if (meta.config['reputation:disabled'] === false) { - return callback(false); + if (parseInt(meta.config['reputation:disabled'], 10) === 1) { + return callback(new Error('[[error:reputation-system-disabled]]')); } toggleVote('upvote', pid, uid, callback); }; Favourites.downvote = function(pid, uid, callback) { - if (meta.config['reputation:disabled'] === false) { - return callback(false); + if (parseInt(meta.config['reputation:disabled'], 10) === 1) { + return callback(new Error('[[error:reputation-system-disabled]]')); + } + + if (parseInt(meta.config['downvote:disabled'], 10) === 1) { + return callback(new Error('[[error:downvoting-disabled]]')); } user.getUserField(uid, 'reputation', function(err, reputation) { - if (reputation < meta.config['privileges:downvote']) { + if (err) { + return callback(err); + } + + if (reputation < parseInt(meta.config['privileges:downvote'], 10)) { return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); } From 14fcd361344bb19ca1bf7e22798174dba57dd584 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 30 Aug 2014 15:39:20 -0400 Subject: [PATCH 080/372] closes #2028 --- public/language/en_GB/error.json | 1 + src/socket.io/posts.js | 14 ++++++++++---- src/topics/posts.js | 1 + src/user/notifications.js | 6 ++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index b0ed8eeb5d..7c457eb497 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -79,6 +79,7 @@ "reputation-system-disabled": "Reputation system is disabled.", "downvoting-disabled": "Downvoting is disabled", "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 5326fc09de..7a825dccd0 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -276,16 +276,22 @@ SocketPosts.flag = function(socket, pid, callback) { async.waterfall([ function(next) { - user.getUserField(socket.uid, 'username', next); + user.getUserFields(socket.uid, ['username', 'reputation'], next); }, - function(username, next) { - message = '[[notifications:user_flagged_post, ' + username + ']]'; + function(userData, next) { + if (parseInt(userData.reputation, 10) < parseInt(meta.config['privileges:flag'] || 1, 10)) { + return next(new Error('[[error:not-enough-reputation-to-flag]]')); + } + message = '[[notifications:user_flagged_post, ' + userData.username + ']]'; posts.getPostFields(pid, ['tid', 'uid', 'content'], next); }, function(postData, next) { postTools.parse(postData.content, function(err, parsed) { + if (err) { + return next(err); + } postData.content = parsed; - next(undefined, postData); + next(null, postData); }); }, function(postData, next) { diff --git a/src/topics/posts.js b/src/topics/posts.js index 35e33b530d..a3366d6c40 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -106,6 +106,7 @@ module.exports = function(Topics) { for (var i = 0; i < postData.length; ++i) { if (postData[i]) { + postData[i].index = results.indices[i]; postData[i].deleted = parseInt(postData[i].deleted, 10) === 1; postData[i].user = results.userData[postData[i].uid]; postData[i].editor = postData[i].editor ? results.editors[postData[i].editor] : null; diff --git a/src/user/notifications.js b/src/user/notifications.js index 9d43845b9c..c71238366e 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -12,7 +12,8 @@ var async = require('async'), posts = require('../posts'), postTools = require('../postTools'), topics = require('../topics'), - privileges = require('../privileges'); + privileges = require('../privileges'), + utils = require('../../public/src/utils'); (function(UserNotifications) { @@ -186,7 +187,8 @@ var async = require('async'), var paths = []; pids.forEach(function(pid, index) { var slug = results.topics[index] ? results.topics[index].slug : null; - var postIndex = results.indices[index] ? parseInt(results.indices[index], 10) + 1 : null; + var postIndex = utils.isNumber(results.indices[index]) ? parseInt(results.indices[index], 10) + 1 : null; + if (slug && postIndex) { paths.push(nconf.get('relative_path') + '/topic/' + slug + '/' + postIndex); } else { From 9ea081deb3d1e7333e158b1af40aed832b16e0d4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 16:13:05 -0400 Subject: [PATCH 081/372] sending 'bind' message to child proc instead of SIGCONT signal, which is not understood by Windows installs --- app.js | 6 +++++- loader.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index bd9cb02e0a..9c836d2c44 100644 --- a/app.js +++ b/app.js @@ -143,7 +143,11 @@ function start() { plugins.ready(function() { webserver.init(function() { // If this callback is called, this means that loader.js is used - process.on('SIGCONT', webserver.listen); + process.on('message', function(msg) { + if (msg === 'bind') { + webserver.listen(); + } + }); process.send({ action: 'ready' }); diff --git a/loader.js b/loader.js index a3ce275704..48d4929238 100644 --- a/loader.js +++ b/loader.js @@ -29,7 +29,7 @@ var nconf = require('nconf'), if (message && typeof message === 'object' && message.action) { switch (message.action) { case 'ready': - if (!callback) return nbb.kill('SIGCONT'); + if (!callback) return nbb.send('bind'); callback(); break; case 'restart': @@ -63,7 +63,7 @@ var nconf = require('nconf'), nbb_restart = function() { nbb_start(function() { nbbOld.removeAllListeners('exit').on('exit', function() { - nbb.kill('SIGCONT'); + nbb.send('bind'); }); nbbOld.kill(); }); From 298d904d454c6953473729d4f85d15ebc74e9c51 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 22:41:13 -0400 Subject: [PATCH 082/372] refactored login process to be a form submit instead of ajax-redirect, implemented error message parsing using req.flash --- public/language/en_GB/error.json | 2 +- public/src/forum/login.js | 51 -------------------------------- src/controllers/index.js | 5 +--- src/routes/authentication.js | 29 +++++++++++++----- 4 files changed, 24 insertions(+), 63 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 7c457eb497..df1654ba17 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -15,6 +15,7 @@ "invalid-title": "Invalid title", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", @@ -31,7 +32,6 @@ "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", "no-group": "Group doesn't exist", - "no-user": "User doesn't exist", "no-teaser": "Teaser doesn't exist", "no-privileges": "You don't have enough privileges for this action.", "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", diff --git a/public/src/forum/login.js b/public/src/forum/login.js index 4f4c9b6061..9f1b5f1ab1 100644 --- a/public/src/forum/login.js +++ b/public/src/forum/login.js @@ -6,58 +6,7 @@ define('forum/login', function() { Login.init = function() { $('#login').on('click', function(e) { - e.preventDefault(); - - var loginData = { - 'username': $('#username').val(), - 'password': $('#password').val(), - 'remember': $('#remember').prop('checked'), - '_csrf': $('#csrf-token').val() - }, - previousUrl = $('input[name="previousUrl"]').val(); - - $('#login').attr('disabled', 'disabled').html('Logging in...'); $('#login-error-notify').hide(); - - $.ajax({ - type: "POST", - url: RELATIVE_PATH + '/login', - data: loginData, - success: function(data, textStatus, jqXHR) { - $('#login').html('Redirecting...'); - if (previousUrl) { - app.previousUrl = previousUrl; - } else if (!app.previousUrl) { - app.previousUrl = RELATIVE_PATH || '/'; - } - - if(app.previousUrl.indexOf('/reset/') !== -1) { - window.location.replace(RELATIVE_PATH + "/?loggedin"); - } else { - var index = app.previousUrl.indexOf('#'); - if(index !== -1) { - window.location.replace(app.previousUrl.slice(0, index) + '?loggedin' + app.previousUrl.slice(index)); - } else { - window.location.replace(app.previousUrl + "?loggedin"); - } - } - - app.loadConfig(); - }, - error: function(data, textStatus, jqXHR) { - var message = data.responseJSON; - if (typeof data.responseJSON !== 'string') { - message = data.responseJSON.message || ''; - } - translator.translate(message, function(errorText) { - $('#login-error-notify').show().html(errorText); - }); - - $('#login').removeAttr('disabled').html('Login'); - }, - dataType: 'json', - async: true - }); }); $('#login-error-notify button').on('click', function(e) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 7a03171ef2..8f1eb2ff2f 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -125,10 +125,7 @@ Controllers.login = function(req, res, next) { data.showResetLink = emailersPresent; data.allowLocalLogin = meta.config.allowLocalLogin === undefined || parseInt(meta.config.allowLocalLogin, 10) === 1; data.allowRegistration = meta.config.allowRegistration; - - if (req.query.next) { - data.previousUrl = req.query.next; - } + data.error = req.flash('error')[0]; res.render('login', data); }; diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 19d13aeae6..e39e72ba3c 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -33,11 +33,17 @@ var continueLogin = function() { passport.authenticate('local', function(err, userData, info) { if (err) { - return res.json(403, err.message); + req.flash('error', info); + return res.redirect(nconf.get('relative_path') + '/login'); } if (!userData) { - return res.json(403, info); + if (typeof info === 'object') { + info = '[[error:invalid-username-or-password]]'; + } + + req.flash('error', info); + return res.redirect(nconf.get('relative_path') + '/login'); } // Alter user cookie depending on passed-in option @@ -57,7 +63,13 @@ user.logIP(userData.uid, req.ip); } - res.json(200, info); + if (!req.session.returnTo) { + res.redirect(nconf.get('relative_path') + '/'); + } else { + var next = req.session.returnTo; + delete req.session.returnTo; + res.redirect(nconf.get('relative_path') + next); + } }); })(req, res, next); }; @@ -193,7 +205,8 @@ Auth.login = function(username, password, next) { if (!username || !password) { - return next(new Error('[[error:invalid-user-data]]')); + next(new Error('[[error:invalid-password]]')); + return; } var userslug = utils.slugify(username); @@ -203,9 +216,11 @@ return next(err); } - if(!uid) { - // To-do: Even if a user doesn't exist, compare passwords anyway, so we don't immediately return - return next(null, false, '[[error:no-user]]'); + if (!uid) { + setTimeout(function() { + next(null, false, '[[error:invalid-password]]'); + }, Math.floor((Math.random() * 1000) + 1500)); // Wait between 1-2.5 seconds before returning + return; } user.auth.logAttempt(uid, function(err) { From 7572c9a803a941f2b73cf06c996e38f11147b513 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 22:42:29 -0400 Subject: [PATCH 083/372] added connect-ensure-login and allowed npm to alphabetize the dependencies listing --- package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e97f1d1660..f76f262134 100644 --- a/package.json +++ b/package.json @@ -17,22 +17,22 @@ "dependencies": { "async": "~0.9.0", "bcryptjs": "~2.0.1", + "body-parser": "^1.0.1", + "compression": "^1.0.1", + "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", + "connect-multiparty": "^1.0.1", + "cookie-parser": "^1.0.1", "cron": "~1.0.4", + "csurf": "^1.1.0", "daemon": "~1.1.0", "express": "4.6.1", - "cookie-parser": "^1.0.1", - "body-parser": "^1.0.1", - "serve-favicon": "^2.0.1", "express-session": "^1.0.2", - "csurf": "^1.1.0", - "compression": "^1.0.1", - "connect-multiparty": "^1.0.1", - "morgan": "^1.0.0", "gm": "1.16.0", "gravatar": "1.0.6", "less": "~1.7.3", "mkdirp": "~0.5.0", + "morgan": "^1.0.0", "nconf": "~0.6.7", "nodebb-plugin-dbsearch": "0.0.13", "nodebb-plugin-markdown": "~0.5.0", @@ -49,16 +49,17 @@ "rimraf": "~2.2.6", "rss": "~0.3.2", "semver": "~2.3.1", + "serve-favicon": "^2.0.1", "sitemap": "~0.7.3", "socket.io": "~0.9.16", "socket.io-wildcard": "~0.1.1", "string": "~1.9.0", + "templates.js": "0.0.13", "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", "underscore": "~1.6.0", "validator": "~3.16.1", "winston": "~0.7.2", - "xregexp": "~2.0.0", - "templates.js": "0.0.13" + "xregexp": "~2.0.0" }, "devDependencies": { "mocha": "~1.13.0" From 08c9cbdf7047aa8c5374390aee059a11de9a35dc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 22:43:00 -0400 Subject: [PATCH 084/372] changing admin API calls to return 404 if unauthenticated, and redirect user to login for regular URL. closes #1885 --- src/middleware/admin.js | 4 +++- src/middleware/middleware.js | 3 +++ src/routes/authentication.js | 2 +- src/routes/index.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/middleware/admin.js b/src/middleware/admin.js index 92ab6de9e2..3cef701d08 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -16,7 +16,9 @@ var app, middleware.isAdmin = function(req, res, next) { if (!req.user) { - return res.redirect(nconf.get('relative_path') + '/login?next=admin'); + return res.json(404, { + error: 'not-found' + }); } user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) { diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 296a445ca4..aa1e6dec61 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -15,6 +15,7 @@ var app, categories = require('./../categories'), topics = require('./../topics'), messaging = require('../messaging'), + ensureLoggedIn = require('connect-ensure-login'), controllers = { api: require('./../controllers/api') @@ -32,6 +33,8 @@ middleware.authenticate = function(req, res, next) { } }; +middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(); + middleware.updateLastOnlineTime = function(req, res, next) { if(req.user) { user.updateLastOnlineTime(req.user.uid); diff --git a/src/routes/authentication.js b/src/routes/authentication.js index e39e72ba3c..9841f3c69d 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -177,7 +177,7 @@ } app.get(strategy.callbackURL, passport.authenticate(strategy.name, { - successRedirect: nconf.get('relative_path') + '/', + successReturnToOrRedirect: nconf.get('relative_path') + '/', failureRedirect: nconf.get('relative_path') + '/login' })); } diff --git a/src/routes/index.js b/src/routes/index.js index 01b095a092..47c67c3457 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -163,7 +163,7 @@ module.exports = function(app, middleware) { app.all(relativePath + '/api/?*', middleware.updateLastOnlineTime, middleware.prepareAPI); app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI); - app.all(relativePath + '/admin/?*', middleware.admin.isAdmin); + app.all(relativePath + '/admin/?*', middleware.ensureLoggedIn, middleware.admin.isAdmin); adminRoutes(router, middleware, controllers); metaRoutes(router, middleware, controllers); From caa9c8c1570c5e5a7c9d87cf3d4ed4a6e4a65422 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 22:51:02 -0400 Subject: [PATCH 085/372] more stuff using new returnTo behaviour --- src/middleware/middleware.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index aa1e6dec61..7c6546c2d9 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -142,7 +142,8 @@ middleware.checkGlobalPrivacySettings = function(req, res, next) { if (res.locals.isAPI) { return res.json(403, 'not-allowed'); } else { - return res.redirect('login?next=' + req.url); + req.session.returnTo = req.url; + return res.redirect('login'); } } @@ -154,7 +155,8 @@ middleware.checkAccountPermissions = function(req, res, next) { var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; if (callerUID === 0) { - return res.redirect('/login?next=' + req.url); + req.session.returnTo = req.url; + return res.redirect('/login'); } user.getUidByUserslug(req.params.userslug, function (err, uid) { From 4edceb0f92e08827b81a6afb3ab433f84e4838c7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 23:02:24 -0400 Subject: [PATCH 086/372] allowing /user to also work in addition to /users --- src/routes/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 47c67c3457..dc2e0f0d94 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -90,7 +90,6 @@ function categoryRoutes(app, middleware, controllers) { } function accountRoutes(app, middleware, controllers) { - app.get('/user/:userslug', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount); app.get('/api/user/:userslug', middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount); @@ -123,7 +122,7 @@ function accountRoutes(app, middleware, controllers) { } function userRoutes(app, middleware, controllers) { - app.get('/users', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); + app.get('/user(s)?', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); app.get('/api/users', middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); app.get('/users/online', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); From ff24339b38d058a61c0de02edc10434574ce7f88 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 31 Aug 2014 23:49:22 -0400 Subject: [PATCH 087/372] added back error:no-user --- public/language/en_GB/error.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index df1654ba17..15874226dd 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -32,6 +32,7 @@ "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", "no-group": "Group doesn't exist", + "no-user": "User doesn't exist", "no-teaser": "Teaser doesn't exist", "no-privileges": "You don't have enough privileges for this action.", "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", From fa2bf7c908b29d096a3af26cb68338ad641443b6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 1 Sep 2014 00:05:16 -0400 Subject: [PATCH 088/372] added disabled class back when form is submitting... aka clickspam handling --- public/src/forum/login.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/public/src/forum/login.js b/public/src/forum/login.js index 9f1b5f1ab1..c4600c36ac 100644 --- a/public/src/forum/login.js +++ b/public/src/forum/login.js @@ -5,13 +5,31 @@ define('forum/login', function() { var Login = {}; Login.init = function() { - $('#login').on('click', function(e) { - $('#login-error-notify').hide(); + var errorEl = $('#login-error-notify'), + submitEl = $('#login'), + formEl = $('#login-form'); + + submitEl.on('click', function(e) { + e.preventDefault(); + + if (!$('#username').val() || !$('#password').val()) { + translator.translate('[[error:invalid-username-or-password]]', function(translated) { + errorEl.find('p').text(translated) + errorEl.show(); + }); + } else { + errorEl.hide(); + + if (!submitEl.hasClass('disabled')) { + submitEl.addClass('disabled'); + formEl.submit(); + } + } }); $('#login-error-notify button').on('click', function(e) { e.preventDefault(); - $('#login-error-notify').hide(); + errorEl.hide(); }); $('#content #username').focus(); From ae6d5e72f4da7ba0d788f7934ea76edf633f335e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 14:09:47 -0400 Subject: [PATCH 089/372] closes #2015 --- public/src/app.js | 2 +- public/src/modules/composer.js | 6 +++--- public/src/translator.js | 8 ++++---- src/controllers/api.js | 3 ++- src/middleware/middleware.js | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 1e98a871e3..7b7f632747 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -147,7 +147,7 @@ var socket, app.cacheBuster = config['cache-buster']; bootbox.setDefaults({ - locale: config.defaultLang + locale: config.userLang }); } } diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 337938b4dd..8ba252b125 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -128,9 +128,9 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting var prevText = bodyEl.val(); if (parseInt(tid, 10) !== parseInt(composer.posts[uuid].tid, 10)) { var link = '[' + title + '](/topic/' + topicSlug + '/' + (parseInt(postIndex, 10) + 1) + ')'; - translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n', onTranslated); + translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n', config.defaultLang, onTranslated); } else { - translator.translate('[[modules:composer.user_said, ' + username + ']]\n', onTranslated); + translator.translate('[[modules:composer.user_said, ' + username + ']]\n', config.defaultLang, onTranslated); } function onTranslated(translated) { @@ -142,7 +142,7 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting }; composer.newReply = function(tid, pid, title, text) { - translator.translate(text, function(translated) { + translator.translate(text, config.defaultLang, function(translated) { push({ tid: tid, toPid: pid, diff --git a/public/src/translator.js b/public/src/translator.js index f81fff1ba8..cdb1e74926 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -21,10 +21,10 @@ translator.prepareDOM = function() { // Load the appropriate timeago locale file - if (config.defaultLang !== 'en_GB' && config.defaultLang !== 'en_US') { + if (config.userLang !== 'en_GB' && config.userLang !== 'en_US') { // Correct NodeBB language codes to timeago codes, if necessary var languageCode; - switch(config.defaultLang) { + switch(config.userLang) { case 'cs': languageCode = 'cz'; break; @@ -46,7 +46,7 @@ break; default: - languageCode = config.defaultLang; + languageCode = config.userLang; break; } @@ -69,7 +69,7 @@ if (typeof language === 'function') { callback = language; if ('undefined' !== typeof window && config) { - language = config.defaultLang || 'en_GB'; + language = config.userLang || 'en_GB'; } else { var meta = require('../../src/meta'); language = meta.config.defaultLang || 'en_GB'; diff --git a/src/controllers/api.js b/src/controllers/api.js index 7944fa45f2..aaa49cda7c 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -42,6 +42,7 @@ apiController.getConfig = function(req, res, next) { config.maximumFileSize = meta.config.maximumFileSize; config['theme:id'] = meta.config['theme:id']; config.defaultLang = meta.config.defaultLang || 'en_GB'; + config.userLang = config.defaultLang; config.environment = process.env.NODE_ENV; config.loggedIn = !!req.user; config['cache-buster'] = meta.config['cache-buster'] || ''; @@ -68,7 +69,7 @@ apiController.getConfig = function(req, res, next) { config.topicsPerPage = settings.topicsPerPage; config.postsPerPage = settings.postsPerPage; config.notificationSounds = settings.notificationSounds; - config.defaultLang = settings.language || config.defaultLang; + config.userLang = settings.language || config.defaultLang; config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab; config.topicPostSort = settings.topicPostSort || config.topicPostSort; diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 7c6546c2d9..dd9b8e9dd2 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -396,7 +396,7 @@ middleware.processRender = function(req, res, next) { middleware.renderHeader(req, res, function(err, template) { str = template + str; - translator.translate(str, res.locals.config.defaultLang, function(translated) { + translator.translate(str, res.locals.config.userLang, function(translated) { fn(err, translated); }); }); From 619ca103c79193fe74c3d9f5b0fab419eb9126d5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 18:46:42 -0400 Subject: [PATCH 090/372] crash fix --- src/categories.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/categories.js b/src/categories.js index 39affb9d1e..8043401830 100644 --- a/src/categories.js +++ b/src/categories.js @@ -363,9 +363,11 @@ var db = require('./database'), var hasRead = results.hasRead; uid = parseInt(uid, 10); for(var i=0; i Date: Mon, 1 Sep 2014 18:51:48 -0400 Subject: [PATCH 091/372] crash fix --- src/posts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/posts.js b/src/posts.js index 8f65074914..7d75869a45 100644 --- a/src/posts.js +++ b/src/posts.js @@ -527,6 +527,7 @@ var async = require('async'), } Posts.getPidIndex = function(pid, uid, callback) { + callback = callback || function() {}; async.parallel({ settings: function(next) { user.getSettings(uid, next); From 487793e692e461c917bf020d348e30730680b85e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 19:44:43 -0400 Subject: [PATCH 092/372] create index on score field --- src/database/mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index a9f765e1dd..97050251a9 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -81,7 +81,7 @@ } function createIndices() { - db.collection('objects').ensureIndex({_key :1}, {background:true}, function(err) { + db.collection('objects').ensureIndex({_key :1, score: -1}, {background:true}, function(err) { if(err) { winston.error('Error creating index ' + err.message); } From 0c8c6632d9aee78e80a3a0f94171129f72acc9fb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 20:12:34 -0400 Subject: [PATCH 093/372] create index on key value --- src/database/mongo.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/database/mongo.js b/src/database/mongo.js index 97050251a9..affb658dba 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -87,6 +87,12 @@ } }); + db.collection('objects').ensureIndex({_key :1, value: -1}, {background:true}, function(err) { + if(err) { + winston.error('Error creating index ' + err.message); + } + }); + db.collection('objects').ensureIndex({'expireAt':1}, {expireAfterSeconds:0, background:true}, function(err) { if(err) { winston.error('Error creating index ' + err.message); From 8b4c61241f91b613d9f9305c5903b5f3cb652d93 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 20:45:24 -0400 Subject: [PATCH 094/372] query change --- src/database/mongo/sorted.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 96015b8f97..8813d77b36 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -72,7 +72,15 @@ module.exports = function(db, module) { count = 0; } - db.collection('objects').find({_key:key, score: {$gte:min, $lte:max}}, {fields:{value:1}}) + var scoreQuery = {}; + if (min !== -Infinity) { + scoreQuery['$gte'] = min; + } + if (max !== Infinity) { + scoreQuery['$lte'] = max; + } + + db.collection('objects').find({_key:key, score: scoreQuery}, {fields:{value:1}}) .limit(count) .skip(start) .sort({score: sort}) From b8dfbdaff26ef1216271429955fccb0e45d8862a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 1 Sep 2014 23:33:32 -0400 Subject: [PATCH 095/372] small optimization to online users --- src/controllers/users.js | 39 +++++++++++---------------------------- src/socket.io/index.js | 4 +++- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/controllers/users.js b/src/controllers/users.js index d40f5acd7f..fa53a3fa1a 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -13,8 +13,8 @@ usersController.getOnlineUsers = function(req, res, next) { if(err) { return next(err); } - var onlineUsers = [], - uid = req.user ? req.user.uid : 0; + + var uid = req.user ? req.user.uid : 0; user.isAdministrator(uid, function (err, isAdministrator) { if(err) { @@ -27,39 +27,22 @@ usersController.getOnlineUsers = function(req, res, next) { }); } - function updateUserOnlineStatus(user, next) { - var online = websockets.isUserOnline(user.uid); - if (!online) { - db.sortedSetRemove('users:online', user.uid); - return next(); - } - - onlineUsers.push(user); - next(); - } - var anonymousUserCount = websockets.getOnlineAnonCount(); - async.each(users, updateUserOnlineStatus, function(err) { + db.sortedSetCard('users:online', function(err, count) { if (err) { return next(err); } - db.sortedSetCard('users:online', function(err, count) { - if (err) { - return next(err); - } - - var userData = { - search_display: 'none', - loadmore_display: count > 50 ? 'block' : 'hide', - users: onlineUsers, - anonymousUserCount: anonymousUserCount, - show_anon: anonymousUserCount?'':'hide' - }; + var userData = { + search_display: 'none', + loadmore_display: count > 50 ? 'block' : 'hide', + users: users, + anonymousUserCount: anonymousUserCount, + show_anon: anonymousUserCount?'':'hide' + }; - res.render('users', userData); - }); + res.render('users', userData); }); }); }); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index c1f2fb1b3d..149f372286 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -236,8 +236,10 @@ Sockets.getUserSockets = function(uid) { return []; } + uid = parseInt(uid, 10); + sockets = sockets.filter(function(s) { - return s.uid === parseInt(uid, 10); + return s.uid === uid; }); return sockets; From 761d59f5ec9f82765150cc4adaf18ae4568a6b6d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 01:34:26 -0400 Subject: [PATCH 096/372] each limit on pushUnreadCount --- src/topics/unread.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index 41385318a4..7ad5a5e709 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -169,7 +169,7 @@ module.exports = function(Topics) { return parseInt(value, 10) !== 0; }); - async.each(uids, function(uid, next) { + async.eachLimit(uids, 5, function(uid, next) { Topics.getTotalUnread(uid, function(err, count) { websockets.in('uid_' + uid).emit('event:unread.updateCount', null, count); next(); From 6672f17fc25b9218da09be90b6095150de3ed585 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 02:26:56 -0400 Subject: [PATCH 097/372] part 1 of online user fix don't call getOnlineUsers when a single person comes online --- public/src/forum/topic/browsing.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 3a2a9e3160..2ac1ec23cd 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -82,7 +82,7 @@ define('forum/topic/browsing', function() { }; Browsing.onUserOnline = function(err, data) { - Browsing.populateOnlineUsers(); + updateOnlineIcon($('.username-field[data-username="' + data.username + '"'), data); updateBrowsingUsers(data); }; @@ -104,17 +104,21 @@ define('forum/topic/browsing', function() { uid = el.parents('li').attr('data-uid'); if (uid && users[uid]) { - translator.translate('[[global:' + users[uid].status + ']]', function(translated) { - el.siblings('i') - .attr('class', 'fa fa-circle status ' + users[uid].status) - .attr('title', translated) - .attr('data-original-title', translated); - }); + updateOnlineIcon(el, users[uid]); } }); }); }; + function updateOnlineIcon(el, userData) { + translator.translate('[[global:' + userData.status + ']]', function(translated) { + el.siblings('i') + .attr('class', 'fa fa-circle status ' + userData.status) + .attr('title', translated) + .attr('data-original-title', translated); + }); + } + function updateBrowsingUsers(data) { var activeEl = $('.thread_active_users'); var user = activeEl.find('a[data-uid="'+ data.uid + '"]'); From ff46d1211d43f489bda09a134218cc74c73307e2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 02:29:42 -0400 Subject: [PATCH 098/372] removed populateONline users not sure why this was called inside updateBrowsingUsers --- public/src/forum/topic/browsing.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 2ac1ec23cd..0b577b13d9 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -77,8 +77,6 @@ define('forum/topic/browsing', function() { getReplyingUsers(); } - - Browsing.populateOnlineUsers(); }; Browsing.onUserOnline = function(err, data) { From 171f02101d3d7a6079d9afc62cdc814e076405ae Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 05:04:39 -0400 Subject: [PATCH 099/372] closes #2035, closes #2036 --- public/src/app.js | 11 +++-------- public/src/forum/account/profile.js | 2 -- public/src/forum/topic.js | 1 - public/src/forum/topic/browsing.js | 25 +------------------------ public/src/modules/chat.js | 28 +++++++++++++--------------- src/controllers/accounts.js | 4 +++- src/posts.js | 9 +++++++-- src/socket.io/index.js | 16 ++++++++++++++++ src/socket.io/posts.js | 3 ++- src/socket.io/user.js | 20 -------------------- 10 files changed, 45 insertions(+), 74 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 7b7f632747..94db871a31 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -375,12 +375,6 @@ var socket, }); }; - function updateOnlineStatus(uid) { - socket.emit('user.isOnline', uid, function(err, data) { - $('#logged-in-menu #user_label #user-profile-link>i').attr('class', 'fa fa-circle status ' + data.status); - }); - } - function exposeConfigToTemplates() { $(document).ready(function() { templates.setGlobal('relative_path', RELATIVE_PATH); @@ -492,11 +486,12 @@ var socket, function handleStatusChange() { $('#user-control-list .user-status').off('click').on('click', function(e) { - socket.emit('user.setStatus', $(this).attr('data-status'), function(err, data) { + var status = $(this).attr('data-status'); + socket.emit('user.setStatus', status, function(err, data) { if(err) { return app.alertError(err.message); } - updateOnlineStatus(data.uid); + $('#logged-in-menu #user_label #user-profile-link>i').attr('class', 'fa fa-circle status ' + status); }); e.preventDefault(); }); diff --git a/public/src/forum/account/profile.js b/public/src/forum/account/profile.js index 343be867c2..a9633b6aba 100644 --- a/public/src/forum/account/profile.js +++ b/public/src/forum/account/profile.js @@ -35,8 +35,6 @@ define('forum/account/profile', ['forum/account/header'], function(header) { socket.on('user.isOnline', handleUserOnline); - socket.emit('user.isOnline', theirid, handleUserOnline); - if (yourid !== theirid) { socket.emit('user.increaseViewCount', theirid); } diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 61ad6f4837..5098d66d5d 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -362,7 +362,6 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT } function processPage(element) { - browsing.populateOnlineUsers(); app.createUserTooltips(); app.replaceSelfLinks(element.find('a')); utils.addCommasToNumbers(element.find('.formatted-number')); diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 0b577b13d9..5d1833bdd0 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -80,34 +80,11 @@ define('forum/topic/browsing', function() { }; Browsing.onUserOnline = function(err, data) { - updateOnlineIcon($('.username-field[data-username="' + data.username + '"'), data); + updateOnlineIcon($('.username-field[data-username="' + data.username + '"]'), data); updateBrowsingUsers(data); }; - Browsing.populateOnlineUsers = function () { - var uids = []; - - $('.post-row').each(function () { - var uid = $(this).attr('data-uid'); - if(uids.indexOf(uid) === -1) { - uids.push(uid); - } - }); - - socket.emit('user.getOnlineUsers', uids, function (err, users) { - - $('.username-field').each(function () { - var el = $(this), - uid = el.parents('li').attr('data-uid'); - - if (uid && users[uid]) { - updateOnlineIcon(el, users[uid]); - } - }); - }); - }; - function updateOnlineIcon(el, userData) { translator.translate('[[global:' + userData.status + ']]', function(translated) { el.siblings('i') diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 73b94a0442..6484c458f9 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -7,7 +7,6 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, var newMessage = false; module.prepareDOM = function() { - // Chats Dropdown var chatsToggleEl = $('#chat_dropdown'), chatsListEl = $('#chat-list'); @@ -74,7 +73,6 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, if (modal.is(":visible")) { module.bringModalToTop(modal); - checkOnlineStatus(modal); taskbar.updateActive(modal.attr('UUID')); Chats.scrollToBottom(modal.find('#chat-content')); } else { @@ -114,6 +112,10 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, var modal = module.getModal(withUid); modal.find('.user-typing').addClass('hide'); }); + + socket.on('user.isOnline', function(err, data) { + updateStatus(data.status); + }); }; module.bringModalToTop = function(chatModal) { @@ -138,20 +140,16 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, function checkStatus(chatModal) { socket.emit('user.isOnline', chatModal.attr('touid'), function(err, data) { - translator.translate('[[global:' + data.status + ']]', function(translated) { - $('#chat-user-status').attr('class', 'fa fa-circle status ' + data.status) - .attr('title', translated) - .attr('data-original-title', translated); - }); + updateStatus(data.status); }); } - function checkOnlineStatus(chatModal) { - if(parseInt(chatModal.attr('intervalId'), 10) === 0) { - chatModal.attr('intervalId', setInterval(function() { - checkStatus(chatModal); - }, 1000)); - } + function updateStatus(status) { + translator.translate('[[global:' + status + ']]', function(translated) { + $('#chat-user-status').attr('class', 'fa fa-circle status ' + status) + .attr('title', translated) + .attr('data-original-title', translated); + }); } module.createModal = function(username, touid, callback) { @@ -221,7 +219,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, addSendHandler(chatModal); getChatMessages(chatModal, function() { - checkOnlineStatus(chatModal); + checkStatus(chatModal); }); chatModal.find('.user-typing .text').translateText('[[modules:chat.user_typing, ' + username + ']]'); @@ -259,7 +257,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, module.load = function(uuid) { var chatModal = $('div[UUID="'+uuid+'"]'); chatModal.removeClass('hide'); - checkOnlineStatus(chatModal); + checkStatus(chatModal); taskbar.updateActive(uuid); Chats.scrollToBottom(chatModal.find('#chat-content')); module.center(chatModal); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index fac96c99a5..a9466e4569 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -18,7 +18,8 @@ var fs = require('fs'), plugins = require('../plugins'), languages = require('../languages'), image = require('../image'), - file = require('../file'); + file = require('../file'), + websockets = require('../socket.io'); function userNotFound(res) { if (res.locals.isAPI) { @@ -116,6 +117,7 @@ function getUserDataByUserSlug(userslug, callerUID, callback) { userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1; userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10); userData.profile_links = results.profile_links; + userData.status = !websockets.isUserOnline(userData.uid) ? 'offline' : userData.status; userData.followingCount = results.followStats.followingCount; userData.followerCount = results.followStats.followerCount; diff --git a/src/posts.js b/src/posts.js index 7d75869a45..10d299b4b3 100644 --- a/src/posts.js +++ b/src/posts.js @@ -21,7 +21,8 @@ var async = require('async'), categories = require('./categories'), plugins = require('./plugins'), meta = require('./meta'), - emitter = require('./emitter'); + emitter = require('./emitter'), + websockets = require('./socket.io'); (function(Posts) { require('./posts/delete')(Posts); @@ -227,7 +228,10 @@ var async = require('async'), groups.getUserGroups(uids, next); }, userData: function(next) { - user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], next); + user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next); + }, + online: function(next) { + websockets.isUsersOnline(uids, next); } }, function(err, results) { if (err) { @@ -237,6 +241,7 @@ var async = require('async'), var userData = results.userData; for(var i=0; i 0; } +Sockets.isUsersOnline = function(uids, callback) { + var sockets = io.sockets.clients(); + + if(!Array.isArray(sockets) || !sockets.length) { + return callback(null, []); + } + + sockets = sockets.map(function(s) { + return s.uid; + }); + + callback(null, uids.map(function(uid) { + return sockets.indexOf(parseInt(uid, 10)) !== -1; + })); +}; + Sockets.updateRoomBrowsingText = updateRoomBrowsingText; function updateRoomBrowsingText(roomName) { diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 7a825dccd0..94f2c1f338 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -39,7 +39,8 @@ SocketPosts.reply = function(socket, data, callback) { websockets.server.sockets.emit('event:new_post', { posts: [postData], privileges: privileges, - 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1 + 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, + 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, }); module.parent.exports.emitTopicPostStats(); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index d1e743d901..fa1a063fc7 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -264,26 +264,6 @@ SocketUser.setTopicSort = function(socket, sort, callback) { } }; -SocketUser.getOnlineUsers = function(socket, uids, callback) { - var returnData = {}; - if (!uids) { - return callback(new Error('[[error:invalid-data]]')); - } - - user.isOnline(uids, function(err, userData) { - if (err) { - return callback(err); - } - - userData.forEach(function(user) { - if (user) { - returnData[user.uid] = user; - } - }); - callback(null, returnData); - }); -}; - SocketUser.getOnlineAnonCount = function(socket, data, callback) { callback(null, module.parent.exports.getOnlineAnonCount()); }; From a7736d19264588679cb9d9a91ae226f28ba97ffa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 2 Sep 2014 11:48:28 -0400 Subject: [PATCH 100/372] removed setTimeout when someone tries to log in with a non-existant username --- src/routes/authentication.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 9841f3c69d..9b30ae08a9 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -216,11 +216,8 @@ return next(err); } - if (!uid) { - setTimeout(function() { - next(null, false, '[[error:invalid-password]]'); - }, Math.floor((Math.random() * 1000) + 1500)); // Wait between 1-2.5 seconds before returning - return; + if(!uid) { + return next(null, false, '[[error:no-user]]'); } user.auth.logAttempt(uid, function(err) { From 74282ffeea64c5f70396cf5376b521d169d1d81a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 13:23:32 -0400 Subject: [PATCH 101/372] removed another unnecessary socket call --- public/src/forum/home.js | 12 ++---------- src/topics/create.js | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/public/src/forum/home.js b/public/src/forum/home.js index 461b5a9c8f..c770aceb36 100644 --- a/public/src/forum/home.js +++ b/public/src/forum/home.js @@ -24,16 +24,8 @@ define('forum/home', function() { }; home.onNewPost = function(data) { - - if (data && data.posts && data.posts.length) { - - socket.emit('posts.getCategory', data.posts[0].pid, function(err, cid) { - if (err) { - return; - } - - renderNewPost(cid, data.posts[0]); - }); + if (data && data.posts && data.posts.length && data.posts[0].topic) { + renderNewPost(data.posts[0].topic.cid, data.posts[0]); } }; diff --git a/src/topics/create.js b/src/topics/create.js index 60c41880b1..2feb2ae787 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -215,7 +215,7 @@ module.exports = function(Topics) { }, function(userInfo, next) { postData.user = userInfo[0]; - Topics.getTopicFields(tid, ['tid', 'title', 'slug'], next); + Topics.getTopicFields(tid, ['tid', 'title', 'slug', 'cid'], next); }, function(topicData, next) { topicData.title = validator.escape(topicData.title); From 26c0b097cfafc6e1f5c42940ae8808ba6d64bfcc Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 13:49:48 -0400 Subject: [PATCH 102/372] removed realtime update of anon count --- public/src/forum/users.js | 22 +--------------------- src/socket.io/index.js | 5 ----- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/public/src/forum/users.js b/public/src/forum/users.js index e30ab1ae33..4df2b9c5be 100644 --- a/public/src/forum/users.js +++ b/public/src/forum/users.js @@ -22,12 +22,8 @@ define('forum/users', function() { handleSearch(); - socket.removeListener('user.anonDisconnect', updateAnonCount); - socket.removeListener('user.anonConnect', updateAnonCount); - socket.removeListener('user.isOnline', onUserIsOnline); - socket.on('user.anonDisconnect', updateAnonCount); - socket.on('user.anonConnect', updateAnonCount); + socket.removeListener('user.isOnline', onUserIsOnline); socket.on('user.isOnline', onUserIsOnline); @@ -158,7 +154,6 @@ define('forum/users', function() { var section = getActiveSection(); if((section.indexOf('online') === 0 || section.indexOf('users') === 0) && !loadingMoreUsers) { updateUser(data); - updateAnonCount(); } } @@ -188,21 +183,6 @@ define('forum/users', function() { }); } - function updateAnonCount() { - var section = getActiveSection(); - if((section.indexOf('online') === 0 || section.indexOf('users') === 0) && !loadingMoreUsers) { - socket.emit('user.getOnlineAnonCount', {} , function(err, anonCount) { - - if(parseInt(anonCount, 10) > 0) { - $('#users-container .anon-user').removeClass('hide'); - $('#online_anon_count').html(anonCount); - } else { - $('#users-container .anon-user').addClass('hide'); - } - }); - } - } - function getActiveSection() { var url = window.location.href, parts = url.split('/'); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index c2aa360e01..b24c1d2132 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -99,7 +99,6 @@ Sockets.init = function(server) { }); }); } else { - socket.broadcast.emit('user.anonConnect'); socket.emit('event:connect', { status: 1, username: '[[global:guest]]', @@ -120,10 +119,6 @@ Sockets.init = function(server) { }); } - if (!uid) { - socket.broadcast.emit('user.anonDisconnect'); - } - emitOnlineUserCount(); for(var roomName in io.sockets.manager.roomClients[socket.id]) { From fb45aa92fd54737dfb7a0737a380363b8578a7aa Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Fri, 29 Aug 2014 06:48:22 -0500 Subject: [PATCH 103/372] Add upvote hook adhere to similar project style --- src/favourites.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/favourites.js b/src/favourites.js index c887c5c7e7..ecc61045c3 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -3,6 +3,7 @@ var async = require('async'), db = require('./database'), posts = require('./posts'), user = require('./user'), + plugins = require('./plugins'), meta = require('./meta'); (function (Favourites) { @@ -102,7 +103,14 @@ var async = require('async'), return callback(false); } - toggleVote('upvote', pid, uid, callback); + toggleVote('upvote', pid, uid, function(err, votes) { + if (err) return callback(err); + plugins.fireHook('action:upvote.post', { + pid: pid, + uid: uid + }); + callback(null, votes); + }); }; Favourites.downvote = function(pid, uid, callback) { From 440c78c949cef10aa65d967795a4a5b8eb2dc004 Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Tue, 2 Sep 2014 13:26:56 -0500 Subject: [PATCH 104/372] match `object.action` nomenclature --- src/favourites.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/favourites.js b/src/favourites.js index ecc61045c3..e2edc36c4f 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -105,7 +105,7 @@ var async = require('async'), toggleVote('upvote', pid, uid, function(err, votes) { if (err) return callback(err); - plugins.fireHook('action:upvote.post', { + plugins.fireHook('action:post.upvote', { pid: pid, uid: uid }); From 625766ba8583788f783a52800c757049fa383597 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 14:29:22 -0400 Subject: [PATCH 105/372] dont send typing notif on each keyup --- public/src/forum/chats.js | 10 ++++++---- public/src/modules/chat.js | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index cf35f8d5f8..6d4e0ed65a 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -49,11 +49,13 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { }); inputEl.on('keyup', function() { - if ($(this).val()) { - Chats.notifyTyping(Chats.getRecipientUid(), true); - } else { - Chats.notifyTyping(Chats.getRecipientUid(), false); + var val = !!$(this).val(); + if ((val && $(this).attr('data-typing') === 'true') || (!val && $(this).attr('data-typing') === 'false')) { + return; } + + Chats.notifyTyping(Chats.getRecipientUid(), val); + $(this).attr('data-typing', val); }); sendEl.on('click', function(e) { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 6484c458f9..8b6d1fe941 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -289,11 +289,13 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, }); input.off('keyup').on('keyup', function() { - if ($(this).val()) { - socket.emit('modules.chats.userStartTyping', {touid:chatModal.attr('touid'), fromUid: app.uid}); - } else { - Chats.notifyTyping(chatModal.attr('touid'), false); + var val = !!$(this).val(); + if ((val && $(this).attr('data-typing') === 'true') || (!val && $(this).attr('data-typing') === 'false')) { + return; } + + Chats.notifyTyping(chatModal.attr('touid'), val); + $(this).attr('data-typing', val); }); chatModal.find('#chat-message-send-btn').off('click').on('click', function(e){ From e495db3065c815b12ab701637b73a5a1fb29ade0 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 15:50:02 -0400 Subject: [PATCH 106/372] dont make a copy of chat message --- public/src/forum/chats.js | 2 +- public/src/modules/chat.js | 2 +- src/socket.io/modules.js | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index 6d4e0ed65a..273abeb509 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -91,7 +91,7 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { containerEl = $('.expanded-chat ul'); if (Chats.isCurrentChat(data.withUid)) { - newMessage = data.message.self === 0; + newMessage = data.self === 0; Chats.parseMessage(data.message, function(html) { var newMessage = $(html); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 8b6d1fe941..61a0cb3aec 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -66,7 +66,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, if (isSelf) { username = data.message.toUser.username; } - newMessage = data.message.self === 0; + newMessage = data.self === 0; if (module.modalExists(data.withUid)) { var modal = module.getModal(data.withUid); module.appendChatMessage(modal, data.message); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 1156b1c379..a9bdd9fc0f 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -202,16 +202,13 @@ SocketModules.chats.send = function(socket, data, callback) { sendChatNotification(socket.uid, touid, message); - // After-the-fact fixing of the "self" property for the message that goes to the receipient - var recipMessage = JSON.parse(JSON.stringify(message)); - recipMessage.self = 0; - // Recipient SocketModules.chats.pushUnreadCount(touid); server.getUserSockets(touid).forEach(function(s) { s.emit('event:chats.receive', { withUid: socket.uid, - message: recipMessage + message: recipMessage, + self: 0 }); }); @@ -220,7 +217,8 @@ SocketModules.chats.send = function(socket, data, callback) { server.getUserSockets(socket.uid).forEach(function(s) { s.emit('event:chats.receive', { withUid: touid, - message: message + message: message, + self: 1 }); }); }); From 6b39e328f508568d1d2ecf9b4e47f048fa0c3286 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 16:01:45 -0400 Subject: [PATCH 107/372] chat fix --- public/src/forum/chats.js | 2 +- public/src/modules/chat.js | 1 + src/socket.io/modules.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index 273abeb509..5ad1bd6229 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -92,7 +92,7 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { if (Chats.isCurrentChat(data.withUid)) { newMessage = data.self === 0; - + data.message.self = data.self; Chats.parseMessage(data.message, function(html) { var newMessage = $(html); newMessage.insertBefore(typingNotifEl); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 61a0cb3aec..62221d8cc1 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -63,6 +63,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, var username = data.message.fromUser.username; var isSelf = parseInt(data.message.fromUser.uid, 10) === parseInt(app.uid, 10); + data.message.self = data.self; if (isSelf) { username = data.message.toUser.username; } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index a9bdd9fc0f..0be1b64a5b 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -207,7 +207,7 @@ SocketModules.chats.send = function(socket, data, callback) { server.getUserSockets(touid).forEach(function(s) { s.emit('event:chats.receive', { withUid: socket.uid, - message: recipMessage, + message: message, self: 0 }); }); From 4f48f1865fdab063a89fbfbc713f3cab3b7fa8dd Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 2 Sep 2014 17:36:59 -0400 Subject: [PATCH 108/372] removed updateLastOnlineTime from every socket call --- src/socket.io/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index b24c1d2132..d85053e2cb 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -129,10 +129,6 @@ Sockets.init = function(server) { socket.on('*', function(payload, callback) { function callMethod(method) { - if(socket.uid) { - user.updateLastOnlineTime(socket.uid); - } - method.call(null, socket, payload.args.length ? payload.args[0] : null, function(err, result) { if (callback) { callback(err?{message:err.message}:null, result); @@ -145,7 +141,7 @@ Sockets.init = function(server) { } var parts = payload.name.toString().split('.'), - namespace = parts.slice(0, 1), + namespace = parts[0], methodToCall = parts.reduce(function(prev, cur) { if (prev !== null && prev[cur]) { return prev[cur]; From e6d0eede4fc542805c017213f73f242517863867 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 2 Sep 2014 18:36:28 -0400 Subject: [PATCH 109/372] pruning empy notifications for daily emailer --- src/user/jobs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/user/jobs.js b/src/user/jobs.js index 160f3bb33c..d06a8386cd 100644 --- a/src/user/jobs.js +++ b/src/user/jobs.js @@ -69,6 +69,10 @@ module.exports = function(User) { winston.error('[user/jobs] Could not send daily digests: ' + err.message); return next(err); } + + // Remove expired notifications + notifications = notifications.filter(Boolean); + // Turn relative URLs into absolute ones for(var i=0; i Date: Tue, 2 Sep 2014 23:35:24 -0400 Subject: [PATCH 110/372] testing --- app.js | 11 +++++++++++ src/webserver.js | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 9c836d2c44..cf0df49229 100644 --- a/app.js +++ b/app.js @@ -103,6 +103,15 @@ function loadConfig() { } function start() { + + var cluster = require('cluster'); + + if (cluster.isMaster) { + for(var i=0; i<2; ++i) { + cluster.fork(); + } + } else { + loadConfig(); winston.info('Time: ' + new Date()); @@ -174,6 +183,8 @@ function start() { }); }); }); + + } } function setup() { diff --git a/src/webserver.js b/src/webserver.js index 3278713dd1..76cba161a2 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -114,11 +114,11 @@ if(nconf.get('ssl')) { }); emitter.on('templates:compiled', function() { - if (process.send) { - callback(); - } else { + //if (process.send) { + // callback(); + //} else { module.exports.listen(); - } + //} }); }; From e00124007fd870b3b368e3f5214aeb4cd30e2385 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 01:06:17 -0400 Subject: [PATCH 111/372] testing 2 --- src/socket.io/index.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index d85053e2cb..8dc75a4b77 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -27,11 +27,22 @@ var io; Sockets.init = function(server) { + var RedisStore = require('socket.io/lib/stores/redis'), + redis = require('redis'), + pub = redis.createClient(), + sub = redis.createClient(), + client = redis.createClient(); + io = socketioWildcard(SocketIO).listen(server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], 'browser client minification': true, - resource: nconf.get('relative_path') + '/socket.io' + resource: nconf.get('relative_path') + '/socket.io', + 'store' : new RedisStore({ + redisPub : pub, + redisSub : sub, + redisClient : client + }), }); Sockets.server = io; @@ -280,13 +291,15 @@ Sockets.isUsersOnline = function(uids, callback) { return callback(null, []); } - sockets = sockets.map(function(s) { - return s.uid; - }); + sockets = sockets.map(function(s) { + return s.uid; + }); - callback(null, uids.map(function(uid) { + var data = uids.map(function(uid) { return sockets.indexOf(parseInt(uid, 10)) !== -1; - })); + }); + + callback(null, data); }; Sockets.updateRoomBrowsingText = updateRoomBrowsingText; From 94d050fd4f9f1a0acdb182fc98e93d80dc8b143c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 01:13:28 -0400 Subject: [PATCH 112/372] removed helpers.done --- src/database/mongo/hash.js | 6 ++++-- src/database/mongo/helpers.js | 8 +------- src/database/mongo/list.js | 6 ++++-- src/database/mongo/main.js | 15 ++++++++++----- src/database/mongo/sets.js | 6 ++++-- src/database/mongo/sorted.js | 9 ++++++--- 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index cf6c55bc58..92db4662b6 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -4,8 +4,9 @@ module.exports = function(db, module) { var helpers = module.helpers.mongo; module.setObject = function(key, data, callback) { + callback = callback || helpers.noop; data._key = key; - db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, helpers.done(callback)); + db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, callback); }; module.setObjectField = function(key, field, value, callback) { @@ -121,10 +122,11 @@ module.exports = function(db, module) { }; module.deleteObjectField = function(key, field, callback) { + callback = callback || helpers.noop; var data = {}; field = helpers.fieldToString(field); data[field] = ''; - db.collection('objects').update({_key:key}, {$unset : data}, helpers.done(callback)); + db.collection('objects').update({_key:key}, {$unset : data}, callback); }; module.incrObjectField = function(key, field, callback) { diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index ca44a2cb46..689a34bbf0 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -31,12 +31,6 @@ helpers.valueToString = function(value) { return value.toString(); }; -helpers.done = function(cb) { - return function(err, result) { - if (typeof cb === 'function') { - cb(err, result); - } - }; -}; +helpers.noop = function() {}; module.exports = helpers; \ No newline at end of file diff --git a/src/database/mongo/list.js b/src/database/mongo/list.js index 9d0a9bdcfa..46c53bfffa 100644 --- a/src/database/mongo/list.js +++ b/src/database/mongo/list.js @@ -21,8 +21,9 @@ module.exports = function(db, module) { }; module.listAppend = function(key, value, callback) { + callback = callback || helpers.noop; value = helpers.valueToString(value); - db.collection('objects').update({ _key: key }, { $push: { array: value } }, {upsert:true, w:1}, helpers.done(callback)); + db.collection('objects').update({ _key: key }, { $push: { array: value } }, {upsert:true, w:1}, callback); }; module.listRemoveLast = function(key, callback) { @@ -50,9 +51,10 @@ module.exports = function(db, module) { }; module.listRemoveAll = function(key, value, callback) { + callback = callback || helpers.noop; value = helpers.valueToString(value); - db.collection('objects').update({_key: key }, { $pull: { array: value } }, helpers.done(callback)); + db.collection('objects').update({_key: key }, { $pull: { array: value } }, callback); }; module.getListRange = function(key, start, stop, callback) { diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 22155ff6d2..3c24830ffa 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -40,11 +40,13 @@ module.exports = function(db, module) { }; module.searchRemove = function(key, id, callback) { - db.collection('search').remove({id:id, key:key}, helpers.done(callback)); + callback = callback || helpers.noop; + db.collection('search').remove({id:id, key:key}, callback); }; module.flushdb = function(callback) { - db.dropDatabase(helpers.done(callback)); + callback = callback || helpers.noop; + db.dropDatabase(callback); }; module.info = function(callback) { @@ -68,7 +70,8 @@ module.exports = function(db, module) { }; module.delete = function(key, callback) { - db.collection('objects').remove({_key:key}, helpers.done(callback)); + callback = callback || helpers.noop; + db.collection('objects').remove({_key:key}, callback); }; module.get = function(key, callback) { @@ -81,11 +84,13 @@ module.exports = function(db, module) { }; module.increment = function(key, callback) { - db.collection('objects').update({_key: key}, { $inc: { value: 1 } }, helpers.done(callback)); + callback = callback || helpers.noop; + db.collection('objects').update({_key: key}, { $inc: { value: 1 } }, callback); }; module.rename = function(oldKey, newKey, callback) { - db.collection('objects').update({_key: oldKey}, {$set:{_key: newKey}}, helpers.done(callback)); + callback = callback || helpers.noop; + db.collection('objects').update({_key: oldKey}, {$set:{_key: newKey}}, callback); }; module.expire = function(key, seconds, callback) { diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 6b00ec5142..264a950165 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -4,6 +4,7 @@ module.exports = function(db, module) { var helpers = module.helpers.mongo; module.setAdd = function(key, value, callback) { + callback = callback || helpers.noop; if(!Array.isArray(value)) { value = [value]; } @@ -23,10 +24,11 @@ module.exports = function(db, module) { }, { upsert: true, w: 1 - }, helpers.done(callback)); + }, callback); }; module.setRemove = function(key, value, callback) { + callback = callback || helpers.noop; if(!Array.isArray(value)) { value = [value]; } @@ -35,7 +37,7 @@ module.exports = function(db, module) { array[index] = helpers.valueToString(element); }); - db.collection('objects').update({_key: key}, {$pullAll: {members: value}}, helpers.done(callback)); + db.collection('objects').update({_key: key}, {$pullAll: {members: value}}, callback); }; module.isSetMember = function(key, value, callback) { diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 8813d77b36..6a9064c87b 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -6,25 +6,28 @@ module.exports = function(db, module) { var helpers = module.helpers.mongo; module.sortedSetAdd = function(key, score, value, callback) { + callback = callback || helpers.noop; value = helpers.valueToString(value); var data = { score: parseInt(score, 10), value: value }; - db.collection('objects').update({_key: key, value: value}, {$set: data}, {upsert:true, w: 1}, helpers.done(callback)); + db.collection('objects').update({_key: key, value: value}, {$set: data}, {upsert:true, w: 1}, callback); }; module.sortedSetRemove = function(key, value, callback) { + callback = callback || helpers.noop; value = helpers.valueToString(value); - db.collection('objects').remove({_key: key, value: value}, helpers.done(callback)); + db.collection('objects').remove({_key: key, value: value}, callback); }; module.sortedSetsRemove = function(keys, value, callback) { + callback = callback || helpers.noop; value = helpers.valueToString(value); - db.collection('objects').remove({_key: {$in: keys}, value: value}, helpers.done(callback)); + db.collection('objects').remove({_key: {$in: keys}, value: value}, callback); }; function getSortedSetRange(key, start, stop, sort, withScores, callback) { From 58a73b52b50de926d20689ea972d6a3c0eb8dc16 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2014 01:24:26 -0400 Subject: [PATCH 113/372] work-in-progress commit to get cluster module working with loader.js --- loader.js | 238 +++++++++++++++++++++++----------------- src/middleware/index.js | 9 +- src/routes/index.js | 7 -- src/webserver.js | 11 +- 4 files changed, 155 insertions(+), 110 deletions(-) diff --git a/loader.js b/loader.js index 48d4929238..6d65b203b5 100644 --- a/loader.js +++ b/loader.js @@ -2,109 +2,147 @@ var nconf = require('nconf'), fs = require('fs'), - pidFilePath = __dirname + '/pidfile', - output = fs.openSync(__dirname + '/logs/output.log', 'a'), - start = function() { - var nbb_start = function(callback) { - if (timesStarted > 3) { - console.log('\n[loader] Experienced three start attempts in 10 seconds, most likely an error on startup. Halting.'); - return nbb_stop(); - } - - timesStarted++; - if (startTimer) { - clearTimeout(startTimer); - } - startTimer = setTimeout(resetTimer, 1000*10); - - if (nbb) { - nbbOld = nbb; - } - - nbb = require('child_process').fork('./app', process.argv.slice(2), { - env: process.env - }); - - nbb.on('message', function(message) { - if (message && typeof message === 'object' && message.action) { - switch (message.action) { - case 'ready': - if (!callback) return nbb.send('bind'); - callback(); - break; - case 'restart': - nbb_restart(); - break; - } - } - }); - - nbb.on('exit', function(code, signal) { - if (code) { - nbb_start(); - } else { - nbb_stop(); - } - }); - }, - nbb_stop = function() { - if (startTimer) { - clearTimeout(startTimer); - } - - nbb.kill(); - if (fs.existsSync(pidFilePath)) { - var pid = parseInt(fs.readFileSync(pidFilePath, { encoding: 'utf-8' }), 10); - if (process.pid === pid) { - fs.unlinkSync(pidFilePath); - } - } - }, - nbb_restart = function() { - nbb_start(function() { - nbbOld.removeAllListeners('exit').on('exit', function() { - nbb.send('bind'); - }); - nbbOld.kill(); - }); - }, - resetTimer = function() { - clearTimeout(startTimer); - timesStarted = 0; - }, - timesStarted = 0, - startTimer; - - process.on('SIGINT', nbb_stop); - process.on('SIGTERM', nbb_stop); - process.on('SIGHUP', nbb_restart); - - nbb_start(); - }, - nbb, nbbOld; + cluster = require('cluster'), + numCPUs = require('os').cpus().length; + + /* TODO + * pidFile and reset timer + * logging + * handling SIGHUP + * restart signal from child + */ + + // pidFilePath = __dirname + '/pidfile', + // output = fs.openSync(__dirname + '/logs/output.log', 'a'), + // start = function() { + // var nbb_start = function(callback) { + // if (timesStarted > 3) { + // console.log('\n[loader] Experienced three start attempts in 10 seconds, most likely an error on startup. Halting.'); + // return nbb_stop(); + // } + + // timesStarted++; + // if (startTimer) { + // clearTimeout(startTimer); + // } + // startTimer = setTimeout(resetTimer, 1000*10); + + // if (nbb) { + // nbbOld = nbb; + // } + + // nbb = require('child_process').fork('./app', process.argv.slice(2), { + // env: process.env + // }); + + // nbb.on('message', function(message) { + // if (message && typeof message === 'object' && message.action) { + // switch (message.action) { + // case 'ready': + // if (!callback) return nbb.send('bind'); + // callback(); + // break; + // case 'restart': + // nbb_restart(); + // break; + // } + // } + // }); + + // nbb.on('exit', function(code, signal) { + // if (code) { + // nbb_start(); + // } else { + // nbb_stop(); + // } + // }); + // }, + // nbb_stop = function() { + // if (startTimer) { + // clearTimeout(startTimer); + // } + + // nbb.kill(); + // if (fs.existsSync(pidFilePath)) { + // var pid = parseInt(fs.readFileSync(pidFilePath, { encoding: 'utf-8' }), 10); + // if (process.pid === pid) { + // fs.unlinkSync(pidFilePath); + // } + // } + // }, + // nbb_restart = function() { + // nbb_start(function() { + // nbbOld.removeAllListeners('exit').on('exit', function() { + // nbb.send('bind'); + // }); + // nbbOld.kill(); + // }); + // }, + // resetTimer = function() { + // clearTimeout(startTimer); + // timesStarted = 0; + // }, + // timesStarted = 0, + // startTimer; + + // process.on('SIGINT', nbb_stop); + // process.on('SIGTERM', nbb_stop); + // process.on('SIGHUP', nbb_restart); + + // nbb_start(); + // }, + // nbb, nbbOld; nconf.argv(); -// Start the daemon! -if (nconf.get('daemon') !== false) { - // Check for a still-active NodeBB process - if (fs.existsSync(pidFilePath)) { - try { - var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); - process.kill(pid, 0); - process.exit(); - } catch (e) { - fs.unlinkSync(pidFilePath); - } - } +cluster.setupMaster({ + exec: "app.js", + silent: true +}); + +for(var x=0;x Date: Wed, 3 Sep 2014 11:30:07 -0400 Subject: [PATCH 114/372] load 50 topics in sitemap instead of all --- src/sitemap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sitemap.js b/src/sitemap.js index 5db793a128..288d75bb13 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -49,7 +49,7 @@ var path = require('path'), }, function(next) { var topicUrls = []; - topics.getTopicsFromSet(0, 'topics:recent', 0, -1, function(err, data) { + topics.getTopicsFromSet(0, 'topics:recent', 0, 49, function(err, data) { if (err) { return next(err); } From 2073d994fc753595169485bd06263e9470ad1b4d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 12:00:25 -0400 Subject: [PATCH 115/372] limit topicsperpage postsPerPage to 20 --- src/user/settings.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/user/settings.js b/src/user/settings.js index 2cf9fba084..8f24f54a05 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -28,8 +28,8 @@ module.exports = function(User) { settings.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab ? parseInt(settings.openOutgoingLinksInNewTab, 10) !== 0 : false; settings.dailyDigestFreq = settings.dailyDigestFreq || 'off'; settings.usePagination = settings.usePagination ? parseInt(settings.usePagination, 10) === 1 : parseInt(meta.config.usePagination, 10) === 1; - settings.topicsPerPage = settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20; - settings.postsPerPage = settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10; + settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20, 20); + settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10, 20); settings.notificationSounds = settings.notificationSounds ? parseInt(settings.notificationSounds, 10) === 1 : true; settings.language = settings.language || meta.config.defaultLang || 'en_GB'; settings.topicPostSort = settings.topicPostSort || meta.config.topicPostSort || 'oldest_to_newest'; @@ -79,8 +79,8 @@ module.exports = function(User) { openOutgoingLinksInNewTab: data.openOutgoingLinksInNewTab, dailyDigestFreq: data.dailyDigestFreq || 'off', usePagination: data.usePagination, - topicsPerPage: data.topicsPerPage, - postsPerPage: data.postsPerPage, + topicsPerPage: Math.min(data.topicsPerPage, 20), + postsPerPage: Math.min(data.postsPerPage, 20), notificationSounds: data.notificationSounds, language: data.language || meta.config.defaultLang, followTopicsOnCreate: data.followTopicsOnCreate, From 5362863588980bf04717e51f199e2e6e08d635b4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 12:28:24 -0400 Subject: [PATCH 116/372] debug --- src/socket.io/index.js | 12 +++++++++++- src/socket.io/user.js | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 8dc75a4b77..50ce5023a5 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -388,7 +388,17 @@ function emitOnlineUserCount(callback) { } } - +Sockets.debug = function(callback) { + var uids = []; + //var clients = io.sockets.clients('uid_1'); + var clients = io.sockets.clients('/'); + for(var i=0; i Date: Wed, 3 Sep 2014 12:31:37 -0400 Subject: [PATCH 117/372] removed cluster from app.js --- app.js | 10 ---------- loader.js | 2 +- src/webserver.js | 12 ++++++------ 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app.js b/app.js index cf0df49229..bf137a3ec0 100644 --- a/app.js +++ b/app.js @@ -104,14 +104,6 @@ function loadConfig() { function start() { - var cluster = require('cluster'); - - if (cluster.isMaster) { - for(var i=0; i<2; ++i) { - cluster.fork(); - } - } else { - loadConfig(); winston.info('Time: ' + new Date()); @@ -183,8 +175,6 @@ function start() { }); }); }); - - } } function setup() { diff --git a/loader.js b/loader.js index 6d65b203b5..304ef1b4b6 100644 --- a/loader.js +++ b/loader.js @@ -97,7 +97,7 @@ nconf.argv(); cluster.setupMaster({ exec: "app.js", - silent: true + silent: false }); for(var x=0;x Date: Wed, 3 Sep 2014 12:49:34 -0400 Subject: [PATCH 118/372] introducing "sort" querystring parameter in topic route, so that sorting can be overridden via query string. --- src/controllers/topics.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index e482efd412..bfc2b4e396 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -16,6 +16,7 @@ var topicsController = {}, topicsController.get = function(req, res, next) { var tid = req.params.topic_id, page = req.query.page || 1, + sort = req.query.sort, uid = req.user ? req.user.uid : 0, userPrivileges; @@ -45,7 +46,15 @@ topicsController.get = function(req, res, next) { var set = 'tid:' + tid + ':posts', reverse = false; - if (settings.topicPostSort === 'newest_to_oldest') { + // `sort` qs has priority over user setting + if (sort === 'oldest_to_newest') { + reverse = false; + } else if (sort === 'newest_to_oldest') { + reverse = true; + } else if (sort === 'most_votes') { + reverse = true; + set = 'tid:' + tid + ':posts:votes'; + } else if (settings.topicPostSort === 'newest_to_oldest') { reverse = true; } else if (settings.topicPostSort === 'most_votes') { reverse = true; From baf72249b6fd9127fa897bfcd9d10bbd4dfbc2eb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2014 12:54:26 -0400 Subject: [PATCH 119/372] made uid parameter optional in getPidIndex --- src/posts.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/posts.js b/src/posts.js index 10d299b4b3..96a00f1397 100644 --- a/src/posts.js +++ b/src/posts.js @@ -532,10 +532,20 @@ var async = require('async'), } Posts.getPidIndex = function(pid, uid, callback) { - callback = callback || function() {}; + // Making uid optional + if ((!uid && !callback) || typeof uid === 'function') { + callback = uid; + uid = undefined; + } + async.parallel({ settings: function(next) { - user.getSettings(uid, next); + if (uid) { + user.getSettings(uid, next); + } else { + // No uid specified, so return empty object so that the check below will assume regular topic post sorting + next(null, {}); + } }, tid: function(next) { Posts.getPostField(pid, 'tid', next); From a5423aff167a91bf1a09c9175d14bfd616a0bcaa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2014 12:55:31 -0400 Subject: [PATCH 120/372] getPidIndex callback if none specified --- src/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 96a00f1397..146b08f977 100644 --- a/src/posts.js +++ b/src/posts.js @@ -534,7 +534,7 @@ var async = require('async'), Posts.getPidIndex = function(pid, uid, callback) { // Making uid optional if ((!uid && !callback) || typeof uid === 'function') { - callback = uid; + callback = uid || function() {}; uid = undefined; } From b5da0e515bbae4ad63d0bbe290b1b17d1cfd6f3c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2014 13:18:15 -0400 Subject: [PATCH 121/372] upping mentions minver for 0.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f76f262134..73e81c4b63 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "nconf": "~0.6.7", "nodebb-plugin-dbsearch": "0.0.13", "nodebb-plugin-markdown": "~0.5.0", - "nodebb-plugin-mentions": "~0.5.0", + "nodebb-plugin-mentions": "~0.6.0", "nodebb-plugin-soundpack-default": "~0.1.1", "nodebb-theme-lavender": "~0.0.74", "nodebb-theme-vanilla": "~0.0.111", From c40355b81605f7930f72cf3c44c6cafdee539e68 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 14:07:12 -0400 Subject: [PATCH 122/372] removed debug --- src/socket.io/index.js | 12 ------------ src/socket.io/user.js | 4 ---- 2 files changed, 16 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 50ce5023a5..20ff8efa76 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -388,18 +388,6 @@ function emitOnlineUserCount(callback) { } } -Sockets.debug = function(callback) { - var uids = []; - //var clients = io.sockets.clients('uid_1'); - var clients = io.sockets.clients('/'); - for(var i=0; i Date: Wed, 3 Sep 2014 14:40:44 -0400 Subject: [PATCH 123/372] closes #2039 --- src/controllers/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/index.js b/src/controllers/index.js index 8f1eb2ff2f..7d7ea579ac 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -98,6 +98,8 @@ Controllers.search = function(req, res, next) { return res.redirect('/404'); } + req.params.term = req.params.term.replace(/"/g, '/"'); + search.search(req.params.term, uid, function(err, results) { if (err) { return next(err); From 4736a68534e990b1efca46cf2fa6a472bf037f02 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 15:16:41 -0400 Subject: [PATCH 124/372] dont create term object on each call --- src/posts.js | 63 +------------------------------------- src/posts/recent.js | 70 +++++++++++++++++++++++++++++++++++++++++++ src/topics/popular.js | 12 ++++---- src/topics/recent.js | 15 +++++----- 4 files changed, 84 insertions(+), 76 deletions(-) create mode 100644 src/posts/recent.js diff --git a/src/posts.js b/src/posts.js index 146b08f977..57a97e71ca 100644 --- a/src/posts.js +++ b/src/posts.js @@ -25,6 +25,7 @@ var async = require('async'), websockets = require('./socket.io'); (function(Posts) { + require('./posts/recent')(Posts); require('./posts/delete')(Posts); Posts.create = function(data, callback) { @@ -160,68 +161,6 @@ var async = require('async'), }); }; - Posts.getRecentPosts = function(uid, start, stop, term, callback) { - var terms = { - day: 86400000, - week: 604800000, - month: 2592000000 - }; - - var since = terms.day; - if (terms[term]) { - since = terms[term]; - } - - var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; - - db.getSortedSetRevRangeByScore('posts:pid', start, count, Infinity, Date.now() - since, function(err, pids) { - if(err) { - return callback(err); - } - - if (!Array.isArray(pids) || !pids.length) { - return callback(null, []); - } - - privileges.posts.filter('read', pids, uid, function(err, pids) { - if (err) { - return callback(err); - } - Posts.getPostSummaryByPids(pids, uid, {stripTags: true}, callback); - }); - }); - }; - - Posts.getRecentPosterUids = function(start, end, callback) { - db.getSortedSetRevRange('posts:pid', start, end, function(err, pids) { - if (err) { - return callback(err); - } - - if (!Array.isArray(pids) || !pids.length) { - return callback(null, []); - } - - pids = pids.map(function(pid) { - return 'post:' + pid; - }); - - db.getObjectsFields(pids, ['uid'], function(err, postData) { - if (err) { - return callback(err); - } - - postData = postData.map(function(post) { - return post && post.uid; - }).filter(function(value, index, array) { - return value && array.indexOf(value) === index; - }); - - callback(null, postData); - }); - }); - }; - Posts.getUserInfoForPosts = function(uids, callback) { async.parallel({ groups: function(next) { diff --git a/src/posts/recent.js b/src/posts/recent.js new file mode 100644 index 0000000000..7f3b43314e --- /dev/null +++ b/src/posts/recent.js @@ -0,0 +1,70 @@ +'use strict'; + +var db = require('../database'), + privileges = require('../privileges'); + + +module.exports = function(Posts) { + var terms = { + day: 86400000, + week: 604800000, + month: 2592000000 + }; + + Posts.getRecentPosts = function(uid, start, stop, term, callback) { + var since = terms.day; + if (terms[term]) { + since = terms[term]; + } + + var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; + + db.getSortedSetRevRangeByScore('posts:pid', start, count, Infinity, Date.now() - since, function(err, pids) { + if (err) { + return callback(err); + } + + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } + + privileges.posts.filter('read', pids, uid, function(err, pids) { + if (err) { + return callback(err); + } + Posts.getPostSummaryByPids(pids, uid, {stripTags: true}, callback); + }); + }); + }; + + Posts.getRecentPosterUids = function(start, end, callback) { + db.getSortedSetRevRange('posts:pid', start, end, function(err, pids) { + if (err) { + return callback(err); + } + + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } + + pids = pids.map(function(pid) { + return 'post:' + pid; + }); + + db.getObjectsFields(pids, ['uid'], function(err, postData) { + if (err) { + return callback(err); + } + + postData = postData.map(function(post) { + return post && post.uid; + }).filter(function(value, index, array) { + return value && array.indexOf(value) === index; + }); + + callback(null, postData); + }); + }); + }; + +}; diff --git a/src/topics/popular.js b/src/topics/popular.js index 167bf1cb83..2dadb9fee6 100644 --- a/src/topics/popular.js +++ b/src/topics/popular.js @@ -7,15 +7,15 @@ var async = require('async'), module.exports = function(Topics) { + var terms = { + daily: 'day', + weekly: 'week', + monthly: 'month', + yearly: 'year' + }; Topics.getPopular = function(term, uid, count, callback) { count = parseInt(count, 10) || 20; - var terms = { - daily: 'day', - weekly: 'week', - monthly: 'month', - yearly: 'year' - }; if (term === 'alltime') { return getAllTimePopular(uid, count, callback); diff --git a/src/topics/recent.js b/src/topics/recent.js index 99ff00107b..8a3d8011e8 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -5,6 +5,12 @@ var db = require('./../database'); module.exports = function(Topics) { + var terms = { + day: 86400000, + week: 604800000, + month: 2592000000, + year: 31104000000 + }; Topics.getLatestTopics = function(uid, start, end, term, callback) { Topics.getLatestTids(start, end, term, function(err, tids) { @@ -17,15 +23,8 @@ module.exports = function(Topics) { }; Topics.getLatestTids = function(start, end, term, callback) { - var terms = { - day: 86400000, - week: 604800000, - month: 2592000000, - year: 31104000000 - }; - var since = terms.day; - if(terms[term]) { + if (terms[term]) { since = terms[term]; } From de41896770b625678ca1e4f6b0f79d9b5aa6473e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 3 Sep 2014 15:23:40 -0400 Subject: [PATCH 125/372] restart handling and crash detection --- app.js | 1 - loader.js | 77 +++++++++++++++++++++++++++++------------ src/middleware/index.js | 2 +- src/routes/debug.js | 6 +++- src/webserver.js | 4 +-- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/app.js b/app.js index bf137a3ec0..c09f2d67c3 100644 --- a/app.js +++ b/app.js @@ -17,7 +17,6 @@ along with this program. If not, see . */ - "use strict"; /*global require, global, process*/ diff --git a/loader.js b/loader.js index 304ef1b4b6..e38a3984c1 100644 --- a/loader.js +++ b/loader.js @@ -3,6 +3,7 @@ var nconf = require('nconf'), fs = require('fs'), cluster = require('cluster'), + async = require('async'), numCPUs = require('os').cpus().length; /* TODO @@ -93,35 +94,65 @@ var nconf = require('nconf'), // }, // nbb, nbbOld; -nconf.argv(); +var Loader = { + timesStarted: 0 +}; -cluster.setupMaster({ - exec: "app.js", - silent: false -}); +Loader.init = function() { + nconf.argv(); -for(var x=0;x Date: Wed, 3 Sep 2014 17:22:29 -0400 Subject: [PATCH 126/372] removed getUserSockets for chat emit to users rooms directly --- loader.js | 2 +- src/socket.io/modules.js | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/loader.js b/loader.js index e38a3984c1..f5399bd899 100644 --- a/loader.js +++ b/loader.js @@ -106,7 +106,7 @@ Loader.init = function() { silent: true }); - for(var x=0;x Date: Wed, 3 Sep 2014 17:23:47 -0400 Subject: [PATCH 127/372] added back num cpus --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index f5399bd899..e38a3984c1 100644 --- a/loader.js +++ b/loader.js @@ -106,7 +106,7 @@ Loader.init = function() { silent: true }); - for(var x=0;x<4;x++) { + for(var x=0;x Date: Wed, 3 Sep 2014 18:07:56 -0400 Subject: [PATCH 128/372] chat typing notif use room --- src/socket.io/modules.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index de9936db6e..12878f7d17 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -266,9 +266,7 @@ function sendTypingNotification(event, socket, data, callback) { if (!socket.uid || !data) { return; } - server.getUserSockets(data.touid).forEach(function(socket) { - socket.emit(event, data.fromUid); - }); + server.in('uid_' + data.touid).emit(event, data.fromUid); } SocketModules.chats.list = function(socket, data, callback) { From f609b1e45d46f02099ec6f493c16a0d6c8cc4a2c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 3 Sep 2014 18:08:47 -0400 Subject: [PATCH 129/372] calololback (how the hell was this working?) --- src/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics.js b/src/topics.js index f5fe901c59..99a1c6b761 100644 --- a/src/topics.js +++ b/src/topics.js @@ -302,7 +302,7 @@ var async = require('async'), Topics.getMainPost = function(tid, uid, callback) { Topics.getMainPosts([tid], uid, function(err, mainPosts) { - calllback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null); + callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null); }); }; From 10b30e303be8dcc33a992697f80a0ea66058217d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 19:38:48 -0400 Subject: [PATCH 130/372] mongo posting fix --- src/database/level/hash.js | 4 ++-- src/database/level/list.js | 4 ++-- src/database/level/sets.js | 8 +++++--- src/database/mongo.js | 5 +++-- src/database/mongo/hash.js | 4 +++- src/database/mongo/sets.js | 4 +++- src/database/redis/hash.js | 4 +++- src/database/redis/sets.js | 5 ++++- src/posts.js | 2 +- src/topics/create.js | 2 +- 10 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/database/level/hash.js b/src/database/level/hash.js index c321fc9dae..b35a1b1193 100644 --- a/src/database/level/hash.js +++ b/src/database/level/hash.js @@ -18,7 +18,7 @@ module.exports = function(db, module) { } ], function(err) { if (typeof callback === 'function') { - callback(err, obj); + callback(err); } }); }; @@ -42,7 +42,7 @@ module.exports = function(db, module) { if (typeof callback === 'function') { callback(err, obj); } - }); + }); } else { if (typeof callback === 'function') { callback(err, {}); diff --git a/src/database/level/list.js b/src/database/level/list.js index 28a899f001..ca579178f0 100644 --- a/src/database/level/list.js +++ b/src/database/level/list.js @@ -2,7 +2,7 @@ module.exports = function(db, module) { var helpers = module.helpers.level; - + module.listPrepend = function(key, value, callback) { module.getListRange(key, 0, -1, function(err, list) { var arr = list || []; @@ -17,7 +17,7 @@ module.exports = function(db, module) { arr.push(value); module.set(key, arr, function(err) { if (typeof callback === 'function') { - callback(err, list); + callback(err); } }); }); diff --git a/src/database/level/sets.js b/src/database/level/sets.js index 5d6ffabf6c..df10f4a632 100644 --- a/src/database/level/sets.js +++ b/src/database/level/sets.js @@ -6,13 +6,15 @@ module.exports = function(db, module) { var helpers = module.helpers.level; module.setAdd = function(key, value, callback) { + callback = callback || function() {}; module.getListRange(key, 0, -1, function(err, set) { + if (err) { + return callback(err); + } if (set.indexOf(value) === -1) { module.listAppend(key, value, callback); } else { - if (typeof callback === 'function') { - callback(null, []); // verify if it sends back true on redis? - } + callback(null); } }); }; diff --git a/src/database/mongo.js b/src/database/mongo.js index affb658dba..9976d323ab 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -38,6 +38,9 @@ } ]; + module.helpers = module.helpers || {}; + module.helpers.mongo = require('./mongo/helpers'); + module.init = function(callback) { try { mongoClient = require('mongodb').MongoClient; @@ -116,7 +119,5 @@ db.close(); }; - module.helpers = module.helpers || {}; - module.helpers.mongo = require('./mongo/helpers'); }(exports)); diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 92db4662b6..78b2e9a61c 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -6,7 +6,9 @@ module.exports = function(db, module) { module.setObject = function(key, data, callback) { callback = callback || helpers.noop; data._key = key; - db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, callback); + db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err) { + callback(err); + }); }; module.setObjectField = function(key, field, value, callback) { diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 264a950165..e7d606092a 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -24,7 +24,9 @@ module.exports = function(db, module) { }, { upsert: true, w: 1 - }, callback); + }, function(err) { + callback(err); + }); }; module.setRemove = function(key, value, callback) { diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 63a85f77ec..b4fb033348 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -3,7 +3,9 @@ module.exports = function(redisClient, module) { module.setObject = function(key, data, callback) { callback = callback || function() {}; - redisClient.hmset(key, data, callback); + redisClient.hmset(key, data, function(err) { + callback(err); + }); }; module.setObjectField = function(key, field, value, callback) { diff --git a/src/database/redis/sets.js b/src/database/redis/sets.js index 88db7ad36a..2ceb2cb252 100644 --- a/src/database/redis/sets.js +++ b/src/database/redis/sets.js @@ -2,7 +2,10 @@ module.exports = function(redisClient, module) { module.setAdd = function(key, value, callback) { - redisClient.sadd(key, value, callback); + callback = callback || function() {}; + redisClient.sadd(key, value, function(err) { + callback(err); + }); }; module.setRemove = function(key, value, callback) { diff --git a/src/posts.js b/src/posts.js index 57a97e71ca..c70002f4d0 100644 --- a/src/posts.js +++ b/src/posts.js @@ -69,7 +69,7 @@ var async = require('async'), function(postData, next) { db.setObject('post:' + postData.pid, postData, next); }, - function(result, next) { + function(next) { db.sortedSetAdd('posts:pid', timestamp, postData.pid); db.incrObjectField('global', 'postCount'); diff --git a/src/topics/create.js b/src/topics/create.js index 2feb2ae787..7d35e9ba92 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -210,7 +210,7 @@ module.exports = function(Topics) { function(next) { Topics.markAsRead(tid, uid, next); }, - function(result, next) { + function(next) { posts.getUserInfoForPosts([postData.uid], next); }, function(userInfo, next) { From 84d0e643610f3c8cc06766c7e40b0033c6f036ed Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 20:19:51 -0400 Subject: [PATCH 131/372] fix topic move --- public/src/forum/topic/move.js | 2 +- src/database/mongo/sorted.js | 8 ++++++-- src/database/redis/sorted.js | 9 +++++++-- src/socket.io/index.js | 2 +- src/threadTools.js | 6 +++--- tests/database.js | 6 +++--- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/public/src/forum/topic/move.js b/public/src/forum/topic/move.js index 11f82c85f3..f0dec0e9b7 100644 --- a/public/src/forum/topic/move.js +++ b/public/src/forum/topic/move.js @@ -43,7 +43,7 @@ define('forum/topic/move', function() { renderCategories(categories); - modal.find('.category-list').on('click', 'li[data-cid]', function(e) { + modal.on('click', '.category-list li[data-cid]', function(e) { selectCategory($(this)); }); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 6a9064c87b..e2bc988f81 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -13,14 +13,18 @@ module.exports = function(db, module) { value: value }; - db.collection('objects').update({_key: key, value: value}, {$set: data}, {upsert:true, w: 1}, callback); + db.collection('objects').update({_key: key, value: value}, {$set: data}, {upsert:true, w: 1}, function(err) { + callback(err); + }); }; module.sortedSetRemove = function(key, value, callback) { callback = callback || helpers.noop; value = helpers.valueToString(value); - db.collection('objects').remove({_key: key, value: value}, callback); + db.collection('objects').remove({_key: key, value: value}, function(err) { + callback(err); + }); }; module.sortedSetsRemove = function(keys, value, callback) { diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index ef29b3b935..8dfc560d2b 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -3,11 +3,16 @@ module.exports = function(redisClient, module) { module.sortedSetAdd = function(key, score, value, callback) { callback = callback || function() {}; - redisClient.zadd(key, score, value, callback); + redisClient.zadd(key, score, value, function(err) { + callback(err); + }); }; module.sortedSetRemove = function(key, value, callback) { - redisClient.zrem(key, value, callback); + callback = callback || function() {}; + redisClient.zrem(key, value, function(err) { + callback(err); + }); }; module.sortedSetsRemove = function(keys, value, callback) { diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 20ff8efa76..5fc81fbbef 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -85,7 +85,7 @@ Sockets.init = function(server) { if (uid) { - db.sortedSetAdd('users:online', Date.now(), uid, function(err, data) { + db.sortedSetAdd('users:online', Date.now(), uid, function(err) { socket.join('uid_' + uid); async.parallel({ diff --git a/src/threadTools.js b/src/threadTools.js index edd478c26a..613a456443 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -189,12 +189,12 @@ var winston = require('winston'), topic = topicData; db.sortedSetRemove('categories:' + topicData.cid + ':tid', tid, next); }, - function(result, next) { + function(next) { var timestamp = parseInt(topic.pinned, 10) ? Math.pow(2, 53) : topic.lastposttime; db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid, next); } - ], function(err, result) { - if(err) { + ], function(err) { + if (err) { return callback(err); } var oldCid = topic.cid; diff --git a/tests/database.js b/tests/database.js index 5153bc0c8d..aa252243b4 100644 --- a/tests/database.js +++ b/tests/database.js @@ -271,14 +271,14 @@ describe('Test database', function() { it('should not throw err', function(done) { function sortedSetAdd(callback) { - db.sortedSetAdd('sortedSet3', 12, 5, function(err, data) { - callback(err, {'sortedSetAdd': data}); + db.sortedSetAdd('sortedSet3', 12, 5, function(err) { + callback(err); }); } function sortedSetRemove(callback) { db.sortedSetRemove('sortedSet3', 12, function(err, data) { - callback(err, {'sortedSetRemove': data}); + callback(err); }); } From 3360752c5cacdb33ccf1d48abde9826db3c4f7a5 Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Wed, 3 Sep 2014 11:42:28 -0500 Subject: [PATCH 132/372] Added action:post.downvote hook --- src/favourites.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/favourites.js b/src/favourites.js index cb4de2e97e..741d3a3c4f 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -131,7 +131,14 @@ var async = require('async'), return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); } - toggleVote('downvote', pid, uid, callback); + toggleVote('downvote', pid, uid, function(err, votes) { + if (err) return callback(err); + plugins.fireHook('action:post.downvote', { + pid: pid, + uid: uid + }); + callback(null, votes); + }); }); }; From f81acd37dbfacaa2b42dbeb6cb3bf6bb85096069 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 3 Sep 2014 23:43:07 -0400 Subject: [PATCH 133/372] added indices on search key and id for mongo --- src/database/mongo.js | 6 ++++++ src/database/mongo/main.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index affb658dba..755a42c505 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -105,6 +105,12 @@ } }); + db.collection('search').ensureIndex({key: 1, id: 1}, {background: true}, function(err) { + if(err) { + winston.error('Error creating index ' + err.message); + } + }); + if(typeof callback === 'function') { callback(); } diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 3c24830ffa..dacce8d9f6 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -13,7 +13,7 @@ module.exports = function(db, module) { content: content }; - db.collection('search').update({id:id, key:key}, {$set:data}, {upsert:true, w: 1}, function(err) { + db.collection('search').update({key:key, id:id}, {$set:data}, {upsert:true, w: 1}, function(err) { if(err) { winston.error('Error indexing ' + err.message); } @@ -41,7 +41,7 @@ module.exports = function(db, module) { module.searchRemove = function(key, id, callback) { callback = callback || helpers.noop; - db.collection('search').remove({id:id, key:key}, callback); + db.collection('search').remove({key:key, id:id}, callback); }; module.flushdb = function(callback) { From 8baea1a25134b889f07e3934e1017e0fc576dcf4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 00:01:08 -0400 Subject: [PATCH 134/372] crash timer --- loader.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/loader.js b/loader.js index e38a3984c1..d819e0e7bb 100644 --- a/loader.js +++ b/loader.js @@ -11,6 +11,7 @@ var nconf = require('nconf'), * logging * handling SIGHUP * restart signal from child + * minifier */ // pidFilePath = __dirname + '/pidfile', @@ -134,6 +135,12 @@ Loader.init = function() { if (code !== 0) { if (Loader.timesStarted < numCPUs*3) { Loader.timesStarted++; + if (Loader.crashTimer) { + clearTimeout(Loader.crashTimer); + } + Loader.crashTimer = setTimeout(function() { + Loader.timesStarted = 0; + }); } else { console.log(numCPUs*3, 'restarts in 10 seconds, most likely an error on startup. Halting.'); process.exit(); From 45afe2e831b52554a89c8215aa2955172600d4d3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 00:20:50 -0400 Subject: [PATCH 135/372] cast return to bool --- src/database/mongo/sets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index e7d606092a..e12bc5caa0 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -61,7 +61,7 @@ module.exports = function(db, module) { } values = values.map(function(value) { - return items && Array.isArray(items.members) && items.members.indexOf(value) !== -1; + return !!(items && Array.isArray(items.members) && items.members.indexOf(value) !== -1); }); callback(null, values); From 956a4319506e3a6dd94f2b98ce95f2e21c4a0bd0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 00:56:50 -0400 Subject: [PATCH 136/372] properly handling SIGHUP --- loader.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/loader.js b/loader.js index d819e0e7bb..99c6a9f1b4 100644 --- a/loader.js +++ b/loader.js @@ -9,7 +9,6 @@ var nconf = require('nconf'), /* TODO * pidFile and reset timer * logging - * handling SIGHUP * restart signal from child * minifier */ @@ -150,6 +149,9 @@ Loader.init = function() { console.log('[cluster] Child Process (' + worker.process.pid + ') has exited (code: ' + code + ')'); cluster.fork(); }); + + process.on('SIGHUP', Loader.restart); + // fs.writeFile(__dirname + '/pidfile', process.pid); }; Loader.restart = function(callback) { From e5795687bb0a2c61bf090180229a6f7cfc493331 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 01:34:27 -0400 Subject: [PATCH 137/372] fix settings on mongo --- src/user/settings.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/user/settings.js b/src/user/settings.js index 8f24f54a05..362019491a 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -24,17 +24,17 @@ module.exports = function(User) { settings = data.settings; - settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : false; - settings.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab ? parseInt(settings.openOutgoingLinksInNewTab, 10) !== 0 : false; + settings.showemail = parseInt(settings.showemail, 10) === 1; + settings.openOutgoingLinksInNewTab = parseInt(settings.openOutgoingLinksInNewTab, 10) === 1; settings.dailyDigestFreq = settings.dailyDigestFreq || 'off'; - settings.usePagination = settings.usePagination ? parseInt(settings.usePagination, 10) === 1 : parseInt(meta.config.usePagination, 10) === 1; + settings.usePagination = (settings.usePagination === null || settings.usePagination === undefined) ? parseInt(meta.config.usePagination, 10) === 1 : parseInt(settings.usePagination, 10) === 1; settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20, 20); settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10, 20); - settings.notificationSounds = settings.notificationSounds ? parseInt(settings.notificationSounds, 10) === 1 : true; + settings.notificationSounds = parseInt(settings.notificationSounds, 10) === 1; settings.language = settings.language || meta.config.defaultLang || 'en_GB'; settings.topicPostSort = settings.topicPostSort || meta.config.topicPostSort || 'oldest_to_newest'; - settings.followTopicsOnCreate = settings.followTopicsOnCreate ? parseInt(settings.followTopicsOnCreate, 10) === 1 : true; - settings.followTopicsOnReply = settings.followTopicsOnReply ? parseInt(settings.followTopicsOnReply, 10) === 1 : false; + settings.followTopicsOnCreate = (settings.followTopicsOnCreate === null || settings.followTopicsOnCreate === undefined) ? true : parseInt(settings.followTopicsOnCreate, 10) === 1; + settings.followTopicsOnReply = parseInt(settings.followTopicsOnReply, 10) === 1; callback(null, settings); }); }); @@ -66,7 +66,6 @@ module.exports = function(User) { }; User.saveSettings = function(uid, data, callback) { - if(!data.topicsPerPage || !data.postsPerPage || parseInt(data.topicsPerPage, 10) <= 0 || parseInt(data.postsPerPage, 10) <= 0) { return callback(new Error('[[error:invalid-pagination-value]]')); } From c62f251afc96b4d3b26a2bf6eda6663bf17bc9a9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 01:48:57 -0400 Subject: [PATCH 138/372] added index and fixed vote button in infinite scroll --- src/database/mongo.js | 6 ++++++ src/socket.io/topics.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/database/mongo.js b/src/database/mongo.js index 455ef87190..58a1ea310f 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -90,6 +90,12 @@ } }); + db.collection('objects').ensureIndex({_key :1, score: 1}, {background:true}, function(err) { + if(err) { + winston.error('Error creating index ' + err.message); + } + }); + db.collection('objects').ensureIndex({_key :1, value: -1}, {background:true}, function(err) { if(err) { winston.error('Error creating index ' + err.message); diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 0b1c308bb8..2f09cbe68f 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -456,6 +456,9 @@ SocketTopics.loadMore = function(socket, data, callback) { }, 'reputation:disabled': function(next) { next(null, parseInt(meta.config['reputation:disabled'], 10) === 1); + }, + 'downvote:disabled': function(next) { + next(null, parseInt(meta.config['downvote:disabled'], 10) === 1); } }, callback); }); From 8a996c45244b03c2ab88364f0d07a010404f2602 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 15:13:16 -0400 Subject: [PATCH 139/372] changing silent value based on environment variable --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 99c6a9f1b4..83b37cbc2e 100644 --- a/loader.js +++ b/loader.js @@ -103,7 +103,7 @@ Loader.init = function() { cluster.setupMaster({ exec: "app.js", - silent: true + silent: process.env.NODE_ENV !== 'development' ? true : false }); for(var x=0;x Date: Thu, 4 Sep 2014 16:39:24 -0400 Subject: [PATCH 140/372] removed /test route --- src/routes/debug.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/routes/debug.js b/src/routes/debug.js index 24f6f8a627..584a70a6b6 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -56,10 +56,6 @@ module.exports = function(app, middleware, controllers) { }); router.get('/test', function(req, res) { - var cluster = require('cluster'); - console.log('answered by worker', cluster.worker.id); - res.send(200); - process.exit(); - // res.redirect(404); + res.redirect(404); }); }; From 08abbe19bca647e0ec2d670ea95c9a8a58f257f7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 17:09:57 -0400 Subject: [PATCH 141/372] zero downtime and reload support with cluster module --- app.js | 7 ++++++ loader.js | 47 ++++++++++++++++++++++++++++++----------- src/meta.js | 2 +- src/meta/templates.js | 11 ++++++++++ src/middleware/index.js | 9 +------- src/socket.io/admin.js | 9 +++++++- src/webserver.js | 4 ++-- 7 files changed, 65 insertions(+), 24 deletions(-) diff --git a/app.js b/app.js index c09f2d67c3..f6c0313abe 100644 --- a/app.js +++ b/app.js @@ -157,6 +157,13 @@ function start() { process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); process.on('SIGHUP', restart); + process.on('message', function(message) { + switch(message) { + case 'reload': + meta.reload(); + break; + } + }) process.on('uncaughtException', function(err) { winston.error(err.message); console.log(err.stack); diff --git a/loader.js b/loader.js index 83b37cbc2e..65d8381c3e 100644 --- a/loader.js +++ b/loader.js @@ -95,7 +95,8 @@ var nconf = require('nconf'), // nbb, nbbOld; var Loader = { - timesStarted: 0 + timesStarted: 0, + shutdown_queue: [] }; Loader.init = function() { @@ -106,11 +107,6 @@ Loader.init = function() { silent: process.env.NODE_ENV !== 'development' ? true : false }); - for(var x=0;x Date: Thu, 4 Sep 2014 17:39:53 -0400 Subject: [PATCH 142/372] js propagation for cluster module, so only 1 thread minifies the js --- app.js | 8 ++- loader.js | 16 +++++- src/meta/js.js | 132 +++++++++++++++++++++++++++---------------------- 3 files changed, 96 insertions(+), 60 deletions(-) diff --git a/app.js b/app.js index f6c0313abe..52e3e000db 100644 --- a/app.js +++ b/app.js @@ -28,6 +28,7 @@ var fs = require('fs'), semver = require('semver'), winston = require('winston'), path = require('path'), + cluster = require('cluster'), pkg = require('./package.json'), utils = require('./public/src/utils.js'); @@ -158,10 +159,15 @@ function start() { process.on('SIGINT', shutdown); process.on('SIGHUP', restart); process.on('message', function(message) { - switch(message) { + switch(message.action) { case 'reload': meta.reload(); break; + case 'js-propagate': + meta.js.cache = message.cache; + meta.js.map = message.map; + winston.info('[cluster] Client-side javascript and mapping propagated to worker ' + cluster.worker.id); + break; } }) process.on('uncaughtException', function(err) { diff --git a/loader.js b/loader.js index 65d8381c3e..a5b90084f8 100644 --- a/loader.js +++ b/loader.js @@ -131,6 +131,18 @@ Loader.init = function() { console.log('[cluster] Reloading...'); Loader.reload(); break; + case 'js-propagate': + var otherWorkers = Object.keys(cluster.workers).filter(function(worker_id) { + return parseInt(worker_id, 10) !== parseInt(worker.id, 10); + }); + otherWorkers.forEach(function(worker_id) { + cluster.workers[worker_id].send({ + action: 'js-propagate', + cache: message.cache, + map: message.map + }); + }); + break; } } }); @@ -180,7 +192,9 @@ Loader.restart = function(callback) { Loader.reload = function() { Object.keys(cluster.workers).forEach(function(worker_id) { - cluster.workers[worker_id].send('reload'); + cluster.workers[worker_id].send({ + action: 'reload' + }); }); }; diff --git a/src/meta/js.js b/src/meta/js.js index f0950bcbf8..c51b5ec402 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -7,6 +7,7 @@ var winston = require('winston'), _ = require('underscore'), os = require('os'), nconf = require('nconf'), + cluster = require('cluster'), plugins = require('../plugins'), emitter = require('../emitter'), @@ -120,70 +121,85 @@ module.exports = function(Meta) { }; Meta.js.minify = function(minify, callback) { - var minifier = Meta.js.minifierProc = fork('minifier.js', { - silent: true - }), - minifiedStream = minifier.stdio[1], - minifiedString = '', - mapStream = minifier.stdio[2], - mapString = '', - step = 0, - onComplete = function() { - if (step === 0) { - return step++; - } + if (!cluster.isWorker || process.env.cluster_setup === 'true') { + var minifier = Meta.js.minifierProc = fork('minifier.js', { + silent: true + }), + minifiedStream = minifier.stdio[1], + minifiedString = '', + mapStream = minifier.stdio[2], + mapString = '', + step = 0, + onComplete = function() { + if (step === 0) { + return step++; + } - Meta.js.cache = minifiedString; - Meta.js.map = mapString; - winston.info('[meta/js] Compilation complete'); - emitter.emit('meta:js.compiled'); - minifier.kill(); - if (typeof callback === 'function') { - callback(); - } - }; + Meta.js.cache = minifiedString; + Meta.js.map = mapString; + winston.info('[meta/js] Compilation complete'); + emitter.emit('meta:js.compiled'); + minifier.kill(); + + if (cluster.isWorker) { + process.send({ + action: 'js-propagate', + cache: minifiedString, + map: mapString + }); + } - minifiedStream.on('data', function(buffer) { - minifiedString += buffer.toString(); - }); - mapStream.on('data', function(buffer) { - mapString += buffer.toString(); - }); + if (typeof callback === 'function') { + callback(); + } + }; - minifier.on('message', function(message) { - switch(message.type) { - case 'end': - if (message.payload === 'script') { - winston.info('[meta/js] Successfully minified.'); - onComplete(); - } else if (message.payload === 'mapping') { - winston.info('[meta/js] Retrieved Mapping.'); - onComplete(); - } - break; - case 'hash': - Meta.js.hash = message.payload; - break; - case 'error': - winston.error('[meta/js] Could not compile client-side scripts! ' + message.payload.message); - minifier.kill(); - if (typeof callback === 'function') { - callback(new Error(message.payload.message)); - } else { - process.exit(0); + minifiedStream.on('data', function(buffer) { + minifiedString += buffer.toString(); + }); + mapStream.on('data', function(buffer) { + mapString += buffer.toString(); + }); + + minifier.on('message', function(message) { + switch(message.type) { + case 'end': + if (message.payload === 'script') { + winston.info('[meta/js] Successfully minified.'); + onComplete(); + } else if (message.payload === 'mapping') { + winston.info('[meta/js] Retrieved Mapping.'); + onComplete(); + } + break; + case 'hash': + Meta.js.hash = message.payload; + break; + case 'error': + winston.error('[meta/js] Could not compile client-side scripts! ' + message.payload.message); + minifier.kill(); + if (typeof callback === 'function') { + callback(new Error(message.payload.message)); + } else { + process.exit(0); + } + break; } - break; - } - }); + }); - Meta.js.prepare(function() { - minifier.send({ - action: 'js', - relativePath: nconf.get('url') + '/', - minify: minify, - scripts: Meta.js.scripts.all + Meta.js.prepare(function() { + minifier.send({ + action: 'js', + relativePath: nconf.get('url') + '/', + minify: minify, + scripts: Meta.js.scripts.all + }); }); - }); + } else { + if (typeof callback === 'function') { + callback(); + } + } }; Meta.js.killMinifier = function(callback) { From 98f338b41ca0e57e17b68fd6e56641625fd57293 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 17:41:00 -0400 Subject: [PATCH 143/372] online count fixes for cluster --- loader.js | 12 +++++- public/src/forum/admin/index.js | 18 +++++--- src/socket.io/admin/user.js | 6 +-- src/socket.io/index.js | 74 ++++++++++++++++++++------------- src/socket.io/meta.js | 14 +++++-- src/socket.io/modules.js | 5 +-- 6 files changed, 82 insertions(+), 47 deletions(-) diff --git a/loader.js b/loader.js index 83b37cbc2e..3c39371a1a 100644 --- a/loader.js +++ b/loader.js @@ -125,11 +125,21 @@ Loader.init = function() { console.log('[cluster] Restarting...'); }); break; + case 'user:connect': + case 'user:disconnect': + notifyWorkers(worker, message); + break; } } }); }); + function notifyWorkers(currentWorker, msg) { + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send(msg); + }); + } + cluster.on('exit', function(worker, code, signal) { if (code !== 0) { if (Loader.timesStarted < numCPUs*3) { @@ -159,7 +169,7 @@ Loader.restart = function(callback) { cluster.workers[id].kill(); next(); }, callback); -} +}; Loader.init(); diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index e45c661628..5516c62d55 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -94,10 +94,12 @@ define('forum/admin/index', function() { Admin.updateRoomUsage = function(err, data) { + var roomData = data.rooms; + function getUserCountIn(room) { var count = 0; - for(var user in data[room]) { - if (data[room].hasOwnProperty(user)) { + for(var user in roomData[room]) { + if (roomData[room].hasOwnProperty(user)) { ++count; } } @@ -114,10 +116,10 @@ define('forum/admin/index', function() { var sortedData = []; - for (var room in data) { + for (var room in roomData) { if (room !== '') { - sortedData.push({room: room, count: data[room].length}); - total += data[room].length; + sortedData.push({room: room, count: roomData[room].length}); + total += roomData[room].length; } } @@ -131,6 +133,12 @@ define('forum/admin/index', function() { sortedData[i].count + " active user" + (sortedData[i].count > 1 ? "s" : "") + ""; } + var parent = active_users.parent(); + parent.prepend('
    '); + parent.prepend('Online Total [ ' + (data.onlineRegisteredCount + data.onlineGuestCount) + ' ]'); + parent.prepend('Online Guests [ ' + data.onlineGuestCount + ' ]
    '); + parent.prepend('Online Users [ ' + data.onlineRegisteredCount + ' ]
    '); + active_users.html(usersHtml); $('#connections').html(total); }; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index dd8cf8de04..50cc6280a3 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -76,11 +76,7 @@ User.banUser = function(uid, callback) { return callback(err); } - var sockets = websockets.getUserSockets(uid); - - for(var i=0; i 0, logger.io_one will hook into this socket */ logger.io_one(socket, uid); @@ -122,7 +156,7 @@ Sockets.init = function(server) { socket.on('disconnect', function() { - if (uid && Sockets.getUserSockets(uid).length <= 1) { + if (uid && (!onlineUsersMap[uid] || onlineUsersMap[uid] <= 1)) { db.sortedSetRemove('users:online', uid, function(err) { socketUser.isOnline(socket, uid, function(err, data) { socket.broadcast.emit('user.isOnline', err, data); @@ -130,6 +164,10 @@ Sockets.init = function(server) { }); } + if (process.send) { + process.send({action: 'user:disconnect', uid: uid}); + } + emitOnlineUserCount(); for(var roomName in io.sockets.manager.roomClients[socket.id]) { @@ -214,22 +252,12 @@ Sockets.uidInRoom = function(uid, room) { }; Sockets.getConnectedClients = function() { - var uids = []; - if (!io) { - return uids; - } - var clients = io.sockets.clients(); - - clients.forEach(function(client) { - if(client.uid && uids.indexOf(client.uid) === -1) { - uids.push(client.uid); - } - }); - return uids; + return onlineUsers; }; Sockets.getOnlineAnonCount = function () { - return Sockets.getUserSockets(0).length; + var count = parseInt(onlineUsersMap[0], 10); + return count ? count : 0; }; Sockets.getUserSockets = function(uid) { @@ -281,22 +309,12 @@ Sockets.reqFromSocket = function(socket) { Sockets.isUserOnline = isUserOnline; function isUserOnline(uid) { - return Sockets.getUserSockets(uid).length > 0; + return !!onlineUsersMap[uid]; } Sockets.isUsersOnline = function(uids, callback) { - var sockets = io.sockets.clients(); - - if(!Array.isArray(sockets) || !sockets.length) { - return callback(null, []); - } - - sockets = sockets.map(function(s) { - return s.uid; - }); - var data = uids.map(function(uid) { - return sockets.indexOf(parseInt(uid, 10)) !== -1; + return !!onlineUsersMap[uid]; }); callback(null, data); @@ -373,7 +391,7 @@ function emitTopicPostStats(callback) { Sockets.emitOnlineUserCount = emitOnlineUserCount; function emitOnlineUserCount(callback) { - var anon = Sockets.getOnlineAnonCount(0); + var anon = Sockets.getOnlineAnonCount(); var registered = Sockets.getConnectedClients().length; var returnObj = { diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 107e645569..42814005ab 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -78,13 +78,19 @@ SocketMeta.rooms.enter = function(socket, data, callback) { module.parent.exports.updateRoomBrowsingText(data.enter); - if (data.enter !== 'admin') { - websockets.in('admin').emit('event:meta.rooms.update', null, websockets.server.sockets.manager.rooms); - } + //if (data.enter !== 'admin') { + // websockets.in('admin').emit('event:meta.rooms.update', null, websockets.server.sockets.manager.rooms); + //} }; SocketMeta.rooms.getAll = function(socket, data, callback) { - callback(null, websockets.server.sockets.manager.rooms); + var userData = { + onlineGuestCount: websockets.getOnlineAnonCount(), + onlineRegisteredCount: websockets.getConnectedClients().length, + rooms: websockets.server.sockets.manager.rooms + } + + callback(null, userData); }; /* Exports */ diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 12878f7d17..1ad5c45e12 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -181,11 +181,8 @@ SocketModules.chats.send = function(socket, data, callback) { Messaging.verifySpammer(socket.uid, function(err, isSpammer) { if (!err && isSpammer) { - var sockets = server.getUserSockets(socket.uid); - for(var i = 0; i < sockets.length; ++i) { - sockets[i].emit('event:banned'); - } + server.in('uid_' + socket.uid).emit('event:banned'); // We're just logging them out, so a "temporary ban" to prevent abuse. Revisit once we implement a way to temporarily ban users server.logoutUser(socket.uid); From fec3ae7e5e93c443493fedf1fce910e926d2524b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 17:48:22 -0400 Subject: [PATCH 144/372] better integration with nodebb executable --- loader.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/loader.js b/loader.js index 1fff83f742..13807869fa 100644 --- a/loader.js +++ b/loader.js @@ -4,13 +4,14 @@ var nconf = require('nconf'), fs = require('fs'), cluster = require('cluster'), async = require('async'), + pidFilePath = __dirname + '/pidfile', + output = fs.openSync(__dirname + '/logs/output.log', 'a'), numCPUs = require('os').cpus().length; /* TODO * pidFile and reset timer * logging * restart signal from child - * minifier */ // pidFilePath = __dirname + '/pidfile', @@ -100,11 +101,9 @@ var Loader = { }; Loader.init = function() { - nconf.argv(); - cluster.setupMaster({ exec: "app.js", - silent: process.env.NODE_ENV !== 'development' ? true : false + silent: false/*process.env.NODE_ENV !== 'development' ? true : false*/ }); cluster.on('fork', function(worker) { @@ -209,6 +208,26 @@ Loader.reload = function() { }); }; +nconf.argv(); + +if (nconf.get('daemon') !== false) { + if (fs.existsSync(pidFilePath)) { + try { + var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); + process.kill(pid, 0); + process.exit(); + } catch (e) { + fs.unlinkSync(pidFilePath); + } + } + + require('daemon')({ + stdout: output + }); + + fs.writeFile(__dirname + '/pidfile', process.pid); +} + Loader.init(); // Start the daemon! From fd0622a1a33e6bcd47b8bbc154c7e22c74f58a3d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 17:49:13 -0400 Subject: [PATCH 145/372] removed console.log --- src/meta/templates.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/meta/templates.js b/src/meta/templates.js index 22c17ad0fc..ccc95c7ab7 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -16,12 +16,9 @@ var mkdirp = require('mkdirp'), Templates.compile = function(callback) { if (cluster.isWorker && process.env.cluster_setup !== 'true') { return setTimeout(function() { - console.log('FAKING TEMPLATE COMPILE'); emitter.emit('templates:compiled'); if (callback) callback(); }, 1000); - } else { - console.log('REAL TEMPLATE COMPILE'); } var baseTemplatesPath = nconf.get('base_templates_path'), From 9a908c165108824fc21afea92bd33c0297ce22da Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 17:49:30 -0400 Subject: [PATCH 146/372] filter users who are already on dom --- public/src/forum/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/src/forum/users.js b/public/src/forum/users.js index 4df2b9c5be..cdf1ab9e73 100644 --- a/public/src/forum/users.js +++ b/public/src/forum/users.js @@ -74,6 +74,10 @@ define('forum/users', function() { } function onUsersLoaded(users) { + users = users.filter(function(user) { + return !$('.users-box[data-uid="' + user.uid + '"]').length; + }); + ajaxify.loadTemplate('users', function(usersTemplate) { var html = templates.parse(templates.getBlock(usersTemplate, 'users'), {users: users}); From 2d0877f80ea50cefa12f06335e7821d35881c44f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 4 Sep 2014 20:07:55 -0400 Subject: [PATCH 147/372] better handling of port in use errors with cluster --- src/webserver.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/webserver.js b/src/webserver.js index bf3e1ff9f9..661c6da6b9 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -108,7 +108,11 @@ if(nconf.get('ssl')) { server.on("error", function(err){ if (err.code === 'EADDRINUSE') { winston.error('NodeBB address in use, exiting...'); - process.exit(1); + if (cluster.isWorker) { + cluster.worker.kill(); + } else { + process.exit(0); + } } else { throw err; } @@ -133,7 +137,7 @@ if(nconf.get('ssl')) { var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port; winston.info('NodeBB attempting to listen on: ' + bind_address); - server.listen(port, nconf.get('bind_address'), function(){ + server.listen(port, nconf.get('bind_address'), function() { winston.info('NodeBB is now listening on: ' + bind_address); if (process.send) { process.send({ From 94a3ecdbd1b3bdc9f6d6e5367b434bd8029aa43c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 20:20:58 -0400 Subject: [PATCH 148/372] cluster socket.io fixes getUserRooms getUidsInRoom getAnonCountInRoom --- src/socket.io/index.js | 84 +++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 62ca0f5300..29b58e6f18 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -27,6 +27,8 @@ var io; var onlineUsersMap = {}; var onlineUsers = []; +var uidToSocketId = {}; +var socketIdToUid = {}; process.on('message', function(msg) { if (typeof msg !== 'object') { @@ -42,6 +44,13 @@ process.on('message', function(msg) { if (msg.uid && onlineUsers.indexOf(msg.uid) === -1) { onlineUsers.push(msg.uid); } + + if (Array.isArray(uidToSocketId[msg.uid])) { + uidToSocketId[msg.uid].push(msg.socketid); + } else { + uidToSocketId[msg.uid] = [msg.socketid]; + } + socketIdToUid[msg.socketid] = msg.uid; } else if(msg.action === 'user:disconnect') { var index = onlineUsers.indexOf(msg.uid); if (index !== -1) { @@ -52,6 +61,14 @@ process.on('message', function(msg) { onlineUsersMap[msg.uid] -= 1; onlineUsersMap[msg.uid] = Math.max(0, onlineUsersMap[msg.uid]); } + + if (uidToSocketId[msg.uid]) { + index = uidToSocketId[msg.uid].indexOf(msg.socketid); + if (index !== -1) { + uidToSocketId[msg.uid].splice(index, 1); + } + } + delete socketIdToUid[msg.socketid]; } }); @@ -112,7 +129,7 @@ Sockets.init = function(server) { socket.uid = parseInt(uid, 10); if (process.send) { - process.send({action: 'user:connect', uid: uid}); + process.send({action: 'user:connect', uid: uid, socketid: socket.id}); } /* If meta.config.loggerIOStatus > 0, logger.io_one will hook into this socket */ logger.io_one(socket, uid); @@ -165,7 +182,7 @@ Sockets.init = function(server) { } if (process.send) { - process.send({action: 'user:disconnect', uid: uid}); + process.send({action: 'user:disconnect', uid: uid, socketid: socket.id}); } emitOnlineUserCount(); @@ -276,14 +293,20 @@ Sockets.getUserSockets = function(uid) { }; Sockets.getUserRooms = function(uid) { - var sockets = Sockets.getUserSockets(uid); var rooms = {}; - for (var i=0; i Date: Thu, 4 Sep 2014 21:22:34 -0400 Subject: [PATCH 149/372] removing listener for templates compilation, in order to resolve race condition that was causing slow servers to not actually end up binding to the port as expected --- src/webserver.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/webserver.js b/src/webserver.js index 661c6da6b9..b47217d419 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -124,13 +124,11 @@ if(nconf.get('ssl')) { emitter.removeAllListeners('templates:compiled').removeAllListeners('meta:js.compiled').removeAllListeners('meta:css.compiled'); }); - emitter.on('templates:compiled', function() { - if (process.send) { - callback(); - } else { - module.exports.listen(); - } - }); + if (process.send) { + callback(); + } else { + module.exports.listen(); + } }; module.exports.listen = function() { From 707a062661d689661c4af9820cea121c8366147c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 22:11:21 -0400 Subject: [PATCH 150/372] one more fix --- src/socket.io/index.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 29b58e6f18..08654eea35 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -253,19 +253,19 @@ Sockets.in = function(room) { }; Sockets.uidInRoom = function(uid, room) { - var clients = io.sockets.clients(room); + var socketIds = io.sockets.manager.rooms[room]; + if (!Array(socketIds) || !socketIds.length) { + return false; + } uid = parseInt(uid, 10); - if (typeof uid === 'number' && uid > 0) { - clients = clients.filter(function(socketObj) { - return uid === socketObj.uid; - }); - - return clients.length ? true : false; - } else { - return false; + for (var i=0; i Date: Thu, 4 Sep 2014 22:32:50 -0400 Subject: [PATCH 151/372] crash fix --- src/socket.io/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 08654eea35..91f4523f18 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -149,8 +149,8 @@ Sockets.init = function(server) { }, function(err, userData) { socket.emit('event:connect', { status: 1, - username: userData.user.username, - userslug: userData.user.userslug, + username: userData.user ? userData.user.username || 'guest', + userslug: userData.user ? userData.user.userslug || '', isAdmin: userData.isAdmin, uid: uid }); From e28d778c0cbd5cb08cb26fd3b029c971de160381 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 22:41:02 -0400 Subject: [PATCH 152/372] fixed my derp --- src/socket.io/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 91f4523f18..5f67e78810 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -149,8 +149,8 @@ Sockets.init = function(server) { }, function(err, userData) { socket.emit('event:connect', { status: 1, - username: userData.user ? userData.user.username || 'guest', - userslug: userData.user ? userData.user.userslug || '', + username: userData.user ? userData.user.username : 'guest', + userslug: userData.user ? userData.user.userslug : '', isAdmin: userData.isAdmin, uid: uid }); From 190647a074ecb34a6e4dbb061a7c839b8f33c4f2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 4 Sep 2014 23:26:55 -0400 Subject: [PATCH 153/372] only update room text if its topic will fix this in a better way --- src/socket.io/index.js | 4 +++- src/socket.io/meta.js | 12 +++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 5f67e78810..0fdee234f4 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -188,7 +188,9 @@ Sockets.init = function(server) { emitOnlineUserCount(); for(var roomName in io.sockets.manager.roomClients[socket.id]) { - updateRoomBrowsingText(roomName.slice(1)); + if (roomName.indexOf('topic') !== -1) { + updateRoomBrowsingText(roomName.slice(1)); + } } }); diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 42814005ab..f4761ccfb0 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -72,15 +72,13 @@ SocketMeta.rooms.enter = function(socket, data, callback) { socket.join(data.enter); - if (data.leave && data.leave !== data.enter) { + if (data.leave && data.leave !== data.enter && data.leave.indexOf('topic') !== -1) { module.parent.exports.updateRoomBrowsingText(data.leave); } - module.parent.exports.updateRoomBrowsingText(data.enter); - - //if (data.enter !== 'admin') { - // websockets.in('admin').emit('event:meta.rooms.update', null, websockets.server.sockets.manager.rooms); - //} + if (data.enter.indexOf('topic') !== -1) { + module.parent.exports.updateRoomBrowsingText(data.enter); + } }; SocketMeta.rooms.getAll = function(socket, data, callback) { @@ -88,7 +86,7 @@ SocketMeta.rooms.getAll = function(socket, data, callback) { onlineGuestCount: websockets.getOnlineAnonCount(), onlineRegisteredCount: websockets.getConnectedClients().length, rooms: websockets.server.sockets.manager.rooms - } + }; callback(null, userData); }; From 2008bf28ff02ca8e22f910eab72b254ed86ff1bd Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 00:28:27 -0400 Subject: [PATCH 154/372] auto update user counts dont transmit all room data --- public/src/forum/admin/index.js | 64 ++++++++------------------------- src/socket.io/index.js | 48 ++++++++++++++++++------- src/socket.io/meta.js | 2 +- 3 files changed, 51 insertions(+), 63 deletions(-) diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index 5516c62d55..e6a8037aec 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -3,14 +3,18 @@ define('forum/admin/index', function() { var Admin = {}; - + var updateIntervalId = 0; Admin.init = function() { app.enterRoom('admin'); socket.emit('meta.rooms.getAll', Admin.updateRoomUsage); - socket.removeListener('event:meta.rooms.update', Admin.updateRoomUsage); - socket.on('event:meta.rooms.update', Admin.updateRoomUsage); + if (updateIntervalId) { + clearInterval(updateIntervalId); + } + updateIntervalId = setInterval(function() { + socket.emit('meta.rooms.getAll', Admin.updateRoomUsage); + }, 2000); $('#logout-link').on('click', function() { $.post(RELATIVE_PATH + '/logout', { @@ -93,54 +97,16 @@ define('forum/admin/index', function() { }; Admin.updateRoomUsage = function(err, data) { - - var roomData = data.rooms; - - function getUserCountIn(room) { - var count = 0; - for(var user in roomData[room]) { - if (roomData[room].hasOwnProperty(user)) { - ++count; - } - } - return count; + if (err) { + return app.alertError(err.message); } +console.log(data); + var html = 'Online Users [ ' + data.onlineRegisteredCount + ' ]
    ' + + 'Online Guests [ ' + data.onlineGuestCount + ' ]
    ' + + 'Online Total [ ' + (data.onlineRegisteredCount + data.onlineGuestCount) + ' ]
    ' + + 'Socket Connections [ ' + data.socketCount + ' ]'; - var active_users = $('#active_users').html(''), - total = 0; - - if(!active_users.length) { - return; - } - - - var sortedData = []; - - for (var room in roomData) { - if (room !== '') { - sortedData.push({room: room, count: roomData[room].length}); - total += roomData[room].length; - } - } - - sortedData.sort(function(a, b) { - return parseInt(b.count, 10) - parseInt(a.count, 10); - }); - - var usersHtml = ''; - for(var i=0; i " + - sortedData[i].count + " active user" + (sortedData[i].count > 1 ? "s" : "") + ""; - } - - var parent = active_users.parent(); - parent.prepend('
    '); - parent.prepend('Online Total [ ' + (data.onlineRegisteredCount + data.onlineGuestCount) + ' ]'); - parent.prepend('Online Guests [ ' + data.onlineGuestCount + ' ]
    '); - parent.prepend('Online Users [ ' + data.onlineRegisteredCount + ' ]
    '); - - active_users.html(usersHtml); - $('#connections').html(total); + $('#active_users').html(html); }; return Admin; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 0fdee234f4..920d8e172a 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -30,7 +30,9 @@ var onlineUsers = []; var uidToSocketId = {}; var socketIdToUid = {}; -process.on('message', function(msg) { +process.on('message', onMessage); + +function onMessage(msg) { if (typeof msg !== 'object') { return; } @@ -41,6 +43,7 @@ process.on('message', function(msg) { } else { onlineUsersMap[msg.uid]++; } + if (msg.uid && onlineUsers.indexOf(msg.uid) === -1) { onlineUsers.push(msg.uid); } @@ -52,16 +55,18 @@ process.on('message', function(msg) { } socketIdToUid[msg.socketid] = msg.uid; } else if(msg.action === 'user:disconnect') { - var index = onlineUsers.indexOf(msg.uid); - if (index !== -1) { - onlineUsers.splice(index, 1); - } - if (onlineUsersMap[msg.uid]) { onlineUsersMap[msg.uid] -= 1; onlineUsersMap[msg.uid] = Math.max(0, onlineUsersMap[msg.uid]); } + if (msg.uid && onlineUsersMap[msg.uid] === 0) { + var index = onlineUsers.indexOf(msg.uid); + if (index !== -1) { + onlineUsers.splice(index, 1); + } + } + if (uidToSocketId[msg.uid]) { index = uidToSocketId[msg.uid].indexOf(msg.socketid); if (index !== -1) { @@ -70,8 +75,25 @@ process.on('message', function(msg) { } delete socketIdToUid[msg.socketid]; } -}); +} + +function onUserConnect(uid, socketid) { + var msg = {action: 'user:connect', uid: uid, socketid: socketid}; + if (process.send) { + process.send(msg); + } else { + onMessage(msg); + } +} +function onUserDisconnect(uid, socketid) { + var msg = {action: 'user:disconnect', uid: uid, socketid: socketid}; + if (process.send) { + process.send(msg); + } else { + onMessage(msg); + } +} Sockets.init = function(server) { var RedisStore = require('socket.io/lib/stores/redis'), @@ -128,9 +150,8 @@ Sockets.init = function(server) { } socket.uid = parseInt(uid, 10); - if (process.send) { - process.send({action: 'user:connect', uid: uid, socketid: socket.id}); - } + onUserConnect(uid, socket.id); + /* If meta.config.loggerIOStatus > 0, logger.io_one will hook into this socket */ logger.io_one(socket, uid); @@ -181,9 +202,7 @@ Sockets.init = function(server) { }); } - if (process.send) { - process.send({action: 'user:disconnect', uid: uid, socketid: socket.id}); - } + onUserDisconnect(uid, socket.id); emitOnlineUserCount(); @@ -270,6 +289,9 @@ Sockets.uidInRoom = function(uid, room) { return false; }; +Sockets.getSocketCount = function() { + return Object.keys(socketIdToUid).length; +} Sockets.getConnectedClients = function() { return onlineUsers; }; diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index f4761ccfb0..28eec77517 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -85,7 +85,7 @@ SocketMeta.rooms.getAll = function(socket, data, callback) { var userData = { onlineGuestCount: websockets.getOnlineAnonCount(), onlineRegisteredCount: websockets.getConnectedClients().length, - rooms: websockets.server.sockets.manager.rooms + socketCount: websockets.getSocketCount() }; callback(null, userData); From 769d6ed57cc1da3736d266eab845c912ed56a0c9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 00:32:54 -0400 Subject: [PATCH 155/372] removed console.log --- public/src/forum/admin/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index e6a8037aec..1596e26e71 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -100,7 +100,7 @@ define('forum/admin/index', function() { if (err) { return app.alertError(err.message); } -console.log(data); + var html = 'Online Users [ ' + data.onlineRegisteredCount + ' ]
    ' + 'Online Guests [ ' + data.onlineGuestCount + ' ]
    ' + 'Online Total [ ' + (data.onlineRegisteredCount + data.onlineGuestCount) + ' ]
    ' + From 95d1bd3058c8f974b37edfd2602dd6c562f439e2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 01:30:08 -0400 Subject: [PATCH 156/372] crash fix --- src/widgets.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/widgets.js b/src/widgets.js index b04101fee5..efcc768646 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -3,7 +3,7 @@ var async = require('async'), winston = require('winston'), templates = require('templates.js'), - + plugins = require('./plugins'), db = require('./database'); @@ -29,7 +29,8 @@ var async = require('async'), var widgets = data.global.concat(data.local); async.eachSeries(widgets, function(widget, next) { - if (!!widget.data['registered-only'] && uid === 0) { + + if (!widget || !widget.data || (!!widget.data['registered-only'] && uid === 0)) { return next(); } @@ -44,7 +45,7 @@ var async = require('async'), body: html }); } - + rendered.push({ html: html }); @@ -72,7 +73,7 @@ var async = require('async'), error: 'Missing location and template data' }); } - + db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets), function(err) { callback(err); }); From 877cf1e15c43781c807a116e784096d08251e681 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 01:36:30 -0400 Subject: [PATCH 157/372] derp --- src/user/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index c71238366e..b3b1055222 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -324,7 +324,7 @@ var async = require('async'), var websockets = require('./../socket.io'); UserNotifications.getUnreadCount(uid, function(err, count) { if (err) { - return winston.warn('[User.pushNotifCount] Count not retrieve unread notifications count to push to uid ' + uid + '\'s client(s)'); + return winston.error(err.stack); } websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); From 7361aec709dc14adb33d6af078cd66c081b41de7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 01:37:38 -0400 Subject: [PATCH 158/372] update every 3 seconds --- public/src/forum/admin/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index 1596e26e71..defbcf661a 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -14,7 +14,7 @@ define('forum/admin/index', function() { } updateIntervalId = setInterval(function() { socket.emit('meta.rooms.getAll', Admin.updateRoomUsage); - }, 2000); + }, 3000); $('#logout-link').on('click', function() { $.post(RELATIVE_PATH + '/logout', { From 1075305444031903a9cb7a3b347d57239c54bbe1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 03:00:53 -0400 Subject: [PATCH 159/372] crash fix --- src/notifications.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/notifications.js b/src/notifications.js index f0e36a5d76..d8c06d17f0 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -242,6 +242,9 @@ var async = require('async'), Notifications.markReadMultiple = function(nids, uid, callback) { callback = callback || function() {}; + if (!nids) { + return callback(null); + } if (!Array.isArray(nids) && parseInt(nids, 10) > 0) { nids = [nids]; } From 2efb816da38b4615646846a448dd2512ba85405e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 5 Sep 2014 13:23:26 -0400 Subject: [PATCH 160/372] restarted threads now grab a copy of the minified js and map from the loader --- loader.js | 146 ++++++++++-------------------------------------------- 1 file changed, 25 insertions(+), 121 deletions(-) diff --git a/loader.js b/loader.js index 13807869fa..c5f325ad9d 100644 --- a/loader.js +++ b/loader.js @@ -6,99 +6,15 @@ var nconf = require('nconf'), async = require('async'), pidFilePath = __dirname + '/pidfile', output = fs.openSync(__dirname + '/logs/output.log', 'a'), - numCPUs = require('os').cpus().length; - - /* TODO - * pidFile and reset timer - * logging - * restart signal from child - */ - - // pidFilePath = __dirname + '/pidfile', - // output = fs.openSync(__dirname + '/logs/output.log', 'a'), - // start = function() { - // var nbb_start = function(callback) { - // if (timesStarted > 3) { - // console.log('\n[loader] Experienced three start attempts in 10 seconds, most likely an error on startup. Halting.'); - // return nbb_stop(); - // } - - // timesStarted++; - // if (startTimer) { - // clearTimeout(startTimer); - // } - // startTimer = setTimeout(resetTimer, 1000*10); - - // if (nbb) { - // nbbOld = nbb; - // } - - // nbb = require('child_process').fork('./app', process.argv.slice(2), { - // env: process.env - // }); - - // nbb.on('message', function(message) { - // if (message && typeof message === 'object' && message.action) { - // switch (message.action) { - // case 'ready': - // if (!callback) return nbb.send('bind'); - // callback(); - // break; - // case 'restart': - // nbb_restart(); - // break; - // } - // } - // }); - - // nbb.on('exit', function(code, signal) { - // if (code) { - // nbb_start(); - // } else { - // nbb_stop(); - // } - // }); - // }, - // nbb_stop = function() { - // if (startTimer) { - // clearTimeout(startTimer); - // } - - // nbb.kill(); - // if (fs.existsSync(pidFilePath)) { - // var pid = parseInt(fs.readFileSync(pidFilePath, { encoding: 'utf-8' }), 10); - // if (process.pid === pid) { - // fs.unlinkSync(pidFilePath); - // } - // } - // }, - // nbb_restart = function() { - // nbb_start(function() { - // nbbOld.removeAllListeners('exit').on('exit', function() { - // nbb.send('bind'); - // }); - // nbbOld.kill(); - // }); - // }, - // resetTimer = function() { - // clearTimeout(startTimer); - // timesStarted = 0; - // }, - // timesStarted = 0, - // startTimer; - - // process.on('SIGINT', nbb_stop); - // process.on('SIGTERM', nbb_stop); - // process.on('SIGHUP', nbb_restart); - - // nbb_start(); - // }, - // nbb, nbbOld; - -var Loader = { - timesStarted: 0, - shutdown_queue: [] -}; + numCPUs = require('os').cpus().length, + Loader = { + timesStarted: 0, + shutdown_queue: [], + js: { + cache: undefined, + map: undefined + } + }; Loader.init = function() { cluster.setupMaster({ @@ -111,7 +27,14 @@ Loader.init = function() { if (message && typeof message === 'object' && message.action) { switch (message.action) { case 'ready': - console.log('[cluster] Child Process (' + worker.process.pid + ') listening for connections.'); + if (Loader.js.cache) { + worker.send({ + action: 'js-propagate', + cache: Loader.js.cache, + map: Loader.js.map + }); + } + worker.send('bind'); // Kill an instance in the shutdown queue @@ -131,6 +54,9 @@ Loader.init = function() { Loader.reload(); break; case 'js-propagate': + Loader.js.cache = message.cache; + Loader.js.map = message.map; + var otherWorkers = Object.keys(cluster.workers).filter(function(worker_id) { return parseInt(worker_id, 10) !== parseInt(worker.id, 10); }); @@ -141,7 +67,6 @@ Loader.init = function() { map: message.map }); }); - break; case 'user:connect': case 'user:disconnect': @@ -152,6 +77,10 @@ Loader.init = function() { }); }); + cluster.on('listening', function(worker) { + console.log('[cluster] Child Process (' + worker.process.pid + ') listening for connections.'); + }); + function notifyWorkers(currentWorker, msg) { Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].send(msg); @@ -184,7 +113,6 @@ Loader.init = function() { process.on('SIGHUP', Loader.restart); Loader.start(); - // fs.writeFile(__dirname + '/pidfile', process.pid); }; Loader.start = function() { @@ -228,28 +156,4 @@ if (nconf.get('daemon') !== false) { fs.writeFile(__dirname + '/pidfile', process.pid); } -Loader.init(); - -// Start the daemon! -// if (nconf.get('daemon') !== false) { -// // Check for a still-active NodeBB process -// if (fs.existsSync(pidFilePath)) { -// try { -// var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); -// process.kill(pid, 0); -// process.exit(); -// } catch (e) { -// fs.unlinkSync(pidFilePath); -// } -// } - -// // Daemonize and record new pid -// require('daemon')({ -// stdout: output -// }); -// fs.writeFile(__dirname + '/pidfile', process.pid); - -// start(); -// } else { -// start(); -// } \ No newline at end of file +Loader.init(); \ No newline at end of file From 7255ce3dbecf33d9dfda1ca080f428f05ec72a7a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 5 Sep 2014 13:44:56 -0400 Subject: [PATCH 161/372] primary worker support, and added notif pruning to primary worker jobs --- loader.js | 18 ++++++++++++++++-- src/routes/debug.js | 2 +- src/webserver.js | 7 ++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index c5f325ad9d..42d198c047 100644 --- a/loader.js +++ b/loader.js @@ -68,6 +68,11 @@ Loader.init = function() { }); }); break; + case 'listening': + if (message.primary) { + Loader.primaryWorker = parseInt(worker.id, 10); + } + break; case 'user:connect': case 'user:disconnect': notifyWorkers(worker, message); @@ -106,7 +111,11 @@ Loader.init = function() { console.log('[cluster] Child Process (' + worker.process.pid + ') has exited (code: ' + code + ')'); if (!worker.suicide) { console.log('[cluster] Spinning up another process...') - cluster.fork(); + + var wasPrimary = parseInt(worker.id, 10) === Loader.primaryWorker; + cluster.fork({ + handle_jobs: wasPrimary + }); } }); @@ -116,9 +125,14 @@ Loader.init = function() { }; Loader.start = function() { + Loader.primaryWorker = 1; + for(var x=0;x Date: Fri, 5 Sep 2014 13:44:58 -0400 Subject: [PATCH 162/372] crash fix --- src/categories.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/categories.js b/src/categories.js index 8043401830..aaf732151b 100644 --- a/src/categories.js +++ b/src/categories.js @@ -414,7 +414,11 @@ var db = require('./database'), Categories.onNewPostMade = function(postData) { topics.getTopicFields(postData.tid, ['cid', 'pinned'], function(err, topicData) { if (err) { - winston.error(err.message); + return winston.error(err.message); + } + + if (!topicData) { + return; } var cid = topicData.cid; From bc00d7d1598cc56f15f2290ec57f2c0633dc18cf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 13:51:02 -0400 Subject: [PATCH 163/372] check just in case --- src/search.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.js b/src/search.js index 63fa50c69b..0f55c6aafb 100644 --- a/src/search.js +++ b/src/search.js @@ -73,9 +73,9 @@ function getMainPids(tids, callback) { if (err) { return callback(err); } - topics = topics.map(function(topics) { - return topics.mainPid; - }); + topics = topics.map(function(topic) { + return topic && topic.mainPid; + }).filter(Boolean); callback(null, topics); }); } From 9ee4c905485d4fd5aef5ba95f73740a2520ea630 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 5 Sep 2014 14:04:42 -0400 Subject: [PATCH 164/372] test route back to 404 --- src/routes/debug.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/debug.js b/src/routes/debug.js index 4cac674955..584a70a6b6 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -56,6 +56,6 @@ module.exports = function(app, middleware, controllers) { }); router.get('/test', function(req, res) { - process.exit(0); + res.redirect(404); }); }; From ff44819d6b37208df9ccd97a0a2c0c1ea3da2408 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 14:14:41 -0400 Subject: [PATCH 165/372] add support to set parentCid to 0 --- public/src/forum/admin/categories.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index c7851c1e7e..37a551ffef 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -239,6 +239,21 @@ define('forum/admin/categories', ['uploader', 'forum/admin/iconSelect'], functio modal.find('select').val($(this).attr('data-parentCid')); modal.attr('data-cid', cid).modal(); }); + + $('button[data-action="removeParent"]').on('click', function() { + var cid = $(this).parents('[data-cid]').attr('data-cid'); + var payload= {}; + payload[cid] = { + parentCid: 0 + }; + socket.emit('admin.categories.update', payload, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.go('admin/categories/active'); + }); + }); + $('#setParent [data-cid]').on('click', function() { var modalEl = $('#setParent'), parentCid = $(this).attr('data-cid'), From 9fac18c4b6a125091137eb2fb8d2031c1ebb166b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 17:39:24 -0400 Subject: [PATCH 166/372] derp --- src/socket.io/topics.js | 2 +- src/topics.js | 5 ----- src/topics/delete.js | 6 +++--- src/topics/recent.js | 13 +++++++++++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 2f09cbe68f..06e7526f89 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -190,7 +190,7 @@ SocketTopics.markAsUnreadForAll = function(socket, tids, callback) { return next(err); } - db.sortedSetAdd('topics:recent', Date.now(), tid, function(err) { + topics.updateRecent(tid, Date.now(), function(err) { if(err) { return next(err); } diff --git a/src/topics.js b/src/topics.js index 99a1c6b761..545945af89 100644 --- a/src/topics.js +++ b/src/topics.js @@ -470,11 +470,6 @@ var async = require('async'), }); }; - Topics.updateTimestamp = function(tid, timestamp) { - db.sortedSetAdd('topics:recent', timestamp, tid); - Topics.setTopicField(tid, 'lastposttime', timestamp); - }; - Topics.getUids = function(tid, callback) { Topics.getPids(tid, function(err, pids) { if (err) { diff --git a/src/topics/delete.js b/src/topics/delete.js index c1966bd7b2..2a654b551c 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -41,7 +41,7 @@ module.exports = function(Topics) { Topics.setTopicField(tid, 'deleted', 1, next); }, function(next) { - db.sortedSetRemove('topics:recent', tid, next); + Topics.removeRecent(tid, next); }, function(next) { db.sortedSetRemove('topics:posts', tid, next); @@ -69,7 +69,7 @@ module.exports = function(Topics) { Topics.setTopicField(tid, 'deleted', 0, next); }, function(next) { - db.sortedSetAdd('topics:recent', topicData.lastposttime, tid, next); + Topics.updateRecent(tid, topicData.lastposttime, next); }, function(next) { db.sortedSetAdd('topics:posts', topicData.postcount, tid, next); @@ -99,7 +99,7 @@ module.exports = function(Topics) { db.sortedSetRemove('topics:tid', tid, next); }, function(next) { - db.sortedSetRemove('topics:recent', tid, next); + Topics.removeRecent(tid, next); }, function(next) { db.sortedSetRemove('topics:posts', tid, next); diff --git a/src/topics/recent.js b/src/topics/recent.js index 8a3d8011e8..77d8023d32 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -33,4 +33,17 @@ module.exports = function(Topics) { db.getSortedSetRevRangeByScore('topics:recent', start, count, Infinity, Date.now() - since, callback); }; + Topics.updateTimestamp = function(tid, timestamp) { + Topics.updateRecent(tid, timestamp); + Topics.setTopicField(tid, 'lastposttime', timestamp); + }; + + Topics.updateRecent = function(tid, timestamp, callback) { + callback = callback || function() {}; + db.sortedSetAdd('topics:recent', timestamp, tid, callback); + }; + + Topics.removeRecent = function(tid, callback) { + db.sortedSetRemove('topics:recent', tid, callback); + }; }; From a6f8bcd7be9d35199015765647acbdac63cec435 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 19:10:21 -0400 Subject: [PATCH 167/372] dont show new posts on popular doesnt makse sense --- public/src/forum/popular.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/public/src/forum/popular.js b/public/src/forum/popular.js index 37729dc90b..7495f5110d 100644 --- a/public/src/forum/popular.js +++ b/public/src/forum/popular.js @@ -5,12 +5,6 @@ define('forum/popular', ['forum/recent', 'forum/infinitescroll'], function(recent, infinitescroll) { var Popular = {}; - $(window).on('action:ajaxify.start', function(ev, data) { - if(data.url.indexOf('recent') !== 0) { - recent.removeListeners(); - } - }); - Popular.init = function() { app.enterRoom('recent_posts'); @@ -18,8 +12,6 @@ define('forum/popular', ['forum/recent', 'forum/infinitescroll'], function(recen $(this).addClass('hide'); }); - recent.watchForNewPosts(); - recent.selectActivePill(); }; From 8e86f4b7873f3d438b6044dbf232eac58a561028 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 20:35:24 -0400 Subject: [PATCH 168/372] crash fix --- src/topics/posts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/topics/posts.js b/src/topics/posts.js index 159aa7546b..ece6c5fcd2 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -227,6 +227,7 @@ module.exports = function(Topics) { }; function incrementFieldAndUpdateSortedSet(tid, field, by, set, callback) { + callback = callback || function() {}; db.incrObjectFieldBy('topic:' + tid, field, by, function(err, value) { if(err) { return callback(err); From 0dc780148ae216ae37170f04a79af0c253b45db2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 22:11:21 -0400 Subject: [PATCH 169/372] removed global room socket.io already puts everyone into a room named '' --- public/src/ajaxify.js | 2 +- public/src/app.js | 12 ++++++------ src/socket.io/meta.js | 6 ++++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 9fa4b16262..0e419fda58 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -46,7 +46,7 @@ $(document).ready(function() { ajaxify.go = function (url, callback, quiet) { // "quiet": If set to true, will not call pushState - app.enterRoom('global'); + app.enterRoom(''); $(window).off('scroll'); diff --git a/public/src/app.js b/public/src/app.js index 94db871a31..260d22734c 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -45,12 +45,12 @@ var socket, case 'admin': room = 'admin'; break; - default: - room = 'global'; + case 'home': + room = 'home'; break; } - - app.enterRoom(room, true); + app.currentRoom = ''; + app.enterRoom(room); socket.emit('meta.reconnected'); @@ -198,9 +198,9 @@ var socket, }); }; - app.enterRoom = function (room, force) { + app.enterRoom = function (room) { if (socket) { - if (app.currentRoom === room && !force) { + if (app.currentRoom === room) { return; } diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 28eec77517..b6397d5fc3 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -66,11 +66,13 @@ SocketMeta.rooms.enter = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - if (data.leave !== null) { + if (data.leave) { socket.leave(data.leave); } - socket.join(data.enter); + if (data.enter) { + socket.join(data.enter); + } if (data.leave && data.leave !== data.enter && data.leave.indexOf('topic') !== -1) { module.parent.exports.updateRoomBrowsingText(data.leave); From 421d091828031c0bdcced05583e68fccc4cbd71d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 00:19:46 -0400 Subject: [PATCH 170/372] optimizing browsing users --- public/src/app.js | 10 +-- public/src/forum/topic/browsing.js | 105 ++++++++++------------------- public/src/forum/topic/events.js | 2 + src/socket.io/index.js | 23 ++++--- src/socket.io/meta.js | 17 +++-- src/socket.io/topics.js | 1 + 6 files changed, 68 insertions(+), 90 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 260d22734c..4fe7871ea7 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -89,6 +89,7 @@ var socket, socket.on('event:connect', function (data) { app.username = data.username; app.userslug = data.userslug; + app.picture = data.picture; app.uid = data.uid; app.isAdmin = data.isAdmin; @@ -142,8 +143,6 @@ var socket, }, 1000); }); - app.enterRoom('global'); - app.cacheBuster = config['cache-buster']; bootbox.setDefaults({ @@ -205,8 +204,11 @@ var socket, } socket.emit('meta.rooms.enter', { - 'enter': room, - 'leave': app.currentRoom + enter: room, + leave: app.currentRoom, + username: app.username, + userslug: app.userslug, + picture: app.picture }); app.currentRoom = room; diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 5d1833bdd0..53c9ae3046 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -10,72 +10,32 @@ define('forum/topic/browsing', function() { Browsing.onUpdateUsersInRoom = function(data) { if(data && data.room.indexOf('topic_' + ajaxify.variables.get('topic_id')) !== -1) { - var activeEl = $('.thread_active_users'); - - // remove users that are no longer here - activeEl.find('a').each(function(index, element) { - if(element) { - var uid = $(element).attr('data-uid'); - var absent = data.users.every(function(user) { - return parseInt(user.uid, 10) !== parseInt(uid, 10); - }); - - if (absent) { - $(element).parent().remove(); - } - } - }); - - var i=0, icon; - // add self - for(i = 0; i 8) { - break; - } + for(var i=0; i'); - activeEl.append(anonLink); - - var title = ''; - if(remainingUsers && anonymousCount) { - title = '[[topic:more_users_and_guests, ' + remainingUsers + ', ' + anonymousCount + ']]'; - } else if(remainingUsers) { - title = '[[topic:more_users, ' + remainingUsers + ']]'; - } else { - title = '[[topic:more_guests, ' + anonymousCount + ']]'; - } + Browsing.onUserEnter = function(data) { + var activeEl = $('.thread_active_users'); + var user = activeEl.find('a[data-uid="' + data.uid + '"]'); + if (!user.length) { + addUserIcon(data); + } else { + user.attr('data-count', parseInt(user.attr('data-count'), 10) + 1); + } + }; - translator.translate(title, function(translated) { - $('.anonymous-box').tooltip({ - placement: 'top', - title: translated - }); - }); + Browsing.onUserLeave = function(uid) { + var activeEl = $('.thread_active_users'); + var user = activeEl.find('a[data-uid="' + uid + '"]'); + if (user.length) { + var count = Math.max(0, parseInt(user.attr('data-count'), 10) - 1); + user.attr('data-count', count); + if (count <= 0) { + user.remove(); } - - getReplyingUsers(); } }; @@ -100,17 +60,26 @@ define('forum/topic/browsing', function() { if (user.length && !data.online) { user.parent().remove(); } else if(!user.length && data.online && data.rooms.indexOf('topic_' + ajaxify.variables.get('topic_id')) !== -1) { - user = createUserIcon(data.uid, data.picture, data.userslug, data.username); - activeEl.append(user); - activeEl.find('a[data-uid] img').tooltip({ - placement: 'top' - }); + addUserIcon(user); } } + function addUserIcon(user) { + if (!user.userslug) { + return; + } + var activeEl = $('.thread_active_users'); + var userEl = createUserIcon(user.uid, user.picture, user.userslug, user.username); + activeEl.append(userEl); + activeEl.find('a[data-uid] img').tooltip({ + placement: 'top' + }); + } + function createUserIcon(uid, picture, userslug, username) { if(!$('.thread_active_users').find('[data-uid="' + uid + '"]').length) { - return $('
    '); + console.log('are u fucking kidming', arguments); + return $('
    '); } } diff --git a/public/src/forum/topic/events.js b/public/src/forum/topic/events.js index e1d7becc8c..a84d7ad1d6 100644 --- a/public/src/forum/topic/events.js +++ b/public/src/forum/topic/events.js @@ -9,6 +9,8 @@ define('forum/topic/events', ['forum/topic/browsing', 'forum/topic/postTools', ' var events = { 'event:update_users_in_room': browsing.onUpdateUsersInRoom, + 'event:user_enter': browsing.onUserEnter, + 'event:user_leave': browsing.onUserLeave, 'user.isOnline': browsing.onUserOnline, 'event:voted': updatePostVotesAndUserReputation, 'event:favourited': updateFavouriteCount, diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 920d8e172a..a2191f34e9 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -162,16 +162,20 @@ Sockets.init = function(server) { async.parallel({ user: function(next) { - user.getUserFields(uid, ['username', 'userslug'], next); + user.getUserFields(uid, ['username', 'userslug', 'picture'], next); }, isAdmin: function(next) { user.isAdministrator(uid, next); } }, function(err, userData) { + if (err || !userData.user) { + return; + } socket.emit('event:connect', { status: 1, - username: userData.user ? userData.user.username : 'guest', - userslug: userData.user ? userData.user.userslug : '', + username: userData.user.username, + userslug: userData.user.userslug, + picture: userData.user.picture, isAdmin: userData.isAdmin, uid: uid }); @@ -208,7 +212,7 @@ Sockets.init = function(server) { for(var roomName in io.sockets.manager.roomClients[socket.id]) { if (roomName.indexOf('topic') !== -1) { - updateRoomBrowsingText(roomName.slice(1)); + io.sockets.in(roomName.slice(1)).emit('event:user_leave', socket.uid); } } }); @@ -368,15 +372,17 @@ Sockets.isUsersOnline = function(uids, callback) { }; Sockets.updateRoomBrowsingText = updateRoomBrowsingText; -function updateRoomBrowsingText(roomName) { +function updateRoomBrowsingText(roomName, selfUid) { if (!roomName) { return; } - var uids = Sockets.getUidsInRoom(roomName), - anonymousCount = Sockets.getAnonCountInRoom(roomName); - + var uids = Sockets.getUidsInRoom(roomName); + uids = uids.slice(0, 9); + if (selfUid) { + uids = [selfUid].concat(uids); + } user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) { if(!err) { users = users.filter(function(user) { @@ -385,7 +391,6 @@ function updateRoomBrowsingText(roomName) { io.sockets.in(roomName).emit('event:update_users_in_room', { users: users, - anonymousCount: anonymousCount, room: roomName }); } diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index b6397d5fc3..af9e624442 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -62,24 +62,23 @@ SocketMeta.getUsageStats = function(socket, data, callback) { /* Rooms */ SocketMeta.rooms.enter = function(socket, data, callback) { - if(!data) { + if (!data) { return callback(new Error('[[error:invalid-data]]')); } if (data.leave) { socket.leave(data.leave); + if (socket.uid && data.leave.indexOf('topic') !== -1) { + websockets.in(data.leave).emit('event:user_leave', socket.uid); + } } if (data.enter) { socket.join(data.enter); - } - - if (data.leave && data.leave !== data.enter && data.leave.indexOf('topic') !== -1) { - module.parent.exports.updateRoomBrowsingText(data.leave); - } - - if (data.enter.indexOf('topic') !== -1) { - module.parent.exports.updateRoomBrowsingText(data.enter); + if (socket.uid && data.enter.indexOf('topic') !== -1) { + data.uid = socket.uid; + websockets.in(data.enter).emit('event:user_enter', data); + } } }; diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 06e7526f89..143f5abb5c 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -64,6 +64,7 @@ SocketTopics.enter = function(socket, tid, callback) { SocketTopics.markAsRead(socket, tid); topics.markTopicNotificationsRead(tid, socket.uid); topics.increaseViewCount(tid); + websockets.updateRoomBrowsingText('topic_' + tid); }; SocketTopics.postcount = function(socket, tid, callback) { From 779b2a8d73541bacc492b1226cd34355875f2145 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 00:58:03 -0400 Subject: [PATCH 171/372] optimize queries --- src/groups.js | 4 +--- src/posts.js | 14 ++++++++++++-- src/topics.js | 11 ++++++++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/groups.js b/src/groups.js index 219fae8e45..06c03bcffa 100644 --- a/src/groups.js +++ b/src/groups.js @@ -439,15 +439,13 @@ }; Groups.getUserGroups = function(uids, callback) { - var ignoredGroups = ['registered-users']; - db.getSetMembers('groups', function(err, groupNames) { if (err) { return callback(err); } var groupKeys = groupNames.filter(function(groupName) { - return ignoredGroups.indexOf(groupName) === -1; + return groupName !== 'registered-users' && groupName.indexOf(':privileges:') === -1; }).map(function(groupName) { return 'group:' + groupName; }); diff --git a/src/posts.js b/src/posts.js index c70002f4d0..d09ad0d555 100644 --- a/src/posts.js +++ b/src/posts.js @@ -404,15 +404,25 @@ var async = require('async'), var tids = posts.map(function(post) { return post.tid; + }).filter(function(tid, index, array) { + return array.indexOf(tid) === index; }); topics.getTopicsFields(tids, ['cid'], function(err, topics) { if (err) { return callback(err); } - var cids = topics.map(function(topic) { - return topic.cid; + var map = {}; + topics.forEach(function(topic) { + if (topic) { + map[topic.tid] = topic.cid; + } }); + + var cids = posts.map(function(post) { + return map[post.tid]; + }) + callback(null, cids); }); }); diff --git a/src/topics.js b/src/topics.js index 545945af89..2e53e2672c 100644 --- a/src/topics.js +++ b/src/topics.js @@ -348,7 +348,7 @@ var async = require('async'), return callback(err); } - var postKeys = pids.map(function(pid) { + var postKeys = pids.filter(Boolean).map(function(pid) { return 'post:' + pid; }); @@ -379,14 +379,19 @@ var async = require('async'), results.users.forEach(function(user) { users[user.uid] = user; }); - + var tidToPost = {}; postData.forEach(function(post, index) { post.user = users[post.uid]; post.index = results.indices[index] + 1; post.timestamp = utils.toISOString(post.timestamp); + tidToPost[post.tid] = post; + }); + + var teasers = tids.map(function(tid) { + return tidToPost[tid]; }); - callback(null, postData); + callback(null, teasers); }); }); }); From a48e95b910f2453e86864294e32634c0b1d6635c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 01:04:20 -0400 Subject: [PATCH 172/372] check keys --- src/database/mongo/hash.js | 4 ++++ src/database/mongo/helpers.js | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 78b2e9a61c..a03d14b463 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -1,5 +1,7 @@ "use strict"; +var winston = require('winston'); + module.exports = function(db, module) { var helpers = module.helpers.mongo; @@ -23,6 +25,7 @@ module.exports = function(db, module) { }; module.getObjects = function(keys, callback) { + helpers.checkKeys(keys); db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function(err, data) { if(err) { return callback(err); @@ -53,6 +56,7 @@ module.exports = function(db, module) { }; module.getObjectsFields = function(keys, fields, callback) { + helpers.checkKeys(keys); var _fields = { _id: 0, _key: 1 diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 689a34bbf0..4a20eed2df 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -1,6 +1,7 @@ "use strict"; -var helpers = {}; +var helpers = {}, + winston = require('winston'); helpers.toMap = function(data) { var map = {}; @@ -33,4 +34,16 @@ helpers.valueToString = function(value) { helpers.noop = function() {}; +helpers.checkKeys = function(keys) { + if (!Array.isArray(keys)) { + var e = new Error('invalid keys'); + winston.warn('[INVALID_KEYS] ', e.stack); + return; + } + if (keys.length >= 50) { + var e = new Error('too many keys'); + winston.warn('[TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); + } +} + module.exports = helpers; \ No newline at end of file From 072db27a3c950b72e276f4171507132fff21f786 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 01:05:20 -0400 Subject: [PATCH 173/372] dont add more than 10 --- public/src/forum/topic/browsing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 53c9ae3046..b7554990f1 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -20,7 +20,7 @@ define('forum/topic/browsing', function() { Browsing.onUserEnter = function(data) { var activeEl = $('.thread_active_users'); var user = activeEl.find('a[data-uid="' + data.uid + '"]'); - if (!user.length) { + if (!user.length && activeEl.children().length < 10) { addUserIcon(data); } else { user.attr('data-count', parseInt(user.attr('data-count'), 10) + 1); From 3f96532c3f285744b0c0eff6e1dee58ab4cd3124 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 01:07:03 -0400 Subject: [PATCH 174/372] disabled 2 calls --- src/topics/unread.js | 1 + src/user/notifications.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/topics/unread.js b/src/topics/unread.js index 7ad5a5e709..548ea0ed93 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -157,6 +157,7 @@ module.exports = function(Topics) { } Topics.pushUnreadCount = function(uids, callback) { + return; var websockets = require('./../socket.io'); if (!uids) { diff --git a/src/user/notifications.js b/src/user/notifications.js index b3b1055222..c50b854560 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -269,6 +269,7 @@ var async = require('async'), UserNotifications.sendPostNotificationToFollowers = function(uid, tid, pid) { + return; db.getSetMembers('followers:' + uid, function(err, followers) { if (err || !followers || !followers.length) { return; From e2697030ec0873ff0eef3081efec203b2897c946 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 01:10:46 -0400 Subject: [PATCH 175/372] removed reuqire --- src/database/mongo/hash.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index a03d14b463..08daf12322 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -1,7 +1,5 @@ "use strict"; -var winston = require('winston'); - module.exports = function(db, module) { var helpers = module.helpers.mongo; From bf67424fd682fd92cb6b02dc4b17adeed2d447f8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 01:55:56 -0400 Subject: [PATCH 176/372] if there are no uids return --- src/socket.io/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index a2191f34e9..2192e9fd06 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -383,6 +383,9 @@ function updateRoomBrowsingText(roomName, selfUid) { if (selfUid) { uids = [selfUid].concat(uids); } + if (!uids.length) { + return; + } user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) { if(!err) { users = users.filter(function(user) { From a4c625a4d68711410f907da2c4acd71970666fd7 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:44:04 -0400 Subject: [PATCH 177/372] use compiled bcrypt --- bcrypt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bcrypt.js b/bcrypt.js index a926310923..e11308b0ab 100644 --- a/bcrypt.js +++ b/bcrypt.js @@ -2,7 +2,7 @@ 'use strict'; -var bcrypt = require('bcryptjs'); +var bcrypt = require('bcrypt'); process.on('message', function(m) { if (m.type === 'hash') { From 4fd9b58a5533c7e8dea1918b6578ebfffc4ec032 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:44:41 -0400 Subject: [PATCH 178/372] give mongo room to breathe --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 42d198c047..b8e9c0fe27 100644 --- a/loader.js +++ b/loader.js @@ -6,7 +6,7 @@ var nconf = require('nconf'), async = require('async'), pidFilePath = __dirname + '/pidfile', output = fs.openSync(__dirname + '/logs/output.log', 'a'), - numCPUs = require('os').cpus().length, + numCPUs = 12,// require('os').cpus().length, Loader = { timesStarted: 0, shutdown_queue: [], From 6c7d7a45e81f59256855ef811e6e8a09ec9c939f Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:45:10 -0400 Subject: [PATCH 179/372] their plugins, in package.json --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 73e81c4b63..a254ba47cc 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "mkdirp": "~0.5.0", "morgan": "^1.0.0", "nconf": "~0.6.7", + "nodebb-plugin-downvote-post": "^0.1.0", + "nodebb-plugin-upvote-post": "^1.0.0", "nodebb-plugin-dbsearch": "0.0.13", "nodebb-plugin-markdown": "~0.5.0", "nodebb-plugin-mentions": "~0.6.0", From 15052143d39d50940feeeb4ea09d030cd1408d5c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:45:57 -0400 Subject: [PATCH 180/372] that preloader, will blow up your datacenter. --- public/src/ajaxify.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 0e419fda58..7fd621a3ff 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -303,33 +303,6 @@ $(document).ready(function() { } }); - $(document.body).on('mouseover', 'a', function (e) { - if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false') { - return; - } - - if (this.host === window.location.host) { - // Internal link - var url = this.href.replace(rootUrl + '/', ''), - currentTime = (new Date()).getTime(); - - if (!ajaxify.preloader[url] || (!ajaxify.preloader[url].loading && currentTime - ajaxify.preloader[url].lastFetched > PRELOADER_RATE_LIMIT)) { - ajaxify.preloader[url] = { - loading: true - }; - ajaxify.loadData(url, function(err, data) { - ajaxify.preloader[url] = err ? null : { - url: url, - data: data, - lastFetched: currentTime, - loading: false - }; - }); - } - } - - }); - templates.registerLoader(ajaxify.loadTemplate); templatesModule.refresh(app.load); }); From 071be4ae7f29820e25819b283465717583497aa7 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:47:58 -0400 Subject: [PATCH 181/372] cache popular route for anons --- src/controllers/categories.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index f7557210fb..f9039e2bd3 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -27,11 +27,19 @@ categoriesController.recent = function(req, res, next) { }); }; +var anonCache = {}, lastUpdateTime = 0; + categoriesController.popular = function(req, res, next) { var uid = req.user ? req.user.uid : 0; var term = req.params.term || 'daily'; + if (uid === 0) { + if (anonCache[term] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) { + return res.render('popular', anonCache[term]); + } + } + topics.getPopular(term, uid, meta.config.topicsPerList, function(err, data) { if (err) { return next(err); @@ -40,6 +48,11 @@ categoriesController.popular = function(req, res, next) { data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; plugins.fireHook('filter:category.get', {topics: data}, uid, function(err, data) { + if (uid === 0) { + anonCache[term] = data; + lastUpdateTime = Date.now(); + } + res.render('popular', data); }); }); From 056f89d6cf69aa52abe88f235ec0e90caabbc46a Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:49:15 -0400 Subject: [PATCH 182/372] use redis session storage instead of connect-mongo --- src/database/mongo.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 58a1ea310f..4f38b9fe3e 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -9,7 +9,9 @@ session = require('express-session'), db, mongoClient, - mongoStore; + redis = require('redis'), + redisClient = redis.createClient(), + connectRedis; module.questions = [ { @@ -44,7 +46,7 @@ module.init = function(callback) { try { mongoClient = require('mongodb').MongoClient; - mongoStore = require('connect-mongo')({session: session}); + connectRedis = require('connect-redis')(session); } catch (err) { winston.error('Unable to initialize MongoDB! Is MongoDB installed? Error :' + err.message); process.exit(); @@ -60,8 +62,9 @@ module.client = db; - module.sessionStore = new mongoStore({ - db: db + module.sessionStore = new connectRedis({ + client: redisClient, + ttl: 60 * 60 * 24 * 14 }); require('./mongo/main')(db, module); From 61895633b3d6ca6a5900372185eac339880b9e17 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:51:27 -0400 Subject: [PATCH 183/372] disabling updateLastOnlineTime until we can move this key to redis --- src/middleware/middleware.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index dd9b8e9dd2..ba74f6730a 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -36,6 +36,8 @@ middleware.authenticate = function(req, res, next) { middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(); middleware.updateLastOnlineTime = function(req, res, next) { + return next(); + if(req.user) { user.updateLastOnlineTime(req.user.uid); } From 0de458141b83664e672d573add2afc9a071a8807 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 01:55:09 -0400 Subject: [PATCH 184/372] separated the sockets.emit to socket.emit + broadcast so OP gets action right away. moved callback sooner. --- src/socket.io/posts.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 94f2c1f338..2fa2ff1bf3 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -36,7 +36,16 @@ SocketPosts.reply = function(socket, data, callback) { 'topics:reply': true }; - websockets.server.sockets.emit('event:new_post', { + callback(); + + socket.emit('event:new_post', { + posts: [postData], + privileges: privileges, + 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, + 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, + }); + + socket.broadcast.emit('event:new_post', { posts: [postData], privileges: privileges, 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, @@ -45,8 +54,6 @@ SocketPosts.reply = function(socket, data, callback) { module.parent.exports.emitTopicPostStats(); topics.pushUnreadCount(); - - callback(); } }); }; From 6aa47efc06e98654a65170660cdcfcb7b1c20f2d Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 02:01:45 -0400 Subject: [PATCH 185/372] we don't trust user.isOnline, waiting on an optimization (disabling for now) --- src/socket.io/user.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/socket.io/user.js b/src/socket.io/user.js index fa1a063fc7..48cc87bd4b 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -87,9 +87,13 @@ SocketUser.reset.commit = function(socket, data, callback) { } }; +var tempCache = null; // temp, as always a false promise --psychobunny SocketUser.isOnline = function(socket, uid, callback) { + if (tempCache) return callback(null, tempCache); + user.isOnline([uid], function(err, data) { - callback(err, Array.isArray(data) ? data[0] : null); + tempCache = Array.isArray(data) ? data[0] : null; + callback(err, tempCache); }); }; From 07376c9a4c45b0887dd2f97e19f3bd098741f839 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 02:16:19 -0400 Subject: [PATCH 186/372] look for unread only for today --- src/topics/unread.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index 548ea0ed93..8594ff371f 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -87,7 +87,7 @@ module.exports = function(Topics) { async.whilst(function() { return unreadTids.length < count && !done; }, function(next) { - Topics.getLatestTids(start, stop, 'month', function(err, tids) { + Topics.getLatestTids(start, stop, 'day', function(err, tids) { if (err) { return next(err); } From 099a04c1c8c083ead62cbd175781ee242ca2002e Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 6 Sep 2014 02:17:02 -0400 Subject: [PATCH 187/372] disabling pushUnreadCount for now, perhaps this might be good on redis too? --- src/topics/unread.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index 8594ff371f..839034b010 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -157,7 +157,12 @@ module.exports = function(Topics) { } Topics.pushUnreadCount = function(uids, callback) { - return; + if (typeof callback === 'function') { + return callback(null); + } else { + return null; + } + var websockets = require('./../socket.io'); if (!uids) { From 51cafe075c4661e63a0cfd45979aaac04dc89159 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:34:39 -0400 Subject: [PATCH 188/372] only get 25 posts --- src/categories/activeusers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories/activeusers.js b/src/categories/activeusers.js index 6dbee43191..2cd10ef2b8 100644 --- a/src/categories/activeusers.js +++ b/src/categories/activeusers.js @@ -8,7 +8,7 @@ module.exports = function(Categories) { Categories.getActiveUsers = function(cid, callback) { async.waterfall([ function(next) { - db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, 49, next); + db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, 24, next); }, function(pids, next) { var keys = pids.map(function(pid) { From 5fd199ae8ade3cfad2ab1c2cd373df05ce52fbe2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:39:54 -0400 Subject: [PATCH 189/372] check privs pids --- src/privileges/posts.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/privileges/posts.js b/src/privileges/posts.js index f68a235866..b630014705 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -2,6 +2,7 @@ 'use strict'; var async = require('async'), + winston = require('winston'), posts = require('../posts'), topics = require('../topics'), @@ -15,7 +16,10 @@ module.exports = function(privileges) { privileges.posts = {}; privileges.posts.get = function(pids, uid, callback) { - + if (pids && pids.length > 100) { + var e = new Error('too many keys') + winston.warn('[TOO_MANY_KEYS] ' + pid.length, e.stack); + } async.parallel({ manage_content: function(next) { helpers.hasEnoughReputationFor('privileges:manage_content', uid, next); From 74050519245d407bbaad6639ebc8d3e5cdaeeaba Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:41:04 -0400 Subject: [PATCH 190/372] 50 --- src/privileges/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/privileges/posts.js b/src/privileges/posts.js index b630014705..f58c4e70b1 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -16,7 +16,7 @@ module.exports = function(privileges) { privileges.posts = {}; privileges.posts.get = function(pids, uid, callback) { - if (pids && pids.length > 100) { + if (pids && pids.length > 50) { var e = new Error('too many keys') winston.warn('[TOO_MANY_KEYS] ' + pid.length, e.stack); } From 017f6bde2cdf2bd090ead63e83b9d924d4e18eb5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:45:38 -0400 Subject: [PATCH 191/372] 50 --- src/database/mongo/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 4a20eed2df..1eec59c3b8 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -40,7 +40,7 @@ helpers.checkKeys = function(keys) { winston.warn('[INVALID_KEYS] ', e.stack); return; } - if (keys.length >= 50) { + if (keys.length > 50) { var e = new Error('too many keys'); winston.warn('[TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); } From 0cfa430444cd2e1fb5c9cea77c50e7478a0faa7f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:46:43 -0400 Subject: [PATCH 192/372] crash fix --- src/privileges/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/privileges/posts.js b/src/privileges/posts.js index f58c4e70b1..4a5b02d108 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -18,7 +18,7 @@ module.exports = function(privileges) { privileges.posts.get = function(pids, uid, callback) { if (pids && pids.length > 50) { var e = new Error('too many keys') - winston.warn('[TOO_MANY_KEYS] ' + pid.length, e.stack); + winston.warn('[TOO_MANY_KEYS] ' + pids.length, e.stack); } async.parallel({ manage_content: function(next) { From 126b1c4f4ca5cf4a98c848a9b9539f087ffc9d7e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 02:59:40 -0400 Subject: [PATCH 193/372] only get 20 chats --- src/controllers/accounts.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index a9466e4569..903853d562 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -490,7 +490,7 @@ accountsController.getNotifications = function(req, res, next) { accountsController.getChats = function(req, res, next) { async.parallel({ contacts: async.apply(user.getFollowing, req.user.uid), - recentChats: async.apply(messaging.getRecentChats, req.user.uid, 0, -1) + recentChats: async.apply(messaging.getRecentChats, req.user.uid, 0, 19) }, function(err, results) { if (err) { return next(err); @@ -507,10 +507,6 @@ accountsController.getChats = function(req, res, next) { }); } - if (results.recentChats.length > 20) { - results.recentChats.length = 20; - } - if (!req.params.userslug) { return res.render('chats', { chats: results.recentChats, From 0d5605ab35115d7f9ce5192614b0007c5da100e4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:09:41 -0400 Subject: [PATCH 194/372] trace in addPostData --- src/topics/posts.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/topics/posts.js b/src/topics/posts.js index ece6c5fcd2..20edb89ea4 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -3,6 +3,7 @@ 'use strict'; var async = require('async'), + winston = require('winston'), db = require('../database'), user = require('../user'), @@ -37,6 +38,10 @@ module.exports = function(Topics) { }; Topics.addPostData = function(postData, uid, callback) { + if (postData && postData.length > 50) { + var e = new Error('too many keys'); + winston.warn('[ADD POST DATA] ' + postData.length, e.stack); + } var pids = postData.map(function(post) { return post && post.pid; }); From 0b83657868547e67b5c464f322a5a3421cabd708 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:19:13 -0400 Subject: [PATCH 195/372] more trace --- src/posts.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/posts.js b/src/posts.js index d09ad0d555..6d22f5eb5d 100644 --- a/src/posts.js +++ b/src/posts.js @@ -113,6 +113,10 @@ var async = require('async'), }; Posts.getPostsByPids = function(pids, callback) { + if (pids && pids.length) { + var e = new Error('getPostsByPids'); + winston.warn('[GET_POST_BY_PIDS ' + pids.length, e.stack); + } var keys = []; for(var x=0, numPids=pids.length; x Date: Sat, 6 Sep 2014 03:21:00 -0400 Subject: [PATCH 196/372] fix --- src/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 6d22f5eb5d..1327b41060 100644 --- a/src/posts.js +++ b/src/posts.js @@ -113,7 +113,7 @@ var async = require('async'), }; Posts.getPostsByPids = function(pids, callback) { - if (pids && pids.length) { + if (pids && pids.length > 100) { var e = new Error('getPostsByPids'); winston.warn('[GET_POST_BY_PIDS ' + pids.length, e.stack); } From 5160a4f90f736af50d687c50508bfbf16fb0341d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:25:32 -0400 Subject: [PATCH 197/372] getTopicwithposts --- src/topics.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/topics.js b/src/topics.js index 2e53e2672c..f3728fabe7 100644 --- a/src/topics.js +++ b/src/topics.js @@ -1,6 +1,7 @@ "use strict"; var async = require('async'), + winston = require('winston'), validator = require('validator'), _ = require('underscore'), @@ -243,6 +244,10 @@ var async = require('async'), }; Topics.getTopicWithPosts = function(tid, set, uid, start, end, reverse, callback) { + if (end - start > 50) { + var e = new Error('TOO LARGE'); + winston.warn('GET_TOPIC_WITH_POSTS set, start, end, uid, tid', set, start, end, uid, tid, e.stack); + } Topics.getTopicData(tid, function(err, topicData) { if (err || !topicData) { return callback(err || new Error('[[error:no-topic]]')); From edd73b5be2d630a8bae9fca3a5fd1913dfe3b8f4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:35:28 -0400 Subject: [PATCH 198/372] again --- src/topics.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/topics.js b/src/topics.js index f3728fabe7..7145cd9f68 100644 --- a/src/topics.js +++ b/src/topics.js @@ -244,10 +244,7 @@ var async = require('async'), }; Topics.getTopicWithPosts = function(tid, set, uid, start, end, reverse, callback) { - if (end - start > 50) { - var e = new Error('TOO LARGE'); - winston.warn('GET_TOPIC_WITH_POSTS set, start, end, uid, tid', set, start, end, uid, tid, e.stack); - } + Topics.getTopicData(tid, function(err, topicData) { if (err || !topicData) { return callback(err || new Error('[[error:no-topic]]')); @@ -268,7 +265,10 @@ var async = require('async'), if (err) { return next(err); } - + if (posts.length > 100) { + var e = new Error('TOO LARGE'); + winston.warn('GET_TOPIC_WITH_POSTS set, start, end, uid, tid', set, start, end, uid, tid, e.stack); + } Topics.addPostData(posts, uid, next); }); }); @@ -337,7 +337,7 @@ var async = require('async'), Topics.addPostData(postData, uid, callback); }); }); - } + }; Topics.getTeasers = function(tids, uid, callback) { if(!Array.isArray(tids)) { From 34a4a2103f3a336f63779a8704e75faa69d1864c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:45:34 -0400 Subject: [PATCH 199/372] isNan check --- src/topics.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/topics.js b/src/topics.js index 7145cd9f68..a828047cfc 100644 --- a/src/topics.js +++ b/src/topics.js @@ -244,7 +244,10 @@ var async = require('async'), }; Topics.getTopicWithPosts = function(tid, set, uid, start, end, reverse, callback) { - + if (isNaN(start) || isNaN(end)) { + var e = new Error('TOO LARGE'); + winston.warn('IS_NAN_CHECK set, start, end, uid, tid', set, start, end, uid, tid, e.stack); + } Topics.getTopicData(tid, function(err, topicData) { if (err || !topicData) { return callback(err || new Error('[[error:no-topic]]')); From f9c9f5856162c5f459122ca3b9364d56f3c46390 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 03:56:15 -0400 Subject: [PATCH 200/372] post_index fix --- src/controllers/topics.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index bfc2b4e396..6e72f3a446 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -62,9 +62,10 @@ topicsController.get = function(req, res, next) { } var postIndex = 0; + req.params.post_index = req.params.post_index || 0; if (!settings.usePagination) { if (reverse) { - if (!req.params.post_index || parseInt(req.params.post_index, 10) === 1) { + if (parseInt(req.params.post_index, 10) === 1) { req.params.post_index = 0; } postIndex = Math.max(postCount - (req.params.post_index || postCount) - (settings.postsPerPage - 1), 0); From 518d731562970c2f77c15bb0b278d1ebcba5b517 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 04:09:13 -0400 Subject: [PATCH 201/372] one more time --- src/controllers/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 6e72f3a446..8cbde07192 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -62,7 +62,7 @@ topicsController.get = function(req, res, next) { } var postIndex = 0; - req.params.post_index = req.params.post_index || 0; + req.params.post_index = parseInt(req.params.post_index, 10) || 0; if (!settings.usePagination) { if (reverse) { if (parseInt(req.params.post_index, 10) === 1) { From 3f4ede0778cf4c6dc789cd4dd49d3b7204da7c92 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 04:11:44 -0400 Subject: [PATCH 202/372] parseInt --- src/database/mongo/sorted.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index e2bc988f81..c290b7000a 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -112,6 +112,7 @@ module.exports = function(db, module) { module.sortedSetCard = function(key, callback) { db.collection('objects').count({_key:key}, function(err, count) { + count = parseInt(count, 10); callback(err, count ? count : 0); }); }; From 4b20e78de2739bf081e6fe5f499845648887293c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 04:29:20 -0400 Subject: [PATCH 203/372] hopefully final fix --- src/controllers/topics.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 8cbde07192..c955f25cbf 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -15,7 +15,7 @@ var topicsController = {}, topicsController.get = function(req, res, next) { var tid = req.params.topic_id, - page = req.query.page || 1, + page = 1, sort = req.query.sort, uid = req.user ? req.user.uid : 0, userPrivileges; @@ -62,10 +62,11 @@ topicsController.get = function(req, res, next) { } var postIndex = 0; + page = parseInt(req.query.page, 10) || 1; req.params.post_index = parseInt(req.params.post_index, 10) || 0; if (!settings.usePagination) { if (reverse) { - if (parseInt(req.params.post_index, 10) === 1) { + if (req.params.post_index === 1) { req.params.post_index = 0; } postIndex = Math.max(postCount - (req.params.post_index || postCount) - (settings.postsPerPage - 1), 0); @@ -73,7 +74,7 @@ topicsController.get = function(req, res, next) { postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage + 1), 0); } } else if (!req.query.page) { - var index = Math.max(parseInt(req.params.post_index, 10), 0); + var index = Math.max(req.params.post_index, 0); page = Math.ceil((index + 1) / settings.postsPerPage); } From 0ad42873d59b46a28b70bdfcd9db2b8e37b1736f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 04:30:25 -0400 Subject: [PATCH 204/372] nasty --- public/src/forum/topic/browsing.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index b7554990f1..80929bf454 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -78,7 +78,6 @@ define('forum/topic/browsing', function() { function createUserIcon(uid, picture, userslug, username) { if(!$('.thread_active_users').find('[data-uid="' + uid + '"]').length) { - console.log('are u fucking kidming', arguments); return $('
    '); } } From 57e204df8e1f6cee59fd5af5ed437378d0528431 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 20:46:18 -0400 Subject: [PATCH 205/372] removed most of the debug dont get more than 6 usernames for upvote tooltips generatePostPaths wont check null pids --- public/src/forum/topic/postTools.js | 22 +++++++--------------- src/posts.js | 7 +++---- src/privileges/posts.js | 4 ---- src/socket.io/posts.js | 23 +++++++++++------------ src/topics.js | 9 +-------- src/topics/posts.js | 4 ---- src/user/notifications.js | 14 ++++++-------- 7 files changed, 28 insertions(+), 55 deletions(-) diff --git a/public/src/forum/topic/postTools.js b/public/src/forum/topic/postTools.js index 3b63650942..2bc22cb06a 100644 --- a/public/src/forum/topic/postTools.js +++ b/public/src/forum/topic/postTools.js @@ -14,8 +14,6 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com share.addShareHandlers(topicName); - addFavouriteHandler(); - addVoteHandler(); }; @@ -37,12 +35,6 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com }); }; - function addFavouriteHandler() { - $('#post-container').on('mouseenter', '.favourite-tooltip', function() { - loadDataAndCreateTooltip($(this), 'posts.getFavouritedUsers'); - }); - } - function addVoteHandler() { $('#post-container').on('mouseenter', '.post-row .votes', function() { loadDataAndCreateTooltip($(this), 'posts.getUpvoters'); @@ -51,21 +43,21 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com function loadDataAndCreateTooltip(el, method) { var pid = el.parents('.post-row').attr('data-pid'); - socket.emit(method, pid, function(err, usernames) { + socket.emit(method, pid, function(err, data) { if (!err) { - createTooltip(el, usernames); + createTooltip(el, data); } }); } - function createTooltip(el, usernames) { + function createTooltip(el, data) { + var usernames = data.usernames; if (!usernames.length) { return; } - if (usernames.length > 6) { - var otherCount = usernames.length - 5; - usernames = usernames.slice(0, 5).join(', ').replace(/,/g, '|'); - translator.translate('[[topic:users_and_others, ' + usernames + ', ' + otherCount + ']]', function(translated) { + if (usernames.length + data.otherCount > 6) { + usernames = usernames.join(', ').replace(/,/g, '|'); + translator.translate('[[topic:users_and_others, ' + usernames + ', ' + data.otherCount + ']]', function(translated) { translated = translated.replace(/\|/g, ','); el.attr('title', translated).tooltip('destroy').tooltip('show'); }); diff --git a/src/posts.js b/src/posts.js index 1327b41060..2a895d329a 100644 --- a/src/posts.js +++ b/src/posts.js @@ -109,14 +109,13 @@ var async = require('async'), }; Posts.getPidsFromSet = function(set, start, end, reverse, callback) { + if (isNaN(start) || isNaN(end)) { + return callback(null, []); + } db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, callback); }; Posts.getPostsByPids = function(pids, callback) { - if (pids && pids.length > 100) { - var e = new Error('getPostsByPids'); - winston.warn('[GET_POST_BY_PIDS ' + pids.length, e.stack); - } var keys = []; for(var x=0, numPids=pids.length; x 50) { - var e = new Error('too many keys') - winston.warn('[TOO_MANY_KEYS] ' + pids.length, e.stack); - } async.parallel({ manage_content: function(next) { helpers.hasEnoughReputationFor('privileges:manage_content', uid, next); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 2fa2ff1bf3..e85114f592 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -254,23 +254,22 @@ SocketPosts.getPrivileges = function(socket, pid, callback) { }); }; -SocketPosts.getFavouritedUsers = function(socket, pid, callback) { - favourites.getFavouritedUidsByPids([pid], function(err, data) { - if (err || !Array.isArray(data) || !data.length) { - return callback(err); - } - - user.getUsernamesByUids(data[0], callback); - }); -}; - SocketPosts.getUpvoters = function(socket, pid, callback) { favourites.getUpvotedUidsByPids([pid], function(err, data) { if (err || !Array.isArray(data) || !data.length) { return callback(err, []); } - - user.getUsernamesByUids(data[0], callback); + var otherCount = 0; + if (data[0].length > 6) { + otherCount = data[0].length - 5; + data[0] = data[0].slice(0, 5); + } + user.getUsernamesByUids(data[0], function(err, usernames) { + callback(err, { + otherCount: otherCount, + usernames: usernames + }); + }); }); }; diff --git a/src/topics.js b/src/topics.js index a828047cfc..d43cb0fb31 100644 --- a/src/topics.js +++ b/src/topics.js @@ -244,10 +244,6 @@ var async = require('async'), }; Topics.getTopicWithPosts = function(tid, set, uid, start, end, reverse, callback) { - if (isNaN(start) || isNaN(end)) { - var e = new Error('TOO LARGE'); - winston.warn('IS_NAN_CHECK set, start, end, uid, tid', set, start, end, uid, tid, e.stack); - } Topics.getTopicData(tid, function(err, topicData) { if (err || !topicData) { return callback(err || new Error('[[error:no-topic]]')); @@ -268,10 +264,7 @@ var async = require('async'), if (err) { return next(err); } - if (posts.length > 100) { - var e = new Error('TOO LARGE'); - winston.warn('GET_TOPIC_WITH_POSTS set, start, end, uid, tid', set, start, end, uid, tid, e.stack); - } + Topics.addPostData(posts, uid, next); }); }); diff --git a/src/topics/posts.js b/src/topics/posts.js index 20edb89ea4..a8e4eacc79 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -38,10 +38,6 @@ module.exports = function(Topics) { }; Topics.addPostData = function(postData, uid, callback) { - if (postData && postData.length > 50) { - var e = new Error('too many keys'); - winston.warn('[ADD POST DATA] ' + postData.length, e.stack); - } var pids = postData.map(function(post) { return post && post.pid; }); diff --git a/src/user/notifications.js b/src/user/notifications.js index c50b854560..e165512178 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -135,7 +135,7 @@ var async = require('async'), return notification ? notification.pid : null; }); - generatePostPaths(pids, uid, function(err, paths) { + generatePostPaths(pids, uid, function(err, pidToPaths) { if (err) { return callback(err); } @@ -146,7 +146,7 @@ var async = require('async'), } notification.read = hasRead[index]; - notification.path = paths[index] || notification.path || ''; + notification.path = pidToPaths[notification.pid] || notification.path || ''; notification.datetimeISO = utils.toISOString(notification.datetime); notification.readClass = !notification.read ? 'label-warning' : ''; return notification; @@ -159,7 +159,7 @@ var async = require('async'), }; function generatePostPaths(pids, uid, callback) { - var postKeys = pids.map(function(pid) { + var postKeys = pids.filter(Boolean).map(function(pid) { return 'post:' + pid; }); @@ -184,18 +184,16 @@ var async = require('async'), return callback(err); } - var paths = []; + var pidToPaths = {}; pids.forEach(function(pid, index) { var slug = results.topics[index] ? results.topics[index].slug : null; var postIndex = utils.isNumber(results.indices[index]) ? parseInt(results.indices[index], 10) + 1 : null; if (slug && postIndex) { - paths.push(nconf.get('relative_path') + '/topic/' + slug + '/' + postIndex); - } else { - paths.push(null); + pidToPaths[pid] = nconf.get('relative_path') + '/topic/' + slug + '/' + postIndex; } }); - callback(null, paths); + callback(null, pidToPaths); }); }); } From fe58ea55d47be76d22b4a13be62ce44e602a6862 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 20:47:20 -0400 Subject: [PATCH 206/372] moved debugs 1 level up increased key limit to 100 --- src/database/mongo/hash.js | 11 +++++++++-- src/database/mongo/helpers.js | 14 ++------------ src/database/mongo/sets.js | 4 ++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 08daf12322..1512fbec20 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -23,7 +23,10 @@ module.exports = function(db, module) { }; module.getObjects = function(keys, callback) { - helpers.checkKeys(keys); + if (keys.length > helpers.KEY_LIMIT) { + var e = new Error('too many keys'); + winston.warn('[HASH - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); + } db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function(err, data) { if(err) { return callback(err); @@ -54,7 +57,11 @@ module.exports = function(db, module) { }; module.getObjectsFields = function(keys, fields, callback) { - helpers.checkKeys(keys); + if (keys.length > helpers.KEY_LIMIT) { + var e = new Error('too many keys'); + winston.warn('[HASH - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); + } + var _fields = { _id: 0, _key: 1 diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 1eec59c3b8..d8e62fb3f1 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -9,7 +9,7 @@ helpers.toMap = function(data) { map[data[i]._key] = data[i]; } return map; -} +}; helpers.fieldToString = function(field) { if(field === null || field === undefined) { @@ -34,16 +34,6 @@ helpers.valueToString = function(value) { helpers.noop = function() {}; -helpers.checkKeys = function(keys) { - if (!Array.isArray(keys)) { - var e = new Error('invalid keys'); - winston.warn('[INVALID_KEYS] ', e.stack); - return; - } - if (keys.length > 50) { - var e = new Error('too many keys'); - winston.warn('[TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); - } -} +helpers.KEY_LIMIT = 100; module.exports = helpers; \ No newline at end of file diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index e12bc5caa0..7b0049068e 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -96,6 +96,10 @@ module.exports = function(db, module) { }; module.getSetsMembers = function(keys, callback) { + if (keys.length > helpers.KEY_LIMIT) { + var e = new Error('too many keys'); + winston.warn('[SET - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); + } db.collection('objects').find({_key: {$in: keys}}, {_key: 1, members: 1}).toArray(function(err, data) { if (err) { return callback(err); From b7ee875b12f59b4fd6c40eba4503a327f55ea9bc Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 22:08:55 -0400 Subject: [PATCH 207/372] user.isOnline fix wont hit db on every user socket connect or user disconnect --- public/src/forum/account/profile.js | 9 +++---- public/src/forum/chats.js | 13 +++++++++ public/src/forum/topic/browsing.js | 14 +++++----- public/src/forum/topic/events.js | 4 +-- public/src/forum/users.js | 39 ++++++++++----------------- public/src/modules/chat.js | 9 ++++--- src/messaging.js | 14 +++++----- src/socket.io/index.js | 21 +++++++-------- src/socket.io/user.js | 41 ++++++++++++++++++++--------- src/user.js | 30 --------------------- 10 files changed, 90 insertions(+), 104 deletions(-) diff --git a/public/src/forum/account/profile.js b/public/src/forum/account/profile.js index a9633b6aba..a7a522f220 100644 --- a/public/src/forum/account/profile.js +++ b/public/src/forum/account/profile.js @@ -33,7 +33,8 @@ define('forum/account/profile', ['forum/account/header'], function(header) { app.openChat($('.account-username').html(), theirid); }); - socket.on('user.isOnline', handleUserOnline); + socket.removeListener('event:user_status_change', onUserStatusChange); + socket.on('event:user_status_change', onUserStatusChange); if (yourid !== theirid) { socket.emit('user.increaseViewCount', theirid); @@ -66,11 +67,7 @@ define('forum/account/profile', ['forum/account/header'], function(header) { return false; } - function handleUserOnline(err, data) { - if (err) { - return app.alertError(err.message); - } - + function onUserStatusChange(data) { var onlineStatus = $('.account-online-status'); if(parseInt(ajaxify.variables.get('theirid'), 10) !== parseInt(data.uid, 10)) { diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index 5ad1bd6229..2d2123b5d3 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -125,6 +125,19 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { $('.chats-list li[data-uid="' + withUid + '"]').removeClass('typing'); }); + + socket.on('event:user_status_change', function(data) { + var userEl = $('.chats-list li[data-uid="' + data.uid +'"]'); + + if (userEl.length) { + var statusEl = userEl.find('.status'); + translator.translate('[[global:' + data.status + ']]', function(translated) { + statusEl.attr('class', 'fa fa-circle status ' + data.status) + .attr('title', translated) + .attr('data-original-title', translated); + }); + } + }); }; Chats.resizeMainWindow = function() { diff --git a/public/src/forum/topic/browsing.js b/public/src/forum/topic/browsing.js index 80929bf454..d2cebbcf14 100644 --- a/public/src/forum/topic/browsing.js +++ b/public/src/forum/topic/browsing.js @@ -39,16 +39,16 @@ define('forum/topic/browsing', function() { } }; - Browsing.onUserOnline = function(err, data) { - updateOnlineIcon($('.username-field[data-username="' + data.username + '"]'), data); + Browsing.onUserStatusChange = function(data) { + updateOnlineIcon($('.username-field[data-uid="' + data.uid + '"]'), data.status); updateBrowsingUsers(data); }; - function updateOnlineIcon(el, userData) { - translator.translate('[[global:' + userData.status + ']]', function(translated) { + function updateOnlineIcon(el, status) { + translator.translate('[[global:' + status + ']]', function(translated) { el.siblings('i') - .attr('class', 'fa fa-circle status ' + userData.status) + .attr('class', 'fa fa-circle status ' + status) .attr('title', translated) .attr('data-original-title', translated); }); @@ -57,10 +57,8 @@ define('forum/topic/browsing', function() { function updateBrowsingUsers(data) { var activeEl = $('.thread_active_users'); var user = activeEl.find('a[data-uid="'+ data.uid + '"]'); - if (user.length && !data.online) { + if (user.length && data.status === 'offline') { user.parent().remove(); - } else if(!user.length && data.online && data.rooms.indexOf('topic_' + ajaxify.variables.get('topic_id')) !== -1) { - addUserIcon(user); } } diff --git a/public/src/forum/topic/events.js b/public/src/forum/topic/events.js index a84d7ad1d6..ec61a8ff0e 100644 --- a/public/src/forum/topic/events.js +++ b/public/src/forum/topic/events.js @@ -1,7 +1,7 @@ 'use strict'; -/* globals app, ajaxify, define, socket, translator */ +/* globals app, ajaxify, define, socket, translator, templates */ define('forum/topic/events', ['forum/topic/browsing', 'forum/topic/postTools', 'forum/topic/threadTools'], function(browsing, postTools, threadTools) { @@ -11,7 +11,7 @@ define('forum/topic/events', ['forum/topic/browsing', 'forum/topic/postTools', ' 'event:update_users_in_room': browsing.onUpdateUsersInRoom, 'event:user_enter': browsing.onUserEnter, 'event:user_leave': browsing.onUserLeave, - 'user.isOnline': browsing.onUserOnline, + 'event:user_status_change': browsing.onUserStatusChange, 'event:voted': updatePostVotesAndUserReputation, 'event:favourited': updateFavouriteCount, diff --git a/public/src/forum/users.js b/public/src/forum/users.js index cdf1ab9e73..77a56c7b51 100644 --- a/public/src/forum/users.js +++ b/public/src/forum/users.js @@ -22,10 +22,8 @@ define('forum/users', function() { handleSearch(); - - socket.removeListener('user.isOnline', onUserIsOnline); - socket.on('user.isOnline', onUserIsOnline); - + socket.removeListener('event:user_status_change', onUserStatusChange); + socket.on('event:user_status_change', onUserStatusChange); $('#load-more-users-btn').on('click', loadMoreUsers); @@ -154,37 +152,28 @@ define('forum/users', function() { }); } - function onUserIsOnline(err, data) { + function onUserStatusChange(data) { var section = getActiveSection(); - if((section.indexOf('online') === 0 || section.indexOf('users') === 0) && !loadingMoreUsers) { + if((section.indexOf('online') === 0 || section.indexOf('users') === 0)) { updateUser(data); } } function updateUser(data) { - var usersContainer = $('#users-container'); - var userEl = usersContainer.find('li[data-uid="' + data.uid +'"]'); - if (!data.online) { - userEl.remove(); + if (data.status === 'offline') { return; } + var usersContainer = $('#users-container'); + var userEl = usersContainer.find('li[data-uid="' + data.uid +'"]'); - ajaxify.loadTemplate('users', function(usersTemplate) { - var html = templates.parse(templates.getBlock(usersTemplate, 'users'), {users: [data]}); - translator.translate(html, function(translated) { - if (userEl.length) { - userEl.replaceWith(translated); - return; - } - - var anonBox = usersContainer.find('li.anon-user'); - if (anonBox.length) { - $(translated).insertBefore(anonBox); - } else { - usersContainer.append(translated); - } + if (userEl.length) { + var statusEl = userEl.find('.status'); + translator.translate('[[global:' + data.status + ']]', function(translated) { + statusEl.attr('class', 'fa fa-circle status ' + data.status) + .attr('title', translated) + .attr('data-original-title', translated); }); - }); + } } function getActiveSection() { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 62221d8cc1..b4234bd488 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -114,7 +114,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, modal.find('.user-typing').addClass('hide'); }); - socket.on('user.isOnline', function(err, data) { + socket.on('event:user_status_change', function(data) { updateStatus(data.status); }); }; @@ -140,8 +140,11 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, }; function checkStatus(chatModal) { - socket.emit('user.isOnline', chatModal.attr('touid'), function(err, data) { - updateStatus(data.status); + socket.emit('user.checkStatus', chatModal.attr('touid'), function(err, status) { + if (err) { + return app.alertError(err.message); + } + updateStatus(status); }); } diff --git a/src/messaging.js b/src/messaging.js index d6cd44be5a..d951b4a3ea 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -216,8 +216,10 @@ var db = require('./database'), }; Messaging.getRecentChats = function(uid, start, end, callback) { + var websockets = require('./socket.io'); + db.getSortedSetRevRange('uid:' + uid + ':chats', start, end, function(err, uids) { - if(err) { + if (err) { return callback(err); } @@ -226,20 +228,20 @@ var db = require('./database'), return callback(err); } - user.isOnline(uids, function(err, users) { + user.getMultipleUserFields(uids, ['uid', 'username', 'picture', 'status'] , function(err, users) { if (err) { return callback(err); } - + users = users.filter(function(user) { + return user && parseInt(user.uid, 10); + }); users.forEach(function(user, index) { if (user) { user.unread = unreadUids[index]; + user.status = websockets.isUserOnline(user.uid) ? user.status : 'offline'; } }); - users = users.filter(function(user) { - return !!user.uid; - }); callback(null, users); }); }); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 2192e9fd06..f768901834 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -12,7 +12,6 @@ var SocketIO = require('socket.io'), db = require('../database'), user = require('../user'), - socketUser = require('./user'), topics = require('../topics'), logger = require('../logger'), meta = require('../meta'), @@ -59,9 +58,9 @@ function onMessage(msg) { onlineUsersMap[msg.uid] -= 1; onlineUsersMap[msg.uid] = Math.max(0, onlineUsersMap[msg.uid]); } - + var index = 0; if (msg.uid && onlineUsersMap[msg.uid] === 0) { - var index = onlineUsers.indexOf(msg.uid); + index = onlineUsers.indexOf(msg.uid); if (index !== -1) { onlineUsers.splice(index, 1); } @@ -162,7 +161,7 @@ Sockets.init = function(server) { async.parallel({ user: function(next) { - user.getUserFields(uid, ['username', 'userslug', 'picture'], next); + user.getUserFields(uid, ['username', 'userslug', 'picture', 'status'], next); }, isAdmin: function(next) { user.isAdministrator(uid, next); @@ -180,9 +179,7 @@ Sockets.init = function(server) { uid: uid }); - socketUser.isOnline(socket, uid, function(err, data) { - socket.broadcast.emit('user.isOnline', err, data); - }); + socket.broadcast.emit('event:user_status_change', {uid:uid, status: userData.user.status}); }); }); } else { @@ -200,9 +197,10 @@ Sockets.init = function(server) { if (uid && (!onlineUsersMap[uid] || onlineUsersMap[uid] <= 1)) { db.sortedSetRemove('users:online', uid, function(err) { - socketUser.isOnline(socket, uid, function(err, data) { - socket.broadcast.emit('user.isOnline', err, data); - }); + if (err) { + return winston.error(err.message); + } + socket.broadcast.emit('event:user_status_change', {uid: uid, status: 'offline'}); }); } @@ -295,7 +293,8 @@ Sockets.uidInRoom = function(uid, room) { Sockets.getSocketCount = function() { return Object.keys(socketIdToUid).length; -} +}; + Sockets.getConnectedClients = function() { return onlineUsers; }; diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 48cc87bd4b..538edac1d9 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -8,7 +8,8 @@ var async = require('async'), notifications = require('../notifications'), messaging = require('../messaging'), plugins = require('../plugins'), - utils = require('./../../public/src/utils'), + utils = require('../../public/src/utils'), + websockets = require('./index'), meta = require('../meta'), SocketUser = {}; @@ -87,13 +88,20 @@ SocketUser.reset.commit = function(socket, data, callback) { } }; -var tempCache = null; // temp, as always a false promise --psychobunny -SocketUser.isOnline = function(socket, uid, callback) { - if (tempCache) return callback(null, tempCache); - - user.isOnline([uid], function(err, data) { - tempCache = Array.isArray(data) ? data[0] : null; - callback(err, tempCache); +SocketUser.checkStatus = function(socket, uid, callback) { + if (!socket.uid) { + return callback('[[error:invalid-uid]]'); + } + var online = websockets.isUserOnline(uid); + if (!online) { + return callback(null, 'offline'); + } + user.getUserField(uid, 'status', function(err, status) { + if (err) { + return callback(err); + } + status = status || 'online'; + callback(null, status); }); }; @@ -321,12 +329,19 @@ SocketUser.loadMore = function(socket, data, callback) { SocketUser.setStatus = function(socket, status, callback) { - var server = require('./index'); + if (!socket.uid) { + return callback(new Error('[[invalid-uid]]')); + } user.setUserField(socket.uid, 'status', status, function(err) { - SocketUser.isOnline(socket, socket.uid, function(err, data) { - server.server.sockets.emit('user.isOnline', err, data); - callback(err, data); - }); + if (err) { + return callback(err); + } + var data = { + uid: socket.uid, + status: status + }; + websockets.server.sockets.emit('event:user_status_change', data); + callback(null, data); }); }; diff --git a/src/user.js b/src/user.js index 67a8c81c4d..2de6ac0778 100644 --- a/src/user.js +++ b/src/user.js @@ -408,36 +408,6 @@ var } }; - User.isOnline = function(uids, callback) { - if (!Array.isArray(uids)) { - uids = [uids]; - } - - User.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'reputation', 'postcount'] , function(err, userData) { - if (err) { - return callback(err); - } - - var websockets = require('./socket.io'); - - userData = userData.map(function(user) { - var online = websockets.isUserOnline(user.uid); - user.status = online ? (user.status || 'online') : 'offline'; - - if (user.status === 'offline') { - online = false; - } - - user.online = online; - user.timestamp = Date.now(); - user.rooms = websockets.getUserRooms(user.uid); - return user; - }); - - callback(null, userData); - }); - }; - User.getIgnoredCategories = function(uid, callback) { db.getSortedSetRange('uid:' + uid + ':ignored:cids', 0, -1, callback); }; From cc3786e22dd904ed571b141f83351b5cefbc52d6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 22:28:09 -0400 Subject: [PATCH 208/372] closes #2055 --- src/events.js | 23 +++++++++++------------ src/socket.io/admin.js | 2 ++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/events.js b/src/events.js index 64b76fdfdd..3bb4c1c78a 100644 --- a/src/events.js +++ b/src/events.js @@ -12,7 +12,7 @@ var fs = require('fs'), var logFileName = 'logs/events.log'; events.logPasswordChange = function(uid) { - logWithUser(uid, 'changed password'); + events.logWithUser(uid, 'changed password'); }; events.logAdminChangeUserPassword = function(adminUid, theirUid, callback) { @@ -35,43 +35,42 @@ var fs = require('fs'), } events.logPasswordReset = function(uid) { - logWithUser(uid, 'reset password'); + events.logWithUser(uid, 'reset password'); }; events.logEmailChange = function(uid, oldEmail, newEmail) { - logWithUser(uid,'changed email from "' + oldEmail + '" to "' + newEmail +'"'); + events.logWithUser(uid,'changed email from "' + oldEmail + '" to "' + newEmail +'"'); }; events.logUsernameChange = function(uid, oldUsername, newUsername) { - logWithUser(uid,'changed username from "' + oldUsername + '" to "' + newUsername +'"'); + events.logWithUser(uid,'changed username from "' + oldUsername + '" to "' + newUsername +'"'); }; events.logAdminLogin = function(uid) { - logWithUser(uid, 'logged into admin panel'); + events.logWithUser(uid, 'logged into admin panel'); }; events.logPostEdit = function(uid, pid) { - logWithUser(uid, 'edited post (pid ' + pid + ')'); + events.logWithUser(uid, 'edited post (pid ' + pid + ')'); }; events.logPostDelete = function(uid, pid) { - logWithUser(uid, 'deleted post (pid ' + pid + ')'); + events.logWithUser(uid, 'deleted post (pid ' + pid + ')'); }; events.logPostRestore = function(uid, pid) { - logWithUser(uid, 'restored post (pid ' + pid + ')'); + events.logWithUser(uid, 'restored post (pid ' + pid + ')'); }; events.logTopicDelete = function(uid, tid) { - logWithUser(uid, 'deleted topic (tid ' + tid + ')'); + events.logWithUser(uid, 'deleted topic (tid ' + tid + ')'); }; events.logTopicRestore = function(uid, tid) { - logWithUser(uid, 'restored topic (tid ' + tid + ')'); + events.logWithUser(uid, 'restored topic (tid ' + tid + ')'); }; - function logWithUser(uid, string) { - + events.logWithUser = function(uid, string) { user.getUserField(uid, 'username', function(err, username) { if(err) { return winston.error('Error logging event. ' + err.message); diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 5ba9e1fff7..aaecf9ad5d 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -40,6 +40,7 @@ SocketAdmin.before = function(socket, method, next) { }; SocketAdmin.reload = function(socket, data, callback) { + events.logWithUser(socket.uid, ' is reloading NodeBB'); if (cluster.isWorker) { process.send({ action: 'reload' @@ -50,6 +51,7 @@ SocketAdmin.reload = function(socket, data, callback) { }; SocketAdmin.restart = function(socket, data, callback) { + events.logWithUser(socket.uid, ' is restarting NodeBB'); meta.restart(); }; From 273db693e764f7daff029c86692f307d1ba2b996 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 6 Sep 2014 23:57:51 -0400 Subject: [PATCH 209/372] closes #2051 --- public/src/forum/topic.js | 4 ++++ public/src/modules/navigator.js | 11 +++++++++++ public/src/utils.js | 10 ++++++++++ 3 files changed, 25 insertions(+) diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 5098d66d5d..6c0ece91df 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -212,6 +212,10 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT }; Topic.navigatorCallback = function(element, elementCount) { + var path = ajaxify.removeRelativePath(window.location.pathname.slice(1)); + if (!path.startsWith('topic')) { + return 1; + } var postIndex = parseInt(element.attr('data-index'), 10); var index = postIndex + 1; if (config.topicPostSort !== 'oldest_to_newest') { diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 3392a7bd83..0d62b52b00 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -24,6 +24,12 @@ define('navigator', ['forum/pagination'], function(pagination) { e.stopPropagation(); }); + $('.pagination-block').off('shown.bs.dropdown', '.dropdown').on('shown.bs.dropdown', '.dropdown', function() { + setTimeout(function() { + $('.pagination-block input').focus(); + }, 100); + }); + $('.pagination-block .pageup').off('click').on('click', navigator.scrollUp); $('.pagination-block .pagedown').off('click').on('click', navigator.scrollDown); $('.pagination-block .pagetop').off('click').on('click', toTop); @@ -72,6 +78,11 @@ define('navigator', ['forum/pagination'], function(pagination) { }; function toggle(flag) { + var path = ajaxify.removeRelativePath(window.location.pathname.slice(1)); + if (flag && (!path.startsWith('topic') && !path.startsWith('category'))) { + return; + } + $('.pagination-block').toggleClass('hidden', !flag); } diff --git a/public/src/utils.js b/public/src/utils.js index 28973b9c8d..104f8e4f6d 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -359,6 +359,16 @@ } }; + if (typeof String.prototype.startsWith != 'function') { + String.prototype.startsWith = function (prefix){ + if (this.length < prefix.length) + return false; + for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) + continue; + return i < 0; + }; + } + if ('undefined' !== typeof window) { window.utils = module.exports; } From 2252ab74797d482f2fc718b88af052a052365924 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 03:17:51 -0400 Subject: [PATCH 210/372] notifications.prune run every hour dont get more than 500 notifications --- src/database/level/main.js | 4 ++++ src/database/mongo/main.js | 5 +++++ src/database/redis/main.js | 8 ++++++++ src/notifications.js | 40 +++++++++++++++++++++----------------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/database/level/main.js b/src/database/level/main.js index 97586c33b2..00c55bb16d 100644 --- a/src/database/level/main.js +++ b/src/database/level/main.js @@ -41,6 +41,10 @@ module.exports = function(db, module) { db.del(key, callback); }; + module.deleteAll = function(keys, callback) { + async.each(keys, module.delete, callback); + }; + module.get = function(key, callback) { db.get(key, function(err, value) { callback(false, value); diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index dacce8d9f6..2382acd28e 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -74,6 +74,11 @@ module.exports = function(db, module) { db.collection('objects').remove({_key:key}, callback); }; + module.deleteAll = function(keys, callback) { + callback = callback || helpers.noop; + db.collection('objects').remove({_key: {$in: keys}}, callback); + }; + module.get = function(key, callback) { module.getObjectField(key, 'value', callback); }; diff --git a/src/database/redis/main.js b/src/database/redis/main.js index 1cf8d35f5c..986c3a7bc8 100644 --- a/src/database/redis/main.js +++ b/src/database/redis/main.js @@ -73,6 +73,14 @@ module.exports = function(redisClient, module) { redisClient.del(key, callback); }; + module.deleteAll = function(keys, callback) { + var multi = redisClient.multi(); + for(var i=0; i Date: Sun, 7 Sep 2014 12:12:12 -0400 Subject: [PATCH 211/372] forgot winston --- src/database/mongo/hash.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 1512fbec20..f5659377a3 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -1,5 +1,7 @@ "use strict"; +var winston = require('winston'); + module.exports = function(db, module) { var helpers = module.helpers.mongo; From ec32a3d243946b7e1515a852af829aa7ff1d7eee Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 12:16:01 -0400 Subject: [PATCH 212/372] mising reuqire --- src/database/mongo/sets.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 7b0049068e..927337fbff 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -1,5 +1,7 @@ "use strict"; +var winston = require('winston'); + module.exports = function(db, module) { var helpers = module.helpers.mongo; From 3c8ca6360833fe1b31459d6e2e4bb8c4c3166609 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 12:36:50 -0400 Subject: [PATCH 213/372] fixed getCidsByPids --- src/posts.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/posts.js b/src/posts.js index 2a895d329a..9c1285a64b 100644 --- a/src/posts.js +++ b/src/posts.js @@ -408,17 +408,18 @@ var async = require('async'), var tids = posts.map(function(post) { return post.tid; }).filter(function(tid, index, array) { - return array.indexOf(tid) === index; + return tid && array.indexOf(tid) === index; }); topics.getTopicsFields(tids, ['cid'], function(err, topics) { if (err) { return callback(err); } + var map = {}; - topics.forEach(function(topic) { + topics.forEach(function(topic, index) { if (topic) { - map[topic.tid] = topic.cid; + map[tids[index]] = topic.cid; } }); From 6cd84ebe81e25dcdf4e04c1e76b5afed435d4217 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 13:03:06 -0400 Subject: [PATCH 214/372] removed parseInt from sorts --- src/categories/recentreplies.js | 4 ++-- src/socket.io/topics.js | 2 +- src/topics/popular.js | 8 +++----- src/user/notifications.js | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 2fdd5306bf..97199e636e 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -46,9 +46,9 @@ module.exports = function(Categories) { function assignPostsToCategory(category, posts, next) { category.posts = posts.filter(function(post) { - return parseInt(post.category.cid, 10) === parseInt(category.cid); + return parseInt(post.category.cid, 10) === parseInt(category.cid, 10); }).sort(function(a, b) { - return parseInt(b.timestamp, 10) - parseInt(a.timestamp, 10); + return b.timestamp - a.timestamp; }).slice(0, parseInt(category.numRecentReplies, 10)); next(); diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 143f5abb5c..699c840bdc 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -550,7 +550,7 @@ SocketTopics.searchAndLoadTags = function(socket, data, callback) { tag.score = results.counts[index]; }); results.tagData.sort(function(a, b) { - return parseInt(b.score, 10) - parseInt(a.score, 10); + return b.score - a.score; }); callback(null, results.tagData); diff --git a/src/topics/popular.js b/src/topics/popular.js index 2dadb9fee6..017153de69 100644 --- a/src/topics/popular.js +++ b/src/topics/popular.js @@ -66,11 +66,9 @@ module.exports = function(Topics) { return callback(err); } - topics.sort(function(a, b) { - return parseInt(b.postcount, 10) - parseInt(a.postcount, 10); - }); - - topics = topics.slice(0, count).map(function(topic) { + topics = topics.sort(function(a, b) { + return b.postcount - a.postcount; + }).slice(0, count).map(function(topic) { return topic.tid; }); diff --git a/src/user/notifications.js b/src/user/notifications.js index e165512178..16feb87d19 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -108,7 +108,7 @@ var async = require('async'), } notifs = notifs.filter(Boolean).sort(function(a, b) { - return parseInt(b.datetime, 10) - parseInt(a.datetime, 10); + return b.datetime - a.datetime; }); callback(null, notifs); From 242721c73fcbca1597b23ee7c1d12661110b38a3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 14:13:05 -0400 Subject: [PATCH 215/372] using websockets --- src/socket.io/posts.js | 6 +++--- src/socket.io/topics.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index e85114f592..8683906988 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -52,7 +52,7 @@ SocketPosts.reply = function(socket, data, callback) { 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, }); - module.parent.exports.emitTopicPostStats(); + websockets.emitTopicPostStats(); topics.pushUnreadCount(); } }); @@ -214,7 +214,7 @@ function deleteOrRestore(command, socket, data, callback) { return callback(err); } - module.parent.exports.emitTopicPostStats(); + websockets.emitTopicPostStats(); var eventName = command === 'restore' ? 'event:post_restored' : 'event:post_deleted'; websockets.server.sockets.in('topic_' + data.tid).emit(eventName, postData); @@ -232,7 +232,7 @@ SocketPosts.purge = function(socket, data, callback) { return callback(err); } - module.parent.exports.emitTopicPostStats(); + websockets.emitTopicPostStats(); websockets.server.sockets.in('topic_' + data.tid).emit('event:post_purged', data.pid); diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 699c840bdc..485344f885 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -49,7 +49,7 @@ SocketTopics.post = function(socket, data, callback) { posts: result.postData }); - module.parent.exports.emitTopicPostStats(); + websockets.emitTopicPostStats(); topics.pushUnreadCount(); callback(null, result.topicData); From a56ab7f15accc7d59b004eb9539cd07161330ebf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 16:41:27 -0400 Subject: [PATCH 216/372] closes #2061 --- public/src/forum/recent.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/src/forum/recent.js b/public/src/forum/recent.js index b4a27e664b..f3e51686aa 100644 --- a/public/src/forum/recent.js +++ b/public/src/forum/recent.js @@ -116,6 +116,15 @@ define('forum/recent', ['forum/infinitescroll'], function(infinitescroll) { }; Recent.onTopicsLoaded = function(templateName, topics, showSelect, callback) { + + topics = topics.filter(function(topic) { + return !$('#topics-container li[data-tid=' + topic.tid + ']').length; + }); + + if (!topics.length) { + callback(); + } + infinitescroll.parseAndTranslate(templateName, 'topics', {topics: topics, showSelect: showSelect}, function(html) { $('#category-no-topics').remove(); From 61e1c51ef6ab9dec6246a1261a93b7d8a7fefad6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 20:03:29 -0400 Subject: [PATCH 217/372] run notif prune every 30mins disabled post edit log --- src/notifications.js | 2 +- src/postTools.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index f6869e1816..c8e4cd35f7 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -21,7 +21,7 @@ var async = require('async'), if (process.env.NODE_ENV === 'development') { winston.info('[notifications.init] Registering jobs.'); } - new cron('0 * * * *', Notifications.prune, null, true); + new cron('0/30 * * * *', Notifications.prune, null, true); }; Notifications.get = function(nid, callback) { diff --git a/src/postTools.js b/src/postTools.js index a162561740..595941fb09 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -101,7 +101,7 @@ var winston = require('winston'), return callback(err); } - events.logPostEdit(uid, pid); + //events.logPostEdit(uid, pid); plugins.fireHook('action:post.edit', postData); callback(null, results); }); From 9b26b600824b32434ba3fffdfcbd1f0947a1acea Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 20:45:31 -0400 Subject: [PATCH 218/372] fixed cron --- src/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications.js b/src/notifications.js index c8e4cd35f7..b5dcdb1059 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -21,7 +21,7 @@ var async = require('async'), if (process.env.NODE_ENV === 'development') { winston.info('[notifications.init] Registering jobs.'); } - new cron('0/30 * * * *', Notifications.prune, null, true); + new cron('*/30 * * * *', Notifications.prune, null, true); }; Notifications.get = function(nid, callback) { From aed9a9f76fa2ea7c6a14986f7dadc58d14b9d067 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 7 Sep 2014 21:41:39 -0400 Subject: [PATCH 219/372] removed console.log --- src/categories/update.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/categories/update.js b/src/categories/update.js index fa04d99deb..002628d723 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -13,7 +13,6 @@ module.exports = function(Categories) { function updateCategory(cid, next) { var category = modified[cid]; var fields = Object.keys(category); - console.log('updating', cid, 'fields:', fields); async.each(fields, function(key, next) { updateCategoryField(cid, key, category[key], next); From f16ced6bcc37d3344f88adab2a59821effa457bc Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Mon, 8 Sep 2014 01:22:47 -0400 Subject: [PATCH 220/372] removed path from notif, its created on the fly when retrieveing the notifications --- src/notifications.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/notifications.js b/src/notifications.js index b5dcdb1059..fcf9467cf7 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -84,7 +84,6 @@ var async = require('async'), var defaults = { bodyShort: '', bodyLong: '', - path: '', importance: 5, datetime: Date.now(), uniqueId: utils.generateUUID() From 5222edb6243a103f2efc0cfad695464299a767e6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 8 Sep 2014 16:13:48 -0400 Subject: [PATCH 221/372] sortedSetsAdd using mullti/bulk for redis/mongo lets you add a score/value pair to multiple sorted sets at once --- src/database/level/sorted.js | 8 ++++++++ src/database/mongo/sorted.js | 21 +++++++++++++++++++++ src/database/redis/sorted.js | 13 +++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/database/level/sorted.js b/src/database/level/sorted.js index 369c820d53..ac681bb65b 100644 --- a/src/database/level/sorted.js +++ b/src/database/level/sorted.js @@ -20,6 +20,14 @@ module.exports = function(db, module) { }); }; + module.sortedSetsAdd = function(keys, score, value, callback) { + async.each(keys, function(key, next) { + module.sortedSetAdd(key, score, value, next); + }, function(err) { + callback(err); + }); + }; + module.sortedSetRemove = function(key, value, callback) { module.getListRange(key, 0, -1, function(err, set) { set = set.filter(function(a) {return a.value !== value.toString();}); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index c290b7000a..9e31335308 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -18,6 +18,26 @@ module.exports = function(db, module) { }); }; + module.sortedSetsAdd = function(keys, score, value, callback) { + callback = callback || helpers.noop; + + value = helpers.valueToString(value); + var data = { + score: parseInt(score, 10), + value: value + }; + + var bulk = db.collection('objects').initializeUnorderedBulkOp(); + + for(var i=0; i Date: Mon, 8 Sep 2014 18:09:35 -0400 Subject: [PATCH 222/372] delete anything older than 1 week --- src/notifications.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index fcf9467cf7..9384d575e2 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -277,21 +277,17 @@ var async = require('async'), ], callback); }; - Notifications.prune = function(cutoff) { + Notifications.prune = function() { var start = process.hrtime(); if (process.env.NODE_ENV === 'development') { winston.info('[notifications.prune] Removing expired notifications from the database.'); } - var today = new Date(), + var week = 604800000, numPruned = 0; - if (!cutoff) { - cutoff = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7); - } - - var cutoffTime = cutoff.getTime(); + var cutoffTime = Date.now() - week; db.getSetMembers('notifications', function(err, nids) { if (err) { From 20eb4def1d98c9428946a3977dfe0c547fd2f682 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 9 Sep 2014 18:05:21 -0400 Subject: [PATCH 223/372] possible fix to online guest count using socket io rooms instead of syncing between workers. --- src/socket.io/index.js | 86 +++++++++++++++++++++--------------------- src/socket.io/meta.js | 2 +- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index f768901834..185cdb6e27 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -24,9 +24,7 @@ var SocketIO = require('socket.io'), var io; -var onlineUsersMap = {}; var onlineUsers = []; -var uidToSocketId = {}; var socketIdToUid = {}; process.on('message', onMessage); @@ -37,41 +35,19 @@ function onMessage(msg) { } if (msg.action === 'user:connect') { - if (!onlineUsersMap[msg.uid]) { - onlineUsersMap[msg.uid] = 1; - } else { - onlineUsersMap[msg.uid]++; - } - if (msg.uid && onlineUsers.indexOf(msg.uid) === -1) { onlineUsers.push(msg.uid); } - if (Array.isArray(uidToSocketId[msg.uid])) { - uidToSocketId[msg.uid].push(msg.socketid); - } else { - uidToSocketId[msg.uid] = [msg.socketid]; - } socketIdToUid[msg.socketid] = msg.uid; } else if(msg.action === 'user:disconnect') { - if (onlineUsersMap[msg.uid]) { - onlineUsersMap[msg.uid] -= 1; - onlineUsersMap[msg.uid] = Math.max(0, onlineUsersMap[msg.uid]); - } - var index = 0; - if (msg.uid && onlineUsersMap[msg.uid] === 0) { - index = onlineUsers.indexOf(msg.uid); + if (msg.uid && msg.socketCount <= 1) { + var index = onlineUsers.indexOf(msg.uid); if (index !== -1) { onlineUsers.splice(index, 1); } } - if (uidToSocketId[msg.uid]) { - index = uidToSocketId[msg.uid].indexOf(msg.socketid); - if (index !== -1) { - uidToSocketId[msg.uid].splice(index, 1); - } - } delete socketIdToUid[msg.socketid]; } } @@ -85,8 +61,8 @@ function onUserConnect(uid, socketid) { } } -function onUserDisconnect(uid, socketid) { - var msg = {action: 'user:disconnect', uid: uid, socketid: socketid}; +function onUserDisconnect(uid, socketid, socketCount) { + var msg = {action: 'user:disconnect', uid: uid, socketid: socketid, socketCount: socketCount}; if (process.send) { process.send(msg); } else { @@ -155,10 +131,9 @@ Sockets.init = function(server) { logger.io_one(socket, uid); if (uid) { - + socket.join('uid_' + uid); + socket.join('online_users'); db.sortedSetAdd('users:online', Date.now(), uid, function(err) { - socket.join('uid_' + uid); - async.parallel({ user: function(next) { user.getUserFields(uid, ['username', 'userslug', 'picture', 'status'], next); @@ -183,6 +158,7 @@ Sockets.init = function(server) { }); }); } else { + socket.join('online_guests'); socket.emit('event:connect', { status: 1, username: '[[global:guest]]', @@ -194,8 +170,8 @@ Sockets.init = function(server) { }); socket.on('disconnect', function() { - - if (uid && (!onlineUsersMap[uid] || onlineUsersMap[uid] <= 1)) { + var socketCount = Sockets.getUserSocketCount(uid); + if (uid && socketCount <= 1) { db.sortedSetRemove('users:online', uid, function(err) { if (err) { return winston.error(err.message); @@ -204,7 +180,7 @@ Sockets.init = function(server) { }); } - onUserDisconnect(uid, socket.id); + onUserDisconnect(uid, socket.id, socketCount); emitOnlineUserCount(); @@ -292,16 +268,42 @@ Sockets.uidInRoom = function(uid, room) { }; Sockets.getSocketCount = function() { - return Object.keys(socketIdToUid).length; + var clients = io.sockets.manager.rooms['']; + if (!Array.isArray(clients)) { + return 0; + } + return clients.length; }; Sockets.getConnectedClients = function() { return onlineUsers; }; +Sockets.getUserSocketCount = function(uid) { + var roomClients = io.sockets.manager.rooms['/uid_' + uid]; + if(!Array.isArray(roomClients)) { + return 0; + } + return roomClients.length; +}; + +Sockets.getOnlineUserCount = function () { + var roomNames = Object.keys(io.sockets.manager.rooms); + if (!Array.isArray(roomNames)) { + return 0; + } + roomNames = roomNames.filter(function(name) { + return name.indexOf('/uid_') === 0; + }); + return roomNames.length; +}; + Sockets.getOnlineAnonCount = function () { - var count = parseInt(onlineUsersMap[0], 10); - return count ? count : 0; + var guestRoom = io.sockets.manager.rooms['/online_guests']; + if (!Array.isArray(guestRoom)) { + return 0; + } + return guestRoom.length; }; Sockets.getUserSockets = function(uid) { @@ -321,7 +323,7 @@ Sockets.getUserSockets = function(uid) { Sockets.getUserRooms = function(uid) { var rooms = {}; - var uidSocketIds = uidToSocketId[uid]; + var uidSocketIds = io.sockets.manager.rooms['/uid_' + uid]; if (!Array.isArray(uidSocketIds)) { return []; } @@ -359,13 +361,11 @@ Sockets.reqFromSocket = function(socket) { Sockets.isUserOnline = isUserOnline; function isUserOnline(uid) { - return !!onlineUsersMap[uid]; + return Array.isArray(io.sockets.manager.rooms['/uid_' + uid]); } Sockets.isUsersOnline = function(uids, callback) { - var data = uids.map(function(uid) { - return !!onlineUsersMap[uid]; - }); + var data = uids.map(isUserOnline); callback(null, data); }; @@ -457,7 +457,7 @@ function emitTopicPostStats(callback) { Sockets.emitOnlineUserCount = emitOnlineUserCount; function emitOnlineUserCount(callback) { var anon = Sockets.getOnlineAnonCount(); - var registered = Sockets.getConnectedClients().length; + var registered = Sockets.getOnlineUserCount(); var returnObj = { users: registered + anon, diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index af9e624442..5fc901d12b 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -85,7 +85,7 @@ SocketMeta.rooms.enter = function(socket, data, callback) { SocketMeta.rooms.getAll = function(socket, data, callback) { var userData = { onlineGuestCount: websockets.getOnlineAnonCount(), - onlineRegisteredCount: websockets.getConnectedClients().length, + onlineRegisteredCount: websockets.getOnlineUserCount(), socketCount: websockets.getSocketCount() }; From b7eddda8c803a3da5e0d5238c9b7cd59c8640046 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 9 Sep 2014 18:32:09 -0400 Subject: [PATCH 224/372] one more fix --- src/socket.io/index.js | 45 ++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 185cdb6e27..6f949177d1 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -25,7 +25,6 @@ var SocketIO = require('socket.io'), var io; var onlineUsers = []; -var socketIdToUid = {}; process.on('message', onMessage); @@ -38,8 +37,6 @@ function onMessage(msg) { if (msg.uid && onlineUsers.indexOf(msg.uid) === -1) { onlineUsers.push(msg.uid); } - - socketIdToUid[msg.socketid] = msg.uid; } else if(msg.action === 'user:disconnect') { if (msg.uid && msg.socketCount <= 1) { var index = onlineUsers.indexOf(msg.uid); @@ -47,8 +44,6 @@ function onMessage(msg) { onlineUsers.splice(index, 1); } } - - delete socketIdToUid[msg.socketid]; } } @@ -252,15 +247,18 @@ Sockets.in = function(room) { }; Sockets.uidInRoom = function(uid, room) { - var socketIds = io.sockets.manager.rooms[room]; - if (!Array(socketIds) || !socketIds.length) { + var userSocketIds = io.sockets.manager.rooms['/uid_' + uid]; + if (!Array.isArray(userSocketIds) || !userSocketIds.length) { return false; } - uid = parseInt(uid, 10); + var roomSocketIds = io.sockets.manager.rooms['/' + room]; + if (!Array.isArray(roomSocketIds) || !roomSocketIds.length) { + return false; + } - for (var i=0; i Date: Wed, 10 Sep 2014 16:04:33 -0400 Subject: [PATCH 225/372] closes https://github.com/NodeBB/NodeBB/issues/2077 --- src/favourites.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/favourites.js b/src/favourites.js index 741d3a3c4f..663bfec5f8 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -44,6 +44,13 @@ var async = require('async'), db.sortedSetAdd('users:reputation', newreputation, postData.uid); + if (parseInt(meta.config['autoban:downvote'], 10) !== 0 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { + var adminUser = require('./socket.io/admin/user'); + adminUser.banUser(postData.uid, function() { + require('winston').info('uid ' + uid + ' was banned for reaching ' + newreputation + ' reputation'); + }); + } + adjustPostVotes(pid, uid, type, unvote, function(err, votes) { postData.votes = votes; callback(err, { From e738d592dcd08deac5f54285514c120185b43b84 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 10 Sep 2014 16:10:53 -0400 Subject: [PATCH 226/372] fixed potential NaN issue https://github.com/NodeBB/NodeBB/issues/2077 --- src/favourites.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/favourites.js b/src/favourites.js index 663bfec5f8..ae55417f5e 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -44,7 +44,7 @@ var async = require('async'), db.sortedSetAdd('users:reputation', newreputation, postData.uid); - if (parseInt(meta.config['autoban:downvote'], 10) !== 0 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { + if (parseInt(meta.config['autoban:downvote'], 10) === 1 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { var adminUser = require('./socket.io/admin/user'); adminUser.banUser(postData.uid, function() { require('winston').info('uid ' + uid + ' was banned for reaching ' + newreputation + ' reputation'); From 61871fae77bc174aced15a40c39c3008d488f4f9 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 10 Sep 2014 16:14:01 -0400 Subject: [PATCH 227/372] merging #2077 --- src/favourites.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/favourites.js b/src/favourites.js index 741d3a3c4f..ae55417f5e 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -44,6 +44,13 @@ var async = require('async'), db.sortedSetAdd('users:reputation', newreputation, postData.uid); + if (parseInt(meta.config['autoban:downvote'], 10) === 1 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { + var adminUser = require('./socket.io/admin/user'); + adminUser.banUser(postData.uid, function() { + require('winston').info('uid ' + uid + ' was banned for reaching ' + newreputation + ' reputation'); + }); + } + adjustPostVotes(pid, uid, type, unvote, function(err, votes) { postData.votes = votes; callback(err, { From 01f983e29c2cd8edb59692668c95a267b0458e62 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Sep 2014 21:44:19 -0400 Subject: [PATCH 228/372] markAsRead improvement takes in an array of tids instead of marking topics read 1 by 1 same for the category.markAsRead function --- public/src/forum/topic.js | 2 +- public/src/forum/unread.js | 2 +- src/categories.js | 12 +++++++++--- src/database/level/sets.js | 4 ++++ src/database/mongo/sets.js | 25 +++++++++++++++++++++++++ src/database/redis/sets.js | 11 +++++++++++ src/socket.io/topics.js | 31 +++++++++++-------------------- src/topics/create.js | 2 +- src/topics/unread.js | 38 ++++++++++++++++++++++---------------- 9 files changed, 85 insertions(+), 42 deletions(-) diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 6c0ece91df..6acbcc7354 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -149,7 +149,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT var postcount = $('.user_postcount_' + data.posts[i].uid); postcount.html(parseInt(postcount.html(), 10) + 1); } - socket.emit('topics.markAsRead', tid); + socket.emit('topics.markAsRead', [tid]); createNewPosts(data); } diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js index f8e1a9be63..a3e3bb1b10 100644 --- a/public/src/forum/unread.js +++ b/public/src/forum/unread.js @@ -25,7 +25,7 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'], if(!tids.length) { return; } - socket.emit('topics.markTidsRead', tids, function(err) { + socket.emit('topics.markAsRead', tids, function(err) { if(err) { return app.alertError(err.message); } diff --git a/src/categories.js b/src/categories.js index aaf732151b..315f6e8c4f 100644 --- a/src/categories.js +++ b/src/categories.js @@ -74,7 +74,7 @@ var db = require('./database'), var category = categories[0]; if (parseInt(uid, 10)) { - Categories.markAsRead(cid, uid); + Categories.markAsRead([cid], uid); } async.parallel({ @@ -250,8 +250,14 @@ var db = require('./database'), }); }; - Categories.markAsRead = function(cid, uid, callback) { - db.setAdd('cid:' + cid + ':read_by_uid', uid, callback); + Categories.markAsRead = function(cids, uid, callback) { + if (!Array.isArray(cids) || !cids.length) { + return callback(); + } + var keys = cids.map(function(cid) { + return 'cid:' + cid + ':read_by_uid'; + }); + db.setsAdd(keys, uid, callback); }; Categories.markAsUnreadForAll = function(cid, callback) { diff --git a/src/database/level/sets.js b/src/database/level/sets.js index df10f4a632..525477db7d 100644 --- a/src/database/level/sets.js +++ b/src/database/level/sets.js @@ -19,6 +19,10 @@ module.exports = function(db, module) { }); }; + module.setsAdd = function(keys, value, callback) { + throw new Error('not-implemented'); + }; + module.setRemove = function(key, value, callback) { module.getListRange(key, 0, -1, function(err, set) { module.set(key, set.splice(set.indexOf(value), 1), callback); diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 927337fbff..d247a1f945 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -31,6 +31,31 @@ module.exports = function(db, module) { }); }; + module.setsAdd = function(keys, value, callback) { + callback = callback || helpers.noop; + if(!Array.isArray(value)) { + value = [value]; + } + + value.forEach(function(element, index, array) { + array[index] = helpers.valueToString(element); + }); + + var bulk = db.collection('objects').initializeUnorderedBulkOp(); + + for(var i=0; i Date: Fri, 12 Sep 2014 19:07:01 -0400 Subject: [PATCH 229/372] fixed resetLockout --- src/user/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/auth.js b/src/user/auth.js index 0aaa6f658a..594e7e843e 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -46,7 +46,7 @@ module.exports = function(User) { User.auth.resetLockout = function(uid, callback) { async.parallel([ - async.apply(db.delete, 'loginAttemps:' + uid), + async.apply(db.delete, 'loginAttempts:' + uid), async.apply(db.delete, 'lockout:' + uid) ], callback); } From 40e5ad075f3e0db35a22f81d9e1f73130d1a806a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 14 Sep 2014 10:21:32 -0400 Subject: [PATCH 230/372] started work on #2082 --- public/src/app.js | 4 +--- public/src/forum/admin/index.js | 4 +--- public/src/modules/uploader.js | 3 --- src/controllers/admin.js | 4 +++- src/controllers/index.js | 4 ++-- src/middleware/admin.js | 2 +- src/middleware/index.js | 3 --- src/middleware/middleware.js | 5 ++++- src/routes/admin.js | 12 ++++++------ src/routes/authentication.js | 4 ++-- src/routes/index.js | 8 ++++---- 11 files changed, 24 insertions(+), 29 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 94db871a31..647623dbfc 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -161,9 +161,7 @@ var socket, }; app.logout = function() { - $.post(RELATIVE_PATH + '/logout', { - _csrf: $('#csrf_token').val() - }, function() { + $.post(RELATIVE_PATH + '/logout', function() { window.location.href = RELATIVE_PATH + '/'; }); }; diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js index e45c661628..b69bf3fc7a 100644 --- a/public/src/forum/admin/index.js +++ b/public/src/forum/admin/index.js @@ -13,9 +13,7 @@ define('forum/admin/index', function() { socket.on('event:meta.rooms.update', Admin.updateRoomUsage); $('#logout-link').on('click', function() { - $.post(RELATIVE_PATH + '/logout', { - _csrf: $('#csrf_token').val() - }, function() { + $.post(RELATIVE_PATH + '/logout', function() { window.location.href = RELATIVE_PATH + '/'; }); }); diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js index 64403f52fe..2d5aa120f3 100644 --- a/public/src/modules/uploader.js +++ b/public/src/modules/uploader.js @@ -58,9 +58,6 @@ define('uploader', function() { return false; } - $(this).find('#imageUploadCsrf').val($('#csrf_token').val()); - - $(this).ajaxSubmit({ error: function(xhr) { xhr = maybeParse(xhr); diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 13b0bfde7b..84834edef0 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -181,7 +181,9 @@ adminController.languages.get = function(req, res, next) { }; adminController.settings.get = function(req, res, next) { - res.render('admin/settings', {}); + res.render('admin/settings', { + 'csrf': req.csrfToken() + }); }; adminController.logger.get = function(req, res, next) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 7d7ea579ac..25b22fa270 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -123,7 +123,7 @@ Controllers.login = function(req, res, next) { data.alternate_logins = num_strategies > 0; data.authentication = login_strategies; - data.token = res.locals.csrf_token; + data.token = req.csrfToken(); data.showResetLink = emailersPresent; data.allowLocalLogin = meta.config.allowLocalLogin === undefined || parseInt(meta.config.allowLocalLogin, 10) === 1; data.allowRegistration = meta.config.allowRegistration; @@ -152,7 +152,7 @@ Controllers.register = function(req, res, next) { data.authentication = login_strategies; - data.token = res.locals.csrf_token; + data.token = req.csrfToken(); data.minimumUsernameLength = meta.config.minimumUsernameLength; data.maximumUsernameLength = meta.config.maximumUsernameLength; data.minimumPasswordLength = meta.config.minimumPasswordLength; diff --git a/src/middleware/admin.js b/src/middleware/admin.js index 3cef701d08..0f7af52358 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -61,7 +61,7 @@ middleware.buildHeader = function(req, res, next) { } }, function(err, pluginData) { var data = { - csrf: res.locals.csrf_token, + csrf: req.csrfToken ? req.csrfToken() : undefined, relative_path: nconf.get('relative_path'), plugins: pluginData.custom_header.plugins, authentication: pluginData.custom_header.authentication, diff --git a/src/middleware/index.js b/src/middleware/index.js index 5ea711b46c..3e3e2f5b06 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -20,7 +20,6 @@ var utils = require('./../../public/src/utils'), compression = require('compression'), favicon = require('serve-favicon'), multipart = require('connect-multiparty'), - csrf = require('csurf'), session = require('express-session'), relativePath, @@ -107,10 +106,8 @@ module.exports = function(app, data) { })); app.use(multipart()); - app.use(csrf()); app.use(function (req, res, next) { - res.locals.csrf_token = req.csrfToken(); res.setHeader('X-Powered-By', 'NodeBB'); res.setHeader('X-Frame-Options', 'SAMEORIGIN'); diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index dd9b8e9dd2..94c2d4d0cb 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -16,6 +16,7 @@ var app, topics = require('./../topics'), messaging = require('../messaging'), ensureLoggedIn = require('connect-ensure-login'), + csrf = require('csurf'), controllers = { api: require('./../controllers/api') @@ -33,6 +34,8 @@ middleware.authenticate = function(req, res, next) { } }; +middleware.requireCSRF = csrf(); + middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(); middleware.updateLastOnlineTime = function(req, res, next) { @@ -251,7 +254,7 @@ middleware.renderHeader = function(req, res, callback) { 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', 'brand:logo': meta.config['brand:logo'] || '', 'brand:logo:display': meta.config['brand:logo']?'':'hide', - csrf: res.locals.csrf_token, + csrf: req.csrfToken ? req.csrfToken() : undefined, navigation: custom_header.navigation, allowRegistration: meta.config.allowRegistration === undefined || parseInt(meta.config.allowRegistration, 10) === 1, searchEnabled: plugins.hasListeners('filter:search.query') diff --git a/src/routes/admin.js b/src/routes/admin.js index 77dc3cc31a..008acfde60 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -9,8 +9,8 @@ function mainRoutes(app, middleware, controllers) { app.get('/admin/plugins', middleware.admin.buildHeader, controllers.admin.plugins.get); app.get('/api/admin/plugins', controllers.admin.plugins.get); - app.get('/admin/settings', middleware.admin.buildHeader, controllers.admin.settings.get); - app.get('/api/admin/settings', controllers.admin.settings.get); + app.get('/admin/settings', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.settings.get); + app.get('/api/admin/settings', middleware.requireCSRF, controllers.admin.settings.get); app.get('/admin/themes', middleware.admin.buildHeader, controllers.admin.themes.get); app.get('/api/admin/themes', controllers.admin.themes.get); @@ -57,10 +57,10 @@ function apiRoutes(app, middleware, controllers) { // todo, needs to be in api namespace app.get('/admin/users/csv', middleware.authenticate, controllers.admin.users.getCSV); - app.post('/admin/category/uploadpicture', middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); - app.post('/admin/uploadfavicon', middleware.authenticate, controllers.admin.uploads.uploadFavicon); - app.post('/admin/uploadlogo', middleware.authenticate, controllers.admin.uploads.uploadLogo); - app.post('/admin/uploadgravatardefault', middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); + app.post('/admin/category/uploadpicture', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); + app.post('/admin/uploadfavicon', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadFavicon); + app.post('/admin/uploadlogo', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadLogo); + app.post('/admin/uploadgravatardefault', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); } function miscRoutes(app, middleware, controllers) { diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 9b30ae08a9..6471814ad5 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -197,8 +197,8 @@ /* End backwards compatibility block */ app.post('/logout', logout); - app.post('/register', register); - app.post('/login', login); + app.post('/register', middleware.requireCSRF, register); + app.post('/login', middleware.requireCSRF, login); }); }); }; diff --git a/src/routes/index.js b/src/routes/index.js index dc2e0f0d94..1e3124a6ea 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -21,11 +21,11 @@ function mainRoutes(app, middleware, controllers) { app.get('/', middleware.buildHeader, controllers.home); app.get('/api', controllers.home); - app.get('/login', middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login); - app.get('/api/login', middleware.redirectToAccountIfLoggedIn, controllers.login); + app.get('/login', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login); + app.get('/api/login', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, controllers.login); - app.get('/register', middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.register); - app.get('/api/register', middleware.redirectToAccountIfLoggedIn, controllers.register); + app.get('/register', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.register); + app.get('/api/register', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, controllers.register); app.get('/confirm/:code', middleware.buildHeader, controllers.confirmEmail); app.get('/api/confirm/:code', controllers.confirmEmail); From 493d0dea1e48f8091ccb57a897a66e826ad9e72e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 8 Sep 2014 23:03:37 -0400 Subject: [PATCH 231/372] part 1 of notif refactor --- public/src/modules/notifications.js | 10 +- src/controllers/accounts.js | 5 +- src/database/level/sorted.js | 12 +- src/database/mongo/sorted.js | 29 +++- src/database/redis/sorted.js | 25 +++- src/messaging.js | 3 +- src/notifications.js | 219 ++++++++++------------------ src/socket.io/modules.js | 8 +- src/socket.io/posts.js | 16 +- src/socket.io/topics.js | 8 +- src/socket.io/user.js | 38 ++--- src/topics/follow.js | 8 +- src/topics/unread.js | 3 + src/upgrade.js | 52 ++++++- src/user/create.js | 7 +- src/user/delete.js | 3 - src/user/notifications.js | 154 +++++++++---------- 17 files changed, 319 insertions(+), 281 deletions(-) diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 750449fc72..95f7baacda 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -71,6 +71,11 @@ define('notifications', ['sounds'], function(sound) { Tinycon.setBubble(count); }; + function increaseNotifCount() { + var count = parseInt(notifIcon.attr('data-content'), 10) + 1; + updateNotifCount(count); + } + socket.emit('notifications.getCount', function(err, count) { if (!err) { updateNotifCount(count); @@ -79,7 +84,7 @@ define('notifications', ['sounds'], function(sound) { } }); - socket.on('event:new_notification', function(notifData, notifCount) { + socket.on('event:new_notification', function(notifData) { app.alert({ alert_id: 'new_notif', title: '[[notifications:new_notification]]', @@ -93,10 +98,11 @@ define('notifications', ['sounds'], function(sound) { ajaxify.refresh(); } - updateNotifCount(notifCount); + increaseNotifCount(); sound.play('notification'); }); + socket.on('event:notifications.updateCount', function(count) { updateNotifCount(count); }); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 903853d562..616cfc50d3 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -480,7 +480,10 @@ accountsController.uploadPicture = function (req, res, next) { }; accountsController.getNotifications = function(req, res, next) { - user.notifications.getAll(req.user.uid, 25, function(err, notifications) { + user.notifications.getAll(req.user.uid, 40, function(err, notifications) { + if (err) { + return next(err); + } res.render('notifications', { notifications: notifications }); diff --git a/src/database/level/sorted.js b/src/database/level/sorted.js index ac681bb65b..b0cbcb415a 100644 --- a/src/database/level/sorted.js +++ b/src/database/level/sorted.js @@ -7,6 +7,9 @@ module.exports = function(db, module) { var helpers = module.helpers.level; module.sortedSetAdd = function(key, score, value, callback) { + if (Array.isArray(score) && Array.isArray(value)) { + return sortedSetAddMulti(key, score, value, callback); + } module.getListRange(key, 0, -1, function(err, set) { set = set.filter(function(a) {return a.value !== value.toString();}); @@ -20,6 +23,10 @@ module.exports = function(db, module) { }); }; + function sortedSetAddMulti(key, scores, values, callback) { + throw new Error('not implemented'); + } + module.sortedSetsAdd = function(keys, score, value, callback) { async.each(keys, function(key, next) { module.sortedSetAdd(key, score, value, next); @@ -29,8 +36,11 @@ module.exports = function(db, module) { }; module.sortedSetRemove = function(key, value, callback) { + if (!Array.isArray(value)) { + value = [value]; + } module.getListRange(key, 0, -1, function(err, set) { - set = set.filter(function(a) {return a.value !== value.toString();}); + set = set.filter(function(a) { return value.indexOf(a) === -1;}); module.set(key, set, callback); }); }; diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 9e31335308..ead67b6115 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -7,6 +7,10 @@ module.exports = function(db, module) { module.sortedSetAdd = function(key, score, value, callback) { callback = callback || helpers.noop; + if (Array.isArray(score) && Array.isArray(value)) { + return sortedSetAddBulk(key, score, value, callback); + } + value = helpers.valueToString(value); var data = { score: parseInt(score, 10), @@ -18,6 +22,24 @@ module.exports = function(db, module) { }); }; + function sortedSetAddBulk(key, scores, values, callback) { + if (scores.length !== values.length) { + return callback(new Error('[[error:invalid-data]]')); + } + + values = values.map(helpers.valueToString); + + var bulk = db.collection('objects').initializeUnorderedBulkOp(); + + for(var i=0; i parseInt(data.importance, 10)) { + return callback(null, null); + } + } + + var now = Date.now(); + data.datetime = now; + async.parallel([ + function(next) { + db.sortedSetAdd('notifications', now, data.nid, next); + }, + function(next) { + db.setObject('notifications:' + data.nid, data, next); + } + ], function(err) { + callback(err, data); }); }); }; - Notifications.push = function(nid, uids, callback) { + Notifications.push = function(notification, uids, callback) { callback = callback || function() {}; var websockets = require('./socket.io'); if (!Array.isArray(uids)) { uids = [uids]; } - Notifications.get(nid, function(err, notif_data) { - if (err) { - return callback(err); - } - - async.eachLimit(uids, 10, function(uid, next) { - if (!parseInt(uid, 10)) { - return next(); - } - - shouldPush(uid, notif_data, function(err, shouldPush) { - if (err || !shouldPush) { - return callback(err); - } - - async.parallel([ - async.apply(db.setObjectField, 'uid:' + uid + ':notifications:uniqueId:nid', notif_data.uniqueId, nid), - async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', notif_data.datetime, notif_data.uniqueId), - async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', notif_data.uniqueId) - ], function(err) { - if (err) { - return next(err); - } + var unreadKeys = []; + var readKeys = []; - User.notifications.getUnreadCount(uid, function(err, count) { - if (!err) { - websockets.in('uid_' + uid).emit('event:new_notification', notif_data, count); - } - }); - - // Plugins - notif_data.uid = uid; - plugins.fireHook('action:notification.pushed', notif_data); - next(); - }); - }); - }, callback); + uids.filter(Boolean).forEach(function(uid) { + unreadKeys.push('uid:' + uid + ':notifications:unread'); + readKeys.push('uid:' + uid + ':notifications:read'); }); - }; - - function shouldPush(uid, newNotifObj, callback) { - if (!newNotifObj) { - return callback(null, false); - } - hasNotification(newNotifObj.uniqueId, uid, function(err, hasNotification) { + async.parallel([ + function(next) { + db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next); + }, + function(next) { + db.sortedSetsRemove(readKeys, notification.nid, next); + } + ], function(err) { if (err) { return callback(err); } - if (!hasNotification) { - return callback(null, true); - } - - db.getObjectField('uid:' + uid + ':notifications:uniqueId:nid', newNotifObj.uniqueId, function(err, nid) { - if (err) { - return callback(err); - } - - db.getObjectFields('notifications:' + nid, ['nid', 'uniqueId', 'importance'], function(err, oldNotifObj) { - if (err) { - return callback(err); - } - - if (!oldNotifObj || newNotifObj.uniqueId !== oldNotifObj.uniqueId) { - return callback(null, true); - } - - callback(null, parseInt(newNotifObj.importance, 10) >= parseInt(oldNotifObj.importance, 10)); - }); - }); - }); - } + plugins.fireHook('action:notification.pushed', {notification: notification, uids: uids}); - function hasNotification(uniqueId, uid, callback) { - async.parallel([ - async.apply(db.isSortedSetMember, 'uid:' + uid + ':notifications:unread', uniqueId), - async.apply(db.isSortedSetMember, 'uid:' + uid + ':notifications:read', uniqueId) - ], function(err, results) { - if (err) { - return callback(err); + for(var i=0; i 0) { - nids = [nids]; - } - - async.each(nids, function(nid, next) { - Notifications.markRead(nid, uid, next); - }, callback); - }; - Notifications.markAllRead = function(uid, callback) { - db.getObjectValues('uid:' + uid + ':notifications:uniqueId:nid', function(err, nids) { + db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, 99, function(err, nids) { if (err) { return callback(err); } if (!Array.isArray(nids) || !nids.length) { - return callback(err); + return callback(); } Notifications.markReadMultiple(nids, uid, callback); }); }; - Notifications.markReadByUniqueId = function(uid, uniqueId, callback) { - async.waterfall([ - async.apply(db.getObjectField, 'uid:' + uid + ':notifications:uniqueId:nid', uniqueId), - function(nid, next) { - Notifications.markRead(nid, uid, next); - } - ], callback); - }; - Notifications.prune = function() { var start = process.hrtime(); @@ -289,12 +224,14 @@ var async = require('async'), var cutoffTime = Date.now() - week; - db.getSetMembers('notifications', function(err, nids) { + db.getSortedSetRange('notifications', 0, 499, function(err, nids) { if (err) { return winston.error(err.message); } + if (!Array.isArray(nids) || !nids.length) { + return; + } var totalNidCount = nids.length; - nids = _.sortBy(nids, function(num) { return parseInt(num, 10); }).slice(0, 500); var keys = nids.map(function(nid) { return 'notifications:' + nid; @@ -319,7 +256,7 @@ var async = require('async'), async.parallel([ function(next) { - db.setRemove('notifications', expiredNids, next); + db.sortedSetRemove('notifications', expiredNids, next); }, function(next) { db.deleteAll(keys, next); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 1ad5c45e12..54e6b4585d 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -224,11 +224,11 @@ function sendChatNotification(fromuid, touid, messageObj) { bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', bodyLong: messageObj.content, path: nconf.get('relative_path') + '/chats/' + utils.slugify(messageObj.fromUser.username), - uniqueId: 'chat_' + fromuid + '_' + touid, + nid: 'chat_' + fromuid + '_' + touid, from: fromuid - }, function(err, nid) { - if (!err) { - notifications.push(nid, [touid]); + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, [touid]); } }); } diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 8683906988..36ae2934a8 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -135,11 +135,11 @@ SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { bodyShort: '[[' + notification + ', ' + results.username + ']]', bodyLong: results.postContent, pid: pid, - uniqueId: 'post:' + pid + ':uid:' + fromuid, + nid: 'post:' + pid + ':uid:' + fromuid, from: fromuid - }, function(err, nid) { - if (!err) { - notifications.push(nid, [postData.uid]); + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, [postData.uid]); } }); }); @@ -310,13 +310,13 @@ SocketPosts.flag = function(socket, pid, callback) { bodyShort: message, bodyLong: post.content, pid: pid, - uniqueId: 'post_flag:' + pid, + nid: 'post_flag:' + pid + ':uid:' + socket.uid, from: socket.uid - }, function(err, nid) { - if (err) { + }, function(err, notification) { + if (err || !notification) { return next(err); } - notifications.push(nid, adminGroup.members, next); + notifications.push(notification, adminGroup.members, next); }); }, function(next) { diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 47958809f1..666b62d244 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -353,11 +353,11 @@ SocketTopics.sendNotificationToTopicOwner = function(tid, fromuid, notification) notifications.create({ bodyShort: '[[' + notification + ', ' + results.username + ']]', path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, - uniqueId: 'topic:' + tid + ':uid:' + fromuid, + nid: 'topic:' + tid + ':uid:' + fromuid, from: fromuid - }, function(err, nid) { - if (!err) { - notifications.push(nid, [results.topicData.uid]); + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, [results.topicData.uid]); } }); }); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 538edac1d9..43bb934bdb 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -181,31 +181,33 @@ SocketUser.changePicture = function(socket, data, callback) { }; SocketUser.follow = function(socket, data, callback) { - if (socket.uid && data) { - toggleFollow('follow', socket.uid, data.uid, function(err) { + if (!socket.uid || !data) { + return; + } + + toggleFollow('follow', socket.uid, data.uid, function(err) { + if (err) { + return callback(err); + } + + user.getUserFields(socket.uid, ['username', 'userslug'], function(err, userData) { if (err) { return callback(err); } - user.getUserFields(socket.uid, ['username', 'userslug'], function(err, userData) { - if (err) { - return callback(err); + notifications.create({ + bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', + path: nconf.get('relative_path') + '/user/' + userData.userslug, + nid: 'follow:uid:' + socket.uid, + from: socket.uid + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, [data.uid]); } - - notifications.create({ - bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', - path: nconf.get('relative_path') + '/user/' + userData.userslug, - uniqueId: 'follow:uid:' + socket.uid, - from: socket.uid - }, function(err, nid) { - if (!err) { - notifications.push(nid, [data.uid]); - } - callback(err); - }); + callback(err); }); }); - } + }); }; SocketUser.unfollow = function(socket, data, callback) { diff --git a/src/topics/follow.js b/src/topics/follow.js index 0ea48620be..9c711ad440 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -56,12 +56,12 @@ module.exports = function(Topics) { bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.title + ']]', bodyLong: results.postContent, pid: pid, - uniqueId: 'topic:' + tid + ':uid:' + exceptUid, + nid: 'topic:' + tid + ':uid:' + exceptUid, tid: tid, from: exceptUid - }, function(err, nid) { - if (!err) { - notifications.push(nid, followers); + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, followers); } }); }); diff --git a/src/topics/unread.js b/src/topics/unread.js index 1c44f8cc5f..b25fd83bd6 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -234,6 +234,9 @@ module.exports = function(Topics) { Topics.markTopicNotificationsRead = function(tid, uid) { user.notifications.getUnreadByField(uid, 'tid', tid, function(err, nids) { + if (err) { + return winston.error(err.stack); + } notifications.markReadMultiple(nids, uid, function() { user.notifications.pushCount(uid); }); diff --git a/src/upgrade.js b/src/upgrade.js index db91530ada..ace94ed833 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -19,7 +19,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2014, 6, 24); + latestSchema = Date.UTC(2014, 8, 8); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -963,6 +963,56 @@ Upgrade.upgrade = function(callback) { winston.info('[2014/7/24] Upgrading chats to sorted set - skipped'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2014, 8, 8); + + if (schemaDate < thisSchemaDate) { + winston.info('[2014/9/8] Deleting old notifications...'); + + async.parallel({ + uids: function(next) { + db.getSortedSetRange('users:joindate', 0, -1, next); + }, + nids: function(next) { + db.getSetMembers('notifications', next); + } + }, function(err, results) { + if (err) { + return next(err); + } + var uidKeys = results.uids.map(function(uid) { + return 'uid:' + uid + ':notifications:uniqueId:nid'; + }); + + var nidKeys = results.nids.filter(Boolean).map(function(nid) { + return 'notifications:' + nid; + }); + + async.series([ + function(next) { + db.deleteAll(nidKeys, next); + }, + function(next) { + db.deleteAll(uidKeys, next); + }, + function(next) { + db.delete('notifications', next); + } + ], function(err, results) { + if (err) { + winston.error('[2014/9/8] Error encountered while deleting notifications'); + return next(err); + } + + winston.info('[2014/9/8] Deleted old notifications'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2014/9/8] Deleting old notifications skipped'); + next(); + } } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!! diff --git a/src/user/create.js b/src/user/create.js index 80a5877d7f..c6ecc17289 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -165,10 +165,11 @@ module.exports = function(User) { bodyShort: '[[user:username_taken_workaround, ' + userData.username + ']]', bodyLong: '', image: 'brand:logo', + nid: 'username_taken:' + uid, datetime: Date.now() - }, function(err, nid) { - if (!err) { - notifications.push(nid, uid); + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, uid); } }); } diff --git a/src/user/delete.js b/src/user/delete.js index 460a8a1df5..ac800d0836 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -65,9 +65,6 @@ module.exports = function(User) { function(next) { db.delete('uid:' + uid + ':notifications:unread', next); }, - function(next) { - db.delete('uid:' + uid + ':notifications:uniqueId:nid', next); - }, function(next) { db.sortedSetRemove('users:joindate', uid, next); }, diff --git a/src/user/notifications.js b/src/user/notifications.js index 16feb87d19..e7dfd97f97 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -45,63 +45,61 @@ var async = require('async'), }; function getNotificationsFromSet(set, uid, start, stop, max, callback) { - db.getSortedSetRevRange(set, start, stop, function(err, uniqueIds) { + db.getSortedSetRevRange(set, start, stop, function(err, nids) { if (err) { return callback(err); } - if(!Array.isArray(uniqueIds) || !uniqueIds.length) { + if(!Array.isArray(nids) || !nids.length) { return callback(null, []); } - if (uniqueIds.length > max) { - uniqueIds.length = max; + if (nids.length > max) { + nids.length = max; } - db.getObjectFields('uid:' + uid + ':notifications:uniqueId:nid', uniqueIds, function(err, uniqueIdToNids) { + UserNotifications.getNotifications(nids, uid, function(err, notifications) { if (err) { return callback(err); } - var nidsToUniqueIds = {}; - var nids = []; - uniqueIds.forEach(function(uniqueId) { - nidsToUniqueIds[uniqueIdToNids[uniqueId]] = uniqueId; - nids.push(uniqueIdToNids[uniqueId]); - }); - - UserNotifications.getNotifications(nids, uid, function(err, notifications) { - if (err) { - return callback(err); - } + var deletedNids = []; - notifications.forEach(function(notification, index) { - if (!notification) { - if (process.env.NODE_ENV === 'development') { - winston.info('[notifications.get] nid ' + nids[index] + ' not found. Removing.'); - } - - db.sortedSetRemove(set, nidsToUniqueIds[nids[index]]); - db.deleteObjectField('uid:' + uid + ':notifications:uniqueId:nid', nidsToUniqueIds[nids[index]]); + notifications.forEach(function(notification, index) { + if (!notification) { + if (process.env.NODE_ENV === 'development') { + winston.info('[notifications.get] nid ' + nids[index] + ' not found. Removing.'); } - }); - callback(null, notifications); + if (nids[index]) { + deletedNids.push(nids[index]); + } + } }); + + if (deletedNids.length) { + db.sortedSetRemove(set, deletedNids); + } + + callback(null, notifications); }); }); } - UserNotifications.getAll = function(uid, limit, callback) { - if (!limit || parseInt(limit, 10) <= 0) { - limit = 25; - } - - db.getObjectValues('uid:' + uid + ':notifications:uniqueId:nid', function(err, nids) { + UserNotifications.getAll = function(uid, count, callback) { + async.parallel({ + unread: function(next) { + db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, count, next); + }, + read: function(next) { + db.getSortedSetRevRange('uid:' + uid + ':notifications:read', 0, count, next); + } + }, function(err, results) { if (err) { return callback(err); } + var nids = results.unread.concat(results.read); UserNotifications.getNotifications(nids, uid, function(err, notifs) { if (err) { return callback(err); @@ -122,11 +120,7 @@ var async = require('async'), return callback(err); } - var uniqueIds = notifications.map(function(notification) { - return notification ? notification.uniqueId : null; - }); - - db.isSortedSetMembers('uid:' + uid + ':notifications:read', uniqueIds, function(err, hasRead) { + db.isSortedSetMembers('uid:' + uid + ':notifications:read', nids, function(err, hasRead) { if (err) { return callback(err); } @@ -159,7 +153,8 @@ var async = require('async'), }; function generatePostPaths(pids, uid, callback) { - var postKeys = pids.filter(Boolean).map(function(pid) { + pids = pids.filter(Boolean); + var postKeys = pids.map(function(pid) { return 'post:' + pid; }); @@ -193,6 +188,7 @@ var async = require('async'), pidToPaths[pid] = nconf.get('relative_path') + '/topic/' + slug + '/' + postIndex; } }); + callback(null, pidToPaths); }); }); @@ -202,26 +198,16 @@ var async = require('async'), var now = Date.now(), yesterday = now - (1000*60*60*24); // Approximate, can be more or less depending on time changes, makes no difference really. - db.getSortedSetRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, yesterday, now, function(err, uniqueIds) { + db.getSortedSetRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, yesterday, now, function(err, nids) { if (err) { return callback(err); } - if (!Array.isArray(uniqueIds) || !uniqueIds.length) { + if (!Array.isArray(nids) || !nids.length) { return callback(null, []); } - db.getObjectFields('uid:' + uid + ':notifications:uniqueId:nid', uniqueIds, function(err, uniqueIdToNids) { - if (err) { - return callback(err); - } - - var nids = Object.keys(uniqueIdToNids).map(function(uniqueId) { - return uniqueIdToNids[uniqueId]; - }); - - UserNotifications.getNotifications(nids, uid, callback); - }); + UserNotifications.getNotifications(nids, uid, callback); }); }; @@ -230,46 +216,35 @@ var async = require('async'), }; UserNotifications.getUnreadByField = function(uid, field, value, callback) { - db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, -1, function(err, uniqueIds) { + db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) { if (err) { return callback(err); } - if (!Array.isArray(uniqueIds) || !uniqueIds.length) { + if (!Array.isArray(nids) || !nids.length) { return callback(null, []); } - db.getObjectFields('uid:' + uid + ':notifications:uniqueId:nid', uniqueIds, function(err, uniqueIdsToNids) { + UserNotifications.getNotifications(nids, uid, function(err, notifications) { if (err) { return callback(err); } - var nids = Object.keys(uniqueIdsToNids).map(function(uniqueId) { - return uniqueIdsToNids[uniqueId]; + nids = notifications.filter(function(notification) { + return notification && notification[field] !== value.toString(); + }).map(function(notification) { + return notification.nid; }); - UserNotifications.getNotifications(nids, uid, function(err, notifications) { - if (err) { - return callback(err); - } - - notifications = notifications.filter(function(notification) { - return notification && notification[field] !== value.toString(); - }).map(function(notification) { - return notification.nid; - }); - - callback(null, nids); - }); + callback(null, nids); }); }); }; UserNotifications.sendPostNotificationToFollowers = function(uid, tid, pid) { - return; db.getSetMembers('followers:' + uid, function(err, followers) { - if (err || !followers || !followers.length) { + if (err || !Array.isArray(followers) || !followers.length) { return; } @@ -296,23 +271,30 @@ var async = require('async'), return !results.topicFollowers[index]; }); - notifications.create({ - bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topic.title + ']]', - bodyLong: results.postContent, - pid: pid, - uniqueId: 'topic:' + tid + ':uid:' + uid, - tid: tid, - from: uid - }, function(err, nid) { - if (err) { + if (!followers.length) { + return; + } + + async.filter(followers, function(uid, next) { + privileges.categories.can('read', results.topic.cid, uid, function(err, canRead) { + next(!err && canRead); + }); + }, function(followers) { + if (!followers.length) { return; } - async.filter(followers, function(uid, next) { - privileges.categories.can('read', results.topic.cid, uid, function(err, canRead) { - next(!err && canRead); - }); - }, function(followers){ - notifications.push(nid, followers); + + notifications.create({ + bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topic.title + ']]', + bodyLong: results.postContent, + pid: pid, + nid: 'topic:' + tid + ':uid:' + uid, + tid: tid, + from: uid + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, followers); + } }); }); }); From 4e6719e0aa73511abab6b2016d973e5f0fcc926d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 9 Sep 2014 15:19:57 -0400 Subject: [PATCH 232/372] added filterUids method to privileges used to filter uids on a single category --- src/categories.js | 4 ++-- src/groups.js | 41 +++++++++++++++++++++++++++++++----- src/privileges/categories.js | 41 +++++++++++++++++++++++++++++++----- src/privileges/helpers.js | 31 ++++++++++++++++++++++++++- src/privileges/posts.js | 2 +- src/privileges/topics.js | 6 +++--- src/user.js | 6 +++++- src/user/notifications.js | 6 +----- 8 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/categories.js b/src/categories.js index 315f6e8c4f..0d33631437 100644 --- a/src/categories.js +++ b/src/categories.js @@ -216,7 +216,7 @@ var db = require('./database'), return callback(null, []); } - privileges.categories.filter(privilege, cids, uid, function(err, cids) { + privileges.categories.filterCids(privilege, cids, uid, function(err, cids) { if (err) { return callback(err); } @@ -402,7 +402,7 @@ var db = require('./database'), async.waterfall([ async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), function(cids, next) { - privileges.categories.filter('find', cids, uid, next); + privileges.categories.filterCids('find', cids, uid, next); }, function (cids, next) { Categories.getCategoriesData(cids, next); diff --git a/src/groups.js b/src/groups.js index 06c03bcffa..3b5c6e8318 100644 --- a/src/groups.js +++ b/src/groups.js @@ -173,6 +173,9 @@ Groups.isMemberOfGroupList = function(uid, groupListKey, callback) { db.getSetMembers('group:' + groupListKey + ':members', function(err, groupNames) { + if (err) { + return callback(err); + } groupNames = internals.removeEphemeralGroups(groupNames); if (groupNames.length === 0) { return callback(null, null); @@ -180,11 +183,7 @@ async.some(groupNames, function(groupName, next) { Groups.isMember(uid, groupName, function(err, isMember) { - if (!err && isMember) { - next(true); - } else { - next(false); - } + next(!err && isMember); }); }, function(result) { callback(null, result); @@ -192,6 +191,38 @@ }); }; + Groups.isMembersOfGroupList = function(uids, groupListKey, callback) { + db.getSetMembers('group:' + groupListKey + ':members', function(err, groupNames) { + if (err) { + return callback(err); + } + groupNames = internals.removeEphemeralGroups(groupNames); + if (groupNames.length === 0) { + return callback(null, null); + } + var results = []; + uids.forEach(function() { + results.push(false); + }); + + async.each(groupNames, function(groupName, next) { + Groups.isMembers(uids, groupName, function(err, isMembers) { + if (err) { + return next(err); + } + results.forEach(function(isMember, index) { + if (!isMember && isMembers[index]) { + results[index] = true; + } + }); + next(); + }); + }, function(err) { + callback(err, results); + }); + }); + }; + Groups.exists = function(name, callback) { if (Array.isArray(name)) { db.isSetMembers('groups', name, callback); diff --git a/src/privileges/categories.js b/src/privileges/categories.js index e7572888f7..a6e1ce7204 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -16,10 +16,10 @@ module.exports = function(privileges) { privileges.categories.get = function(cid, uid, callback) { async.parallel({ 'topics:create': function(next) { - helpers.allowedTo('topics:create', uid, [cid], next); + helpers.isUserAllowedTo('topics:create', uid, [cid], next); }, read: function(next) { - helpers.allowedTo('read', uid, [cid], next); + helpers.isUserAllowedTo('read', uid, [cid], next); }, isAdministrator: function(next) { user.isAdministrator(uid, next); @@ -55,7 +55,7 @@ module.exports = function(privileges) { helpers.some([ function(next) { - helpers.allowedTo(privilege, uid, [cid], function(err, results) { + helpers.isUserAllowedTo(privilege, uid, [cid], function(err, results) { next(err, Array.isArray(results) && results.length ? results[0] : false); }); }, @@ -69,7 +69,7 @@ module.exports = function(privileges) { }); }; - privileges.categories.filter = function(privilege, cids, uid, callback) { + privileges.categories.filterCids = function(privilege, cids, uid, callback) { if (!cids.length) { return callback(null, []); } @@ -80,7 +80,7 @@ module.exports = function(privileges) { async.parallel({ allowedTo: function(next) { - helpers.allowedTo(privilege, uid, cids, next); + helpers.isUserAllowedTo(privilege, uid, cids, next); }, isModerators: function(next) { user.isModerator(uid, cids, next); @@ -104,6 +104,37 @@ module.exports = function(privileges) { }); }; + privileges.categories.filterUids = function(privilege, cid, uids, callback) { + if (!uids.length) { + return callback(null, []); + } + + uids = uids.filter(function(uid, index, array) { + return array.indexOf(uid) === index; + }); + + async.parallel({ + allowedTo: function(next) { + helpers.isUsersAllowedTo(privilege, uids, cid, next); + }, + isModerators: function(next) { + user.isModerator(uids, cid, next); + }, + isAdmin: function(next) { + user.isAdministrator(uids, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + uids = uids.filter(function(uid, index) { + return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index]; + }); + callback(null, uids); + }); + }; + privileges.categories.isAdminOrMod = function(cids, uid, callback) { async.parallel({ isModerators: function(next) { diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index ee6583b408..17a8eee1eb 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -20,7 +20,7 @@ helpers.some = function(tasks, callback) { }); }; -helpers.allowedTo = function(privilege, uid, cids, callback) { +helpers.isUserAllowedTo = function(privilege, uid, cids, callback) { if (parseInt(uid, 10) === 0) { return isGuestAllowedTo(privilege, cids, callback); } @@ -61,6 +61,35 @@ helpers.allowedTo = function(privilege, uid, cids, callback) { }); }; +helpers.isUsersAllowedTo = function(privilege, uids, cid, callback) { + async.parallel({ + userPrivilegeExists: function(next) { + groups.exists('cid:' + cid + ':privileges:' + privilege, next); + }, + groupPrivilegeExists: function(next) { + groups.exists('cid:' + cid + ':privileges:groups:' + privilege, next); + }, + hasUserPrivilege: function(next) { + groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege, next); + }, + hasGroupPrivilege: function(next) { + groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + var result = []; + + for(var i=0; i Date: Tue, 9 Sep 2014 15:24:03 -0400 Subject: [PATCH 233/372] check err --- src/user/notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index 779c5ef38a..caa9fe5ef4 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -275,8 +275,8 @@ var async = require('async'), return; } - privileges.categories.filterUids('read', results.topic.cid, followers, function(followers) { - if (!followers.length) { + privileges.categories.filterUids('read', results.topic.cid, followers, function(err, followers) { + if (err || !followers.length) { return; } From 7a61645256421e8a21cadca19cbfd02ed5844082 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 9 Sep 2014 15:36:46 -0400 Subject: [PATCH 234/372] less db calls --- src/groups.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/groups.js b/src/groups.js index 3b5c6e8318..b4ee1b9c17 100644 --- a/src/groups.js +++ b/src/groups.js @@ -181,12 +181,12 @@ return callback(null, null); } - async.some(groupNames, function(groupName, next) { - Groups.isMember(uid, groupName, function(err, isMember) { - next(!err && isMember); - }); - }, function(result) { - callback(null, result); + Groups.isMemberOfGroups(uid, groupNames, function(err, isMembers) { + if (err) { + return callback(err); + } + + callback(null, isMembers.indexOf(true) !== -1) }); }); }; From 523f245cd85977d86f7c7fc01ca02196e2fb9dd2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Sep 2014 02:00:24 -0400 Subject: [PATCH 235/372] removed derp --- src/posts.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/posts.js b/src/posts.js index 9c1285a64b..346965709d 100644 --- a/src/posts.js +++ b/src/posts.js @@ -485,20 +485,9 @@ var async = require('async'), } Posts.getPidIndex = function(pid, uid, callback) { - // Making uid optional - if ((!uid && !callback) || typeof uid === 'function') { - callback = uid || function() {}; - uid = undefined; - } - async.parallel({ settings: function(next) { - if (uid) { - user.getSettings(uid, next); - } else { - // No uid specified, so return empty object so that the check below will assume regular topic post sorting - next(null, {}); - } + user.getSettings(uid, next); }, tid: function(next) { Posts.getPostField(pid, 'tid', next); From 5204dc1e23c062b6606929cce1c7577ac4888b5d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Sep 2014 19:58:18 -0400 Subject: [PATCH 236/372] removed total count --- src/notifications.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index 74c265b1d8..cd1f441e00 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -229,9 +229,9 @@ var async = require('async'), return winston.error(err.message); } if (!Array.isArray(nids) || !nids.length) { + events.log('No notifications to prune'); return; } - var totalNidCount = nids.length; var keys = nids.map(function(nid) { return 'notifications:' + nid; @@ -242,7 +242,7 @@ var async = require('async'), return winston.error(err.message); } - var expiredNids = nids.filter(function(nids, index) { + var expiredNids = nids.filter(function(nid, index) { return !notifs[index].nid || parseInt(notifs[index].datetime, 10) < cutoffTime; }).filter(Boolean); @@ -252,7 +252,7 @@ var async = require('async'), numPruned = expiredNids.length; - events.log('Notification pruning. Total nids = ' + totalNidCount + '. Expired Nids = ' + numPruned); + events.log('Notification pruning. Expired Nids = ' + numPruned); async.parallel([ function(next) { From 35a903f9c86df525cc050cbdc5ea4f577dcd7d45 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Sep 2014 20:51:16 -0400 Subject: [PATCH 237/372] closes #2074 when new notifications are pushed to uids clear their old notifications. --- src/database/level/sorted.js | 4 ++++ src/database/mongo/sorted.js | 6 ++++++ src/database/redis/sorted.js | 10 ++++++++++ src/notifications.js | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/src/database/level/sorted.js b/src/database/level/sorted.js index b0cbcb415a..d262af89c2 100644 --- a/src/database/level/sorted.js +++ b/src/database/level/sorted.js @@ -51,6 +51,10 @@ module.exports = function(db, module) { }, callback); }; + module.sortedSetsRemoveRangeByScore = function(keys, min, max, callback) { + throw new Error('not implemented'); + }; + function flattenSortedSet(set, callback) { callback(null, !set.length ? [] : set.reduce(function(a, b) { return (a.length ? a : [a.value]).concat([b.value]); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index ead67b6115..a1dd33576f 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -79,6 +79,12 @@ module.exports = function(db, module) { db.collection('objects').remove({_key: {$in: keys}, value: value}, callback); }; + module.sortedSetsRemoveRangeByScore = function(keys, min, max, callback) { + callback = callback || helpers.noop; + db.collection('objects').remove({_key: {$in: keys}, score: {$lte: max, $gte: min}}, function(err) { + callback(err); + }); + }; function getSortedSetRange(key, start, stop, sort, withScores, callback) { db.collection('objects').find({_key:key}, {fields: {_id: 0, value: 1, score: 1}}) diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index abdd2ae435..36ef4f6c73 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -55,6 +55,16 @@ module.exports = function(redisClient, module) { multi('zrem', keys, value, callback); }; + module.sortedSetsRemoveRangeByScore = function(keys, min, max, callback) { + var multi = redisClient.multi(); + for(var i=0; i Date: Thu, 11 Sep 2014 17:16:28 -0400 Subject: [PATCH 238/372] faster notif prune no need to loop tru and check datetime since notifications is a sorted set now --- src/notifications.js | 58 +++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index c945331f68..328e033502 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -228,54 +228,40 @@ var async = require('async'), var cutoffTime = Date.now() - week; - db.getSortedSetRange('notifications', 0, 499, function(err, nids) { + db.getSortedSetRangeByScore('notifications', 0, 500, 0, cutoffTime, function(err, nids) { if (err) { return winston.error(err.message); } + if (!Array.isArray(nids) || !nids.length) { - events.log('No notifications to prune'); - return; + return events.log('No notifications to prune'); } - var keys = nids.map(function(nid) { + var keys = nids.map(function(nid) { return 'notifications:' + nid; }); - db.getObjectsFields(keys, ['nid', 'datetime'], function(err, notifs) { - if (err) { - return winston.error(err.message); - } - - var expiredNids = nids.filter(function(nid, index) { - return !notifs[index].nid || parseInt(notifs[index].datetime, 10) < cutoffTime; - }).filter(Boolean); - - keys = expiredNids.map(function(nid) { - return 'notifications:' + nid; - }); + numPruned = nids.length; - numPruned = expiredNids.length; + events.log('Notification pruning. Expired Nids = ' + numPruned); - events.log('Notification pruning. Expired Nids = ' + numPruned); - - async.parallel([ - function(next) { - db.sortedSetRemove('notifications', expiredNids, next); - }, - function(next) { - db.deleteAll(keys, next); - } - ], function(err) { - if (err) { - return winston.error('Encountered error pruning notifications: ' + err.message); - } + async.parallel([ + function(next) { + db.sortedSetRemove('notifications', nids, next); + }, + function(next) { + db.deleteAll(keys, next); + } + ], function(err) { + if (err) { + return winston.error('Encountered error pruning notifications: ' + err.message); + } - if (process.env.NODE_ENV === 'development') { - winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.'); - } - var diff = process.hrtime(start); - events.log('Pruning '+ numPruned + ' notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms'); - }); + if (process.env.NODE_ENV === 'development') { + winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.'); + } + var diff = process.hrtime(start); + events.log('Pruning '+ numPruned + ' notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms'); }); }); }; From b3d9db10f2d951c8b4a77b6b3da1a217c4d271af Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Sep 2014 16:35:30 -0400 Subject: [PATCH 239/372] notification fixes made post notifications unique per post so they dont overwrite other types. --- src/database/mongo/sorted.js | 27 ++++++++++++++++++++++++++- src/notifications.js | 2 +- src/socket.io/topics.js | 2 +- src/socket.io/user.js | 2 +- src/topics/follow.js | 2 +- src/user/notifications.js | 2 +- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index a1dd33576f..78a8d629fe 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -170,7 +170,32 @@ module.exports = function(db, module) { }; module.sortedSetsCard = function(keys, callback) { - async.map(keys, module.sortedSetCard, callback); + var pipeline = [ + { $match : { _key : { $in: keys } } } , + { $group: { _id: {_key: '$_key'}, count: { $sum: 1 } } }, + { $project: { _id: 1, count: '$count' } } + ]; + db.collection('objects').aggregate(pipeline, function(err, results) { + if (err) { + return callback(err); + } + + if (!Array.isArray(results)) { + results = []; + } + + var map = {}; + results.forEach(function(item) { + if (item && item._id._key) { + map[item._id._key] = item.count; + } + }); + + results = keys.map(function(key) { + return map[key] || 0; + }); + callback(null, results); + }); }; module.sortedSetRank = function(key, value, callback) { diff --git a/src/notifications.js b/src/notifications.js index 328e033502..b1ac1639f6 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -95,7 +95,7 @@ var async = require('async'), if (oldNotification) { if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) { - return callback(null, null); + return callback(); } } diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 666b62d244..500fae9cc6 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -353,7 +353,7 @@ SocketTopics.sendNotificationToTopicOwner = function(tid, fromuid, notification) notifications.create({ bodyShort: '[[' + notification + ', ' + results.username + ']]', path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, - nid: 'topic:' + tid + ':uid:' + fromuid, + nid: 'tid:' + tid + ':uid:' + fromuid, from: fromuid }, function(err, notification) { if (!err && notification) { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 43bb934bdb..44d6f4b41c 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -198,7 +198,7 @@ SocketUser.follow = function(socket, data, callback) { notifications.create({ bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', path: nconf.get('relative_path') + '/user/' + userData.userslug, - nid: 'follow:uid:' + socket.uid, + nid: 'follow:' + data.uid + ':uid:' + socket.uid, from: socket.uid }, function(err, notification) { if (!err && notification) { diff --git a/src/topics/follow.js b/src/topics/follow.js index 9c711ad440..588d629c82 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -56,7 +56,7 @@ module.exports = function(Topics) { bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.title + ']]', bodyLong: results.postContent, pid: pid, - nid: 'topic:' + tid + ':uid:' + exceptUid, + nid: 'tid:' + tid + ':pid:' + pid + ':uid:' + exceptUid, tid: tid, from: exceptUid }, function(err, notification) { diff --git a/src/user/notifications.js b/src/user/notifications.js index caa9fe5ef4..08703d6237 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -284,7 +284,7 @@ var async = require('async'), bodyShort: '[[notifications:user_posted_to, ' + results.username + ', ' + results.topic.title + ']]', bodyLong: results.postContent, pid: pid, - nid: 'topic:' + tid + ':uid:' + uid, + nid: 'tid:' + tid + ':pid:' + pid + ':uid:' + uid, tid: tid, from: uid }, function(err, notification) { From e56b18822b4b954616e26537e31bd33c1fc1bd10 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Sep 2014 18:05:09 -0400 Subject: [PATCH 240/372] added topic titles to notifs upvote, favourite and flag has topic titles now --- public/language/en_GB/notifications.json | 6 ++-- src/controllers/users.js | 4 ++- src/socket.io/posts.js | 39 +++++++++++------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json index 5bb153ccbc..3cb6ac6872 100644 --- a/public/language/en_GB/notifications.json +++ b/public/language/en_GB/notifications.json @@ -12,11 +12,11 @@ "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", "moved_your_post": "%1 has moved your post.", "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to" : "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", "user_started_following_you": "%1 started following you.", diff --git a/src/controllers/users.js b/src/controllers/users.js index fa53a3fa1a..23a2e39b39 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -65,7 +65,9 @@ function getUsers(set, res, next) { if (err) { return next(err); } - + data = data.filter(function(user) { + return user && parseInt(user.uid, 10); + }); db.sortedSetCard(set, function(err, count) { if (err) { return next(err); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 36ae2934a8..23c9a6fed3 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -63,7 +63,7 @@ SocketPosts.upvote = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } favouriteCommand('upvote', 'voted', socket, data, callback); - SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:upvoted_your_post'); + SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:upvoted_your_post_in'); }; SocketPosts.downvote = function(socket, data, callback) { @@ -79,7 +79,7 @@ SocketPosts.favourite = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } favouriteCommand('favourite', 'favourited', socket, data, callback); - SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:favourited_your_post'); + SocketPosts.sendNotificationToPostOwner(data.pid, socket.uid, 'notifications:favourited_your_post_in'); }; SocketPosts.unfavourite = function(socket, data, callback) { @@ -107,7 +107,7 @@ SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { if(!pid || !fromuid) { return; } - posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) { + posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) { if (err) { return; } @@ -118,21 +118,15 @@ SocketPosts.sendNotificationToPostOwner = function(pid, fromuid, notification) { async.parallel({ username: async.apply(user.getUserField, fromuid, 'username'), - postContent: function(next) { - async.waterfall([ - async.apply(posts.getPostField, pid, 'content'), - function(content, next) { - postTools.parse(content, next); - } - ], next); - } + topicTitle: async.apply(topics.getTopicField, postData.tid, 'title'), + postContent: async.apply(postTools.parse, postData.content) }, function(err, results) { if (err) { return; } notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicTitle + ']]', bodyLong: results.postContent, pid: pid, nid: 'post:' + pid + ':uid:' + fromuid, @@ -279,6 +273,7 @@ SocketPosts.flag = function(socket, pid, callback) { } var message = '', + userName = '', post; async.waterfall([ @@ -289,20 +284,20 @@ SocketPosts.flag = function(socket, pid, callback) { if (parseInt(userData.reputation, 10) < parseInt(meta.config['privileges:flag'] || 1, 10)) { return next(new Error('[[error:not-enough-reputation-to-flag]]')); } - message = '[[notifications:user_flagged_post, ' + userData.username + ']]'; + userName = userData.username; + posts.getPostFields(pid, ['tid', 'uid', 'content'], next); }, - function(postData, next) { - postTools.parse(postData.content, function(err, parsed) { - if (err) { - return next(err); - } - postData.content = parsed; - next(null, postData); - }); - }, function(postData, next) { post = postData; + topics.getTopicField(postData.tid, 'title', next); + }, + function(topicTitle, next) { + message = '[[notifications:user_flagged_post_in, ' + userName + ', ' + topicTitle + ']]'; + postTools.parse(post.content, next); + }, + function(postContent, next) { + post.content = postContent; groups.get('administrators', {}, next); }, function(adminGroup, next) { From ddb02978c89afb9b05ed0385e5b394b17f0a2b40 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Sep 2014 18:31:04 -0400 Subject: [PATCH 241/372] cleanup --- src/controllers/users.js | 97 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/src/controllers/users.js b/src/controllers/users.js index 23a2e39b39..039bb4468f 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -3,48 +3,45 @@ var usersController = {}; var async = require('async'), - user = require('./../user'), - db = require('./../database'); + user = require('../user'), + db = require('../database'); usersController.getOnlineUsers = function(req, res, next) { var websockets = require('../socket.io'); - - user.getUsersFromSet('users:online', 0, 49, function (err, users) { - if(err) { + var uid = req.user ? req.user.uid : 0; + + async.parallel({ + users: function(next) { + user.getUsersFromSet('users:online', 0, 49, next); + }, + count: function(next) { + db.sortedSetCard('users:online', next); + }, + isAdministrator: function(next) { + user.isAdministrator(uid, next); + } + }, function(err, results) { + if (err) { return next(err); } - var uid = req.user ? req.user.uid : 0; - - user.isAdministrator(uid, function (err, isAdministrator) { - if(err) { - return next(err); - } - - if (!isAdministrator) { - users = users.filter(function(user) { - return user.status !== 'offline'; - }); - } - - var anonymousUserCount = websockets.getOnlineAnonCount(); + if (!results.isAdministrator) { + results.users = results.users.filter(function(user) { + return user && user.status !== 'offline'; + }); + } - db.sortedSetCard('users:online', function(err, count) { - if (err) { - return next(err); - } + var anonymousUserCount = websockets.getOnlineAnonCount(); - var userData = { - search_display: 'none', - loadmore_display: count > 50 ? 'block' : 'hide', - users: users, - anonymousUserCount: anonymousUserCount, - show_anon: anonymousUserCount?'':'hide' - }; + var userData = { + search_display: 'none', + loadmore_display: results.count > 50 ? 'block' : 'hide', + users: results.users, + anonymousUserCount: anonymousUserCount, + show_anon: anonymousUserCount ? '' : 'hide' + }; - res.render('users', userData); - }); - }); + res.render('users', userData); }); }; @@ -61,27 +58,29 @@ usersController.getUsersSortedByJoinDate = function(req, res, next) { }; function getUsers(set, res, next) { - user.getUsersFromSet(set, 0, 49, function (err, data) { + async.parallel({ + users: function(next) { + user.getUsersFromSet(set, 0, 49, next); + }, + count: function(next) { + db.sortedSetCard(set, next); + } + }, function(err, results) { if (err) { return next(err); } - data = data.filter(function(user) { + results.users = results.users.filter(function(user) { return user && parseInt(user.uid, 10); }); - db.sortedSetCard(set, function(err, count) { - if (err) { - return next(err); - } - - var userData = { - search_display: 'none', - loadmore_display: count > 50 ? 'block' : 'hide', - users: data, - show_anon: 'hide' - }; - - res.render('users', userData); - }); + + var userData = { + search_display: 'none', + loadmore_display: results.count > 50 ? 'block' : 'hide', + users: results.users, + show_anon: 'hide' + }; + + res.render('users', userData); }); } From f1d5db6be02c3818ba59b92d34c9d3c9cfd1c0e2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 13 Sep 2014 18:50:35 -0400 Subject: [PATCH 242/372] pagination fix --- src/controllers/topics.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index c955f25cbf..bb772d1dac 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -74,8 +74,8 @@ topicsController.get = function(req, res, next) { postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage + 1), 0); } } else if (!req.query.page) { - var index = Math.max(req.params.post_index, 0); - page = Math.ceil((index + 1) / settings.postsPerPage); + var index = Math.max(req.params.post_index - 1, 0); + page = Math.max(1, Math.ceil(index / settings.postsPerPage)); } var start = (page - 1) * settings.postsPerPage + postIndex, From 82e721e8541578d93ffdb2c26856821180593cc3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 14 Sep 2014 14:19:36 -0400 Subject: [PATCH 243/372] send new post/topic to users who can read it --- src/socket.io/posts.js | 44 ++++++++++++++++++++++------------------- src/socket.io/topics.js | 34 ++++++++++++++++--------------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 23c9a6fed3..582e3fa7c9 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -27,34 +27,38 @@ SocketPosts.reply = function(socket, data, callback) { data.req = websockets.reqFromSocket(socket); topics.reply(data, function(err, postData) { - if(err) { + if (err) { return callback(err); } - if (postData) { - var privileges = { + var result = { + posts: [postData], + privileges: { 'topics:reply': true - }; + }, + 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, + 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, + }; - callback(); + callback(); - socket.emit('event:new_post', { - posts: [postData], - privileges: privileges, - 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, - 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, - }); + socket.emit('event:new_post', result); - socket.broadcast.emit('event:new_post', { - posts: [postData], - privileges: privileges, - 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, - 'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1, - }); + var uids = websockets.getConnectedClients(); - websockets.emitTopicPostStats(); - topics.pushUnreadCount(); - } + privileges.categories.filterUids('read', postData.topic.cid, uids, function(err, uids) { + if (err) { + return; + } + for(var i=0; i Date: Sun, 14 Sep 2014 19:35:10 -0400 Subject: [PATCH 244/372] closes #2084 --- src/middleware/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 5ea711b46c..6897e34052 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -64,7 +64,15 @@ function routeCurrentTheme(app, themeId, themesData) { } // Theme's templates path - nconf.set('theme_templates_path', themeObj.templates ? path.join(themesPath, themeObj.id, themeObj.templates) : nconf.get('base_templates_path')); + var themePath = nconf.get('base_templates_path'), + fallback = path.join(themesPath, themeObj.id, 'templates'); + if (themeObj.templates) { + themePath = path.join(themesPath, themeObj.id, themeObj.templates); + } else if (fs.existsSync(fallback)) { + themePath = fallback; + } + + nconf.set('theme_templates_path', themePath); } module.exports = function(app, data) { From 51cefb00d2b1d6b6ef410da92beb80b65226e7a4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 5 Sep 2014 14:14:41 -0400 Subject: [PATCH 245/372] add support to set parentCid to 0 --- public/src/forum/admin/categories.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index c7851c1e7e..37a551ffef 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -239,6 +239,21 @@ define('forum/admin/categories', ['uploader', 'forum/admin/iconSelect'], functio modal.find('select').val($(this).attr('data-parentCid')); modal.attr('data-cid', cid).modal(); }); + + $('button[data-action="removeParent"]').on('click', function() { + var cid = $(this).parents('[data-cid]').attr('data-cid'); + var payload= {}; + payload[cid] = { + parentCid: 0 + }; + socket.emit('admin.categories.update', payload, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.go('admin/categories/active'); + }); + }); + $('#setParent [data-cid]').on('click', function() { var modalEl = $('#setParent'), parentCid = $(this).attr('data-cid'), From d22fe4ad1e47814baac2eb000ab1ebcaa6421297 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 14 Sep 2014 21:25:01 -0400 Subject: [PATCH 246/372] removing disabled categories from being listed as children, #2080 --- src/categories.js | 3 ++- src/categories/update.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/categories.js b/src/categories.js index 8043401830..e33d8a7aa3 100644 --- a/src/categories.js +++ b/src/categories.js @@ -402,9 +402,10 @@ var db = require('./database'), Categories.getCategoriesData(cids, next); }, function (categories, next) { + // Filter categories to isolate children, and remove disabled categories async.map(cids, function(cid, next) { next(null, categories.filter(function(category) { - return parseInt(category.parentCid, 10) === parseInt(cid, 10); + return parseInt(category.parentCid, 10) === parseInt(cid, 10) && !category.disabled; })); }, next); } diff --git a/src/categories/update.js b/src/categories/update.js index fa04d99deb..002628d723 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -13,7 +13,6 @@ module.exports = function(Categories) { function updateCategory(cid, next) { var category = modified[cid]; var fields = Object.keys(category); - console.log('updating', cid, 'fields:', fields); async.each(fields, function(key, next) { updateCategoryField(cid, key, category[key], next); From 9cba8d7ecb94d44a79e4d85bf306a70cb879c8d4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2014 08:35:10 -0400 Subject: [PATCH 247/372] latest translations and fallbacks, closes #2085" --- public/language/ar/category.json | 3 +- public/language/ar/error.json | 8 ++++- public/language/ar/notifications.json | 9 +++-- public/language/ar/recent.json | 1 + public/language/ar/search.json | 3 +- public/language/ar/user.json | 6 +++- public/language/cs/category.json | 3 +- public/language/cs/error.json | 8 ++++- public/language/cs/notifications.json | 9 +++-- public/language/cs/recent.json | 1 + public/language/cs/search.json | 3 +- public/language/cs/user.json | 6 +++- public/language/de/category.json | 3 +- public/language/de/error.json | 8 ++++- public/language/de/notifications.json | 9 +++-- public/language/de/pages.json | 2 +- public/language/de/recent.json | 1 + public/language/de/search.json | 3 +- public/language/de/topic.json | 2 +- public/language/de/user.json | 6 +++- public/language/en@pirate/category.json | 3 +- public/language/en@pirate/error.json | 8 ++++- public/language/en@pirate/notifications.json | 9 +++-- public/language/en@pirate/recent.json | 1 + public/language/en@pirate/search.json | 3 +- public/language/en@pirate/user.json | 6 +++- public/language/en_US/category.json | 3 +- public/language/en_US/error.json | 8 ++++- public/language/en_US/notifications.json | 9 +++-- public/language/en_US/recent.json | 1 + public/language/en_US/search.json | 3 +- public/language/en_US/user.json | 6 +++- public/language/es/category.json | 3 +- public/language/es/email.json | 36 ++++++++++---------- public/language/es/error.json | 10 ++++-- public/language/es/groups.json | 10 +++--- public/language/es/notifications.json | 15 ++++---- public/language/es/pages.json | 2 +- public/language/es/recent.json | 1 + public/language/es/search.json | 3 +- public/language/es/topic.json | 2 +- public/language/es/user.json | 6 +++- public/language/et/category.json | 3 +- public/language/et/error.json | 8 ++++- public/language/et/notifications.json | 9 +++-- public/language/et/recent.json | 1 + public/language/et/search.json | 3 +- public/language/et/user.json | 6 +++- public/language/fa_IR/category.json | 3 +- public/language/fa_IR/error.json | 8 ++++- public/language/fa_IR/notifications.json | 9 +++-- public/language/fa_IR/recent.json | 1 + public/language/fa_IR/search.json | 3 +- public/language/fa_IR/user.json | 6 +++- public/language/fi/category.json | 3 +- public/language/fi/error.json | 8 ++++- public/language/fi/notifications.json | 9 +++-- public/language/fi/recent.json | 1 + public/language/fi/search.json | 3 +- public/language/fi/user.json | 6 +++- public/language/fr/category.json | 3 +- public/language/fr/error.json | 8 ++++- public/language/fr/notifications.json | 9 +++-- public/language/fr/recent.json | 1 + public/language/fr/search.json | 3 +- public/language/fr/user.json | 6 +++- public/language/he/category.json | 3 +- public/language/he/error.json | 8 ++++- public/language/he/notifications.json | 9 +++-- public/language/he/recent.json | 1 + public/language/he/search.json | 3 +- public/language/he/user.json | 6 +++- public/language/hu/category.json | 3 +- public/language/hu/error.json | 8 ++++- public/language/hu/notifications.json | 9 +++-- public/language/hu/recent.json | 1 + public/language/hu/search.json | 3 +- public/language/hu/user.json | 6 +++- public/language/it/category.json | 3 +- public/language/it/error.json | 8 ++++- public/language/it/notifications.json | 9 +++-- public/language/it/recent.json | 1 + public/language/it/search.json | 3 +- public/language/it/user.json | 6 +++- public/language/ja/category.json | 3 +- public/language/ja/error.json | 8 ++++- public/language/ja/notifications.json | 9 +++-- public/language/ja/recent.json | 1 + public/language/ja/search.json | 3 +- public/language/ja/user.json | 6 +++- public/language/ko/category.json | 3 +- public/language/ko/error.json | 8 ++++- public/language/ko/notifications.json | 9 +++-- public/language/ko/recent.json | 1 + public/language/ko/search.json | 3 +- public/language/ko/user.json | 6 +++- public/language/lt/category.json | 3 +- public/language/lt/error.json | 8 ++++- public/language/lt/notifications.json | 9 +++-- public/language/lt/recent.json | 1 + public/language/lt/search.json | 3 +- public/language/lt/user.json | 6 +++- public/language/ms/category.json | 3 +- public/language/ms/error.json | 8 ++++- public/language/ms/notifications.json | 9 +++-- public/language/ms/recent.json | 1 + public/language/ms/search.json | 3 +- public/language/ms/user.json | 6 +++- public/language/nb/category.json | 3 +- public/language/nb/error.json | 8 ++++- public/language/nb/notifications.json | 9 +++-- public/language/nb/recent.json | 1 + public/language/nb/search.json | 3 +- public/language/nb/user.json | 6 +++- public/language/nl/category.json | 3 +- public/language/nl/email.json | 2 +- public/language/nl/error.json | 10 ++++-- public/language/nl/global.json | 8 ++--- public/language/nl/login.json | 2 +- public/language/nl/notifications.json | 9 +++-- public/language/nl/recent.json | 1 + public/language/nl/search.json | 3 +- public/language/nl/topic.json | 20 +++++------ public/language/nl/user.json | 6 +++- public/language/pl/category.json | 3 +- public/language/pl/email.json | 36 ++++++++++---------- public/language/pl/error.json | 10 ++++-- public/language/pl/global.json | 2 +- public/language/pl/groups.json | 10 +++--- public/language/pl/modules.json | 2 +- public/language/pl/notifications.json | 15 ++++---- public/language/pl/pages.json | 2 +- public/language/pl/recent.json | 3 +- public/language/pl/search.json | 3 +- public/language/pl/tags.json | 8 ++--- public/language/pl/topic.json | 20 +++++------ public/language/pl/user.json | 10 ++++-- public/language/pl/users.json | 4 +-- public/language/pt_BR/category.json | 3 +- public/language/pt_BR/error.json | 8 ++++- public/language/pt_BR/notifications.json | 9 +++-- public/language/pt_BR/pages.json | 2 +- public/language/pt_BR/recent.json | 1 + public/language/pt_BR/search.json | 3 +- public/language/pt_BR/topic.json | 2 +- public/language/pt_BR/user.json | 6 +++- public/language/ro/category.json | 3 +- public/language/ro/error.json | 8 ++++- public/language/ro/notifications.json | 9 +++-- public/language/ro/recent.json | 1 + public/language/ro/search.json | 3 +- public/language/ro/topic.json | 4 +-- public/language/ro/user.json | 6 +++- public/language/ru/category.json | 3 +- public/language/ru/error.json | 8 ++++- public/language/ru/notifications.json | 9 +++-- public/language/ru/recent.json | 1 + public/language/ru/search.json | 3 +- public/language/ru/user.json | 6 +++- public/language/sc/category.json | 3 +- public/language/sc/error.json | 8 ++++- public/language/sc/notifications.json | 9 +++-- public/language/sc/recent.json | 1 + public/language/sc/search.json | 3 +- public/language/sc/user.json | 6 +++- public/language/sk/category.json | 3 +- public/language/sk/error.json | 8 ++++- public/language/sk/notifications.json | 9 +++-- public/language/sk/recent.json | 1 + public/language/sk/search.json | 3 +- public/language/sk/user.json | 6 +++- public/language/sv/category.json | 3 +- public/language/sv/error.json | 8 ++++- public/language/sv/notifications.json | 9 +++-- public/language/sv/recent.json | 1 + public/language/sv/search.json | 3 +- public/language/sv/user.json | 6 +++- public/language/th/category.json | 3 +- public/language/th/error.json | 8 ++++- public/language/th/notifications.json | 9 +++-- public/language/th/recent.json | 1 + public/language/th/search.json | 3 +- public/language/th/user.json | 6 +++- public/language/tr/category.json | 3 +- public/language/tr/error.json | 8 ++++- public/language/tr/notifications.json | 9 +++-- public/language/tr/recent.json | 1 + public/language/tr/search.json | 3 +- public/language/tr/user.json | 6 +++- public/language/vi/category.json | 3 +- public/language/vi/error.json | 8 ++++- public/language/vi/notifications.json | 9 +++-- public/language/vi/recent.json | 1 + public/language/vi/search.json | 3 +- public/language/vi/user.json | 6 +++- public/language/zh_CN/category.json | 3 +- public/language/zh_CN/error.json | 8 ++++- public/language/zh_CN/notifications.json | 9 +++-- public/language/zh_CN/pages.json | 2 +- public/language/zh_CN/recent.json | 1 + public/language/zh_CN/search.json | 3 +- public/language/zh_CN/topic.json | 2 +- public/language/zh_CN/user.json | 6 +++- public/language/zh_TW/category.json | 3 +- public/language/zh_TW/error.json | 8 ++++- public/language/zh_TW/notifications.json | 9 +++-- public/language/zh_TW/recent.json | 1 + public/language/zh_TW/search.json | 3 +- public/language/zh_TW/user.json | 6 +++- 209 files changed, 816 insertions(+), 320 deletions(-) diff --git a/public/language/ar/category.json b/public/language/ar/category.json index 27f20b6d25..7d62f59e6f 100644 --- a/public/language/ar/category.json +++ b/public/language/ar/category.json @@ -3,5 +3,6 @@ "no_topics": "لا توجد مواضيع في هذه الفئةلماذا لا تحاول نشر موضوع؟
    ", "browsing": "تصفح", "no_replies": "لم يرد أحد", - "share_this_category": "انشر هذه الفئة" + "share_this_category": "انشر هذه الفئة", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ar/error.json b/public/language/ar/error.json index df25651826..feb589fd8f 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "كلمة السر غير مقبولة", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "اسم المستخدم ماخوذ", "email-taken": "البريد الالكتروني ماخوذ", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "المستخدم محظور", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "مشكلة في الرفع: 1%", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ar/notifications.json b/public/language/ar/notifications.json index 32d6b62dcf..eec61f2062 100644 --- a/public/language/ar/notifications.json +++ b/public/language/ar/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/ar/recent.json b/public/language/ar/recent.json index fa2a081df6..866d0e28fc 100644 --- a/public/language/ar/recent.json +++ b/public/language/ar/recent.json @@ -4,5 +4,6 @@ "week": "أسبوع", "month": "شهر", "year": "Year", + "alltime": "All Time", "no_recent_topics": "There are no recent topics." } \ No newline at end of file diff --git a/public/language/ar/search.json b/public/language/ar/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/ar/search.json +++ b/public/language/ar/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ar/user.json b/public/language/ar/user.json index 3ec0b2df33..a1aa9dc80f 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -4,6 +4,8 @@ "username": "إسم المستخدم", "email": "البريد الإلكتروني", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "الاسم الكامل", "website": "الموقع الإلكتروني", "location": "موقع", @@ -61,5 +63,7 @@ "posts_per_page": "Posts per Page", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/cs/category.json b/public/language/cs/category.json index fcaf82c582..0c5e3af859 100644 --- a/public/language/cs/category.json +++ b/public/language/cs/category.json @@ -3,5 +3,6 @@ "no_topics": "V této kategorii zatím nejsou žádné příspěvky.
    Můžeš být první!", "browsing": "prohlíží", "no_replies": "Nikdo ještě neodpověděl", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json index e315bfd6c0..8a77183b5e 100644 --- a/public/language/cs/notifications.json +++ b/public/language/cs/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/cs/recent.json b/public/language/cs/recent.json index 34933d6d13..d769997c6f 100644 --- a/public/language/cs/recent.json +++ b/public/language/cs/recent.json @@ -4,5 +4,6 @@ "week": "Týden", "month": "Měsíc", "year": "Year", + "alltime": "All Time", "no_recent_topics": "There are no recent topics." } \ No newline at end of file diff --git a/public/language/cs/search.json b/public/language/cs/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/cs/search.json +++ b/public/language/cs/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 6d2bba05e5..13be6ba018 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -4,6 +4,8 @@ "username": "Uživatelské jméno", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Jméno a příjmení", "website": "Webové stránky", "location": "Poloha", @@ -61,5 +63,7 @@ "posts_per_page": "Posts per Page", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/de/category.json b/public/language/de/category.json index df8b9796ef..185540948a 100644 --- a/public/language/de/category.json +++ b/public/language/de/category.json @@ -3,5 +3,6 @@ "no_topics": "Es gibt noch keine Themen in dieser Kategorie.
    Warum beginnst du nicht das erste?", "browsing": "Aktiv", "no_replies": "Niemand hat geantwortet", - "share_this_category": "Teile diese Kategorie" + "share_this_category": "Teile diese Kategorie", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 73e0f64c0e..4217b44a31 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -12,11 +12,13 @@ "invalid-title": "Ungültiger Titel", "invalid-user-data": "Ungültige Benutzerdaten", "invalid-password": "Ungültiges Passwort", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Die Nummerierung ist ungültig", "username-taken": "Der Benutzername ist bereits vergeben", "email-taken": "Die E-Mail-Adresse ist bereits vergeben", "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt. Bitte klicke hier, um deine E-Mail zu bestätigen.", "username-too-short": "Benutzername ist zu kurz", + "username-too-long": "Username too long", "user-banned": "Der Benutzer ist gesperrt", "no-category": "Die Kategorie existiert nicht", "no-topic": "Das Thema existiert nicht", @@ -52,5 +54,9 @@ "upload-error": "Upload-Fehler: %1", "signature-too-long": "Die Signatur darf maximal %1 Zeichen enthalten!", "cant-chat-with-yourself": "Du kannst nicht mit dir selber chatten!", - "not-enough-reputation-to-downvote": "Deine Reputation ist zu niedrig, um diesen Beitrag negativ zu bewerten." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Deine Reputation ist zu niedrig, um diesen Beitrag negativ zu bewerten.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/de/notifications.json b/public/language/de/notifications.json index 295796f5d6..a12ad1694c 100644 --- a/public/language/de/notifications.json +++ b/public/language/de/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Neue Benachrichtigung", "you_have_unread_notifications": "Du hast ungelesene Benachrichtigungen.", "new_message_from": "Neue Nachricht von %1", - "upvoted_your_post": "%1 hat deinen Beitrag positiv bewertet.", - "favourited_your_post": "%1 favorisiert deinen Beitrag.", - "user_flagged_post": "%1 hat einen Beitrag markiert.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 hat auf %2 geantwortet.", "user_mentioned_you_in": "%1 erwähnte dich in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "E-Mail bestätigt", "email-confirmed-message": "Vielen Dank für Ihre E-Mail-Validierung. Ihr Konto ist nun vollständig aktiviert.", "email-confirm-error": "Es ist ein Fehler aufgetreten ...", diff --git a/public/language/de/pages.json b/public/language/de/pages.json index c49854a2a4..2df893a878 100644 --- a/public/language/de/pages.json +++ b/public/language/de/pages.json @@ -5,7 +5,7 @@ "recent": "Neueste Themen", "users": "Registrierte User", "notifications": "Benachrichtigungen", - "tags": "Topics tagged under \"%1\"", + "tags": "Themen markiert unter \"%1\"", "user.edit": "Bearbeite \"%1\"", "user.following": "Nutzer, die %1 folgt", "user.followers": "Nutzer, die %1 folgen", diff --git a/public/language/de/recent.json b/public/language/de/recent.json index b428875786..7e5cc47101 100644 --- a/public/language/de/recent.json +++ b/public/language/de/recent.json @@ -4,5 +4,6 @@ "week": "Woche", "month": "Monat", "year": "Jahr", + "alltime": "All Time", "no_recent_topics": "Es gibt keine aktuellen Themen." } \ No newline at end of file diff --git a/public/language/de/search.json b/public/language/de/search.json index 47ca0be16b..7176039ead 100644 --- a/public/language/de/search.json +++ b/public/language/de/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 Ergebniss(e) stimmen mit \"%2\" überein, (%3 Sekunden)" + "results_matching": "%1 Ergebniss(e) stimmen mit \"%2\" überein, (%3 Sekunden)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 85edcd53c7..c3642ea0bd 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 weitere(r) Nutzer und %2 Gäste", "more_users": "%1 weitere(r) Nutzer", "more_guests": "%1 weitere Gäste", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 und %2 andere", "sort_by": "Sortieren nach", "oldest_to_newest": "Älteste zuerst", "newest_to_oldest": "Neuster zuerst", diff --git a/public/language/de/user.json b/public/language/de/user.json index 524a2ae08b..c264b87190 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -4,6 +4,8 @@ "username": "Nutzername", "email": "E-Mail", "confirm_email": "E-Mail bestätigen", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Kompletter Name", "website": "Homepage", "location": "Wohnort", @@ -61,5 +63,7 @@ "posts_per_page": "Beiträge pro Seite", "notification_sounds": "Ton abspielen, wenn ich eine Benachrichtigung erhalte.", "browsing": "Browser Einstellungen", - "open_links_in_new_tab": "Externe Links in neuem Tab öffnen?" + "open_links_in_new_tab": "Externe Links in neuem Tab öffnen?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/en@pirate/category.json b/public/language/en@pirate/category.json index 44e19baa01..74b946d216 100644 --- a/public/language/en@pirate/category.json +++ b/public/language/en@pirate/category.json @@ -3,5 +3,6 @@ "no_topics": "Thar be no topics in 'tis category.
    Why don't ye give a go' postin' one?", "browsing": "browsin'", "no_replies": "No one has replied to ye message", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/en@pirate/notifications.json b/public/language/en@pirate/notifications.json index cc1705eff6..49a3a10e05 100644 --- a/public/language/en@pirate/notifications.json +++ b/public/language/en@pirate/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/en@pirate/recent.json b/public/language/en@pirate/recent.json index 6e6df209fa..bb4318cb39 100644 --- a/public/language/en@pirate/recent.json +++ b/public/language/en@pirate/recent.json @@ -4,5 +4,6 @@ "week": "Week", "month": "Month", "year": "Year", + "alltime": "All Time", "no_recent_topics": "There be no recent topics." } \ No newline at end of file diff --git a/public/language/en@pirate/search.json b/public/language/en@pirate/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/en@pirate/search.json +++ b/public/language/en@pirate/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index 286d72030a..a8efc3d857 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -4,6 +4,8 @@ "username": "User Name", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Full Name", "website": "Website", "location": "Location", @@ -61,5 +63,7 @@ "posts_per_page": "Posts per Page", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/en_US/category.json b/public/language/en_US/category.json index 99ba34a172..93272e1a06 100644 --- a/public/language/en_US/category.json +++ b/public/language/en_US/category.json @@ -3,5 +3,6 @@ "no_topics": "There are no topics in this category.
    Why don't you try posting one?", "browsing": "browsing", "no_replies": "No one has replied", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index bfec6e00d7..200ce83066 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/en_US/notifications.json b/public/language/en_US/notifications.json index 7f97d92275..4d2461226d 100644 --- a/public/language/en_US/notifications.json +++ b/public/language/en_US/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favorited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/en_US/recent.json b/public/language/en_US/recent.json index 1bf0cf9f01..d683cce436 100644 --- a/public/language/en_US/recent.json +++ b/public/language/en_US/recent.json @@ -4,5 +4,6 @@ "week": "Week", "month": "Month", "year": "Year", + "alltime": "All Time", "no_recent_topics": "There are no recent topics." } \ No newline at end of file diff --git a/public/language/en_US/search.json b/public/language/en_US/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/en_US/search.json +++ b/public/language/en_US/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index 0c68cc21b0..50848c7f83 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -4,6 +4,8 @@ "username": "User Name", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Full Name", "website": "Website", "location": "Location", @@ -61,5 +63,7 @@ "posts_per_page": "Posts per Page", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/es/category.json b/public/language/es/category.json index cd64161deb..ff01be1a31 100644 --- a/public/language/es/category.json +++ b/public/language/es/category.json @@ -3,5 +3,6 @@ "no_topics": "No hay temas en esta categoría.
    ¿Por que no te animas y publicas uno?", "browsing": "viendo ahora", "no_replies": "Nadie ha respondido aún", - "share_this_category": "Compartir esta categoría" + "share_this_category": "Compartir esta categoría", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/es/email.json b/public/language/es/email.json index 98e591ab02..b791157e63 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -1,20 +1,20 @@ { - "password-reset-requested": "Password Reset Requested - %1!", - "welcome-to": "Welcome to %1", - "greeting_no_name": "Hello", - "greeting_with_name": "Hello %1", - "welcome.text1": "Thank you for registering with %1!", - "welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.", - "welcome.cta": "Click here to confirm your email address", - "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.", - "reset.text2": "To continue with the password reset, please click on the following link:", - "reset.cta": "Click here to reset your password", - "digest.notifications": "You have some unread notifications from %1:", - "digest.latest_topics": "Latest topics from %1", - "digest.cta": "Click here to visit %1", - "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", - "digest.daily.no_topics": "There have been no active topics in the past day", - "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", - "closing": "Thanks!" + "password-reset-requested": "Reinicio de contraseña solicitado - %1!", + "welcome-to": "Bienvenido a %1", + "greeting_no_name": "Hola", + "greeting_with_name": "Hola %1", + "welcome.text1": "Gracias por registrarte con %1!", + "welcome.text2": "Para activar completamente tu cuenta, necesitamos verificar que la dirección email con la que te registraste te pertenece.", + "welcome.cta": "Cliquea aquí para confirmar tu dirección email.", + "reset.text1": "Recibimos una solicitud para reiniciar tu contraseña, posiblemente porque la olvidaste. Si no es así, por favor ignora este email.", + "reset.text2": "Para continuar con el reinicio de contraseña, por favor cliquea en el siguiente vínculo:", + "reset.cta": "Cliquea aquí para reiniciar tu contraseña", + "digest.notifications": "Tienes algunas notificaciónes de %1 sin leer:", + "digest.latest_topics": "Últimos temas de %1", + "digest.cta": "Cliquea aquí para visitar %1", + "digest.unsub.info": "Este compendio te fue enviado debido a tus ajustes de subscripción.", + "digest.unsub.cta": "Cliquea aquí para alterar estos ajustes", + "digest.daily.no_topics": "No han habido temas activos en el día pasado", + "test.text1": "Este es un email de prueba para verificar que el envío de email está ajustado correctamente para tu NodeBB", + "closing": "¡Gracias!" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index 38ff5515ae..d3fa14fe91 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -12,11 +12,13 @@ "invalid-title": "Título no válido!", "invalid-user-data": "Datos de Usuario no válidos", "invalid-password": "Contraseña no válida", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Valor de paginación no válido.", "username-taken": "Nombre de usuario ya escogido", "email-taken": "El correo electrónico ya está escogido.", "email-not-confirmed": "Tu correo electrónico está sin confirmar, por favor haz click aquí para confirmar tu email.", "username-too-short": "El nombre de usuario es demasiado corto", + "username-too-long": "Username too long", "user-banned": "Usuario expulsado", "no-category": "La categoría no existe", "no-topic": "El tema no existe.", @@ -25,7 +27,7 @@ "no-user": "El usuario no existe", "no-teaser": "El extracto del tema no existe.", "no-privileges": "No tienes los privilegios necesarios para esa acción.", - "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", + "no-emailers-configured": "Ningún plugin para email fue cargado, así que no se pudo enviar email de prueba.", "category-disabled": "Categoría deshabilitada.", "topic-locked": "Tema bloqueado.", "still-uploading": "Por favor, espera a que terminen las subidas.", @@ -52,5 +54,9 @@ "upload-error": "Error de subida: %1", "signature-too-long": "Las firmas no pueden ser más largas de %1 caracteres!", "cant-chat-with-yourself": "No puedes conversar contigo mismo!", - "not-enough-reputation-to-downvote": "No tienes suficiente reputación para votar negativo este post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "No tienes suficiente reputación para votar negativo este post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/es/groups.json b/public/language/es/groups.json index c00c111e11..bb4b46d1f8 100644 --- a/public/language/es/groups.json +++ b/public/language/es/groups.json @@ -1,7 +1,7 @@ { - "view_group": "View Group", - "details.title": "Group Details", - "details.members": "Member List", - "details.has_no_posts": "This group's members have not made any posts.", - "details.latest_posts": "Latest Posts" + "view_group": "Ver Grupo", + "details.title": "Detalles de Grupo", + "details.members": "Lista de Miembros", + "details.has_no_posts": "Los miembros de este grupo no han hecho ninguna publicación.", + "details.latest_posts": "Últimas Publicaciones" } \ No newline at end of file diff --git a/public/language/es/notifications.json b/public/language/es/notifications.json index 3a7168a44d..3be3cccdc8 100644 --- a/public/language/es/notifications.json +++ b/public/language/es/notifications.json @@ -4,17 +4,20 @@ "see_all": "Ver todas las notificaciones", "back_to_home": "Volver a %1", "outgoing_link": "Enlace Externo", - "outgoing_link_message": "You are now leaving %1.", - "continue_to": "Continue to %1", - "return_to": "Return to %1", + "outgoing_link_message": "Ahora estás saliendo %1.", + "continue_to": "Continuar a %1", + "return_to": "Regresar a %1", "new_notification": "Nueva Notificación", "you_have_unread_notifications": "Tienes notificaciones sin leer.", "new_message_from": "Nuevo mensaje de %1", - "upvoted_your_post": "%1 ha marcado como favorita tu respuesta.", - "favourited_your_post": "%1 ha marcado como favorita tu respuesta.", - "user_flagged_post": "%1 ha marcado como indebida una respuesta.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 ha publicado una respuesta a: %2", "user_mentioned_you_in": "%1 te mencionó en %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Correo electrónico confirmado", "email-confirmed-message": "Gracias por validar tu correo electrónico. Tu cuenta ya está completamente activa.", "email-confirm-error": "Un error ocurrió...", diff --git a/public/language/es/pages.json b/public/language/es/pages.json index 4b79bc293f..ab6e4eaedd 100644 --- a/public/language/es/pages.json +++ b/public/language/es/pages.json @@ -5,7 +5,7 @@ "recent": "Temas Recientes", "users": "Usuarios Registrado", "notifications": "Notificaciones", - "tags": "Topics tagged under \"%1\"", + "tags": "Temas etiquetados bajo \"%1\"", "user.edit": "Editando \"%1\"", "user.following": "Gente que sigue %1 ", "user.followers": "Seguidores de %1", diff --git a/public/language/es/recent.json b/public/language/es/recent.json index 8b25e63d46..a187f9d661 100644 --- a/public/language/es/recent.json +++ b/public/language/es/recent.json @@ -4,5 +4,6 @@ "week": "Semana", "month": "Mes", "year": "Año", + "alltime": "All Time", "no_recent_topics": "No hay publicaciones recientes" } \ No newline at end of file diff --git a/public/language/es/search.json b/public/language/es/search.json index d0ffc64f36..8ac377c932 100644 --- a/public/language/es/search.json +++ b/public/language/es/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 resuldado(s) coinciden con \"%2\". (%3 segundos)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/es/topic.json b/public/language/es/topic.json index ae3fa08fc1..1522291a09 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 usuario(s) y %2 invitado(s) más", "more_users": "%1 usuario(s) más", "more_guests": "%1 invitado(s) más", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 y otros %2", "sort_by": "Ordenar por", "oldest_to_newest": "Más antiguo a más nuevo", "newest_to_oldest": "Más nuevo a más antiguo", diff --git a/public/language/es/user.json b/public/language/es/user.json index c020d86931..3d711138d2 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -4,6 +4,8 @@ "username": "Nombre de usuario", "email": "Correo Electrónico", "confirm_email": "Repetir correo electrónico", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nombre completo", "website": "Sitio Web", "location": "Ubicación", @@ -61,5 +63,7 @@ "posts_per_page": "Post por página", "notification_sounds": "Reproducir un sonido al recibir una notificación", "browsing": "Preferencias de navegación.", - "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña?" + "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/et/category.json b/public/language/et/category.json index 1d35948aac..865d8dbae9 100644 --- a/public/language/et/category.json +++ b/public/language/et/category.json @@ -3,5 +3,6 @@ "no_topics": "Kahjuks ei leidu siin kategoorias ühtegi teemat.
    Soovid postitada?", "browsing": "vaatab", "no_replies": "Keegi pole vastanud", - "share_this_category": "Jaga seda kategooriat" + "share_this_category": "Jaga seda kategooriat", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/et/error.json b/public/language/et/error.json index 596a04b4df..459b749ca0 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -12,11 +12,13 @@ "invalid-title": "Vigane pealkiri!", "invalid-user-data": "Vigased kasutaja andmed", "invalid-password": "Vigane parool", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Vigane lehe väärtus", "username-taken": "Kasutajanimi on juba võetud", "email-taken": "Email on võetud", "email-not-confirmed": "Su emaili aadress ei ole kinnitatud, vajuta siia et kinnitada.", "username-too-short": "Kasutajanimi on liiga lühike", + "username-too-long": "Username too long", "user-banned": "Kasutaja bannitud", "no-category": "Kategooriat ei eksisteeri", "no-topic": "Teemat ei eksisteeri", @@ -52,5 +54,9 @@ "upload-error": "Üleslaadimise viga: %1", "signature-too-long": "Allkiri ei saa olla pikem kui %1 tähemärki!", "cant-chat-with-yourself": "Sa ei saa endaga vestelda!", - "not-enough-reputation-to-downvote": "Sul ei ole piisavalt reputatsiooni, et anda negatiivset hinnangut sellele postitusele." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Sul ei ole piisavalt reputatsiooni, et anda negatiivset hinnangut sellele postitusele.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/et/notifications.json b/public/language/et/notifications.json index 4de13a6e0f..8ee69d3072 100644 --- a/public/language/et/notifications.json +++ b/public/language/et/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Uus teade", "you_have_unread_notifications": "Sul ei ole lugemata teateid.", "new_message_from": "Uus sõnum kasutajalt %1", - "upvoted_your_post": "%1 hääletas sinu postituse poolt.", - "favourited_your_post": "%1 märkis sinu postituse lemmikuks.", - "user_flagged_post": "%1 märgistas postituse.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "Kasutaja %1 postitas vastuse teemasse %2", "user_mentioned_you_in": "%1 mainis sind postituses %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Emaili aadress kinnitatud", "email-confirmed-message": "Täname, et kinnitasite oma emaili aadressi. Teie kasutaja omn nüüd täielikult aktiveeritud.", "email-confirm-error": "Süsteemis tekkis viga...", diff --git a/public/language/et/recent.json b/public/language/et/recent.json index 7afd6c41cd..53c112df2d 100644 --- a/public/language/et/recent.json +++ b/public/language/et/recent.json @@ -4,5 +4,6 @@ "week": "Nädal", "month": "Kuu", "year": "Aasta", + "alltime": "All Time", "no_recent_topics": "Hetkel ei ole hiljutisi teemasid." } \ No newline at end of file diff --git a/public/language/et/search.json b/public/language/et/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/et/search.json +++ b/public/language/et/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/et/user.json b/public/language/et/user.json index 7e9a6cc46d..4ecb16dac4 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -4,6 +4,8 @@ "username": "Kasutajanimi", "email": "Email", "confirm_email": "Kinnita email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Täisnimi", "website": "Koduleht", "location": "Asukoht", @@ -61,5 +63,7 @@ "posts_per_page": "Postitusi ühe lehekülje kohta", "notification_sounds": "Tee häält, kui saabub teade.", "browsing": "Sirvimis sätted", - "open_links_in_new_tab": "Ava väljaminevad lingid uues vaheaknas?" + "open_links_in_new_tab": "Ava väljaminevad lingid uues vaheaknas?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/fa_IR/category.json b/public/language/fa_IR/category.json index 1f5299e534..c1c61a1a9f 100644 --- a/public/language/fa_IR/category.json +++ b/public/language/fa_IR/category.json @@ -3,5 +3,6 @@ "no_topics": "هیچ جستاری در این دسته نیست.
    چرا شما یکی نفرستید؟", "browsing": "بیننده‌ها", "no_replies": "هیچ کسی پاسخ نداده است.", - "share_this_category": "به اشتراک‌گذاری این دسته" + "share_this_category": "به اشتراک‌گذاری این دسته", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index cf82fa6622..29caeaa0c1 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -12,11 +12,13 @@ "invalid-title": "عنوان نامعتبر است!", "invalid-user-data": "داده‌های کاربری نامعتبر است.", "invalid-password": "گذرواژه نامعتبر است.", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "عدد صفحه‌بندی نامعتبر است.", "username-taken": "این نام کاربری گرفته شده است.", "email-taken": "این رایانامه گرفته شده است.", "email-not-confirmed": "رایانامه شما تأیید نشده است، لطفاً برای تأیید رایانامه‌تان اینجا را بفشارید.", "username-too-short": "نام کاربری خیلی کوتاه است.", + "username-too-long": "Username too long", "user-banned": "کاربر محروم شد.", "no-category": "چنین دسته‌ای وجود ندارد.", "no-topic": "چنین جستاری وجود ندارد.", @@ -52,5 +54,9 @@ "upload-error": "خطای بارگذاری: %1", "signature-too-long": "امضا نمی‌تواند بیش‌تر از %1 نویسه داشته باشد.", "cant-chat-with-yourself": "شما نمی‌توانید با خودتان گفتگو کنید!", - "not-enough-reputation-to-downvote": "شما اعتبار کافی برای دادن رای منفی به این دیدگاه را ندارید." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "شما اعتبار کافی برای دادن رای منفی به این دیدگاه را ندارید.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/fa_IR/notifications.json b/public/language/fa_IR/notifications.json index a643005e04..62b48b71af 100644 --- a/public/language/fa_IR/notifications.json +++ b/public/language/fa_IR/notifications.json @@ -10,11 +10,14 @@ "new_notification": "آکاه‌سازی تازه", "you_have_unread_notifications": "شما آگاه‌سازی‌های نخوانده دارید.", "new_message_from": "پیام تازه از %1", - "upvoted_your_post": "%1 به دیدگاه شما رای داده است.", - "favourited_your_post": "%1 دیدگاه شما را پسندیده است.", - "user_flagged_post": "پرچم خوردن یک دیدگاه از سوی %1", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "پاسخ دادن به %2 از سوی %1", "user_mentioned_you_in": "%1 در %2 به شما اشاره کرد", + "user_started_following_you": "%1 started following you.", "email-confirmed": "رایانامه تایید شد", "email-confirmed-message": "بابت تایید ایمیلتان سپاس‌گزاریم. حساب کاربری شما اکنون به صورت کامل فعال شده است.", "email-confirm-error": "خطایی پیش آمده است...", diff --git a/public/language/fa_IR/recent.json b/public/language/fa_IR/recent.json index 74a8876ebf..4f6f3256a5 100644 --- a/public/language/fa_IR/recent.json +++ b/public/language/fa_IR/recent.json @@ -4,5 +4,6 @@ "week": "هفته", "month": "ماه", "year": "سال", + "alltime": "All Time", "no_recent_topics": "هیچ جستار تازه‌ای نیست." } \ No newline at end of file diff --git a/public/language/fa_IR/search.json b/public/language/fa_IR/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/fa_IR/search.json +++ b/public/language/fa_IR/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index cfe346d5fd..f0212723e2 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -4,6 +4,8 @@ "username": "نام کاربری", "email": "رایانامه", "confirm_email": "تأیید رایانامه", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "نام کامل", "website": "تارنما", "location": "محل سکونت", @@ -61,5 +63,7 @@ "posts_per_page": "شمار دیدگاه‌ها در هر برگه", "notification_sounds": "پخش صدا هنگامی که شما یک آگاه‌سازی دریافت می‌کنید.", "browsing": "تنظیمات مرور", - "open_links_in_new_tab": "بازکردن لینک‌های خارجی در تب جدید؟" + "open_links_in_new_tab": "بازکردن لینک‌های خارجی در تب جدید؟", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/fi/category.json b/public/language/fi/category.json index 67c3c2125a..f3ae42f69f 100644 --- a/public/language/fi/category.json +++ b/public/language/fi/category.json @@ -3,5 +3,6 @@ "no_topics": "Tällä aihealueella ei ole yhtään aihetta.
    Miksi et aloittaisi uutta?", "browsing": "selaamassa", "no_replies": "Kukaan ei ole vastannut", - "share_this_category": "Jaa tämä kategoria" + "share_this_category": "Jaa tämä kategoria", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index a9c4718431..738eb62699 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -12,11 +12,13 @@ "invalid-title": "Virheellinen otsikko!", "invalid-user-data": "Virheellinen käyttäjätieto", "invalid-password": "Virheellinen salasana", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Virheellinen taittoarvo", "username-taken": "Käyttäjänimi varattu", "email-taken": "Sähköpostiosoite varattu", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Käyttäjänimi on liian lyhyt", + "username-too-long": "Username too long", "user-banned": "Käyttäjä on estetty", "no-category": "Kategoriaa ei ole olemassa", "no-topic": "Aihetta ei ole olemassa", @@ -52,5 +54,9 @@ "upload-error": "Lähetysvirhe: %1", "signature-too-long": "Allekirjoitus ei voi olla pidempi kuin %1 merkkiä!", "cant-chat-with-yourself": "Et voi keskustella itsesi kanssa!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/fi/notifications.json b/public/language/fi/notifications.json index e9507997a0..2eb5163d4a 100644 --- a/public/language/fi/notifications.json +++ b/public/language/fi/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Uusi ilmoitus", "you_have_unread_notifications": "Sinulla on lukemattomia ilmoituksia.", "new_message_from": "Uusi viesti käyttäjältä %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 lisäsi viestisi suosikkeihinsa.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Sähköpostiosoite vahvistettu", "email-confirmed-message": "Kiitos sähköpostiosoitteesi vahvistamisesta. Käyttäjätilisi on nyt täysin aktivoitu.", "email-confirm-error": "Tapahtui virhe...", diff --git a/public/language/fi/recent.json b/public/language/fi/recent.json index fb3bd49dbf..b2bff39da7 100644 --- a/public/language/fi/recent.json +++ b/public/language/fi/recent.json @@ -4,5 +4,6 @@ "week": "Viikko", "month": "Kuukausi", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Ei viimeisimpiä aiheita." } \ No newline at end of file diff --git a/public/language/fi/search.json b/public/language/fi/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/fi/search.json +++ b/public/language/fi/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/fi/user.json b/public/language/fi/user.json index 47b8df6b9c..d008e94b62 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -4,6 +4,8 @@ "username": "Käyttäjän nimi", "email": "Sähköposti", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Koko nimi", "website": "Kotisivu", "location": "Sijainti", @@ -61,5 +63,7 @@ "posts_per_page": "Viestiä per sivu", "notification_sounds": "Soita merkkiääni ilmoituksen saapuessa.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/fr/category.json b/public/language/fr/category.json index d53466e617..c680ae7db9 100644 --- a/public/language/fr/category.json +++ b/public/language/fr/category.json @@ -3,5 +3,6 @@ "no_topics": "Il n'y a aucun sujet dans cette catégorie.
    Pourquoi ne pas en créer un ?", "browsing": "parcouru par", "no_replies": "Personne n'a répondu", - "share_this_category": "Partager cette catégorie" + "share_this_category": "Partager cette catégorie", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 63d9a1765f..821061cee1 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -12,11 +12,13 @@ "invalid-title": "Titre invalide !", "invalid-user-data": "Données utilisateur invalides", "invalid-password": "Mot de passe invalide", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Valeur de pagination invalide", "username-taken": "Nom d’utilisateur déjà utilisé", "email-taken": "Email déjà utilisé", "email-not-confirmed": "Votre adresse email n'est pas confirmée, cliquez ici pour la valider.", "username-too-short": "Nom d'utilisateur trop court", + "username-too-long": "Username too long", "user-banned": "Utilisateur banni", "no-category": "Cette catégorie n'existe pas", "no-topic": "Ce sujet n'existe pas", @@ -52,5 +54,9 @@ "upload-error": "Erreur d'envoi : %1", "signature-too-long": "La signature ne peut dépasser %1 caractères !", "cant-chat-with-yourself": "Vous ne pouvez chatter avec vous même !", - "not-enough-reputation-to-downvote": "Vous n'avez pas une réputation assez élevée pour noter négativement ce message" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Vous n'avez pas une réputation assez élevée pour noter négativement ce message", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/fr/notifications.json b/public/language/fr/notifications.json index 560fb83751..5e1178f78c 100644 --- a/public/language/fr/notifications.json +++ b/public/language/fr/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Nouvelle notification", "you_have_unread_notifications": "Vous avez des notifications non-lues", "new_message_from": "Nouveau message de %1", - "upvoted_your_post": "%1 a voté pour votre message.", - "favourited_your_post": "%1 a mis votre message en favoris.", - "user_flagged_post": "%1 a signalé un message.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 a répondu à : %2", "user_mentioned_you_in": "%1 vous a mentionné dans %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email vérifié", "email-confirmed-message": "Merci pour la validation de votre adresse email. Votre compte est désormais activé.", "email-confirm-error": "Un erreur est survenue ...", diff --git a/public/language/fr/recent.json b/public/language/fr/recent.json index 2e69d07a4e..6af7b3e81a 100644 --- a/public/language/fr/recent.json +++ b/public/language/fr/recent.json @@ -4,5 +4,6 @@ "week": "Semaine", "month": "Mois", "year": "An", + "alltime": "All Time", "no_recent_topics": "Il n'y a aucun sujet récent." } \ No newline at end of file diff --git a/public/language/fr/search.json b/public/language/fr/search.json index ab88db2e52..8974b7f593 100644 --- a/public/language/fr/search.json +++ b/public/language/fr/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 résultat(s) correspondant(s) à \"%2\", (%3 secondes)" + "results_matching": "%1 résultat(s) correspondant(s) à \"%2\", (%3 secondes)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 6ee1f52ce5..68a99a577e 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -4,6 +4,8 @@ "username": "Nom d'utilisateur", "email": "Email", "confirm_email": "Confirmer l'adresse email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nom", "website": "Site web", "location": "Emplacement", @@ -61,5 +63,7 @@ "posts_per_page": "Messages par page", "notification_sounds": "Émettre un son lors de la réception de notifications.", "browsing": "Paramètres de navigation", - "open_links_in_new_tab": "Ouvrir les liens externes dans un nouvel onglet ?" + "open_links_in_new_tab": "Ouvrir les liens externes dans un nouvel onglet ?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/he/category.json b/public/language/he/category.json index 4d32d01bea..bc5e530729 100644 --- a/public/language/he/category.json +++ b/public/language/he/category.json @@ -3,5 +3,6 @@ "no_topics": "קטגוריה זו ריקה מנושאים.
    למה שלא תנסה להוסיף נושא חדש?", "browsing": "צופים בנושא זה כעת", "no_replies": "אין תגובות", - "share_this_category": "שתף קטגוריה זו" + "share_this_category": "שתף קטגוריה זו", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/he/error.json b/public/language/he/error.json index c5cd72a2a0..2df9c1b918 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -12,11 +12,13 @@ "invalid-title": "כותרת שגויה", "invalid-user-data": "מידע משתמש שגוי", "invalid-password": "סיסמא שגויה", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "ערך דפדוף שגוי", "username-taken": "שם משתמש תפוס", "email-taken": "כתובת אימייל תפוסה", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "שם משתמש קצר מדי", + "username-too-long": "Username too long", "user-banned": "המשתמש חסום", "no-category": "קטגוריה אינה קיימת", "no-topic": "נושא אינו קיים", @@ -52,5 +54,9 @@ "upload-error": "שגיאה בהעלאה : %1", "signature-too-long": "חתימה אינה יכולה להיות ארוכה מ %1 תווים!", "cant-chat-with-yourself": "לא ניתן לעשות צ'אט עם עצמך!", - "not-enough-reputation-to-downvote": "אין לך מספיק מוניטין כדי להוריד את הדירוג של פוסט זה" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "אין לך מספיק מוניטין כדי להוריד את הדירוג של פוסט זה", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/he/notifications.json b/public/language/he/notifications.json index bcbb5eac8b..f3f8efa00f 100644 --- a/public/language/he/notifications.json +++ b/public/language/he/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/he/recent.json b/public/language/he/recent.json index 5629c9f23f..0a20c6301a 100644 --- a/public/language/he/recent.json +++ b/public/language/he/recent.json @@ -4,5 +4,6 @@ "week": "שבוע", "month": "חודש", "year": "Year", + "alltime": "All Time", "no_recent_topics": "אין נושאים חדשים" } \ No newline at end of file diff --git a/public/language/he/search.json b/public/language/he/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/he/search.json +++ b/public/language/he/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/he/user.json b/public/language/he/user.json index 1897b49f7d..292d31f90b 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -4,6 +4,8 @@ "username": "שם משתמש", "email": "כתובת אימייל", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "שם מלא", "website": "אתר", "location": "מיקום", @@ -61,5 +63,7 @@ "posts_per_page": "כמות פוסטים בעמוד", "notification_sounds": "השמע צליל כאשר מתקבלת הודעה עבורך.", "browsing": "הגדרות צפייה", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/hu/category.json b/public/language/hu/category.json index f6f1002ad9..f0eb8a400b 100644 --- a/public/language/hu/category.json +++ b/public/language/hu/category.json @@ -3,5 +3,6 @@ "no_topics": "Még nincs nyitva egy téma sem ebben a kategóriában.Miért nem hozol létre egyet?", "browsing": "jelenlévők", "no_replies": "Senki sem válaszolt még", - "share_this_category": "Hozzászólás megosztása" + "share_this_category": "Hozzászólás megosztása", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/hu/notifications.json b/public/language/hu/notifications.json index c189a4e5bb..86ce42f88a 100644 --- a/public/language/hu/notifications.json +++ b/public/language/hu/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/hu/recent.json b/public/language/hu/recent.json index 332aaf1de2..b595ffd958 100644 --- a/public/language/hu/recent.json +++ b/public/language/hu/recent.json @@ -4,5 +4,6 @@ "week": "Hét", "month": "Hónap", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Nincs friss topik." } \ No newline at end of file diff --git a/public/language/hu/search.json b/public/language/hu/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/hu/search.json +++ b/public/language/hu/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 9e371cc20b..909e4ad553 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -4,6 +4,8 @@ "username": "Felhasználónév", "email": "E-mail", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Teljes Név", "website": "Weboldal", "location": "Lakhely", @@ -61,5 +63,7 @@ "posts_per_page": "Hozzászólás oldalanként", "notification_sounds": "Hang lejátszása ha értesítés érkezett.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/it/category.json b/public/language/it/category.json index 4d813335c3..c1f4ace222 100644 --- a/public/language/it/category.json +++ b/public/language/it/category.json @@ -3,5 +3,6 @@ "no_topics": "Non ci sono discussioni in questa categoria.
    Perché non ne inizi una?", "browsing": "visualizzando", "no_replies": "Nessuno ha risposto", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/it/error.json b/public/language/it/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json index 1d2e122b74..957afe9eec 100644 --- a/public/language/it/notifications.json +++ b/public/language/it/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Nuove Notifiche", "you_have_unread_notifications": "Hai notifiche non lette.", "new_message_from": "Nuovo messaggio da %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/it/recent.json b/public/language/it/recent.json index 56a1191bc0..2047204baf 100644 --- a/public/language/it/recent.json +++ b/public/language/it/recent.json @@ -4,5 +4,6 @@ "week": "Settimana", "month": "Mese", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Non ci sono discussioni recenti." } \ No newline at end of file diff --git a/public/language/it/search.json b/public/language/it/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/it/search.json +++ b/public/language/it/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/it/user.json b/public/language/it/user.json index 7f437d56c5..d490e3e1ef 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -4,6 +4,8 @@ "username": "Nome Utente", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nome e Cognome", "website": "Sito Internet", "location": "Località", @@ -61,5 +63,7 @@ "posts_per_page": "Post per Pagina", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/ja/category.json b/public/language/ja/category.json index 43bc8f22d5..c5ffc01c13 100644 --- a/public/language/ja/category.json +++ b/public/language/ja/category.json @@ -3,5 +3,6 @@ "no_topics": "まだスレッドはありません.
    一番目のスレッドを書いてみないか?", "browsing": "閲覧中", "no_replies": "返事はまだありません", - "share_this_category": "この板を共有" + "share_this_category": "この板を共有", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index cbaf323360..3d88578169 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "無効なユーザーデータ", "invalid-password": "無効なパスワード", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "無効な改ページ設定値", "username-taken": "ユーザー名が取られた", "email-taken": "メールアドレスが使用された", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "ユーザーが停止された", "no-category": "板が存在しない", "no-topic": "スレッドが存在しない", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "署名は最大%1文字までです!", "cant-chat-with-yourself": "自分にチャットすることはできません!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ja/notifications.json b/public/language/ja/notifications.json index efe8333fac..9823deff19 100644 --- a/public/language/ja/notifications.json +++ b/public/language/ja/notifications.json @@ -10,11 +10,14 @@ "new_notification": "新しい通知", "you_have_unread_notifications": "未読の通知があります。", "new_message_from": "%1からの新しいメッセージ", - "upvoted_your_post": "%1はあなたのポストを評価しました。", - "favourited_your_post": "%1はあなたのポストをお気に入りにしました。", - "user_flagged_post": "%1 ポストを報告しました。", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1%2 への返事を作成しました。", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/ja/recent.json b/public/language/ja/recent.json index 4ca75a039d..35cdd2b23f 100644 --- a/public/language/ja/recent.json +++ b/public/language/ja/recent.json @@ -4,5 +4,6 @@ "week": "最近 1 週", "month": "最近 1 ヶ月", "year": "Year", + "alltime": "All Time", "no_recent_topics": "最近のスレッドはありません。" } \ No newline at end of file diff --git a/public/language/ja/search.json b/public/language/ja/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/ja/search.json +++ b/public/language/ja/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ja/user.json b/public/language/ja/user.json index f37a215849..ddb43c4dd5 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -4,6 +4,8 @@ "username": "ユーザー名", "email": "メール", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "フルネーム", "website": "ウェブサイト", "location": "ロケーション", @@ -61,5 +63,7 @@ "posts_per_page": "ページ毎のポスト数", "notification_sounds": "通知が来たとき音を流す", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/ko/category.json b/public/language/ko/category.json index 5437b07304..0ce8558aa9 100644 --- a/public/language/ko/category.json +++ b/public/language/ko/category.json @@ -3,5 +3,6 @@ "no_topics": "이 카테고리에는 생성된 주제가 없습니다.
    먼저 주제를 생성해 보세요.", "browsing": "이 주제를 읽고 있는 사용자", "no_replies": "답글이 없습니다.", - "share_this_category": "이 카테고리를 공유" + "share_this_category": "이 카테고리를 공유", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index fa961bc709..fd74542ee3 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -12,11 +12,13 @@ "invalid-title": "올바르지 않은 제목입니다.", "invalid-user-data": "올바르지 않은 사용자 정보입니다.", "invalid-password": "올바르지 않은 비밀번호입니다.", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "올바르지 않은 페이지입니다.", "username-taken": "이미 사용 중인 사용자 이름입니다.", "email-taken": "이미 사용 중인 이메일입니다.", "email-not-confirmed": "아직 이메일이 인증되지 않았습니다. 여기를 누르면 인증 메일을 발송할 수 있습니다.", "username-too-short": "사용자 이름이 너무 짧습니다.", + "username-too-long": "Username too long", "user-banned": "차단된 사용자입니다.", "no-category": "존재하지 않는 카테고리입니다.", "no-topic": "존재하지 않는 주제입니다.", @@ -52,5 +54,9 @@ "upload-error": "업로드 오류가 발생했습니다. : %1", "signature-too-long": "서명은 최대 %1자로 제한됩니다.", "cant-chat-with-yourself": "자신과는 채팅할 수 없습니다.", - "not-enough-reputation-to-downvote": "인기도가 낮아 이 게시물에 반대할 수 없습니다." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "인기도가 낮아 이 게시물에 반대할 수 없습니다.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json index 8ececb6c49..90d004d7e0 100644 --- a/public/language/ko/notifications.json +++ b/public/language/ko/notifications.json @@ -10,11 +10,14 @@ "new_notification": "새 알림", "you_have_unread_notifications": "읽지 않은 알림이 있습니다.", "new_message_from": "%1님이 메시지를 보냈습니다.", - "upvoted_your_post": "%1님이 내 게시물을 추천했습니다.", - "favourited_your_post": "%1님이 내 게시물을 관심글로 등록했습니다.", - "user_flagged_post": "%1님이 게시물을 신고했습니다.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1님이 %2님의 게시물에 답글을 작성했습니다.", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/ko/recent.json b/public/language/ko/recent.json index 6190dda700..5e3f728004 100644 --- a/public/language/ko/recent.json +++ b/public/language/ko/recent.json @@ -4,5 +4,6 @@ "week": "주간", "month": "월간", "year": "Year", + "alltime": "All Time", "no_recent_topics": "최근 생성된 주제가 없습니다." } \ No newline at end of file diff --git a/public/language/ko/search.json b/public/language/ko/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/ko/search.json +++ b/public/language/ko/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ko/user.json b/public/language/ko/user.json index baf5183211..e3b7b29e83 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -4,6 +4,8 @@ "username": "사용자 이름", "email": "이메일", "confirm_email": "이메일 확인", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "이름", "website": "웹 사이트", "location": "거주지", @@ -61,5 +63,7 @@ "posts_per_page": "페이지 당 게시물 수", "notification_sounds": "알림을 수신하면 알림음을 재생", "browsing": "페이지 열기", - "open_links_in_new_tab": "외부 링크를 새 탭에서 열기" + "open_links_in_new_tab": "외부 링크를 새 탭에서 열기", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/lt/category.json b/public/language/lt/category.json index ba84df3cf4..1c9dd0899b 100644 --- a/public/language/lt/category.json +++ b/public/language/lt/category.json @@ -3,5 +3,6 @@ "no_topics": "Šioje kategorijoje temų nėra.
    Kodėl gi jums nesukūrus naujos?", "browsing": "naršo", "no_replies": "Niekas dar neatsakė", - "share_this_category": "Pasidalinti šią kategoriją" + "share_this_category": "Pasidalinti šią kategoriją", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index ca771bc828..f41fc62c24 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -12,11 +12,13 @@ "invalid-title": "Neteisingas pavadinimas!", "invalid-user-data": "Klaidingi vartotojo duomenys", "invalid-password": "Klaidingas slaptažodis", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Klaidinga puslapiavimo reikšmė", "username-taken": "Vartotojo vardas jau užimtas", "email-taken": "El. pašto adresas jau užimtas", "email-not-confirmed": "Jūsų el. paštas nepatvirtintas, prašome paspausti čia norint jį patvirtinti.", "username-too-short": "Slapyvardis per trumpas", + "username-too-long": "Username too long", "user-banned": "Vartotojas užblokuotas", "no-category": "Kategorija neegzistuoja", "no-topic": "Tema neegzistuoja", @@ -52,5 +54,9 @@ "upload-error": "Įkėlimo klaida: %1", "signature-too-long": "Parašas negali būti ilgesnis negu %1 simboliai!", "cant-chat-with-yourself": "Jūs negalite susirašinėti su savimi!", - "not-enough-reputation-to-downvote": "Jūs neturite pakankamai reputacijos balsuoti prieš šį pranešimą" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Jūs neturite pakankamai reputacijos balsuoti prieš šį pranešimą", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/lt/notifications.json b/public/language/lt/notifications.json index 96ffad091b..864652cb57 100644 --- a/public/language/lt/notifications.json +++ b/public/language/lt/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Naujas pranešimas", "you_have_unread_notifications": "Jūs turite neperskaitytų pranešimų.", "new_message_from": "Nauja žinutė nuo %1", - "upvoted_your_post": "%1 teigiamai įvertino jūsų pranešimą.", - "favourited_your_post": "%1 pamėgo jūsų pranešimą.", - "user_flagged_post": "%1 pažymėjo pranešimą moderavimui.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 parašė atsaką %2", "user_mentioned_you_in": "%1 paminėjo Jus %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "El. paštas patvirtintas", "email-confirmed-message": "Dėkojame už el. pašto patvirtinimą. Jūsų paskyra pilnai aktyvuota.", "email-confirm-error": "Įvyko klaida...", diff --git a/public/language/lt/recent.json b/public/language/lt/recent.json index 5be1ef3ff5..0ccdef0f8c 100644 --- a/public/language/lt/recent.json +++ b/public/language/lt/recent.json @@ -4,5 +4,6 @@ "week": "Savaitė", "month": "Mėnesis", "year": "Metai", + "alltime": "All Time", "no_recent_topics": "Paskutinių temų nėra" } \ No newline at end of file diff --git a/public/language/lt/search.json b/public/language/lt/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/lt/search.json +++ b/public/language/lt/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 7c811b34a4..1902cdd399 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -4,6 +4,8 @@ "username": "Vartotojo vardas", "email": "El. paštas", "confirm_email": "Patvirtinti el. paštą", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Vardas ir pavardė", "website": "Tinklalapis", "location": "Vieta", @@ -61,5 +63,7 @@ "posts_per_page": "Pranešimų puslapyje", "notification_sounds": "Groti garsą kai gaunate pranešimą.", "browsing": "Naršymo nustatymai", - "open_links_in_new_tab": "Atidaryti nuorodas naujame skirtuke?" + "open_links_in_new_tab": "Atidaryti nuorodas naujame skirtuke?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/ms/category.json b/public/language/ms/category.json index 755fb94858..db2dc8d039 100644 --- a/public/language/ms/category.json +++ b/public/language/ms/category.json @@ -3,5 +3,6 @@ "no_topics": "Tiada topik dalam kategori ini.
    Cuba menghantar topik yang baru?", "browsing": "melihat", "no_replies": "Tiada jawapan", - "share_this_category": "Kongsi kategori ini" + "share_this_category": "Kongsi kategori ini", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index c76dc586f1..6c9f213d94 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Password salah!", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Nama pengunna terlalu pendek", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ms/notifications.json b/public/language/ms/notifications.json index 530c9d1f93..02cc302284 100644 --- a/public/language/ms/notifications.json +++ b/public/language/ms/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Pemberitahuan baru", "you_have_unread_notifications": "Anda ada pemberitahuan yang belum dibaca", "new_message_from": "Pesanan baru daripada %1", - "upvoted_your_post": "%1 telah undi-naik posting anda", - "favourited_your_post": "strong>%1
    telah menggemari posting anda", - "user_flagged_post": "%1 menandakan posting", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 telah membalas posting kepada: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/ms/recent.json b/public/language/ms/recent.json index 8342257702..bface6de72 100644 --- a/public/language/ms/recent.json +++ b/public/language/ms/recent.json @@ -4,5 +4,6 @@ "week": "Minggu", "month": "Bulan", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Tiada topik terkini" } \ No newline at end of file diff --git a/public/language/ms/search.json b/public/language/ms/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/ms/search.json +++ b/public/language/ms/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ms/user.json b/public/language/ms/user.json index f560bec8d5..74fffdbd04 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -4,6 +4,8 @@ "username": "Nama pengguna", "email": "Emel", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nama penuh", "website": "Laman Web", "location": "Lokasi", @@ -61,5 +63,7 @@ "posts_per_page": "Kiriman setiap muka", "notification_sounds": "Mainkan muzik apabila anda menerima maklumbalas", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/nb/category.json b/public/language/nb/category.json index 14ab70de34..af2119d658 100644 --- a/public/language/nb/category.json +++ b/public/language/nb/category.json @@ -3,5 +3,6 @@ "no_topics": "Det er ingen emner i denne kategorien
    Hvorfor ikke lage ett?", "browsing": "leser", "no_replies": "Ingen har svart", - "share_this_category": "Del denne kategorien" + "share_this_category": "Del denne kategorien", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json index 96afebd800..2ffabea7c1 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Nytt varsel", "you_have_unread_notifications": "Du har uleste varsler.", "new_message_from": "Ny melding fra %1", - "upvoted_your_post": "%1 har stemt opp ditt innlegg.", - "favourited_your_post": "%1 har favorittmerket ditt innlegg.", - "user_flagged_post": "%1 flagget ett innlegg.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 har skrevet et svar til: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/nb/recent.json b/public/language/nb/recent.json index 608c10c8b6..d3b44fc0f4 100644 --- a/public/language/nb/recent.json +++ b/public/language/nb/recent.json @@ -4,5 +4,6 @@ "week": "Uke", "month": "Måned", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Det er ingen nye tråder." } \ No newline at end of file diff --git a/public/language/nb/search.json b/public/language/nb/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/nb/search.json +++ b/public/language/nb/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/nb/user.json b/public/language/nb/user.json index c19894c3f3..5f3f3db9ab 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -4,6 +4,8 @@ "username": "Brukernavn", "email": "E-post", "confirm_email": "Bekfreft e-post", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Fullt navn", "website": "Nettsted", "location": "Plassering", @@ -61,5 +63,7 @@ "posts_per_page": "Innlegg per side", "notification_sounds": "Spill av en lyd når du mottar ett varsel.", "browsing": "Surfeinnstillinger", - "open_links_in_new_tab": "Åpne utgående linker i en ny fane?" + "open_links_in_new_tab": "Åpne utgående linker i en ny fane?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/nl/category.json b/public/language/nl/category.json index 59ca9e6530..7c6651aa9a 100644 --- a/public/language/nl/category.json +++ b/public/language/nl/category.json @@ -3,5 +3,6 @@ "no_topics": "Er zijn geen onderwerpen in deze categorie.
    Waarom maak je er niet een aan?", "browsing": "verkennen", "no_replies": "Niemand heeft gereageerd", - "share_this_category": "Deel deze categorie" + "share_this_category": "Deel deze categorie", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/nl/email.json b/public/language/nl/email.json index a4bd403e2a..e662dce397 100644 --- a/public/language/nl/email.json +++ b/public/language/nl/email.json @@ -14,7 +14,7 @@ "digest.cta": "Klik hier om deze website te bezoeken %1 ", "digest.unsub.info": "Deze overzicht was verzonden naar jou vanwege je abbonement instellingen", "digest.unsub.cta": "Klik hier om u instellingen te wijzigen", - "digest.daily.no_topics": "Er zijn geen actieve topics vandaag", + "digest.daily.no_topics": "Er zijn geen actieve onderwerpen vandaag", "test.text1": "Dit is een test email om te verifiëren dat de email service correct is opgezet voor jou NodeBB", "closing": "Bedankt!" } \ No newline at end of file diff --git a/public/language/nl/error.json b/public/language/nl/error.json index b6a85567d1..173db65af9 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -9,14 +9,16 @@ "invalid-uid": "Ongeldig Gebruikers ID", "invalid-username": "Ongeldig Gebruikersnaam", "invalid-email": "Ongeldig Email Adres", - "invalid-title": "Ongeldig Titel!", + "invalid-title": "Ongeldige Titel!", "invalid-user-data": "Ongeldig Gebruikersdata", "invalid-password": "Ongeldig wachtwoord", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Ongeldig pagineringswaarde", "username-taken": "Gebruikersnaam is al bezet", "email-taken": "Email adres is al gebruikt", "email-not-confirmed": "U email adres is niet bevestigd, Klik hier om uw email adres te bevestigen", "username-too-short": "Gebruikersnaam is te kort", + "username-too-long": "Username too long", "user-banned": "Gebruiker verbannen", "no-category": "Categorie bestaat niet", "no-topic": "Onderwerp bestaat niet", @@ -52,5 +54,9 @@ "upload-error": "Upload Fout : %1", "signature-too-long": "Deze handtekening kan niet groter zijn dan %1 karakters!", "cant-chat-with-yourself": "Je kan niet met jezelf chatten!", - "not-enough-reputation-to-downvote": "U heeft niet de benodigde reputatie om dit bericht te downvoten" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "U heeft niet de benodigde reputatie om dit bericht te downvoten", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 94f5da88ba..446c5d35bd 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -50,16 +50,16 @@ "read_more": "Lees meer", "posted_ago_by_guest": "geplaatst %1 door gast", "posted_ago_by": "geplaatst %1 door %2", - "posted_ago": "geplaatst %1", - "posted_in_ago_by_guest": "geplaatst in % 1 %2 door gast", + "posted_ago": "geplaatst door %1", + "posted_in_ago_by_guest": "geplaatst in %1 %2 door gast", "posted_in_ago_by": "geplaatst in %1 %2 door %3", "posted_in_ago": "geplaatst in %1 %2", "replied_ago": "gereageerd %1", - "user_posted_ago": "%1 geplaatst %2", + "user_posted_ago": "%1 plaatste %2", "guest_posted_ago": "Gast plaatste %1", "last_edited_by_ago": "voor het laatst aangepast door %1 %2", "norecentposts": "Geen Recente Berichten", - "norecenttopics": "Geen Recente Topics", + "norecenttopics": "Geen Recente Onderwerpen", "recentposts": "Recente Berichten", "recentips": "Recente Ingelogde IPs", "away": "Afwezig", diff --git a/public/language/nl/login.json b/public/language/nl/login.json index ea7a6e3bcd..fa8f5d55d7 100644 --- a/public/language/nl/login.json +++ b/public/language/nl/login.json @@ -2,7 +2,7 @@ "username": "Gebruikersnaam / Emailadres", "remember_me": "Mij Onthouden?", "forgot_password": "Wachtwoord Vergeten?", - "alternative_logins": "Alternatieve Logins", + "alternative_logins": "Alternatieve Loginmethodes", "failed_login_attempt": "Mislukte inlog poging, probeer het later opnieuw.", "login_successful": "Je bent succesvol ingelogd!", "dont_have_account": "Heeft u nog geen account?" diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index 6758b7959f..6559d5df3e 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -10,11 +10,14 @@ "new_notification": "een nieuwe notificatie", "you_have_unread_notifications": "U heeft ongelezen notificaties", "new_message_from": "Nieuw bericht van %1", - "upvoted_your_post": "%1 heeft uw bericht geupvote", - "favourited_your_post": "%1 heeft uw bericht aan zijn favorieten toegevoegd", - "user_flagged_post": "%1 rapporteert uw bericht", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 heeft een reactie op het bericht gegeven aan %2", "user_mentioned_you_in": "%1 heeft u genoemd in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email adres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van uw email adres. Uw account is nu volledig actief.", "email-confirm-error": "Een fout vond plaats", diff --git a/public/language/nl/recent.json b/public/language/nl/recent.json index 2bfc4af744..f111931b98 100644 --- a/public/language/nl/recent.json +++ b/public/language/nl/recent.json @@ -4,5 +4,6 @@ "week": "Week", "month": "Maand", "year": "Jaar", + "alltime": "All Time", "no_recent_topics": "Er zijn geen recente reacties." } \ No newline at end of file diff --git a/public/language/nl/search.json b/public/language/nl/search.json index b7035add16..7b1aab07ca 100644 --- a/public/language/nl/search.json +++ b/public/language/nl/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 resulta(a)ten was een match \"%2\", (%3 seconds)" + "results_matching": "%1 resulta(a)ten was een match \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index 800d6348ca..9a9d1a2f81 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -17,7 +17,7 @@ "purge": "weggooien", "restore": "Herstellen", "move": "Verplaatsen", - "fork": "Fork", + "fork": "Afsplitsen", "banned": "verbannen", "link": "Link", "share": "Delen", @@ -36,7 +36,7 @@ "watch": "Volgen", "watch.title": "Krijg notificaties van nieuwe reacties op dit onderwerp", "share_this_post": "Deel dit Bericht", - "thread_tools.title": "Thread Gereedschap", + "thread_tools.title": "Acties", "thread_tools.markAsUnreadForAll": "Ongelezen Markeren", "thread_tools.pin": "Onderwerp Vastmaken", "thread_tools.unpin": "Onderwerp Losmaken", @@ -44,13 +44,13 @@ "thread_tools.unlock": "Onderwerp Openen", "thread_tools.move": "Onderwerp Verplaatsen", "thread_tools.move_all": "Verplaats alles", - "thread_tools.fork": "Onderwerp Forken", + "thread_tools.fork": "Onderwerp Afsplitsen", "thread_tools.delete": "Onderwerp Verwijderen", "thread_tools.delete_confirm": "Weet u het zeker dat u dit onderwerp wilt verwijderen?", "thread_tools.restore": "Onderwerp Herstellen", "thread_tools.restore_confirm": "Weet u het zeker dat u het onderwerp wilt herstellen?", - "thread_tools.purge": "Gooi onderwerp weg", - "thread_tools.purge_confirm": "Weet u het zeker dat u dit onderwerp wilt weggooien?", + "thread_tools.purge": "Wis Onderwerp ", + "thread_tools.purge_confirm": "Weet u het zeker dat u dit onderwerp wilt wissen?", "topic_move_success": "Deze onderwerp is succesvol verplaatst naar %1", "post_delete_confirm": "Weet u het zeker dat u dit bericht wilt verwijderen?", "post_restore_confirm": "Weet u het zeker dat u dit bericht wilt herstellen?", @@ -58,7 +58,7 @@ "load_categories": "Categorieën Laden", "disabled_categories_note": "Uitgeschakelde Categorieën zijn grijs", "confirm_move": "Verplaatsen", - "confirm_fork": "Fork", + "confirm_fork": "Splits", "favourite": "Favoriet", "favourites": "Favorieten", "favourites.has_no_favourites": "Je hebt geen favorieten, sla een aantal berichten op als favoriet om ze hier te zien!", @@ -67,11 +67,11 @@ "move_topics": "Verplaats onderwerpen", "move_post": "Bericht Verplaatsen", "post_moved": "Bericht verplaatst!", - "fork_topic": "Onderwerp Forken", + "fork_topic": "Afgesplitste Onderwerp ", "topic_will_be_moved_to": "Dit onderwerp zal verplaatst worden naar de categorie", "fork_topic_instruction": "Klik op de berichten die je wilt forken", "fork_no_pids": "Geen berichten geselecteerd!", - "fork_success": "Onderwerp is met succes geforkt!", + "fork_success": "Onderwerp is met succes gesplitst!", "composer.title_placeholder": "Vul de titel voor het onderwerp hier in...", "composer.discard": "Annuleren", "composer.submit": "Opslaan", @@ -84,9 +84,9 @@ "composer.thumb_file_label": "Of upload een bestand", "composer.thumb_remove": "Velden leegmaken", "composer.drag_and_drop_images": "Sleep en Zet Afbeeldingen Hier", - "more_users_and_guests": "%1 meer gebruiker(s) en %2 gast(en)", + "more_users_and_guests": "%1 of meerdere gebruiker(s) en %2 gast(en)", "more_users": "%1 meer gebruiker(s)", - "more_guests": "%1 meer gast(en)", + "more_guests": "%1 of meerdere gast(en)", "users_and_others": "%1 en %2 anderen", "sort_by": "gesorteerd op", "oldest_to_newest": "Oud naar Nieuw", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index 37103e7644..df0f112c6f 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -4,6 +4,8 @@ "username": "Gebruikersnaam", "email": "Email", "confirm_email": "Bevestig uw email adres", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Volledige Naam", "website": "Website", "location": "Locatie", @@ -61,5 +63,7 @@ "posts_per_page": "Berichten per Pagina", "notification_sounds": "Speel een geluid af wanneer ik een notificatie ontvang.", "browsing": "Zoek Instellingen", - "open_links_in_new_tab": "Open de uitgaande links in een nieuw tabblad?" + "open_links_in_new_tab": "Open de uitgaande links in een nieuw tabblad?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/pl/category.json b/public/language/pl/category.json index 8e6707e13d..997aae19ae 100644 --- a/public/language/pl/category.json +++ b/public/language/pl/category.json @@ -3,5 +3,6 @@ "no_topics": "W tej kategorii nie ma jeszcze żadnych wątków.
    Dlaczego ty nie utworzysz jakiegoś?", "browsing": "przegląda", "no_replies": "Nikt jeszcze nie odpowiedział", - "share_this_category": "Udostępnij tę kategorię" + "share_this_category": "Udostępnij tę kategorię", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/pl/email.json b/public/language/pl/email.json index 98e591ab02..2e5df85e6a 100644 --- a/public/language/pl/email.json +++ b/public/language/pl/email.json @@ -1,20 +1,20 @@ { - "password-reset-requested": "Password Reset Requested - %1!", - "welcome-to": "Welcome to %1", - "greeting_no_name": "Hello", - "greeting_with_name": "Hello %1", - "welcome.text1": "Thank you for registering with %1!", - "welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.", - "welcome.cta": "Click here to confirm your email address", - "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.", - "reset.text2": "To continue with the password reset, please click on the following link:", - "reset.cta": "Click here to reset your password", - "digest.notifications": "You have some unread notifications from %1:", - "digest.latest_topics": "Latest topics from %1", - "digest.cta": "Click here to visit %1", - "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", - "digest.daily.no_topics": "There have been no active topics in the past day", - "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", - "closing": "Thanks!" + "password-reset-requested": "Wybrano przywrócenie hasła - %1", + "welcome-to": "Witaj w %1", + "greeting_no_name": "Witaj", + "greeting_with_name": "Witaj %1", + "welcome.text1": "Dziękujemy za rejestrację w %1", + "welcome.text2": "Aby aktywować swoje konto, musisz potwierdzić, że skorzystałeś z własnego adresu e-mail.", + "welcome.cta": "Kliknij tutaj, by potwierdzić swój adres", + "reset.text1": "Otrzymaliśmy żądanie przywrócenia Twojego hasła. Jeśli nie żądałeś przywrócenia hasła, zignoruj ten e-mail.", + "reset.text2": "Aby przywrócić swoje hasło, skorzystaj z poniższego linku:", + "reset.cta": "Kliknij tu, by przywrócić swoje hasło", + "digest.notifications": "Masz nieprzeczytane powiadomienia z %1:", + "digest.latest_topics": "Ostatnie tematy z %1", + "digest.cta": "Kliknij, by odwiedzić %1", + "digest.unsub.info": "To podsumowanie zostało wysłane zgodnie z Twoimi ustawieniami.", + "digest.unsub.cta": "Kliknij tutaj, aby je zmienić", + "digest.daily.no_topics": "Wczoraj nie było żadnych aktywnych tematów", + "test.text1": "To jest e-mail testowy, aby sprawdzić, czy poprawnie skonfigurowałeś e-mailer w swoim NodeBB.", + "closing": "Dziękujemy!" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index e0df9469bb..75fd7e8daa 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -12,11 +12,13 @@ "invalid-title": "Błędny tytuł.", "invalid-user-data": "Błędne dane użytkownika.", "invalid-password": "Błędne hasło", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Błędna wartość paginacji", "username-taken": "Login zajęty.", "email-taken": "E-mail zajęty.", "email-not-confirmed": "Twój email nie jest potwierdzony, kliknij tutaj aby go potwierdzić.", "username-too-short": "Nazwa użytkownika za krótka.", + "username-too-long": "Username too long", "user-banned": "Użytkownik zbanowany", "no-category": "Kategoria nie istnieje.", "no-topic": "Temat nie istnieje", @@ -25,7 +27,7 @@ "no-user": "Użytkownik nie istnieje", "no-teaser": "Podgląd nie istnieje", "no-privileges": "Nie masz wystarczających praw by to wykonać.", - "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", + "no-emailers-configured": "Nie zainstalowano żadnego dodatku obsługującego e-mail, więc nie można wysłać testowej wiadomości.", "category-disabled": "Kategoria wyłączona.", "topic-locked": "Temat zamknięty", "still-uploading": "Poczekaj na pełne załadowanie", @@ -52,5 +54,9 @@ "upload-error": "Błąd uploadu: %1", "signature-too-long": "Sygnatura nie może mieć więcej niż %1 znaków.", "cant-chat-with-yourself": "Nie możesz czatować ze sobą", - "not-enough-reputation-to-downvote": "Masz za mało reputacji by ocenić ten post." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Masz za mało reputacji by ocenić ten post.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/pl/global.json b/public/language/pl/global.json index 84ac6226d5..6a4b6c40b2 100644 --- a/public/language/pl/global.json +++ b/public/language/pl/global.json @@ -18,7 +18,7 @@ "save_changes": "Zapisz zmiany", "close": "Zamknij", "pagination": "Numerowanie stron", - "pagination.out_of": "%1 poza %2", + "pagination.out_of": "%1 z %2", "pagination.enter_index": "Wpisz indeks.", "header.admin": "Administracja", "header.recent": "Ostatnie", diff --git a/public/language/pl/groups.json b/public/language/pl/groups.json index c00c111e11..ecce894b28 100644 --- a/public/language/pl/groups.json +++ b/public/language/pl/groups.json @@ -1,7 +1,7 @@ { - "view_group": "View Group", - "details.title": "Group Details", - "details.members": "Member List", - "details.has_no_posts": "This group's members have not made any posts.", - "details.latest_posts": "Latest Posts" + "view_group": "Obejrzyj grupę", + "details.title": "Szczegóły grupy", + "details.members": "Lista członków", + "details.has_no_posts": "Członkowie tej grupy nie napisali żadnych postów.", + "details.latest_posts": "Ostatnie posty" } \ No newline at end of file diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index e63c958dc9..154ae27501 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -14,5 +14,5 @@ "chat.maximize": "Maksymalizuj", "composer.user_said_in": "%1 powiedział w %2:", "composer.user_said": "%1 powiedział:", - "composer.discard": "Na pewno chcesz ignorować ten post?" + "composer.discard": "Na pewno chcesz porzucić ten post?" } \ No newline at end of file diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json index 493b232147..c715ae9567 100644 --- a/public/language/pl/notifications.json +++ b/public/language/pl/notifications.json @@ -2,19 +2,22 @@ "title": "Powiadomienia", "no_notifs": "Nie masz nowych powiadomień", "see_all": "Zobacz wszystkie powiadomienia", - "back_to_home": "Back to %1", + "back_to_home": "Wróć do %1", "outgoing_link": "Łącze wychodzące", - "outgoing_link_message": "You are now leaving %1.", + "outgoing_link_message": "Opuszczasz %1.", "continue_to": "Continue to %1", "return_to": "Return to %1", "new_notification": "Nowe powiadomienie", "you_have_unread_notifications": "Masz nieprzeczytane powiadomienia.", "new_message_from": "Nowa wiadomość od %1", - "upvoted_your_post": "%1
    zagłosował na Twój post", - "favourited_your_post": "%1 polubił/a Twój post.", - "user_flagged_post": "%1 oznaczył/a Twój post", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 dodał odpowiedź do %2", - "user_mentioned_you_in": "%1 mentioned you in %2", + "user_mentioned_you_in": "%1 wspomniał cię w %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "E-mail potwierdzony", "email-confirmed-message": "Dziękujemy za potwierdzenie maila. Twoje konto zostało aktywowane.", "email-confirm-error": "Wystąpił błąd.", diff --git a/public/language/pl/pages.json b/public/language/pl/pages.json index fb43e50c1a..02e459b55a 100644 --- a/public/language/pl/pages.json +++ b/public/language/pl/pages.json @@ -5,7 +5,7 @@ "recent": "Ostatnie wątki", "users": "Zarejestrowani użytkownicy", "notifications": "Powiadomienia", - "tags": "Topics tagged under \"%1\"", + "tags": "Tematy oznaczone \"%1\"", "user.edit": "Edytowanie \"%1\"", "user.following": "Obserwowani przez %1", "user.followers": "Obserwujący %1", diff --git a/public/language/pl/recent.json b/public/language/pl/recent.json index 74050bc028..8f92e35c6a 100644 --- a/public/language/pl/recent.json +++ b/public/language/pl/recent.json @@ -3,6 +3,7 @@ "day": "Dzień", "week": "Tydzień", "month": "Miesiąc", - "year": "Year", + "year": "Rok", + "alltime": "All Time", "no_recent_topics": "Brak ostatnich wątków." } \ No newline at end of file diff --git a/public/language/pl/search.json b/public/language/pl/search.json index d0ffc64f36..2a9252fdab 100644 --- a/public/language/pl/search.json +++ b/public/language/pl/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 wyników pasujących do \"%2\", (%3 sekund)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/pl/tags.json b/public/language/pl/tags.json index f065d4bbfa..d4b35e3b39 100644 --- a/public/language/pl/tags.json +++ b/public/language/pl/tags.json @@ -1,6 +1,6 @@ { - "no_tag_topics": "There are no topics with this tag.", - "tags": "Tags", - "enter_tags_here": "Enter tags here. Press enter after each tag.", - "no_tags": "There are no tags yet." + "no_tag_topics": "Nie ma tematów z tym tagiem", + "tags": "Tagi", + "enter_tags_here": "Tutaj wpisz tagi. Naciśnij enter po każdym.", + "no_tags": "Jeszcze nie ma tagów." } \ No newline at end of file diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index bcb2ad0721..6ef02c02a4 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -14,7 +14,7 @@ "reply": "Odpowiedz", "edit": "Edytuj", "delete": "Usuń", - "purge": "Purge", + "purge": "Wyczyść", "restore": "Przywróć", "move": "Przenieś", "fork": "Skopiuj", @@ -23,7 +23,7 @@ "share": "Udostępnij", "tools": "Narzędzia", "flag": "Zgłoś", - "locked": "Locked", + "locked": "Zablokowany", "bookmark_instructions": "Kliknij tutaj, aby wrócić do ostatniej pozycji lub zamknij, aby odrzucić.", "flag_title": "Zgłoś post do moderacji", "flag_confirm": "Na pewno chcesz oznaczyć ten post?", @@ -49,12 +49,12 @@ "thread_tools.delete_confirm": "Na pewno chcesz usunąć ten wątek?", "thread_tools.restore": "Przywróć wątek", "thread_tools.restore_confirm": "Na pewno chcesz przywrócić ten wątek?", - "thread_tools.purge": "Purge Topic", - "thread_tools.purge_confirm": "Are you sure you want to purge this thread?", + "thread_tools.purge": "Wyczyść wątek", + "thread_tools.purge_confirm": "Jesteś pewien, że chcesz wyczyścić ten wątek?", "topic_move_success": "Temat przeniesiono do %1", "post_delete_confirm": "Na pewno chcesz usunąć ten post?", "post_restore_confirm": "Na pewno chcesz przywrócić ten post?", - "post_purge_confirm": "Are you sure you want to purge this post?", + "post_purge_confirm": "Jesteś pewien, że chcesz wyczyścić ten post?", "load_categories": "Ładowanie kategorii", "disabled_categories_note": "Zablokowane kategorie zostały wyszarzone.", "confirm_move": "Przenieś", @@ -87,9 +87,9 @@ "more_users_and_guests": "%1 więcej użytkownik(ów) i %2 gośc(i)", "more_users": "%1 więcej użytkownik(ów)", "more_guests": "%1 więcej gośc(i)", - "users_and_others": "%1 and %2 others", - "sort_by": "Sort by", - "oldest_to_newest": "Oldest to Newest", - "newest_to_oldest": "Newest to Oldest", - "most_votes": "Most votes" + "users_and_others": "%1 i %2 innych", + "sort_by": "Sortuj po", + "oldest_to_newest": "Najpierw najstarsze", + "newest_to_oldest": "Najpierw najnowsze", + "most_votes": "Najwięcej głosów" } \ No newline at end of file diff --git a/public/language/pl/user.json b/public/language/pl/user.json index ceb114e4af..65a0258933 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -3,7 +3,9 @@ "offline": "Offline", "username": "Nazwa użytkownika", "email": "Adres e-mail", - "confirm_email": "Confirm Email", + "confirm_email": "Potwierdź e-mail", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Pełna nazwa", "website": "Strona WWW", "location": "Położenie", @@ -37,7 +39,7 @@ "change_password_success": "Twoje hasło jest zaktualizowane!", "confirm_password": "Potwierdź hasło", "password": "Hasło", - "username_taken_workaround": "Login jakiego chciałeś/aś użyć jest już zajęty, ale my to załatwimy. Jesteś znany/a jako %1", + "username_taken_workaround": "Wybrany login jest już zajęty, więc zmieniliśmy go trochę. Proponujemy %1", "upload_picture": "Prześlij zdjęcie", "upload_a_picture": "Prześlij zdjęcie", "image_spec": "Możesz przesłać tylko pliki PNG, JPG lub GIF.", @@ -61,5 +63,7 @@ "posts_per_page": "Postów na stronę", "notification_sounds": "Odtwórz dźwięk po otrzymaniu powiadomienia.", "browsing": "Ustawienia szukania", - "open_links_in_new_tab": "Otwierać linki zewnętrzne w nowych kartach?" + "open_links_in_new_tab": "Otwierać linki zewnętrzne w nowych kartach?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/pl/users.json b/public/language/pl/users.json index 6a12eb61d4..1c87248f14 100644 --- a/public/language/pl/users.json +++ b/public/language/pl/users.json @@ -5,6 +5,6 @@ "search": "Szukaj", "enter_username": "Wpisz nazwę użytkownika", "load_more": "Więcej", - "user-not-found": "User not found!", - "users-found-search-took": "%1 user(s) found! Search took %2 ms." + "user-not-found": "Nie znaleziono użytkownika!", + "users-found-search-took": "Znaleziono %1 użytkowników. Szukanie zajęło %2 ms." } \ No newline at end of file diff --git a/public/language/pt_BR/category.json b/public/language/pt_BR/category.json index 7b63409f19..13a29a5a02 100644 --- a/public/language/pt_BR/category.json +++ b/public/language/pt_BR/category.json @@ -3,5 +3,6 @@ "no_topics": "Não tem nenhum tópico nesta categoria.
    Por que não tenta postar o primeiro?", "browsing": "navegando", "no_replies": "Ninguém respondeu", - "share_this_category": "Compartilhar" + "share_this_category": "Compartilhar", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index 9dca9b0ae2..dd6fe32c3c 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -12,11 +12,13 @@ "invalid-title": "Título inválido.", "invalid-user-data": "Informação de usuário inválida", "invalid-password": "Senha inválida", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Informação inválida de paginação", "username-taken": "Usuário já existe", "email-taken": "Email já foi utilizado em um cadastro", "email-not-confirmed": "Seu email não está confirmado, por favor, clique aqui para confirmá-lo", "username-too-short": "Nome de usuário muito curto", + "username-too-long": "Username too long", "user-banned": "Usuário banido", "no-category": "Categoria não existe", "no-topic": "Tópico não existe", @@ -52,5 +54,9 @@ "upload-error": "Erro de Upload : %1", "signature-too-long": "Assinatura não pode conter mais que %1 caracteres!", "cant-chat-with-yourself": "Impossível bater papo consigo mesmo!", - "not-enough-reputation-to-downvote": "Você não possui reputação suficiente para negativar este post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Você não possui reputação suficiente para negativar este post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/pt_BR/notifications.json b/public/language/pt_BR/notifications.json index 08a4240c19..fc82e93866 100644 --- a/public/language/pt_BR/notifications.json +++ b/public/language/pt_BR/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Nova notificação", "you_have_unread_notifications": "Você possui notificações não lidas.", "new_message_from": "Nova mensagem de %1", - "upvoted_your_post": "%1 votou no seu post.", - "favourited_your_post": "%1 favoritou seu post.", - "user_flagged_post": "%1 sinalizou seu post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 respondeu para: %2", "user_mentioned_you_in": "%1 mensionou você em %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmado", "email-confirmed-message": "Obrigado por validar seu email. Sua conta foi ativada.", "email-confirm-error": "Ocorreu um erro...", diff --git a/public/language/pt_BR/pages.json b/public/language/pt_BR/pages.json index b700cccb53..48c03f52d9 100644 --- a/public/language/pt_BR/pages.json +++ b/public/language/pt_BR/pages.json @@ -5,7 +5,7 @@ "recent": "Tópicos Recentes", "users": "Usuários Registrados", "notifications": "Notificações", - "tags": "Topics tagged under \"%1\"", + "tags": "Tópicos com a tag sob \"%1\"", "user.edit": "Editando \"%1\"", "user.following": "Seguidos por %1", "user.followers": "Seguidores de %1", diff --git a/public/language/pt_BR/recent.json b/public/language/pt_BR/recent.json index 27a5e4e108..3185b1419b 100644 --- a/public/language/pt_BR/recent.json +++ b/public/language/pt_BR/recent.json @@ -4,5 +4,6 @@ "week": "Semana", "month": "Mês", "year": "Ano", + "alltime": "All Time", "no_recent_topics": "Nenhum tópico recente." } \ No newline at end of file diff --git a/public/language/pt_BR/search.json b/public/language/pt_BR/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/pt_BR/search.json +++ b/public/language/pt_BR/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index eaa541ed33..8a4912a100 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 mais usuário(s) e %2 visitante(s)", "more_users": "%1 mais usuário(s)", "more_guests": "%1 mais visitante(s)", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 e %2 outros", "sort_by": "Ordenar por", "oldest_to_newest": "Mais Antigo para Recente", "newest_to_oldest": "Recente para mais Antigo", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 8ecaf1e00b..366c291a5d 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -4,6 +4,8 @@ "username": "Usuário", "email": "Email", "confirm_email": "Confirmar Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nome Completo", "website": "Website", "location": "Localização", @@ -61,5 +63,7 @@ "posts_per_page": "Posts por Página", "notification_sounds": "Tocar um som quando receber notificação.", "browsing": "Configurações de Navegação", - "open_links_in_new_tab": "Abrir links externos em nova aba?" + "open_links_in_new_tab": "Abrir links externos em nova aba?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/ro/category.json b/public/language/ro/category.json index fbd7cd2393..8c5034e4fd 100644 --- a/public/language/ro/category.json +++ b/public/language/ro/category.json @@ -3,5 +3,6 @@ "no_topics": "Nu există nici un subiect de discuție în această categorie.
    De ce nu încerci să postezi tu unul?", "browsing": "navighează", "no_replies": "Nu a răspuns nimeni", - "share_this_category": "Distribuie această categorie" + "share_this_category": "Distribuie această categorie", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ro/error.json b/public/language/ro/error.json index 5b54c36e72..f8346aa636 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -12,11 +12,13 @@ "invalid-title": "Titlu invalid!", "invalid-user-data": "Date utilizator invalide", "invalid-password": "Parolă Invalidă", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Date paginație invalide", "username-taken": "Numele de utilizator este deja folosit", "email-taken": "Adresa de email este deja folostă", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Numele de utilizator este prea scurt", + "username-too-long": "Username too long", "user-banned": "Utilizator banat", "no-category": "Categoria nu există", "no-topic": "Subiectul nu există", @@ -52,5 +54,9 @@ "upload-error": "Eroare Upload : %1", "signature-too-long": "Semnătura ta nu poate fi mai lungă de %1 caractere!", "cant-chat-with-yourself": "Nu poți conversa cu tine!", - "not-enough-reputation-to-downvote": "Nu ai destulă reputație pentru a vota negativ acest post." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Nu ai destulă reputație pentru a vota negativ acest post.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ro/notifications.json b/public/language/ro/notifications.json index 682cb05ee2..568af20877 100644 --- a/public/language/ro/notifications.json +++ b/public/language/ro/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Notificare Nouă", "you_have_unread_notifications": "Ai notificări necitite.", "new_message_from": "Un mesaj nou de la %1", - "upvoted_your_post": "%1 a votat pozitiv mesajul tău.", - "favourited_your_post": "%1 a adăugat mesajul tău la favorite.", - "user_flagged_post": "%1 a semnalizat un mesaj.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 a postat un răspuns la: %2", "user_mentioned_you_in": "%1 te-a menționat în %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email confirmat", "email-confirmed-message": "Îți mulțumim pentru validarea emailului. Contul tău este acuma activat.", "email-confirm-error": "S-a produs o eroare ...", diff --git a/public/language/ro/recent.json b/public/language/ro/recent.json index b17702e5e9..c0600afba9 100644 --- a/public/language/ro/recent.json +++ b/public/language/ro/recent.json @@ -4,5 +4,6 @@ "week": "Săptămână", "month": "Lună", "year": "An", + "alltime": "All Time", "no_recent_topics": "Nu există subiecte recente." } \ No newline at end of file diff --git a/public/language/ro/search.json b/public/language/ro/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/ro/search.json +++ b/public/language/ro/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index eccb976288..0463625d25 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -54,7 +54,7 @@ "topic_move_success": "Acest mesaj a fost mutat cu succes în %1", "post_delete_confirm": "Ești sigur că vrei să ștergi acest mesaj?", "post_restore_confirm": "Esti sigur că vrei să restaurezi acest mesaj?", - "post_purge_confirm": "Are you sure you want to purge this post?", + "post_purge_confirm": "Ești sigur că vrei să cureți acest mesaj?", "load_categories": "Se Încarcă Categoriile", "disabled_categories_note": "Categoriile dezactivate sunt decolorate cu gri", "confirm_move": "Mută", @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 utlizator(i) și %2 vizitator(i)", "more_users": "%1 utilizator(i)", "more_guests": "%1 vizitator(i)", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 și alți %2", "sort_by": "Sortează de la", "oldest_to_newest": "Vechi la Noi", "newest_to_oldest": "Noi la Vechi", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 19de3dccf8..124f9076b6 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -4,6 +4,8 @@ "username": "Nume utilizator", "email": "Adresă Email", "confirm_email": "Confirmă Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nume Întreg", "website": "Pagină Web", "location": "Locație", @@ -61,5 +63,7 @@ "posts_per_page": "Mesaje pe pagină", "notification_sounds": "Redă un sunet când primești o notificare.", "browsing": "Setări navigare", - "open_links_in_new_tab": "Deschide linkurile externe intr-un tab nou?" + "open_links_in_new_tab": "Deschide linkurile externe intr-un tab nou?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/ru/category.json b/public/language/ru/category.json index 9248f1cfd9..9454af6989 100644 --- a/public/language/ru/category.json +++ b/public/language/ru/category.json @@ -3,5 +3,6 @@ "no_topics": "В этой категории еще нет тем.
    Почему бы вам не создать первую?", "browsing": "просматривают", "no_replies": "Нет ответов", - "share_this_category": "Поделиться этой категорией" + "share_this_category": "Поделиться этой категорией", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 63a6a93355..875a018f95 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -12,11 +12,13 @@ "invalid-title": "Неверный заголовок!", "invalid-user-data": "Неверные Пользовательские Данные", "invalid-password": "Неверный Пароль", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Неверное значение пагинации", "username-taken": "Имя пользователя занято", "email-taken": "Email занят", "email-not-confirmed": "Ваш email не подтвержден, нажмите для подтверждения.", "username-too-short": "Слишком короткое имя пользователя", + "username-too-long": "Username too long", "user-banned": "Пользователь заблокирован", "no-category": "Несуществующая категория", "no-topic": "Несуществующая тема", @@ -52,5 +54,9 @@ "upload-error": "Ошибка загрузки : %1", "signature-too-long": "Подпись не может быть длиннее %1 символов", "cant-chat-with-yourself": "Вы не можете общаться с самим собой", - "not-enough-reputation-to-downvote": "У Вас недостаточно репутации для понижения оценки поста" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "У Вас недостаточно репутации для понижения оценки поста", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/ru/notifications.json b/public/language/ru/notifications.json index 1648ebc8f8..0bd4597dd7 100644 --- a/public/language/ru/notifications.json +++ b/public/language/ru/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Новое Уведомление", "you_have_unread_notifications": "У вас есть непрочитанные уведомления", "new_message_from": "Новое сообщение от %1", - "upvoted_your_post": "%1 проголосовал за Ваш пост", - "favourited_your_post": "%1 добавил Ваш пост в избранное", - "user_flagged_post": "%1 пометил пост", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 ответил на запись: %2", "user_mentioned_you_in": "%1 упомянул вас в %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Подтвержден", "email-confirmed-message": "Спасибо за подтверждение Вашего Email-адреса. Ваш аккаунт активирован.", "email-confirm-error": "Произошла ошибка...", diff --git a/public/language/ru/recent.json b/public/language/ru/recent.json index 7dabef695e..428c2c6b3d 100644 --- a/public/language/ru/recent.json +++ b/public/language/ru/recent.json @@ -4,5 +4,6 @@ "week": "Неделя", "month": "Месяц", "year": "Год", + "alltime": "All Time", "no_recent_topics": "Нет свежих тем." } \ No newline at end of file diff --git a/public/language/ru/search.json b/public/language/ru/search.json index 5f0a069bef..17b82606bd 100644 --- a/public/language/ru/search.json +++ b/public/language/ru/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 результатов по фразе \"%2\", (%3 секунды) " + "results_matching": "%1 результатов по фразе \"%2\", (%3 секунды) ", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 6f3e7c92e2..6da3902849 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -4,6 +4,8 @@ "username": "Имя пользователя", "email": "Email", "confirm_email": "Подтвердить Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Полное имя", "website": "Сайт", "location": "Откуда", @@ -61,5 +63,7 @@ "posts_per_page": "Постов на Странице", "notification_sounds": "Звук при получении уведомления", "browsing": "Настройки просмотра", - "open_links_in_new_tab": "Открывать ссылки, ведущие на другие сайты, в новой вкладке?" + "open_links_in_new_tab": "Открывать ссылки, ведущие на другие сайты, в новой вкладке?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/sc/category.json b/public/language/sc/category.json index d249aa6ffd..35e7f7b522 100644 --- a/public/language/sc/category.json +++ b/public/language/sc/category.json @@ -3,5 +3,6 @@ "no_topics": "Non bi sunt arresonadas in custa creze.
    Pro ite non nde pones una?", "browsing": "navighende", "no_replies": "Perunu at rispostu", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/sc/notifications.json b/public/language/sc/notifications.json index c5c59c39e1..0fecd4d1a7 100644 --- a/public/language/sc/notifications.json +++ b/public/language/sc/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/sc/recent.json b/public/language/sc/recent.json index b6d27107b3..2e2d9401c7 100644 --- a/public/language/sc/recent.json +++ b/public/language/sc/recent.json @@ -4,5 +4,6 @@ "week": "Chida", "month": "Mese", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Non bi sunt ùrtimas arresonadas." } \ No newline at end of file diff --git a/public/language/sc/search.json b/public/language/sc/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/sc/search.json +++ b/public/language/sc/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/sc/user.json b/public/language/sc/user.json index d1dfdd8b2e..7c8781dfb8 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -4,6 +4,8 @@ "username": "Nùmene de Impitadore", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Nùmene e Sambenadu", "website": "Giassu web", "location": "Logu", @@ -61,5 +63,7 @@ "posts_per_page": "Arresonos pro Pàgina", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/sk/category.json b/public/language/sk/category.json index e804178494..ead803aae6 100644 --- a/public/language/sk/category.json +++ b/public/language/sk/category.json @@ -3,5 +3,6 @@ "no_topics": "V tejto kategórií zatiaľ nie sú žiadne príspevky.
    Môžeš byť prvý!", "browsing": "prehliada", "no_replies": "Nikdo ešte neodpovedal", - "share_this_category": "zdielaj túto kategóriu" + "share_this_category": "zdielaj túto kategóriu", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 8481519d05..08c0106813 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -12,11 +12,13 @@ "invalid-title": "Nesprávny titulok!", "invalid-user-data": "Neplatné užívatelské údaje", "invalid-password": "Nesprávne heslo", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Nesprávna hodnota stránkovania", "username-taken": "Užívateľske meno je obsadené", "email-taken": "Email je obsadený", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "Užívateľ je zakázaný", "no-category": "Kategória neexistuje", "no-topic": "Téme neexistuje", @@ -52,5 +54,9 @@ "upload-error": "Upload chyba: %1", "signature-too-long": "Podpis nesmie byť dlhší ako %1 znakov!", "cant-chat-with-yourself": "Nemôžete chatovat so samým sebou.", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/sk/notifications.json b/public/language/sk/notifications.json index 7a5b20cbb7..cffd1a1fc0 100644 --- a/public/language/sk/notifications.json +++ b/public/language/sk/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Nová notifikácia", "you_have_unread_notifications": "Máte neprečítané notifikácie", "new_message_from": "Nova spáva od %1", - "upvoted_your_post": "%1 zahlasoval za Váš príspevok.", - "favourited_your_post": "%1 pridal do obľubených Váš príspevok.", - "user_flagged_post": "%1 označil Váš príspevok", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 odpovedal: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email bol potvrdený", "email-confirmed-message": "Ďakujeme za potvrdenie tvojho emailu. Účet je plne aktivovaný.", "email-confirm-error": "Vyskytla sa chyba...", diff --git a/public/language/sk/recent.json b/public/language/sk/recent.json index cfb6b18bc5..963740ba54 100644 --- a/public/language/sk/recent.json +++ b/public/language/sk/recent.json @@ -4,5 +4,6 @@ "week": "Týždeň", "month": "Mesiac", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Nie sú žiadne posledné témy" } \ No newline at end of file diff --git a/public/language/sk/search.json b/public/language/sk/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/sk/search.json +++ b/public/language/sk/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/sk/user.json b/public/language/sk/user.json index 3a42def9f0..cefdf45dfe 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -4,6 +4,8 @@ "username": "Uživateľské meno", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Meno a priezvisko", "website": "Webová stránka", "location": "Poloha", @@ -61,5 +63,7 @@ "posts_per_page": "Príspevkov na stranu", "notification_sounds": "Prehraj zvuk ked príde notifikácia", "browsing": "Hľadaj v nadstaveniach", - "open_links_in_new_tab": "Otvoriť tieto odkazy v novom tabe ?" + "open_links_in_new_tab": "Otvoriť tieto odkazy v novom tabe ?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/sv/category.json b/public/language/sv/category.json index 25b9f9fa83..c9662de9c0 100644 --- a/public/language/sv/category.json +++ b/public/language/sv/category.json @@ -3,5 +3,6 @@ "no_topics": "Det finns inga ämnen i denna kategori.
    Varför inte skapa ett?", "browsing": "läser", "no_replies": "Ingen har svarat", - "share_this_category": "Dela den här kategorin" + "share_this_category": "Dela den här kategorin", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 8de10daaae..2c10713a84 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -12,11 +12,13 @@ "invalid-title": "Ogiltig rubrik.", "invalid-user-data": "Ogiltig användardata", "invalid-password": "Ogiltigt lösenord", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Ogiltigt sidnummer", "username-taken": "Användarnamn upptaget", "email-taken": "Epostadress upptagen", "email-not-confirmed": "Din epostadress är ännu inte bekräftad. Klicka här för att bekräfta din epostadress.", "username-too-short": "Användarnamnet är för kort", + "username-too-long": "Username too long", "user-banned": "Användare bannad", "no-category": "Kategori hittades inte", "no-topic": "Ämne hittades inte", @@ -52,5 +54,9 @@ "upload-error": "Fel vid uppladdning: %1", "signature-too-long": "Signaturer kan inte vara längre än %1 tecken.", "cant-chat-with-yourself": "Du kan inte chatta med dig själv.", - "not-enough-reputation-to-downvote": "Du har inte tillräckligt förtroende för att rösta ner det här meddelandet" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Du har inte tillräckligt förtroende för att rösta ner det här meddelandet", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json index 9f262b008a..267c761ba5 100644 --- a/public/language/sv/notifications.json +++ b/public/language/sv/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Ny notis", "you_have_unread_notifications": "Du har olästa notiser.", "new_message_from": "Nytt medelande från %1", - "upvoted_your_post": "%1 har röstat på ditt inlägg.", - "favourited_your_post": "%1 har favoriserat ditt inlägg.", - "user_flagged_post": "%1 flaggade ett inlägg.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 har skrivit ett svar på: %2", "user_mentioned_you_in": "%1 nämnde dig i %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Epost bekräftad", "email-confirmed-message": "Tack för att du bekräftat din epostadress. Ditt konto är nu fullt ut aktiverat.", "email-confirm-error": "Ett fel uppstod...", diff --git a/public/language/sv/recent.json b/public/language/sv/recent.json index 4478c47921..f9e04c9081 100644 --- a/public/language/sv/recent.json +++ b/public/language/sv/recent.json @@ -4,5 +4,6 @@ "week": "Vecka", "month": "Månad", "year": "År", + "alltime": "All Time", "no_recent_topics": "Det finns inga olästa ämnen." } \ No newline at end of file diff --git a/public/language/sv/search.json b/public/language/sv/search.json index 996a7d81fd..49e8bde9a0 100644 --- a/public/language/sv/search.json +++ b/public/language/sv/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 resultat matchar \"%2\", (%3 sekunder)" + "results_matching": "%1 resultat matchar \"%2\", (%3 sekunder)", + "no-matches": "Inga inlägg hittades" } \ No newline at end of file diff --git a/public/language/sv/user.json b/public/language/sv/user.json index a6b3a753fb..e4e4c01dcb 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -4,6 +4,8 @@ "username": "Användarnamn", "email": "Epost", "confirm_email": "Bekräfta epostadress ", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Hela namnet", "website": "Webbsida", "location": "Plats", @@ -61,5 +63,7 @@ "posts_per_page": "Inlägg per sida", "notification_sounds": "Spela ett ljud när du får en notis.", "browsing": "Inställning för bläddring", - "open_links_in_new_tab": "Öppna utgående länkar på ny flik?" + "open_links_in_new_tab": "Öppna utgående länkar på ny flik?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/th/category.json b/public/language/th/category.json index 790f7752fc..31246e2a05 100644 --- a/public/language/th/category.json +++ b/public/language/th/category.json @@ -3,5 +3,6 @@ "no_topics": "ยังไม่มีกระทู้ในหมวดนี้
    โพสต์กระทู้แรก?", "browsing": "เรียกดู", "no_replies": "ยังไม่มีใครตอบ", - "share_this_category": "Share this category" + "share_this_category": "Share this category", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 77c582b23f..26e3c9d80c 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Invalid pagination value", "username-taken": "Username taken", "email-taken": "Email taken", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "User banned", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Signature can't be longer than %1 characters!", "cant-chat-with-yourself": "You can't chat with yourself!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/th/notifications.json b/public/language/th/notifications.json index 4c33f78276..d78ad8e8f6 100644 --- a/public/language/th/notifications.json +++ b/public/language/th/notifications.json @@ -10,11 +10,14 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", "new_message_from": "New message from %1", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/th/recent.json b/public/language/th/recent.json index c7a2e6bc19..05eec4d503 100644 --- a/public/language/th/recent.json +++ b/public/language/th/recent.json @@ -4,5 +4,6 @@ "week": "สัปดาห์", "month": "เดือน", "year": "Year", + "alltime": "All Time", "no_recent_topics": "ไม่มีกระทู้ล่าสุด" } \ No newline at end of file diff --git a/public/language/th/search.json b/public/language/th/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/th/search.json +++ b/public/language/th/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/th/user.json b/public/language/th/user.json index 9f1e022fd1..f6d6861e31 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -4,6 +4,8 @@ "username": "ชื่อผู้ใช้", "email": "อีเมล์", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "ชื่อเต็ม", "website": "เว็บไซต์", "location": "สถานที่", @@ -61,5 +63,7 @@ "posts_per_page": "จำนวนโพสต์ต่อหน้า", "notification_sounds": "Play a sound when you receive a notification.", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/tr/category.json b/public/language/tr/category.json index 7ec2a32e14..9fae15699d 100644 --- a/public/language/tr/category.json +++ b/public/language/tr/category.json @@ -3,5 +3,6 @@ "no_topics": " Bu kategoride hiç konu yok.
    Yeni bir konu açmak istemez misiniz?", "browsing": "dolaşıyor", "no_replies": "Kimse yanıtlamadı", - "share_this_category": "Bu kategoriyi paylaş" + "share_this_category": "Bu kategoriyi paylaş", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index cec4db9683..6c6a37053a 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -12,11 +12,13 @@ "invalid-title": "Geçersiz başlık!", "invalid-user-data": "Geçersiz Kullancı Verisi", "invalid-password": "Geçersiz Şifre", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Geçersiz Sayfa Değeri", "username-taken": "Kullanıcı İsmi Alınmış", "email-taken": "E-posta Alınmış", "email-not-confirmed": "E-postanız onaylanmamış, onaylamak için lütfen buraya tıklayın.", "username-too-short": "Kullanıcı ismi çok kısa", + "username-too-long": "Username too long", "user-banned": "Kullanıcı Yasaklı", "no-category": "Kategori Yok", "no-topic": "Başlık Yok", @@ -52,5 +54,9 @@ "upload-error": "Yükleme Hatası : %1", "signature-too-long": "İmza en fazla %1 karakter olabilir!", "cant-chat-with-yourself": "Kendinizle sohbet edemezsiniz!", - "not-enough-reputation-to-downvote": "Bu iletiyi aşagı oylamak için yeterince saygınlığınız yok." + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "Bu iletiyi aşagı oylamak için yeterince saygınlığınız yok.", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index 3ff542c19a..8b5db97764 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Yeni bildirim", "you_have_unread_notifications": "Okunmamış bildirimleriniz var.", "new_message_from": "%1 size bir mesaj gönderdi", - "upvoted_your_post": "%1 iletinizi beğendi.", - "favourited_your_post": "%1 iletinizi favorilerine ekledi.", - "user_flagged_post": "%1 bir iletiyi sakıncalı buldu.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 %2 başlığına bir ileti gönderdi.", "user_mentioned_you_in": "%1 %2 başlığında sizden bahsetti.", + "user_started_following_you": "%1 started following you.", "email-confirmed": "E-posta onaylandı", "email-confirmed-message": "E-postanızı onaylandığınız için teşekkürler. Hesabınız tamamen aktive edildi.", "email-confirm-error": "Bir hata oluştu...", diff --git a/public/language/tr/recent.json b/public/language/tr/recent.json index 333a85f772..088eacdff1 100644 --- a/public/language/tr/recent.json +++ b/public/language/tr/recent.json @@ -4,5 +4,6 @@ "week": "Hafta", "month": "Ay", "year": "Yıl", + "alltime": "All Time", "no_recent_topics": "Güncel konular yok." } \ No newline at end of file diff --git a/public/language/tr/search.json b/public/language/tr/search.json index b384c82c72..307b7cffb3 100644 --- a/public/language/tr/search.json +++ b/public/language/tr/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 tane “%2“ bulundu (%3 saniye)" + "results_matching": "%1 tane “%2“ bulundu (%3 saniye)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/tr/user.json b/public/language/tr/user.json index f8b6b75e3c..fb1316fd7e 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -4,6 +4,8 @@ "username": "Kullanıcı Adı", "email": "E-posta", "confirm_email": "E-posta onayla", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Tam Ad", "website": "Websitesi", "location": "Konum", @@ -61,5 +63,7 @@ "posts_per_page": "Sayfa başına İletiler", "notification_sounds": "Bildirim alındığında ses çal", "browsing": "Tarayıcı Ayaları", - "open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç?" + "open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/vi/category.json b/public/language/vi/category.json index 972df0d4f3..70ab036818 100644 --- a/public/language/vi/category.json +++ b/public/language/vi/category.json @@ -3,5 +3,6 @@ "no_topics": "Không có bài viết trong danh mục.
    Hãy đăng một bài viết mới?", "browsing": "đang duyệt", "no_replies": "Chưa có ai bình luận", - "share_this_category": "Chia sẻ phần mục này" + "share_this_category": "Chia sẻ phần mục này", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 6b93d3cd98..189c8de1a5 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "Dữ liệu tài khoản không hợp lệ", "invalid-password": "Mật khẩu không hợp lệ", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "Số trang không hợp lệ", "username-taken": "Tên đăng nhập đã tồn tại", "email-taken": "Email đã tồn tại", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "Tài khoản bị ban", "no-category": "Phần mục không tồn tại", "no-topic": "Chủ đề không tồn tại", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "Chứ ký không được dài quá %1 ký tự", "cant-chat-with-yourself": "Bạn không thể chat với chính bạn!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json index 431305ad23..029eb60475 100644 --- a/public/language/vi/notifications.json +++ b/public/language/vi/notifications.json @@ -10,11 +10,14 @@ "new_notification": "Thông báo mới", "you_have_unread_notifications": "Bạn có thông báo chưa đọc", "new_message_from": "Tin nhắn mới từ %1", - "upvoted_your_post": "%1 đã hủy vote cho bài viết của bạn", - "favourited_your_post": "%1 thích bài viết của bạn", - "user_flagged_post": "%1 đã flag một bài viết", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 đã trả lời %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/vi/recent.json b/public/language/vi/recent.json index 907cf76923..544c3cefb2 100644 --- a/public/language/vi/recent.json +++ b/public/language/vi/recent.json @@ -4,5 +4,6 @@ "week": "Tuần", "month": "Tháng", "year": "Year", + "alltime": "All Time", "no_recent_topics": "Không có chủ đề nào gần đây" } \ No newline at end of file diff --git a/public/language/vi/search.json b/public/language/vi/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/vi/search.json +++ b/public/language/vi/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json index b30a8526d9..1f5cb24b9a 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -4,6 +4,8 @@ "username": "Tên truy cập", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "Tên đầy đủ", "website": "Website", "location": "Địa điểm", @@ -61,5 +63,7 @@ "posts_per_page": "Số bài viết trong một trang", "notification_sounds": "Xuất hiện âm thanh khi bạn nhận được một thông báo", "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?" + "open_links_in_new_tab": "Open outgoing links in new tab?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/zh_CN/category.json b/public/language/zh_CN/category.json index aafcf3e407..2f776788d1 100644 --- a/public/language/zh_CN/category.json +++ b/public/language/zh_CN/category.json @@ -3,5 +3,6 @@ "no_topics": "此版块还没有任何内容。
    赶紧来发帖吧!", "browsing": "正在浏览", "no_replies": "尚无回复", - "share_this_category": "分享此版块" + "share_this_category": "分享此版块", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index b0678b8537..561c57a0da 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -12,11 +12,13 @@ "invalid-title": "无效标题!", "invalid-user-data": "无效用户数据", "invalid-password": "无效密码", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "无效页码", "username-taken": "用户名已注册", "email-taken": "电子邮箱已注册", "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。", "username-too-short": "用户名太短", + "username-too-long": "Username too long", "user-banned": "用户已禁止", "no-category": "版块不存在", "no-topic": "主题不存在", @@ -52,5 +54,9 @@ "upload-error": "上传错误:%1", "signature-too-long": "签名档不能超过 %1 字!", "cant-chat-with-yourself": "您不能和自己聊天!", - "not-enough-reputation-to-downvote": "您还没有足够的威望为此帖扣分" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "您还没有足够的威望为此帖扣分", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json index e2054a17f7..6e4faf216f 100644 --- a/public/language/zh_CN/notifications.json +++ b/public/language/zh_CN/notifications.json @@ -10,11 +10,14 @@ "new_notification": "新通知", "you_have_unread_notifications": "您有未读的通知。", "new_message_from": "来自 %1 的新消息", - "upvoted_your_post": "%1 赞了您的帖子。", - "favourited_your_post": "%1 收藏了您的帖子。", - "user_flagged_post": "%1 标记了一个帖子。", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 回复了:%2", "user_mentioned_you_in": "%1%2 中提到了您", + "user_started_following_you": "%1 started following you.", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全面激活。", "email-confirm-error": "出错了.……", diff --git a/public/language/zh_CN/pages.json b/public/language/zh_CN/pages.json index 32c3ba5e56..28af9ea173 100644 --- a/public/language/zh_CN/pages.json +++ b/public/language/zh_CN/pages.json @@ -5,7 +5,7 @@ "recent": "最新主题", "users": "已注册用户", "notifications": "提醒", - "tags": "Topics tagged under \"%1\"", + "tags": "话题为 \"%1\" 的主题", "user.edit": "正在编辑 \"%1\"", "user.following": "%1 关注", "user.followers": "关注 %1 的人", diff --git a/public/language/zh_CN/recent.json b/public/language/zh_CN/recent.json index 6d9a34e12d..c363d4921c 100644 --- a/public/language/zh_CN/recent.json +++ b/public/language/zh_CN/recent.json @@ -4,5 +4,6 @@ "week": "本周", "month": "本月", "year": "本年", + "alltime": "All Time", "no_recent_topics": "无最新主题。" } \ No newline at end of file diff --git a/public/language/zh_CN/search.json b/public/language/zh_CN/search.json index acbed61943..484acb3626 100644 --- a/public/language/zh_CN/search.json +++ b/public/language/zh_CN/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 条结果,匹配 \"%2\",(耗时 %3 秒)" + "results_matching": "共 %1 条结果匹配 \"%2\",(耗时 %3 秒)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json index 84275cb148..183cbfcc23 100644 --- a/public/language/zh_CN/topic.json +++ b/public/language/zh_CN/topic.json @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 名会员和 %2 名游客", "more_users": "%1 名会员", "more_guests": "%1 名游客", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 和 %2 其他人", "sort_by": "排序", "oldest_to_newest": "从旧到新", "newest_to_oldest": "从新到旧", diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index 173234796e..51d0b25817 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -4,6 +4,8 @@ "username": "用户名", "email": "电子邮件", "confirm_email": "确认电子邮箱", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "姓名", "website": "网站", "location": "位置", @@ -61,5 +63,7 @@ "posts_per_page": "每页帖子数", "notification_sounds": "收到通知时播放提示音。", "browsing": "浏览设置", - "open_links_in_new_tab": "在新标签中打开外部链接?" + "open_links_in_new_tab": "在新标签中打开外部链接?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file diff --git a/public/language/zh_TW/category.json b/public/language/zh_TW/category.json index f1e867bb47..3fb8b883ee 100644 --- a/public/language/zh_TW/category.json +++ b/public/language/zh_TW/category.json @@ -3,5 +3,6 @@ "no_topics": "這個版面還沒有任何內容。
    趕緊來發文章吧!", "browsing": "正在瀏覽", "no_replies": "還沒有回覆", - "share_this_category": "分享這類別" + "share_this_category": "分享這類別", + "ignore": "Ignore" } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index a843fee3b9..d965ca117e 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -12,11 +12,13 @@ "invalid-title": "Invalid title!", "invalid-user-data": "無效的使用者資料", "invalid-password": "無效的密碼", + "invalid-username-or-password": "Please specify both a username and password", "invalid-pagination-value": "無效的分頁數值", "username-taken": "該使用者名稱已被使用", "email-taken": "該信箱已被使用", "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", "username-too-short": "Username too short", + "username-too-long": "Username too long", "user-banned": "該使用者已被停用", "no-category": "類別並不存在", "no-topic": "主題並不存在", @@ -52,5 +54,9 @@ "upload-error": "Upload Error : %1", "signature-too-long": "簽名檔長度不能超過 %1 字元!", "cant-chat-with-yourself": "你不能與自己聊天!", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post" + "reputation-system-disabled": "Reputation system is disabled.", + "downvoting-disabled": "Downvoting is disabled", + "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", + "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/zh_TW/notifications.json b/public/language/zh_TW/notifications.json index 56c9534641..e9132a07df 100644 --- a/public/language/zh_TW/notifications.json +++ b/public/language/zh_TW/notifications.json @@ -10,11 +10,14 @@ "new_notification": "新訊息通知", "you_have_unread_notifications": "您有未讀的訊息!", "new_message_from": "來自 %1 的新訊息", - "upvoted_your_post": "%1 has upvoted your post.", - "favourited_your_post": "%1 has favourited your post.", - "user_flagged_post": "%1 flagged a post.", + "upvoted_your_post_in": "%1 has upvoted your post in %2.", + "moved_your_post": "%1 has moved your post.", + "moved_your_topic": "%1 has moved your topic.", + "favourited_your_post_in": "%1 has favourited your post in %2.", + "user_flagged_post_in": "%1 flagged a post in %2", "user_posted_to": "%1 has posted a reply to: %2", "user_mentioned_you_in": "%1 mentioned you in %2", + "user_started_following_you": "%1 started following you.", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", "email-confirm-error": "An error occurred...", diff --git a/public/language/zh_TW/recent.json b/public/language/zh_TW/recent.json index f264357969..1b53b1220c 100644 --- a/public/language/zh_TW/recent.json +++ b/public/language/zh_TW/recent.json @@ -4,5 +4,6 @@ "week": "本周", "month": "本月", "year": "Year", + "alltime": "All Time", "no_recent_topics": "最近沒新主題." } \ No newline at end of file diff --git a/public/language/zh_TW/search.json b/public/language/zh_TW/search.json index d0ffc64f36..e9eef4632a 100644 --- a/public/language/zh_TW/search.json +++ b/public/language/zh_TW/search.json @@ -1,3 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)" + "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", + "no-matches": "No posts found" } \ No newline at end of file diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index b516f98795..f73d573c02 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -4,6 +4,8 @@ "username": "使用者名稱", "email": "Email", "confirm_email": "Confirm Email", + "delete_account": "Delete Account", + "delete_account_confirm": "Are you sure you want to delete your account?", "fullname": "姓名", "website": "網站", "location": "地址", @@ -61,5 +63,7 @@ "posts_per_page": "每頁的文章數", "notification_sounds": "當收到新消息時播放提示音", "browsing": "瀏覽設定", - "open_links_in_new_tab": "在新的分頁開啟外部連結?" + "open_links_in_new_tab": "在新的分頁開啟外部連結?", + "follow_topics_you_reply_to": "Follow topics that you reply to.", + "follow_topics_you_create": "Follow topics you create." } \ No newline at end of file From 6e21b7a8306a1021638c031ebe04c982ef3267ed Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2014 10:47:25 -0400 Subject: [PATCH 248/372] disabled parents no longer show up as parents of a category, re: #2080 --- src/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories.js b/src/categories.js index e33d8a7aa3..473bf50c8a 100644 --- a/src/categories.js +++ b/src/categories.js @@ -366,7 +366,7 @@ var db = require('./database'), if (categories[i]) { categories[i]['unread-class'] = (parseInt(categories[i].topic_count, 10) === 0 || (hasRead[i] && uid !== 0)) ? '' : 'unread'; categories[i].children = results.children[i]; - categories[i].parent = results.parents[i]; + categories[i].parent = results.parents[i] && !results.parents[i].disabled ? results.parents[i] : null; } } From 84d4035597e09d1d5da3ec589eb119661105c742 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Sep 2014 10:50:58 -0400 Subject: [PATCH 249/372] Child categories no longer show up on homepage, re: #2080 --- src/controllers/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/index.js b/src/controllers/index.js index 7d7ea579ac..97afb0d4e4 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -69,6 +69,11 @@ Controllers.home = function(req, res, next) { return next(err); } + // Remove child categories, as they don't belong on the home page + categoryData = categoryData.filter(function(categoryObj) { + return !categoryObj.parent; + }); + categories.getRecentTopicReplies(categoryData, uid, function(err) { next(err, categoryData); }); From 7c3dc4877a34530d54a69bb284a3a5cbd2307da9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Sep 2014 14:34:01 -0400 Subject: [PATCH 250/372] closes #2089 --- public/src/forum/account/profile.js | 43 ++++++++++++++++++++++++++++- public/src/forum/recent.js | 2 +- src/controllers/accounts.js | 1 + src/socket.io/user.js | 11 ++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/public/src/forum/account/profile.js b/public/src/forum/account/profile.js index a7a522f220..2d273a33ca 100644 --- a/public/src/forum/account/profile.js +++ b/public/src/forum/account/profile.js @@ -2,7 +2,7 @@ /* globals define, ajaxify, app, utils, socket, translator*/ -define('forum/account/profile', ['forum/account/header'], function(header) { +define('forum/account/profile', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) { var Account = {}, yourid, theirid, @@ -39,6 +39,8 @@ define('forum/account/profile', ['forum/account/header'], function(header) { if (yourid !== theirid) { socket.emit('user.increaseViewCount', theirid); } + + infinitescroll.init(loadMoreTopics); }; function processPage() { @@ -82,5 +84,44 @@ define('forum/account/profile', ['forum/account/header'], function(header) { } + function loadMoreTopics(direction) { + if(direction < 0 || !$('.user-recent-posts').length) { + return; + } + + $('.loading-indicator').removeClass('hidden'); + + infinitescroll.loadMore('user.loadMoreRecentPosts', { + after: $('.user-recent-posts').attr('data-nextstart'), + uid: theirid + }, function(data, done) { + if (data.posts && data.posts.length) { + onPostsLoaded(data.posts, done); + $('.user-recent-posts').attr('data-nextstart', data.nextStart); + } else { + done(); + } + $('.loading-indicator').addClass('hidden'); + }); + } + + function onPostsLoaded(posts, callback) { + posts = posts.filter(function(post) { + return !$('.user-recent-posts div[data-pid=' + post.pid + ']').length; + }); + + if (!posts.length) { + return callback(); + } + + infinitescroll.parseAndTranslate('account/profile', 'posts', {posts: posts}, function(html) { + + $('.user-recent-posts .loading-indicator').before(html); + html.find('span.timeago').timeago(); + + callback(); + }); + } + return Account; }); diff --git a/public/src/forum/recent.js b/public/src/forum/recent.js index f3e51686aa..78cbff6c14 100644 --- a/public/src/forum/recent.js +++ b/public/src/forum/recent.js @@ -122,7 +122,7 @@ define('forum/recent', ['forum/infinitescroll'], function(infinitescroll) { }); if (!topics.length) { - callback(); + return callback(); } infinitescroll.parseAndTranslate(templateName, 'topics', {topics: topics, showSelect: showSelect}, function(html) { diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 616cfc50d3..fe40a3ea9a 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -166,6 +166,7 @@ accountsController.getAccount = function(req, res, next) { return p && parseInt(p.deleted, 10) !== 1; }); + userData.nextStart = results.posts.nextStart; userData.isFollowing = results.isFollowing; if (!userData.profileviews) { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 44d6f4b41c..81667079b9 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -5,6 +5,7 @@ var async = require('async'), user = require('../user'), groups = require('../groups'), topics = require('../topics'), + posts = require('../posts'), notifications = require('../notifications'), messaging = require('../messaging'), plugins = require('../plugins'), @@ -329,6 +330,16 @@ SocketUser.loadMore = function(socket, data, callback) { }); }; +SocketUser.loadMoreRecentPosts = function(socket, data, callback) { + if(!data || !data.uid || !utils.isNumber(data.after)) { + return callback(new Error('[[error:invalid-data]]')); + } + + var start = Math.max(0, parseInt(data.after, 10)), + end = start + 9; + + posts.getPostsByUid(socket.uid, data.uid, start, end, callback); +}; SocketUser.setStatus = function(socket, status, callback) { if (!socket.uid) { From 9a96458f79e1325218396e2a7c8d8e430fe4d1a1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Sep 2014 16:26:25 -0400 Subject: [PATCH 251/372] closes #2053 --- public/src/forum/chats.js | 49 ++++++++++++++++++++++++++++++++++++- public/src/modules/chat.js | 4 +-- src/controllers/accounts.js | 10 +++++--- src/messaging.js | 37 +++++++++++++++++++--------- src/socket.io/modules.js | 10 ++++++-- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index 2d2123b5d3..110afa4473 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -2,7 +2,7 @@ /* globals define, app, ajaxify, utils, socket, templates */ -define('forum/chats', ['string', 'sounds'], function(S, sounds) { +define('forum/chats', ['string', 'sounds', 'forum/infinitescroll'], function(S, sounds, infinitescroll) { var Chats = { initialised: false }; @@ -70,6 +70,14 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { app.openChat(username, uid); }, true); }); + + $('.recent-chats').on('scroll', function() { + var $this = $(this); + var bottom = ($this[0].scrollHeight - $this.height()) * 0.9; + if ($this.scrollTop() > bottom) { + loadMoreRecentChats(); + } + }); }; Chats.addGlobalEventListeners = function() { @@ -197,5 +205,44 @@ define('forum/chats', ['string', 'sounds'], function(S, sounds) { }, callback); }; + function loadMoreRecentChats() { + var recentChats = $('.recent-chats'); + if (recentChats.attr('loading')) { + return; + } + recentChats.attr('loading', 1); + socket.emit('modules.chats.getRecentChats', { + after: recentChats.attr('data-nextstart') + }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + + if (data && data.users.length) { + onRecentChatsLoaded(data.users, function() { + recentChats.removeAttr('loading'); + recentChats.attr('data-nextstart', data.nextStart); + }); + } else { + recentChats.removeAttr('loading'); + } + }); + } + + function onRecentChatsLoaded(users, callback) { + users = users.filter(function(user) { + return !$('.recent-chats li[data-uid=' + user.uid + ']').length; + }); + + if (!users.length) { + return callback(); + } + + infinitescroll.parseAndTranslate('chats', 'chats', {chats: users}, function(html) { + $('.recent-chats').append(html); + callback(); + }); + } + return Chats; }); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index b4234bd488..24bc182716 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -15,11 +15,11 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, return; } - socket.emit('modules.chats.list', function(err, chats) { + socket.emit('modules.chats.getRecentChats', {after: 0}, function(err, chats) { if (err) { return app.alertError(err.message); } - + chats = chats.users; var userObj; chatsListEl.empty(); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index fe40a3ea9a..1e21ecc62a 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -500,20 +500,21 @@ accountsController.getChats = function(req, res, next) { return next(err); } - // Remove entries if they were already present as a followed contact + //Remove entries if they were already present as a followed contact if (results.contacts && results.contacts.length) { var contactUids = results.contacts.map(function(contact) { return parseInt(contact.uid, 10); }); - results.recentChats = results.recentChats.filter(function(chatObj) { + results.recentChats.users = results.recentChats.users.filter(function(chatObj) { return contactUids.indexOf(parseInt(chatObj.uid, 10)) === -1; }); } if (!req.params.userslug) { return res.render('chats', { - chats: results.recentChats, + chats: results.recentChats.users, + nextStart: results.recentChats.nextStart, contacts: results.contacts }); } @@ -532,7 +533,8 @@ accountsController.getChats = function(req, res, next) { } res.render('chats', { - chats: results.recentChats, + chats: results.recentChats.users, + nextStart: results.recentChats.nextStart, contacts: results.contacts, meta: data.toUser, messages: data.messages diff --git a/src/messaging.js b/src/messaging.js index 8c9c902c5c..16a56dd23e 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -222,26 +222,39 @@ var db = require('./database'), return callback(err); } - db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, function(err, unreadUids) { + async.parallel({ + unread: function(next) { + db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, next); + }, + users: function(next) { + user.getMultipleUserFields(uids, ['uid', 'username', 'picture', 'status'] , next); + } + }, function(err, results) { if (err) { return callback(err); } - user.getMultipleUserFields(uids, ['uid', 'username', 'picture', 'status'] , function(err, users) { + results.users = results.users.filter(function(user) { + return user && parseInt(user.uid, 10); + }); + + if (!results.users.length) { + return callback(null, {users: [], nextStart: end + 1}); + } + + results.users.forEach(function(user, index) { + if (user) { + user.unread = results.unread[index]; + user.status = websockets.isUserOnline(user.uid) ? user.status : 'offline'; + } + }); + + db.sortedSetRevRank('uid:' + uid + ':chats', results.users[results.users.length - 1].uid, function(err, rank) { if (err) { return callback(err); } - users = users.filter(function(user) { - return user && parseInt(user.uid, 10); - }); - users.forEach(function(user, index) { - if (user) { - user.unread = unreadUids[index]; - user.status = websockets.isUserOnline(user.uid) ? user.status : 'offline'; - } - }); - callback(null, users); + callback(null, {users: results.users, nextStart: rank + 1}); }); }); }); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 54e6b4585d..724407fe34 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -266,8 +266,14 @@ function sendTypingNotification(event, socket, data, callback) { server.in('uid_' + data.touid).emit(event, data.fromUid); } -SocketModules.chats.list = function(socket, data, callback) { - Messaging.getRecentChats(socket.uid, 0, 9, callback); +SocketModules.chats.getRecentChats = function(socket, data, callback) { + if (!data || !utils.isNumber(data.after)) { + return callback(new Error('[[error:invalid-data]]')); + } + var start = parseInt(data.after, 10), + end = start + 9; + + Messaging.getRecentChats(socket.uid, start, end, callback); }; /* Notifications */ From 31eccb8e7f38dd240ced2bedefa15d2ab7d1ffb2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Sep 2014 17:01:20 -0400 Subject: [PATCH 252/372] configurable post delay before first post --- install/data/defaults.json | 4 ++++ public/language/en_GB/error.json | 1 + src/database/redis/sorted.js | 1 + src/user.js | 13 +++++++------ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 0ecd7b5fd1..1443cab11b 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -7,6 +7,10 @@ "field": "postDelay", "value": 10 }, + { + "field": "initialPostDelay", + "value": 10 + }, { "field": "minimumPostLength", "value": 8 diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 15874226dd..62908a30db 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -27,6 +27,7 @@ "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 36ef4f6c73..900497c087 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -56,6 +56,7 @@ module.exports = function(redisClient, module) { }; module.sortedSetsRemoveRangeByScore = function(keys, min, max, callback) { + callback = callback || function() {}; var multi = redisClient.multi(); for(var i=0; i Date: Mon, 15 Sep 2014 17:51:08 -0400 Subject: [PATCH 253/372] closes #2086 --- public/language/en_GB/topic.json | 2 +- public/src/forum/topic/fork.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index c6fe5bc4ad..1fa24dbe23 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", - "fork_success": "Succesfully forked topic!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", diff --git a/public/src/forum/topic/fork.js b/public/src/forum/topic/fork.js index 38052fa35b..6c462b4566 100644 --- a/public/src/forum/topic/fork.js +++ b/public/src/forum/topic/fork.js @@ -42,18 +42,26 @@ define('forum/topic/fork', function() { socket.emit('topics.createTopicFromPosts', { title: forkModal.find('#fork-title').val(), pids: pids - }, function(err) { + }, function(err, newTopic) { function fadeOutAndRemove(pid) { $('#post-container li[data-pid="' + pid + '"]').fadeOut(500, function() { $(this).remove(); }); } - if(err) { + if (err) { return app.alertError(err.message); } - app.alertSuccess('[[topic:fork_success]]'); + app.alert({ + timeout: 5000, + title: '[[global:alert.success]]', + message: '[[topic:fork_success]]', + type: 'success', + clickfn: function() { + ajaxify.go('topic/' + newTopic.slug); + } + }); for(var i=0; i Date: Tue, 16 Sep 2014 09:31:06 -0400 Subject: [PATCH 254/372] switching back to bcryptjs --- bcrypt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bcrypt.js b/bcrypt.js index e11308b0ab..a926310923 100644 --- a/bcrypt.js +++ b/bcrypt.js @@ -2,7 +2,7 @@ 'use strict'; -var bcrypt = require('bcrypt'); +var bcrypt = require('bcryptjs'); process.on('message', function(m) { if (m.type === 'hash') { From 76fb2c3378b0e7d4dc59dd3b1f4450b26f91b62d Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 09:31:44 -0400 Subject: [PATCH 255/372] daily + monthly pageview analytics middleware --- src/middleware/middleware.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index ba74f6730a..706480af9b 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -47,6 +47,24 @@ middleware.updateLastOnlineTime = function(req, res, next) { next(); }; +middleware.incrementPageViews = function(req, res, next) { + var nextMonth = new Date(), + nextDay = new Date(); + + nextMonth.setMonth(nextMonth.getMonth() + 1, 1); + nextMonth.setHours(0, 0, 0, 0); + + nextDay.setDate(nextDay.getDate() + 1); + nextDay.setHours(0, 0, 0, 0); + + db.increment('pageviews:monthly'); + db.increment('pageviews:daily'); + db.expireAt('pageviews:monthly', nextMonth.getTime()); + db.expireAt('pageviews:daily', nextDay.getTime()); + + next(); +}; + middleware.redirectToAccountIfLoggedIn = function(req, res, next) { if (req.user) { user.getUserField(req.user.uid, 'userslug', function (err, userslug) { From 0ecaa05c76d0e9da7d9939b20a114573efa0ec0a Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 09:32:42 -0400 Subject: [PATCH 256/372] rearranging routers to prevent multiple hits on incrementPageViews and potentially updateLastOnlineTime --- src/routes/index.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index c74a350fd2..ee2779ee9d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -152,6 +152,7 @@ function groupRoutes(app, middleware, controllers) { module.exports = function(app, middleware) { var router = express.Router(), + pageRouter = express.Router(), pluginRouter = express.Router(), relativePath = nconf.get('relative_path'); @@ -171,25 +172,29 @@ module.exports = function(app, middleware) { pluginRoutes(router, middleware, controllers); authRoutes.createRoutes(router, middleware, controllers); + app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { + maxAge: app.enabled('cache') ? 5184000000 : 0 + })); + /** * Every view has an associated API route. * */ - mainRoutes(router, middleware, controllers); - staticRoutes(router, middleware, controllers); - topicRoutes(router, middleware, controllers); - tagRoutes(router, middleware, controllers); - categoryRoutes(router, middleware, controllers); - accountRoutes(router, middleware, controllers); - userRoutes(router, middleware, controllers); - groupRoutes(router, middleware, controllers); - - // Add the routers to the application + pageRouter.use(middleware.incrementPageViews); + + mainRoutes(pageRouter, middleware, controllers); + staticRoutes(pageRouter, middleware, controllers); + topicRoutes(pageRouter, middleware, controllers); + tagRoutes(pageRouter, middleware, controllers); + categoryRoutes(pageRouter, middleware, controllers); + accountRoutes(pageRouter, middleware, controllers); + userRoutes(pageRouter, middleware, controllers); + groupRoutes(pageRouter, middleware, controllers); + app.use(relativePath, router); app.use(relativePath, pluginRouter); - app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { - maxAge: app.enabled('cache') ? 5184000000 : 0 - })); + app.use(relativePath, pageRouter); + if (process.env.NODE_ENV === 'development') { require('./debug')(app, middleware, controllers); } From ecd00bb679eab63eb9a77b546628f4ff90c019d7 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 10:12:12 -0400 Subject: [PATCH 257/372] refactored widgets api call to render all at once rather than one area at a time, closes https://github.com/NodeBB/NodeBB/issues/2062 --- public/src/widgets.js | 82 ++++++++++++++++++++---------------------- src/controllers/api.js | 29 +++++++++++---- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/public/src/widgets.js b/public/src/widgets.js index 23a37e4222..32b01f599f 100644 --- a/public/src/widgets.js +++ b/public/src/widgets.js @@ -30,60 +30,56 @@ ajaxify.widgets.reposition(); } - function renderWidgets(location) { - var area = $('#content [widget-area="' + location + '"]'), - areaData = { - location: location, - template: template + '.tpl', - url: url - }; - - $.get(RELATIVE_PATH + '/api/widgets/render', areaData, function(renderedWidgets) { - var html = ''; - - for (var i=0; i
    ')); - } else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) { - $('#content > *').wrapAll($('
    ')); - $('#content').append($('
    ')); - } else if (location === 'header' && !$('#content [widget-area="header"]').length) { - $('#content').prepend($('
    ')); + function renderWidgets(locations) { + var areaDatas = []; + + $.get(RELATIVE_PATH + '/api/widgets/render', { + locations: locations, + template: template + '.tpl', + url: url + }, function(renderedAreas) { + for (var x=0; x
    ')); + } else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) { + $('#content > *').wrapAll($('
    ')); + $('#content').append($('
    ')); + } else if (location === 'header' && !$('#content [widget-area="header"]').length) { + $('#content').prepend($('
    ')); + } - if (!renderedWidgets.length) { - area.addClass('hidden'); - ajaxify.widgets.reposition(location); - } + area = $('#content [widget-area="' + location + '"]'); + } - $('#content [widget-area] img:not(.user-img)').addClass('img-responsive'); - checkCallback(); - }); - } + area.html(html); - function checkCallback() { - numLocations--; - if (numLocations < 0) { + if (!renderedWidgets.length) { + area.addClass('hidden'); + ajaxify.widgets.reposition(location); + } + + $('#content [widget-area] img:not(.user-img)').addClass('img-responsive'); + } + $(window).trigger('action:widgets.loaded', {}); + if (typeof callback === 'function') { callback(); } - } - } - - for (var i=0; i Date: Tue, 16 Sep 2014 10:17:27 -0400 Subject: [PATCH 258/372] re-enabling updateLastOnlineTime - calling that middleware for page loads only instead of every API call --- src/middleware/middleware.js | 2 -- src/routes/index.js | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 706480af9b..c9e1ce42c1 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -36,8 +36,6 @@ middleware.authenticate = function(req, res, next) { middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(); middleware.updateLastOnlineTime = function(req, res, next) { - return next(); - if(req.user) { user.updateLastOnlineTime(req.user.uid); } diff --git a/src/routes/index.js b/src/routes/index.js index ee2779ee9d..af603db310 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -161,7 +161,7 @@ module.exports = function(app, middleware) { }; pluginRouter.hotswapId = 'plugins'; - app.all(relativePath + '/api/?*', middleware.updateLastOnlineTime, middleware.prepareAPI); + app.all(relativePath + '/api/?*', middleware.prepareAPI); app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI); app.all(relativePath + '/admin/?*', middleware.ensureLoggedIn, middleware.admin.isAdmin); @@ -181,6 +181,7 @@ module.exports = function(app, middleware) { * */ pageRouter.use(middleware.incrementPageViews); + pageRouter.use(middleware.updateLastOnlineTime); mainRoutes(pageRouter, middleware, controllers); staticRoutes(pageRouter, middleware, controllers); From 22577917da9f3727101ca3a7e6dd20d5914e10b3 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 10:37:10 -0400 Subject: [PATCH 259/372] closes https://github.com/NodeBB/NodeBB/issues/2093 --- src/controllers/admin.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 13b0bfde7b..241afa9699 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -38,6 +38,9 @@ adminController.home = function(req, res, next) { stats: function(next) { getStats(next); }, + pageviews: function(next) { + getPageviews(next); + }, notices: function(next) { var notices = [ {done: !meta.restartRequired, doneText: 'Restart not required', notDoneText:'Restart required'}, @@ -53,11 +56,24 @@ adminController.home = function(req, res, next) { res.render('admin/index', { version: pkg.version, notices: results.notices, - stats: results.stats + stats: results.stats, + pageviews: results.pageviews }); }); }; +function getPageviews(callback) { + async.parallel({ + monthly: function(next) { + db.get('pageviews:monthly', next); + }, + daily: function(next) { + db.get('pageviews:daily', next); + } + }, function(err, results) { + callback(null, results); + }); +} function getStats(callback) { async.parallel([ function(next) { From 32257c9b2f35334837404dd69d575e2d08fb634c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 11:06:10 -0400 Subject: [PATCH 260/372] updateUnreadCount --- public/src/forum/footer.js | 16 ++++++++++++++++ src/socket.io/posts.js | 3 +-- src/socket.io/topics.js | 5 ++--- src/topics/unread.js | 36 ++++++++---------------------------- src/user.js | 4 ++-- 5 files changed, 29 insertions(+), 35 deletions(-) diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index e7e8209a1b..c983e0a834 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -24,6 +24,22 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) .attr('data-content', count > 20 ? '20+' : count); } + function onNewPost(data) { + if (data && data.posts && data.posts.length) { + var post = data.posts[0]; + if (parseInt(post.uid, 10) !== parseInt(app.uid, 10)) { + increaseUnreadCount(); + } + } + } + + function increaseUnreadCount() { + var count = parseInt($('#unread-count').attr('data-content'), 10) + 1; + updateUnreadTopicCount(null, count); + } + + + socket.on('event:new_post', onNewPost); socket.on('event:unread.updateCount', updateUnreadTopicCount); socket.emit('user.getUnreadCount', updateUnreadTopicCount); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 582e3fa7c9..e7d5e330d3 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -51,14 +51,13 @@ SocketPosts.reply = function(socket, data, callback) { return; } for(var i=0; i Date: Tue, 16 Sep 2014 11:07:59 -0400 Subject: [PATCH 261/372] fixed derp --- src/topics/unread.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index 46e045fb2a..0a282d36b2 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -157,7 +157,7 @@ module.exports = function(Topics) { } Topics.pushUnreadCount = function(uid, callback) { - callback = callback || function() {}: + callback = callback || function() {}; if (!uid || parseInt(uid, 10) === 0) { return callback(); From 32a35164d4e2c686bbc907bad20b12f649e34b87 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 11:08:25 -0400 Subject: [PATCH 262/372] ability to set # of workers to run via config or command line (default = max cpus) --- loader.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/loader.js b/loader.js index b8e9c0fe27..628169b99a 100644 --- a/loader.js +++ b/loader.js @@ -2,11 +2,12 @@ var nconf = require('nconf'), fs = require('fs'), + path = require('path'), cluster = require('cluster'), async = require('async'), pidFilePath = __dirname + '/pidfile', output = fs.openSync(__dirname + '/logs/output.log', 'a'), - numCPUs = 12,// require('os').cpus().length, + numCPUs, Loader = { timesStarted: 0, shutdown_queue: [], @@ -16,6 +17,12 @@ var nconf = require('nconf'), } }; +nconf.argv().file({ + file: path.join(__dirname, '/config.json') +}); + +numCPUs = nconf.get('cluster') || require('os').cpus().length; + Loader.init = function() { cluster.setupMaster({ exec: "app.js", @@ -150,8 +157,6 @@ Loader.reload = function() { }); }; -nconf.argv(); - if (nconf.get('daemon') !== false) { if (fs.existsSync(pidFilePath)) { try { From 101ac701efddd1ce2221444eea38c9c2edbbd52d Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 11:11:25 -0400 Subject: [PATCH 263/372] removed hardcoded silent flag from cluster setup --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 628169b99a..0a05bb8d9e 100644 --- a/loader.js +++ b/loader.js @@ -26,7 +26,7 @@ numCPUs = nconf.get('cluster') || require('os').cpus().length; Loader.init = function() { cluster.setupMaster({ exec: "app.js", - silent: false/*process.env.NODE_ENV !== 'development' ? true : false*/ + silent: process.env.NODE_ENV !== 'development' ? true : false }); cluster.on('fork', function(worker) { From e60d88afa646feaafe96d48b9d1b28bbdacaae61 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 11:17:57 -0400 Subject: [PATCH 264/372] cluster by default is now opt-in --- loader.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index 0a05bb8d9e..8b7afb6827 100644 --- a/loader.js +++ b/loader.js @@ -17,12 +17,6 @@ var nconf = require('nconf'), } }; -nconf.argv().file({ - file: path.join(__dirname, '/config.json') -}); - -numCPUs = nconf.get('cluster') || require('os').cpus().length; - Loader.init = function() { cluster.setupMaster({ exec: "app.js", @@ -157,6 +151,16 @@ Loader.reload = function() { }); }; + + + +nconf.argv().file({ + file: path.join(__dirname, '/config.json') +}); + +numCPUs = nconf.get('cluster') || 1; +numCPUs = (numCPUs === true) ? require('os').cpus().length : numCPUs; + if (nconf.get('daemon') !== false) { if (fs.existsSync(pidFilePath)) { try { From 48c123592e49b8fdc6e0bd0e0e8c66ac517d83f4 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 12:19:58 -0400 Subject: [PATCH 265/372] prevent administrators from being deleted - first remove them from admin group --- src/socket.io/admin/user.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 50cc6280a3..b861806df1 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -98,15 +98,21 @@ User.deleteUsers = function(socket, uids, callback) { } async.each(uids, function(uid, next) { - user.delete(uid, function(err) { - if (err) { - return next(err); + user.isAdministrator(uid, function(err, isAdmin) { + if (err || isAdmin) { + return callback(err || new Error('[[error:cant-ban-other-admins]]')); } - events.logAdminUserDelete(socket.uid, uid); + user.delete(uid, function(err) { + if (err) { + return next(err); + } + + events.logAdminUserDelete(socket.uid, uid); - websockets.logoutUser(uid); - next(); + websockets.logoutUser(uid); + next(); + }); }); }, callback); }; From 0a936e5dd18dcf84beae8344e76e1b816c75fe37 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 12:38:27 -0400 Subject: [PATCH 266/372] unreadTids --- src/database/mongo/helpers.js | 2 +- src/groups.js | 10 ++-- src/socket.io/topics.js | 3 +- src/topics/unread.js | 109 ++++++++++++++++------------------ 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index d8e62fb3f1..7a4c160bb8 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -34,6 +34,6 @@ helpers.valueToString = function(value) { helpers.noop = function() {}; -helpers.KEY_LIMIT = 100; +helpers.KEY_LIMIT = 300; module.exports = helpers; \ No newline at end of file diff --git a/src/groups.js b/src/groups.js index b4ee1b9c17..2a1587fd98 100644 --- a/src/groups.js +++ b/src/groups.js @@ -196,15 +196,17 @@ if (err) { return callback(err); } - groupNames = internals.removeEphemeralGroups(groupNames); - if (groupNames.length === 0) { - return callback(null, null); - } + var results = []; uids.forEach(function() { results.push(false); }); + groupNames = internals.removeEphemeralGroups(groupNames); + if (groupNames.length === 0) { + return callback(null, results); + } + async.each(groupNames, function(groupName, next) { Groups.isMembers(uids, groupName, function(err, isMembers) { if (err) { diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 706c43eaed..44f78efcbe 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -46,9 +46,10 @@ SocketTopics.post = function(socket, data, callback) { if (err) { return; } + for(var i=0; i Date: Tue, 16 Sep 2014 12:45:22 -0400 Subject: [PATCH 267/372] update unread count only for logged in users --- public/src/forum/footer.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index c983e0a834..94c07caef8 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -25,20 +25,11 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) } function onNewPost(data) { - if (data && data.posts && data.posts.length) { - var post = data.posts[0]; - if (parseInt(post.uid, 10) !== parseInt(app.uid, 10)) { - increaseUnreadCount(); - } + if (parseInt(app.uid, 10)) { + socket.emit('user.getUnreadCount', updateUnreadTopicCount); } } - function increaseUnreadCount() { - var count = parseInt($('#unread-count').attr('data-content'), 10) + 1; - updateUnreadTopicCount(null, count); - } - - socket.on('event:new_post', onNewPost); socket.on('event:unread.updateCount', updateUnreadTopicCount); From ae88eee8e3086afa863e44ac02c6ab1417aba95a Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 12:52:25 -0400 Subject: [PATCH 268/372] improving the warning message before deleting account --- public/language/ar/user.json | 2 +- public/language/cs/user.json | 2 +- public/language/de/user.json | 2 +- public/language/en@pirate/user.json | 2 +- public/language/en_GB/user.json | 2 +- public/language/en_US/user.json | 2 +- public/language/es/user.json | 2 +- public/language/et/user.json | 2 +- public/language/fa_IR/user.json | 2 +- public/language/fi/user.json | 2 +- public/language/fr/user.json | 2 +- public/language/he/user.json | 2 +- public/language/hu/user.json | 2 +- public/language/it/user.json | 2 +- public/language/ja/user.json | 2 +- public/language/ko/user.json | 2 +- public/language/lt/user.json | 2 +- public/language/ms/user.json | 2 +- public/language/nb/user.json | 2 +- public/language/nl/user.json | 2 +- public/language/pl/user.json | 2 +- public/language/pt_BR/user.json | 2 +- public/language/ro/user.json | 2 +- public/language/ru/user.json | 2 +- public/language/sc/user.json | 2 +- public/language/sk/user.json | 2 +- public/language/sv/user.json | 2 +- public/language/th/user.json | 2 +- public/language/tr/user.json | 2 +- public/language/vi/user.json | 2 +- public/language/zh_CN/user.json | 2 +- public/language/zh_TW/user.json | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/public/language/ar/user.json b/public/language/ar/user.json index a1aa9dc80f..980fe54962 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -5,7 +5,7 @@ "email": "البريد الإلكتروني", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "الاسم الكامل", "website": "الموقع الإلكتروني", "location": "موقع", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 13be6ba018..fc341e2e27 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Jméno a příjmení", "website": "Webové stránky", "location": "Poloha", diff --git a/public/language/de/user.json b/public/language/de/user.json index c264b87190..ac5142d047 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -5,7 +5,7 @@ "email": "E-Mail", "confirm_email": "E-Mail bestätigen", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Kompletter Name", "website": "Homepage", "location": "Wohnort", diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index a8efc3d857..81446843ba 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Full Name", "website": "Website", "location": "Location", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index a113ac03d8..772994f440 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -6,7 +6,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Full Name", "website": "Website", diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index 50848c7f83..1156c94b85 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Full Name", "website": "Website", "location": "Location", diff --git a/public/language/es/user.json b/public/language/es/user.json index 3d711138d2..7096d48bc1 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -5,7 +5,7 @@ "email": "Correo Electrónico", "confirm_email": "Repetir correo electrónico", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nombre completo", "website": "Sitio Web", "location": "Ubicación", diff --git a/public/language/et/user.json b/public/language/et/user.json index 4ecb16dac4..e791df8333 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Kinnita email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Täisnimi", "website": "Koduleht", "location": "Asukoht", diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index f0212723e2..73a1370f48 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -5,7 +5,7 @@ "email": "رایانامه", "confirm_email": "تأیید رایانامه", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "نام کامل", "website": "تارنما", "location": "محل سکونت", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index d008e94b62..c3577bb745 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -5,7 +5,7 @@ "email": "Sähköposti", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Koko nimi", "website": "Kotisivu", "location": "Sijainti", diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 68a99a577e..8af112838f 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirmer l'adresse email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nom", "website": "Site web", "location": "Emplacement", diff --git a/public/language/he/user.json b/public/language/he/user.json index 292d31f90b..d306d4b410 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -5,7 +5,7 @@ "email": "כתובת אימייל", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "שם מלא", "website": "אתר", "location": "מיקום", diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 909e4ad553..ff3f13efb2 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -5,7 +5,7 @@ "email": "E-mail", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Teljes Név", "website": "Weboldal", "location": "Lakhely", diff --git a/public/language/it/user.json b/public/language/it/user.json index d490e3e1ef..b225460481 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nome e Cognome", "website": "Sito Internet", "location": "Località", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index ddb43c4dd5..7bdc9a761e 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -5,7 +5,7 @@ "email": "メール", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "フルネーム", "website": "ウェブサイト", "location": "ロケーション", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index e3b7b29e83..c619b7015a 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -5,7 +5,7 @@ "email": "이메일", "confirm_email": "이메일 확인", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "이름", "website": "웹 사이트", "location": "거주지", diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 1902cdd399..d3839a30cb 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -5,7 +5,7 @@ "email": "El. paštas", "confirm_email": "Patvirtinti el. paštą", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Vardas ir pavardė", "website": "Tinklalapis", "location": "Vieta", diff --git a/public/language/ms/user.json b/public/language/ms/user.json index 74fffdbd04..ddf8f2d012 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -5,7 +5,7 @@ "email": "Emel", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nama penuh", "website": "Laman Web", "location": "Lokasi", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 5f3f3db9ab..ceb25363f2 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -5,7 +5,7 @@ "email": "E-post", "confirm_email": "Bekfreft e-post", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Fullt navn", "website": "Nettsted", "location": "Plassering", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index df0f112c6f..11e13421c4 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Bevestig uw email adres", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Volledige Naam", "website": "Website", "location": "Locatie", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 65a0258933..0c1918be92 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -5,7 +5,7 @@ "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Pełna nazwa", "website": "Strona WWW", "location": "Położenie", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 366c291a5d..88307d657f 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirmar Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nome Completo", "website": "Website", "location": "Localização", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 124f9076b6..12ab225a2a 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -5,7 +5,7 @@ "email": "Adresă Email", "confirm_email": "Confirmă Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nume Întreg", "website": "Pagină Web", "location": "Locație", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 6da3902849..599fe21700 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Подтвердить Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Полное имя", "website": "Сайт", "location": "Откуда", diff --git a/public/language/sc/user.json b/public/language/sc/user.json index 7c8781dfb8..3557fa4082 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Nùmene e Sambenadu", "website": "Giassu web", "location": "Logu", diff --git a/public/language/sk/user.json b/public/language/sk/user.json index cefdf45dfe..e23a35b187 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Meno a priezvisko", "website": "Webová stránka", "location": "Poloha", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index e4e4c01dcb..677cd7a0e5 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -5,7 +5,7 @@ "email": "Epost", "confirm_email": "Bekräfta epostadress ", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Hela namnet", "website": "Webbsida", "location": "Plats", diff --git a/public/language/th/user.json b/public/language/th/user.json index f6d6861e31..84d59d5cae 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -5,7 +5,7 @@ "email": "อีเมล์", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "ชื่อเต็ม", "website": "เว็บไซต์", "location": "สถานที่", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index fb1316fd7e..ae5b8f5956 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -5,7 +5,7 @@ "email": "E-posta", "confirm_email": "E-posta onayla", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Tam Ad", "website": "Websitesi", "location": "Konum", diff --git a/public/language/vi/user.json b/public/language/vi/user.json index 1f5cb24b9a..a7923a0189 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "Tên đầy đủ", "website": "Website", "location": "Địa điểm", diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index 51d0b25817..d7b76aab4c 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -5,7 +5,7 @@ "email": "电子邮件", "confirm_email": "确认电子邮箱", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "姓名", "website": "网站", "location": "位置", diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index f73d573c02..ed09318784 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", "fullname": "姓名", "website": "網站", "location": "地址", From a224e531f8f0c37a44bda59b55a667919a372c30 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 16 Sep 2014 12:58:30 -0400 Subject: [PATCH 269/372] added a confirmation dialog that asks you to type in your username before being able to delete account --- public/language/ar/user.json | 2 +- public/language/cs/user.json | 2 +- public/language/de/user.json | 2 +- public/language/en@pirate/user.json | 2 +- public/language/en_GB/user.json | 2 +- public/language/en_US/user.json | 2 +- public/language/es/user.json | 2 +- public/language/et/user.json | 2 +- public/language/fa_IR/user.json | 2 +- public/language/fi/user.json | 2 +- public/language/fr/user.json | 2 +- public/language/he/user.json | 2 +- public/language/hu/user.json | 2 +- public/language/it/user.json | 2 +- public/language/ja/user.json | 2 +- public/language/ko/user.json | 2 +- public/language/lt/user.json | 2 +- public/language/ms/user.json | 2 +- public/language/nb/user.json | 2 +- public/language/nl/user.json | 2 +- public/language/pl/user.json | 2 +- public/language/pt_BR/user.json | 2 +- public/language/ro/user.json | 2 +- public/language/ru/user.json | 2 +- public/language/sc/user.json | 2 +- public/language/sk/user.json | 2 +- public/language/sv/user.json | 2 +- public/language/th/user.json | 2 +- public/language/tr/user.json | 2 +- public/language/vi/user.json | 2 +- public/language/zh_CN/user.json | 2 +- public/language/zh_TW/user.json | 2 +- public/src/forum/account/edit.js | 18 ++++++++++++------ 33 files changed, 44 insertions(+), 38 deletions(-) diff --git a/public/language/ar/user.json b/public/language/ar/user.json index 980fe54962..3f6b8cbe4e 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -5,7 +5,7 @@ "email": "البريد الإلكتروني", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "الاسم الكامل", "website": "الموقع الإلكتروني", "location": "موقع", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index fc341e2e27..0668457ebc 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Jméno a příjmení", "website": "Webové stránky", "location": "Poloha", diff --git a/public/language/de/user.json b/public/language/de/user.json index ac5142d047..7e827ae1ac 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -5,7 +5,7 @@ "email": "E-Mail", "confirm_email": "E-Mail bestätigen", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Kompletter Name", "website": "Homepage", "location": "Wohnort", diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index 81446843ba..10c8d164a2 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Full Name", "website": "Website", "location": "Location", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 772994f440..92d50ee4ab 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -6,7 +6,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Full Name", "website": "Website", diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index 1156c94b85..414532fe8f 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Full Name", "website": "Website", "location": "Location", diff --git a/public/language/es/user.json b/public/language/es/user.json index 7096d48bc1..edaa890672 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -5,7 +5,7 @@ "email": "Correo Electrónico", "confirm_email": "Repetir correo electrónico", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nombre completo", "website": "Sitio Web", "location": "Ubicación", diff --git a/public/language/et/user.json b/public/language/et/user.json index e791df8333..848c4daddd 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Kinnita email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Täisnimi", "website": "Koduleht", "location": "Asukoht", diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index 73a1370f48..76ab51bacd 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -5,7 +5,7 @@ "email": "رایانامه", "confirm_email": "تأیید رایانامه", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "نام کامل", "website": "تارنما", "location": "محل سکونت", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index c3577bb745..64fd3511b2 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -5,7 +5,7 @@ "email": "Sähköposti", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Koko nimi", "website": "Kotisivu", "location": "Sijainti", diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 8af112838f..6d832ee81d 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirmer l'adresse email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nom", "website": "Site web", "location": "Emplacement", diff --git a/public/language/he/user.json b/public/language/he/user.json index d306d4b410..7d2b777bfa 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -5,7 +5,7 @@ "email": "כתובת אימייל", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "שם מלא", "website": "אתר", "location": "מיקום", diff --git a/public/language/hu/user.json b/public/language/hu/user.json index ff3f13efb2..024d83aecc 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -5,7 +5,7 @@ "email": "E-mail", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Teljes Név", "website": "Weboldal", "location": "Lakhely", diff --git a/public/language/it/user.json b/public/language/it/user.json index b225460481..691a818241 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nome e Cognome", "website": "Sito Internet", "location": "Località", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 7bdc9a761e..8d69a78188 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -5,7 +5,7 @@ "email": "メール", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "フルネーム", "website": "ウェブサイト", "location": "ロケーション", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index c619b7015a..7e4f4f760a 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -5,7 +5,7 @@ "email": "이메일", "confirm_email": "이메일 확인", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "이름", "website": "웹 사이트", "location": "거주지", diff --git a/public/language/lt/user.json b/public/language/lt/user.json index d3839a30cb..7d502c9c1c 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -5,7 +5,7 @@ "email": "El. paštas", "confirm_email": "Patvirtinti el. paštą", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Vardas ir pavardė", "website": "Tinklalapis", "location": "Vieta", diff --git a/public/language/ms/user.json b/public/language/ms/user.json index ddf8f2d012..7d9a3e2989 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -5,7 +5,7 @@ "email": "Emel", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nama penuh", "website": "Laman Web", "location": "Lokasi", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index ceb25363f2..283348db31 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -5,7 +5,7 @@ "email": "E-post", "confirm_email": "Bekfreft e-post", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Fullt navn", "website": "Nettsted", "location": "Plassering", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index 11e13421c4..916a856227 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Bevestig uw email adres", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Volledige Naam", "website": "Website", "location": "Locatie", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 0c1918be92..bcbe736105 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -5,7 +5,7 @@ "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Pełna nazwa", "website": "Strona WWW", "location": "Położenie", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 88307d657f..2ce231dd81 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirmar Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nome Completo", "website": "Website", "location": "Localização", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 12ab225a2a..a3a45e9731 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -5,7 +5,7 @@ "email": "Adresă Email", "confirm_email": "Confirmă Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nume Întreg", "website": "Pagină Web", "location": "Locație", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 599fe21700..86e3a3dd9d 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Подтвердить Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Полное имя", "website": "Сайт", "location": "Откуда", diff --git a/public/language/sc/user.json b/public/language/sc/user.json index 3557fa4082..b8426137bc 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Nùmene e Sambenadu", "website": "Giassu web", "location": "Logu", diff --git a/public/language/sk/user.json b/public/language/sk/user.json index e23a35b187..8281beeff1 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Meno a priezvisko", "website": "Webová stránka", "location": "Poloha", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index 677cd7a0e5..a55c26bb7d 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -5,7 +5,7 @@ "email": "Epost", "confirm_email": "Bekräfta epostadress ", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Hela namnet", "website": "Webbsida", "location": "Plats", diff --git a/public/language/th/user.json b/public/language/th/user.json index 84d59d5cae..e5debb6388 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -5,7 +5,7 @@ "email": "อีเมล์", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "ชื่อเต็ม", "website": "เว็บไซต์", "location": "สถานที่", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index ae5b8f5956..7f4281ce96 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -5,7 +5,7 @@ "email": "E-posta", "confirm_email": "E-posta onayla", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Tam Ad", "website": "Websitesi", "location": "Konum", diff --git a/public/language/vi/user.json b/public/language/vi/user.json index a7923a0189..accaa5bcbd 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Tên đầy đủ", "website": "Website", "location": "Địa điểm", diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index d7b76aab4c..4e22cd19b5 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -5,7 +5,7 @@ "email": "电子邮件", "confirm_email": "确认电子邮箱", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "姓名", "website": "网站", "location": "位置", diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index ed09318784..ed42254f3b 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -5,7 +5,7 @@ "email": "Email", "confirm_email": "Confirm Email", "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account? This action is irreversible and you will not be able to recover any of your data", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "姓名", "website": "網站", "location": "地址", diff --git a/public/src/forum/account/edit.js b/public/src/forum/account/edit.js index d93ca01bbd..4b2645a976 100644 --- a/public/src/forum/account/edit.js +++ b/public/src/forum/account/edit.js @@ -129,15 +129,21 @@ define('forum/account/edit', ['forum/account/header', 'uploader'], function(head function handleAccountDelete() { $('#deleteAccountBtn').on('click', function() { translator.translate('[[user:delete_account_confirm]]', function(translated) { - bootbox.confirm(translated, function(confirm) { + bootbox.confirm(translated + '

    ', function(confirm) { if (!confirm) { return; } - socket.emit('user.deleteAccount', {}, function(err) { - if (!err) { - app.logout(); - } - }); + + if ($('#confirm-username').val() !== app.username) { + app.alertError('[[error:invalid-username]]'); + return false; + } else { + socket.emit('user.deleteAccount', {}, function(err) { + if (!err) { + app.logout(); + } + }); + } }); }); return false; From 97cb138cfceb613e3d3053555246c7392617ec2d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 12:58:51 -0400 Subject: [PATCH 270/372] doesnt seem to be used --- public/src/forum/unread.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js index a3e3bb1b10..342102f634 100644 --- a/public/src/forum/unread.js +++ b/public/src/forum/unread.js @@ -45,11 +45,6 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'], $('#topics-container').empty(); $('#category-no-topics').removeClass('hidden'); $('.markread').addClass('hidden'); - - $('#numUnreadBadge') - .removeClass('badge-important') - .addClass('badge-inverse') - .html('0'); }); }); From 451c5b40fd092c25013084755f7ae71bb77cda1e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 13:10:30 -0400 Subject: [PATCH 271/372] removed debug --- src/database/mongo/hash.js | 9 --------- src/database/mongo/helpers.js | 2 -- src/database/mongo/sets.js | 4 ---- 3 files changed, 15 deletions(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index f5659377a3..791cbc12cc 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -25,10 +25,6 @@ module.exports = function(db, module) { }; module.getObjects = function(keys, callback) { - if (keys.length > helpers.KEY_LIMIT) { - var e = new Error('too many keys'); - winston.warn('[HASH - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); - } db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function(err, data) { if(err) { return callback(err); @@ -59,11 +55,6 @@ module.exports = function(db, module) { }; module.getObjectsFields = function(keys, fields, callback) { - if (keys.length > helpers.KEY_LIMIT) { - var e = new Error('too many keys'); - winston.warn('[HASH - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); - } - var _fields = { _id: 0, _key: 1 diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 7a4c160bb8..e74c3fb373 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -34,6 +34,4 @@ helpers.valueToString = function(value) { helpers.noop = function() {}; -helpers.KEY_LIMIT = 300; - module.exports = helpers; \ No newline at end of file diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index d247a1f945..b70b3a044a 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -123,10 +123,6 @@ module.exports = function(db, module) { }; module.getSetsMembers = function(keys, callback) { - if (keys.length > helpers.KEY_LIMIT) { - var e = new Error('too many keys'); - winston.warn('[SET - TOO_MANY_KEYS] ' + keys.length + ' ' + keys[0] + ' ' + keys[keys.length - 1] + '\n', e.stack); - } db.collection('objects').find({_key: {$in: keys}}, {_key: 1, members: 1}).toArray(function(err, data) { if (err) { return callback(err); From b85ff61de6c7dcf30a58ea22e67f3db07c2bfd49 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 16 Sep 2014 13:27:26 -0400 Subject: [PATCH 272/372] refactored chat notification system to wait a bit before sending notif/email, closes #2098 --- public/language/en_GB/email.json | 7 ++++- src/emailer.js | 4 +-- src/messaging.js | 53 ++++++++++++++++++++++++++++++-- src/notifications.js | 1 + src/socket.io/modules.js | 19 +----------- 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/public/language/en_GB/email.json b/public/language/en_GB/email.json index 87d3a5be39..bfcf159de8 100644 --- a/public/language/en_GB/email.json +++ b/public/language/en_GB/email.json @@ -17,10 +17,15 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", + "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", + "closing": "Thanks!" } \ No newline at end of file diff --git a/src/emailer.js b/src/emailer.js index 7d3d281add..362309f7d7 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -31,7 +31,7 @@ Emailer.send = function(template, uid, params) { email: async.apply(User.getUserField, uid, 'email'), settings: async.apply(User.getSettings, uid) }, function(err, results) { - async.map([results.html, results.plaintext], function(raw, next) { + async.map([results.html, results.plaintext, params.subject], function(raw, next) { translator.translate(raw, results.settings.language || meta.config.defaultLang || 'en_GB', function(translated) { next(undefined, translated); }); @@ -45,7 +45,7 @@ Emailer.send = function(template, uid, params) { Plugins.fireHook('action:email.send', { to: results.email, from: Meta.config['email:from'] || 'no-reply@localhost.lan', - subject: params.subject, + subject: translated[2], html: translated[0], plaintext: translated[1], template: template, diff --git a/src/messaging.js b/src/messaging.js index 16a56dd23e..bbea385473 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -2,16 +2,19 @@ var db = require('./database'), async = require('async'), + nconf = require('nconf'), winston = require('winston'), user = require('./user'), plugins = require('./plugins'), meta = require('./meta'), utils = require('../public/src/utils'), notifications = require('./notifications'), - userNotifications = require('./user/notifications'); - + userNotifications = require('./user/notifications'), + websockets = require('./socket.io'), + emailer = require('./emailer'); (function(Messaging) { + Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser function sortUids(fromuid, touid) { return [fromuid, touid].sort(); @@ -336,6 +339,52 @@ var db = require('./database'), } return (matrix[b.length][a.length] / b.length < 0.1); + }; + + Messaging.notifyUser = function(fromuid, touid, messageObj) { + var queueObj = Messaging.notifyQueue[fromuid + ':' + touid]; + if (queueObj) { + queueObj.message.content += '\n' + messageObj.content; + clearTimeout(queueObj.timeout); + } else { + queueObj = Messaging.notifyQueue[fromuid + ':' + touid] = { + message: messageObj + }; + } + + queueObj.timeout = setTimeout(function() { + sendNotifications(fromuid, touid, queueObj.message, function(err) { + if (!err) { + delete Messaging.notifyQueue[fromuid + ':' + touid]; + } + }); + }, 1000*60); // wait 60s before sending + }; + + function sendNotifications(fromuid, touid, messageObj, callback) { + // todo #1798 -- this should check if the user is in room `chat_{uidA}_{uidB}` instead, see `Sockets.uidInRoom(uid, room);` + if (!websockets.isUserOnline(touid)) { + notifications.create({ + bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', + bodyLong: messageObj.content, + path: nconf.get('relative_path') + '/chats/' + utils.slugify(messageObj.fromUser.username), + nid: 'chat_' + fromuid + '_' + touid, + from: fromuid + }, function(err, notification) { + if (!err && notification) { + notifications.push(notification, [touid], callback); + } + }); + + emailer.send('notif_chat', touid, { + subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]', + username: messageObj.toUser.username, + summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', + message: messageObj, + site_title: meta.config.site_title || 'NodeBB', + url: nconf.get('url') + '/chats/' + utils.slugify(messageObj.fromUser.username) + }); + } } }(exports)); diff --git a/src/notifications.js b/src/notifications.js index b1ac1639f6..0e38a3eec7 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -146,6 +146,7 @@ var async = require('async'), db.sortedSetsRemoveRangeByScore(readKeys, 0, oneWeekAgo); plugins.fireHook('action:notification.pushed', {notification: notification, uids: uids}); + callback(); for(var i=0; i Date: Tue, 16 Sep 2014 13:54:03 -0400 Subject: [PATCH 273/372] re: #2098, chat message emails are now opt-in --- public/language/en_GB/user.json | 1 + src/messaging.js | 18 +++++++++++------- src/user/settings.js | 5 ++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 92d50ee4ab..2948c47292 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -57,6 +57,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(", diff --git a/src/messaging.js b/src/messaging.js index bbea385473..0d9d9ca3fb 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -376,13 +376,17 @@ var db = require('./database'), } }); - emailer.send('notif_chat', touid, { - subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]', - username: messageObj.toUser.username, - summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', - message: messageObj, - site_title: meta.config.site_title || 'NodeBB', - url: nconf.get('url') + '/chats/' + utils.slugify(messageObj.fromUser.username) + user.getSettings(messageObj.toUser.uid, function(err, settings) { + if (settings.sendChatNotifications && !parseInt(meta.config.disableEmailSubscriptions, 10)) { + emailer.send('notif_chat', touid, { + subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]', + username: messageObj.toUser.username, + summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', + message: messageObj, + site_title: meta.config.site_title || 'NodeBB', + url: nconf.get('url') + '/chats/' + utils.slugify(messageObj.fromUser.username) + }); + } }); } } diff --git a/src/user/settings.js b/src/user/settings.js index 362019491a..a98a050006 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -35,6 +35,8 @@ module.exports = function(User) { settings.topicPostSort = settings.topicPostSort || meta.config.topicPostSort || 'oldest_to_newest'; settings.followTopicsOnCreate = (settings.followTopicsOnCreate === null || settings.followTopicsOnCreate === undefined) ? true : parseInt(settings.followTopicsOnCreate, 10) === 1; settings.followTopicsOnReply = parseInt(settings.followTopicsOnReply, 10) === 1; + settings.sendChatNotifications = parseInt(settings.sendChatNotifications, 10) === 1; + callback(null, settings); }); }); @@ -83,7 +85,8 @@ module.exports = function(User) { notificationSounds: data.notificationSounds, language: data.language || meta.config.defaultLang, followTopicsOnCreate: data.followTopicsOnCreate, - followTopicsOnReply: data.followTopicsOnReply + followTopicsOnReply: data.followTopicsOnReply, + sendChatNotifications: data.sendChatNotifications }, callback); }; From aae7f2434d7e1de30c1ae163ab69069f70c8af1f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 16:10:02 -0400 Subject: [PATCH 274/372] closes #2088 --- src/categories/recentreplies.js | 4 ++++ src/controllers/index.js | 26 ++++++++++++++++++++------ src/posts.js | 4 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 97199e636e..a215a764d5 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -25,7 +25,11 @@ module.exports = function(Categories) { }; Categories.getRecentTopicReplies = function(categoryData, uid, callback) { + if (!Array.isArray(categoryData) || !categoryData.length) { + return callback(null, []); + } async.map(categoryData, getRecentTopicPids, function(err, results) { + var pids = _.flatten(results); pids = pids.filter(function(pid, index, array) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 97afb0d4e4..29c916a1cf 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -68,13 +68,27 @@ Controllers.home = function(req, res, next) { if (err) { return next(err); } + var childCategories = []; - // Remove child categories, as they don't belong on the home page - categoryData = categoryData.filter(function(categoryObj) { - return !categoryObj.parent; - }); + for(var i=categoryData.length - 1; i>=0; --i) { + + if (Array.isArray(categoryData[i].children) && categoryData[i].children.length) { + childCategories.push.apply(childCategories, categoryData[i].children); + } + + if (categoryData[i].parent) { + categoryData.splice(i, 1); + } + } - categories.getRecentTopicReplies(categoryData, uid, function(err) { + async.parallel([ + function(next) { + categories.getRecentTopicReplies(categoryData, uid, next); + }, + function(next) { + categories.getRecentTopicReplies(childCategories, uid, next); + } + ], function(err) { next(err, categoryData); }); }); @@ -182,7 +196,7 @@ Controllers.confirmEmail = function(req, res, next) { Controllers.sitemap = function(req, res, next) { if (meta.config['feeds:disableSitemap'] === '1') { - return res.redirect(nconf.get('relative_path') + '/404') + return res.redirect(nconf.get('relative_path') + '/404'); } var sitemap = require('../sitemap.js'); diff --git a/src/posts.js b/src/posts.js index 6eb1e9eae9..c0f0d91cb8 100644 --- a/src/posts.js +++ b/src/posts.js @@ -221,6 +221,10 @@ var async = require('async'), options.stripTags = options.hasOwnProperty('stripTags') ? options.stripTags : false; options.parse = options.hasOwnProperty('parse') ? options.parse : true; + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } + var keys = pids.map(function(pid) { return 'post:' + pid; }); From b48d81310cc8b8e25d7381b83f3442f39246a08f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 21:43:11 -0400 Subject: [PATCH 275/372] #2095 --- src/batch.js | 61 ++++++++++++++++++++++++++++++++++++++++ src/categories/delete.js | 22 ++++----------- src/posts/delete.js | 24 +++++----------- src/threadTools.js | 31 ++++++++++---------- src/topics/delete.js | 30 +++----------------- 5 files changed, 93 insertions(+), 75 deletions(-) create mode 100644 src/batch.js diff --git a/src/batch.js b/src/batch.js new file mode 100644 index 0000000000..1c3c75627a --- /dev/null +++ b/src/batch.js @@ -0,0 +1,61 @@ + + +'use strict'; + +var async = require('async'), + db = require('./database'), + utils = require('../public/src/utils'); + +(function(Batch) { + + var DEFAULT_BATCH_SIZE = 100; + + Batch.processSortedSet = function(setKey, process, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + callback = typeof callback === 'function' ? callback : function(){}; + options = options || {}; + + if (typeof process !== 'function') { + return callback(new Error('[[error:process-not-a-function]]')); + } + + // custom done condition + options.doneIf = typeof options.doneIf === 'function' ? options.doneIf : function(){}; + + var batch = options.batch || DEFAULT_BATCH_SIZE; + var start = 0; + var end = batch; + var done = false; + + async.whilst( + function() { + return !done; + }, + function(next) { + db.getSortedSetRange(setKey, start, end, function(err, ids) { + if (err) { + return next(err); + } + if (!ids.length || options.doneIf(start, end, ids)) { + done = true; + return next(); + } + process(err, ids, function(err) { + if (err) { + return next(err); + } + start += utils.isNumber(options.alwaysStartAt) ? options.alwaysStartAt : batch + 1; + end = start + batch; + next(); + }); + }); + }, + callback + ); + }; + +}(exports)); \ No newline at end of file diff --git a/src/categories/delete.js b/src/categories/delete.js index 1783ae77b1..c4dfc06c21 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -2,27 +2,23 @@ var async = require('async'), db = require('../database'), + batch = require('../batch'), threadTools = require('../threadTools'); module.exports = function(Categories) { Categories.purge = function(cid, callback) { - - Categories.getTopicIds(cid, 0, -1, function(err, tids) { + batch.processSortedSet('categories:' + cid + ':tid', function(err, tids, next) { if (err) { return callback(err); } async.eachLimit(tids, 10, function(tid, next) { threadTools.purge(tid, 0, next); - }, function(err) { - if (err) { - return callback(err); - } - - purgeCategory(cid, callback); - }); + }, next); + }, {alwaysStartAt: 0}, function(err) { + purgeCategory(cid, callback); }); }; @@ -32,13 +28,7 @@ module.exports = function(Categories) { db.sortedSetRemove('categories:cid', cid, next); }, function(next) { - db.delete('categories:' + cid + ':tid', next); - }, - function(next) { - db.delete('categories:recent_posts:cid:' + cid, next); - }, - function(next) { - db.delete('category:' + cid, next); + db.deleteAll(['categories:' + cid + ':tid', 'categories:recent_posts:cid:' + cid, 'category:' + cid], next); } ], callback); } diff --git a/src/posts/delete.js b/src/posts/delete.js index 3fe36abf8e..dfcff4dc35 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -40,17 +40,11 @@ module.exports = function(Posts) { return callback(err); } - async.parallel([ - function(next) { - db.sortedSetRemove('tid:' + postData.tid + ':posts', pid, next); - }, - function(next) { - db.sortedSetRemove('tid:' + postData.tid + ':posts:votes', pid, next); - }, - function(next) { - db.sortedSetRemove('uid:' + postData.uid + ':posts', pid, next); - } - ], function(err) { + db.sortedSetsRemove([ + 'tid:' + postData.tid + ':posts', + 'tid:' + postData.tid + ':posts:votes', + 'uid:' + postData.uid + ':posts' + ], pid, function(err) { if (err) { return callback(err); } @@ -101,7 +95,7 @@ module.exports = function(Posts) { } var sets = uids.map(function(uid) { - return 'uid:' + uid + ':favourites' + return 'uid:' + uid + ':favourites'; }); db.sortedSetsRemove(sets, pid, function(err) { @@ -143,14 +137,10 @@ module.exports = function(Posts) { db.sortedSetsRemove(downvoterSets, pid, next); }, function(next) { - db.delete('pid:' + pid + ':upvote', next); - }, - function(next) { - db.delete('pid:' + pid + ':downvote', next); + db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], next); } ], callback); }); } - }; diff --git a/src/threadTools.js b/src/threadTools.js index 613a456443..1e69868e62 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -13,7 +13,8 @@ var winston = require('winston'), meta = require('./meta'), websockets = require('./socket.io'), events = require('./events'), - plugins = require('./plugins'); + plugins = require('./plugins'), + batch = require('./batch'); (function(ThreadTools) { @@ -71,26 +72,24 @@ var winston = require('winston'), } ThreadTools.purge = function(tid, uid, callback) { - async.parallel({ - topic: function(next) { - topics.getTopicFields(tid, ['cid'], next); - }, - pids: function(next) { - topics.getPids(tid, next); - } - }, function(err, results) { + batch.processSortedSet('tid:' + tid + ':posts', function(err, pids, next) { + async.eachLimit(pids, 10, posts.purge, next); + }, {alwaysStartAt: 0}, function(err) { if (err) { return callback(err); } - async.parallel([ - function(next) { - async.eachLimit(results.pids, 10, posts.purge, next); - }, - function(next) { - topics.purge(tid, next); + topics.getTopicField(tid, 'mainPid', function(err, mainPid) { + if (err) { + return callback(err); } - ], callback); + posts.purge(mainPid, function(err) { + if (err) { + return callback(err); + } + topics.purge(tid, callback); + }); + }); }); }; diff --git a/src/topics/delete.js b/src/topics/delete.js index 2a654b551c..06e289bdaf 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -44,10 +44,7 @@ module.exports = function(Topics) { Topics.removeRecent(tid, next); }, function(next) { - db.sortedSetRemove('topics:posts', tid, next); - }, - function(next) { - db.sortedSetRemove('topics:views', tid, next); + db.sortedSetsRemove(['topics:posts', 'topics:views'], tid, next); } ], function(err) { if (err) { @@ -90,22 +87,10 @@ module.exports = function(Topics) { Topics.purge = function(tid, callback) { async.parallel([ function(next) { - db.delete('tid:' + tid + ':followers', next); - }, - function(next) { - db.delete('tid:' + tid + ':read_by_uid', next); - }, - function(next) { - db.sortedSetRemove('topics:tid', tid, next); - }, - function(next) { - Topics.removeRecent(tid, next); + db.deleteAll(['tid:' + tid + ':followers', 'tid:' + tid + ':read_by_uid'], next); }, function(next) { - db.sortedSetRemove('topics:posts', tid, next); - }, - function(next) { - db.sortedSetRemove('topics:views', tid, next); + db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next); }, function(next) { deleteTopicFromCategoryAndUser(tid, next); @@ -128,14 +113,7 @@ module.exports = function(Topics) { return callback(err); } - async.parallel([ - function(next) { - db.sortedSetRemove('categories:' + topicData.cid + ':tid', tid, next); - }, - function(next) { - db.sortedSetRemove('uid:' + topicData.uid + ':topics', tid, next); - } - ], function(err) { + db.sortedSetsRemove(['categories:' + topicData.cid + ':tid', 'uid:' + topicData.uid + ':topics'], tid, function(err) { if (err) { return callback(err); } From 6a64a3ab9227474c5766f902db1c682129439ab5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 21:45:46 -0400 Subject: [PATCH 276/372] check err --- src/categories/delete.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/categories/delete.js b/src/categories/delete.js index c4dfc06c21..6328817ecc 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -18,6 +18,9 @@ module.exports = function(Categories) { threadTools.purge(tid, 0, next); }, next); }, {alwaysStartAt: 0}, function(err) { + if (err) { + return callback(err); + } purgeCategory(cid, callback); }); }; From 4b80bbb9c4850652996a3b48445ea035272ac18e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Sep 2014 22:25:12 -0400 Subject: [PATCH 277/372] optimized user.delete --- src/database/level/sets.js | 4 ++ src/database/mongo/sets.js | 21 +++++++++++ src/database/redis/sets.js | 12 ++++++ src/user/delete.js | 77 +++++++++++--------------------------- 4 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/database/level/sets.js b/src/database/level/sets.js index 525477db7d..b7697f1209 100644 --- a/src/database/level/sets.js +++ b/src/database/level/sets.js @@ -29,6 +29,10 @@ module.exports = function(db, module) { }); }; + module.setsRemove = function(keys, value, callback) { + throw new Error('not-implemented'); + }; + module.isSetMember = function(key, value, callback) { module.getListRange(key, 0, -1, function(err, set) { callback(err, set.indexOf(value) !== -1); diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index b70b3a044a..a48b51cded 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -69,6 +69,27 @@ module.exports = function(db, module) { db.collection('objects').update({_key: key}, {$pullAll: {members: value}}, callback); }; + module.setsRemove = function(keys, value, callback) { + callback = callback || helpers.noop; + if (!Array.isArray(keys) || !keys.length) { + return callback(); + } + value = helpers.valueToString(value); + + var bulk = db.collection('objects').initializeUnorderedBulkOp(); + + for(var i=0; i Date: Tue, 16 Sep 2014 23:28:04 -0400 Subject: [PATCH 278/372] faster topic move --- src/categories/recentreplies.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index a215a764d5..7365956219 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -103,24 +103,13 @@ module.exports = function(Categories) { Categories.moveRecentReplies = function(tid, oldCid, cid) { - function movePost(postData, next) { - async.parallel([ - function(next) { - db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, postData.pid, next); - }, - function(next) { - db.sortedSetAdd('categories:recent_posts:cid:' + cid, postData.timestamp, postData.pid, next); - } - ], next); - } - updatePostCount(tid, oldCid, cid); topics.getPids(tid, function(err, pids) { if (err) { return winston.error(err.message); } - if (pids && !pids.length) { + if (!Array.isArray(pids) || !pids.length) { return; } @@ -128,12 +117,23 @@ module.exports = function(Categories) { return 'post:' + pid; }); - db.getObjectsFields(keys, ['pid', 'timestamp'], function(err, postData) { + db.getObjectsFields(keys, ['timestamp'], function(err, postData) { if (err) { return winston.error(err.message); } - async.each(postData, movePost, function(err) { + var timestamps = postData.map(function(post) { + return post && post.timestamp; + }); + + async.parallel([ + function(next) { + db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, pids, next); + }, + function(next) { + db.sortedSetAdd('categories:recent_posts:cid:' + cid, timestamps, pids, next); + } + ], function(err) { if (err) { winston.error(err.message); } From ca7f35dd5efbb5603bfbc044dc3f02fd1f274e34 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 00:28:19 -0400 Subject: [PATCH 279/372] trimming alert title/message --- public/src/modules/alerts.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 83e4daa833..bdf55ae917 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -2,12 +2,13 @@ /* globals define, translator, templates */ define('alerts', function() { - var module = {}; module.alert = function (params) { params.alert_id = 'alert_button_' + (params.alert_id ? params.alert_id : new Date().getTime()); - params.title = params.title || ''; + params.title = params.title.trim() || ''; + params.message = params.message.trim(); + params.location = params.location || 'right-top'; params.type = params.type || 'info'; From a6089ba4aa2547bf5036ca7737b09fada351e2ab Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 00:41:19 -0400 Subject: [PATCH 280/372] closes https://github.com/NodeBB/NodeBB/issues/2096 --- src/middleware/middleware.js | 9 +++++++++ src/routes/index.js | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index c9e1ce42c1..aec822d3a5 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -77,6 +77,15 @@ middleware.redirectToAccountIfLoggedIn = function(req, res, next) { } }; +middleware.redirectToLoginIfGuest = function(req, res, next) { + if (!req.user || parseInt(req.user.uid, 10) === 0) { + req.session.returnTo = req.url; + return res.redirect('/login'); + } else { + next(); + } +}; + middleware.addSlug = function(req, res, next) { function redirect(method, id, name) { method(id, 'slug', function(err, slug) { diff --git a/src/routes/index.js b/src/routes/index.js index af603db310..ab72136b29 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -117,8 +117,8 @@ function accountRoutes(app, middleware, controllers) { app.get('/notifications', middleware.buildHeader, middleware.authenticate, controllers.accounts.getNotifications); app.get('/api/notifications', middleware.authenticate, controllers.accounts.getNotifications); - app.get('/chats/:userslug?', middleware.buildHeader, middleware.authenticate, controllers.accounts.getChats); - app.get('/api/chats/:userslug?', middleware.authenticate, controllers.accounts.getChats); + app.get('/chats/:userslug?', middleware.buildHeader, middleware.redirectToLoginIfGuest, controllers.accounts.getChats); + app.get('/api/chats/:userslug?', middleware.redirectToLoginIfGuest, controllers.accounts.getChats); } function userRoutes(app, middleware, controllers) { From 74c438dc710dcc901b3a345917afda53c2d4c287 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 09:21:22 -0400 Subject: [PATCH 281/372] latest translations and fallbacks --- public/language/ar/email.json | 5 ++- public/language/ar/error.json | 1 + public/language/ar/topic.json | 2 +- public/language/ar/user.json | 1 + public/language/cs/email.json | 5 ++- public/language/cs/error.json | 1 + public/language/cs/topic.json | 2 +- public/language/cs/user.json | 1 + public/language/de/email.json | 5 ++- public/language/de/error.json | 13 ++++---- public/language/de/topic.json | 2 +- public/language/de/user.json | 7 +++-- public/language/en@pirate/email.json | 5 ++- public/language/en@pirate/error.json | 1 + public/language/en@pirate/topic.json | 2 +- public/language/en@pirate/user.json | 1 + public/language/en_US/email.json | 5 ++- public/language/en_US/error.json | 1 + public/language/en_US/topic.json | 2 +- public/language/en_US/user.json | 1 + public/language/es/email.json | 5 ++- public/language/es/error.json | 1 + public/language/es/topic.json | 2 +- public/language/es/user.json | 1 + public/language/et/email.json | 5 ++- public/language/et/error.json | 1 + public/language/et/topic.json | 2 +- public/language/et/user.json | 1 + public/language/fa_IR/email.json | 5 ++- public/language/fa_IR/error.json | 1 + public/language/fa_IR/topic.json | 2 +- public/language/fa_IR/user.json | 1 + public/language/fi/email.json | 5 ++- public/language/fi/error.json | 1 + public/language/fi/topic.json | 2 +- public/language/fi/user.json | 1 + public/language/fr/category.json | 2 +- public/language/fr/email.json | 5 ++- public/language/fr/error.json | 1 + public/language/fr/topic.json | 2 +- public/language/fr/user.json | 1 + public/language/he/email.json | 5 ++- public/language/he/error.json | 1 + public/language/he/topic.json | 2 +- public/language/he/user.json | 1 + public/language/hu/email.json | 5 ++- public/language/hu/error.json | 1 + public/language/hu/topic.json | 2 +- public/language/hu/user.json | 1 + public/language/it/email.json | 5 ++- public/language/it/error.json | 1 + public/language/it/topic.json | 2 +- public/language/it/user.json | 1 + public/language/ja/email.json | 5 ++- public/language/ja/error.json | 1 + public/language/ja/topic.json | 2 +- public/language/ja/user.json | 1 + public/language/ko/email.json | 5 ++- public/language/ko/error.json | 1 + public/language/ko/topic.json | 2 +- public/language/ko/user.json | 1 + public/language/lt/email.json | 5 ++- public/language/lt/error.json | 1 + public/language/lt/topic.json | 2 +- public/language/lt/user.json | 1 + public/language/ms/email.json | 5 ++- public/language/ms/error.json | 1 + public/language/ms/topic.json | 2 +- public/language/ms/user.json | 1 + public/language/nb/email.json | 5 ++- public/language/nb/error.json | 1 + public/language/nb/topic.json | 2 +- public/language/nb/user.json | 1 + public/language/nl/category.json | 2 +- public/language/nl/email.json | 5 ++- public/language/nl/error.json | 13 ++++---- public/language/nl/notifications.json | 12 ++++---- public/language/nl/recent.json | 2 +- public/language/nl/search.json | 2 +- public/language/nl/topic.json | 2 +- public/language/nl/user.json | 7 +++-- public/language/pl/email.json | 5 ++- public/language/pl/error.json | 11 ++++--- public/language/pl/topic.json | 2 +- public/language/pl/user.json | 7 +++-- public/language/pt_BR/email.json | 5 ++- public/language/pt_BR/error.json | 1 + public/language/pt_BR/topic.json | 2 +- public/language/pt_BR/user.json | 1 + public/language/ro/email.json | 5 ++- public/language/ro/error.json | 1 + public/language/ro/topic.json | 2 +- public/language/ro/user.json | 1 + public/language/ru/email.json | 5 ++- public/language/ru/error.json | 1 + public/language/ru/topic.json | 2 +- public/language/ru/user.json | 1 + public/language/sc/email.json | 5 ++- public/language/sc/error.json | 1 + public/language/sc/topic.json | 2 +- public/language/sc/user.json | 1 + public/language/sk/email.json | 5 ++- public/language/sk/error.json | 1 + public/language/sk/topic.json | 2 +- public/language/sk/user.json | 1 + public/language/sv/email.json | 5 ++- public/language/sv/error.json | 13 ++++---- public/language/sv/pages.json | 2 +- public/language/sv/topic.json | 4 +-- public/language/sv/user.json | 7 +++-- public/language/th/email.json | 5 ++- public/language/th/error.json | 1 + public/language/th/topic.json | 2 +- public/language/th/user.json | 1 + public/language/tr/email.json | 5 ++- public/language/tr/error.json | 1 + public/language/tr/topic.json | 2 +- public/language/tr/user.json | 1 + public/language/vi/email.json | 39 +++++++++++++----------- public/language/vi/error.json | 31 ++++++++++--------- public/language/vi/global.json | 12 ++++---- public/language/vi/groups.json | 10 +++--- public/language/vi/login.json | 2 +- public/language/vi/modules.json | 24 +++++++-------- public/language/vi/pages.json | 2 +- public/language/vi/tags.json | 8 ++--- public/language/vi/topic.json | 26 ++++++++-------- public/language/vi/user.json | 13 ++++---- public/language/vi/users.json | 4 +-- public/language/zh_CN/category.json | 2 +- public/language/zh_CN/email.json | 5 ++- public/language/zh_CN/error.json | 13 ++++---- public/language/zh_CN/notifications.json | 12 ++++---- public/language/zh_CN/recent.json | 2 +- public/language/zh_CN/search.json | 2 +- public/language/zh_CN/topic.json | 2 +- public/language/zh_CN/user.json | 7 +++-- public/language/zh_TW/email.json | 5 ++- public/language/zh_TW/error.json | 1 + public/language/zh_TW/topic.json | 2 +- public/language/zh_TW/user.json | 1 + 141 files changed, 363 insertions(+), 208 deletions(-) diff --git a/public/language/ar/email.json b/public/language/ar/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ar/email.json +++ b/public/language/ar/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ar/error.json b/public/language/ar/error.json index feb589fd8f..6e6956b0ec 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "المستخدم محظور", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index 930f642748..eed42e4f01 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "هذا الموضوع سوف ينقل إلى فئة", "fork_topic_instruction": "إضغط على الردود لتفريعهم", "fork_no_pids": "لم تختار أي رد", - "fork_success": "تفريع الموضوع بنجاح!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/ar/user.json b/public/language/ar/user.json index 3f6b8cbe4e..01f1439527 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "هذا المستخدم ليس لديه أي أتباع :(", "follows_no_one": "هذا المستخدم لا يتبع أحد :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/cs/email.json b/public/language/cs/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/cs/email.json +++ b/public/language/cs/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index fd1e316089..b3a0eb1aeb 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Toto téma bude přesunuto do kategorie", "fork_topic_instruction": "Vyber příspěvky, které chceš oddělit", "fork_no_pids": "Žádné příspěvky nebyly vybrány!", - "fork_success": "Téma bylo úspěšně rozděleno!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 0668457ebc..1f1230bde3 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Tohoto uživatele nikdo nesleduje :(", "follows_no_one": "Tento uživatel nikoho nesleduje :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/de/email.json b/public/language/de/email.json index e3c67dc47b..6936a71750 100644 --- a/public/language/de/email.json +++ b/public/language/de/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Aktuellste Themen vom %1", "digest.cta": "Klicke hier, um %1 zu besuchen", "digest.unsub.info": "Diese Zusammenfassung wurde dir aufgrund deiner Abonnement-Einstellungen gesendet.", - "digest.unsub.cta": "Klicke hier, um diese Einstellungen zu ändern", "digest.daily.no_topics": "Es gab heute keine aktiven Themen", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Dies ist eine Test-E-Mail, um zu überprüfen, ob der E-Mailer deines NodeBB korrekt eingestellt wurde.", + "unsub.cta": "Click here to alter those settings", "closing": "Danke!" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 4217b44a31..ddf1509f60 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ungültiger Titel", "invalid-user-data": "Ungültige Benutzerdaten", "invalid-password": "Ungültiges Passwort", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Bitte gebe einen Benutzernamen und ein Passwort an", "invalid-pagination-value": "Die Nummerierung ist ungültig", "username-taken": "Der Benutzername ist bereits vergeben", "email-taken": "Die E-Mail-Adresse ist bereits vergeben", "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt. Bitte klicke hier, um deine E-Mail zu bestätigen.", "username-too-short": "Benutzername ist zu kurz", - "username-too-long": "Username too long", + "username-too-long": "Der Benutzername ist zu lang", "user-banned": "Der Benutzer ist gesperrt", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Die Kategorie existiert nicht", "no-topic": "Das Thema existiert nicht", "no-post": "Der Beitrag existiert nicht", @@ -54,9 +55,9 @@ "upload-error": "Upload-Fehler: %1", "signature-too-long": "Die Signatur darf maximal %1 Zeichen enthalten!", "cant-chat-with-yourself": "Du kannst nicht mit dir selber chatten!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.", + "downvoting-disabled": "Downvotes sind deaktiviert.", "not-enough-reputation-to-downvote": "Deine Reputation ist zu niedrig, um diesen Beitrag negativ zu bewerten.", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "Deine Reputation ist nicht gut genug, um diesen Beitrag zu melden.", + "reload-failed": "Es ist ein Problem während des Reloads von NodeBB aufgetreten: \"%1\". NodeBB wird weiterhin clientseitige Assets bereitstellen, allerdings solltest du das, was du vor dem Reload gemacht hast, rückgängig machen." } \ No newline at end of file diff --git a/public/language/de/topic.json b/public/language/de/topic.json index c3642ea0bd..77fbaf000f 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dieses Thema wird verschoben nach", "fork_topic_instruction": "Klicke auf die Beiträge, die du aufspalten willst", "fork_no_pids": "Keine Beiträge ausgewählt!", - "fork_success": "Thema erfolgreich aufgespalten!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Hier den Titel des Themas eingeben...", "composer.discard": "Verwerfen", "composer.submit": "Absenden", diff --git a/public/language/de/user.json b/public/language/de/user.json index 7e827ae1ac..d3b4e12d45 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -4,7 +4,7 @@ "username": "Nutzername", "email": "E-Mail", "confirm_email": "E-Mail bestätigen", - "delete_account": "Delete Account", + "delete_account": "Konto löschen", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Kompletter Name", "website": "Homepage", @@ -52,6 +52,7 @@ "digest_daily": "Täglich", "digest_weekly": "Wöchentlich", "digest_monthly": "Monatlich", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Dieser User hat noch keine Follower.", "follows_no_one": "Dieser User folgt noch niemandem :(", "has_no_posts": "Dieser Nutzer hat noch nichts gepostet.", @@ -64,6 +65,6 @@ "notification_sounds": "Ton abspielen, wenn ich eine Benachrichtigung erhalte.", "browsing": "Browser Einstellungen", "open_links_in_new_tab": "Externe Links in neuem Tab öffnen?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Folge Themen, auf die du antwortest.", + "follow_topics_you_create": "Folge Themen, die du erstellst." } \ No newline at end of file diff --git a/public/language/en@pirate/email.json b/public/language/en@pirate/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/en@pirate/email.json +++ b/public/language/en@pirate/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/en@pirate/topic.json b/public/language/en@pirate/topic.json index aa69261f90..6093dc2302 100644 --- a/public/language/en@pirate/topic.json +++ b/public/language/en@pirate/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", - "fork_success": "Succesfully forked topic!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index 10c8d164a2..6492a2ab79 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/en_US/email.json b/public/language/en_US/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/en_US/email.json +++ b/public/language/en_US/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 200ce83066..c63073194a 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/en_US/topic.json b/public/language/en_US/topic.json index 8ffca481ab..c64d0c9881 100644 --- a/public/language/en_US/topic.json +++ b/public/language/en_US/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", - "fork_success": "Succesfully forked topic!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index 414532fe8f..cdb91a91d1 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/es/email.json b/public/language/es/email.json index b791157e63..c83494b565 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Últimos temas de %1", "digest.cta": "Cliquea aquí para visitar %1", "digest.unsub.info": "Este compendio te fue enviado debido a tus ajustes de subscripción.", - "digest.unsub.cta": "Cliquea aquí para alterar estos ajustes", "digest.daily.no_topics": "No han habido temas activos en el día pasado", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Este es un email de prueba para verificar que el envío de email está ajustado correctamente para tu NodeBB", + "unsub.cta": "Click here to alter those settings", "closing": "¡Gracias!" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index d3fa14fe91..79fd819d2f 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -20,6 +20,7 @@ "username-too-short": "El nombre de usuario es demasiado corto", "username-too-long": "Username too long", "user-banned": "Usuario expulsado", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "La categoría no existe", "no-topic": "El tema no existe.", "no-post": "La publicación no existe", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 1522291a09..d8eafd749c 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Este tema será movido a la categoría", "fork_topic_instruction": "Click en las publicaciones que quieres bifurcar", "fork_no_pids": "¡No seleccionaste publicaciones!", - "fork_success": "¡Bifurcado con exito!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Ingresa el titulo de tu tema", "composer.discard": "Descartar", "composer.submit": "Enviar", diff --git a/public/language/es/user.json b/public/language/es/user.json index edaa890672..f244ffc9ff 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -52,6 +52,7 @@ "digest_daily": "Diariamente", "digest_weekly": "Semanalmente", "digest_monthly": "Mensualmente", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Este miembro no tiene seguidores. :(", "follows_no_one": "Este miembro no sigue a nadie. :(", "has_no_posts": "Este usuario aún no ha publicado nada.", diff --git a/public/language/et/email.json b/public/language/et/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/et/email.json +++ b/public/language/et/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/et/error.json b/public/language/et/error.json index 459b749ca0..51926c86b5 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -20,6 +20,7 @@ "username-too-short": "Kasutajanimi on liiga lühike", "username-too-long": "Username too long", "user-banned": "Kasutaja bannitud", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategooriat ei eksisteeri", "no-topic": "Teemat ei eksisteeri", "no-post": "Postitust ei eksisteeri", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index dc4dcc62cb..8b57bef58d 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "See teema liigutatakse antud kategooriasse", "fork_topic_instruction": "Vajuta postitustele, mida soovid forkida", "fork_no_pids": "Sa ei ole postitusi valinud!", - "fork_success": "Edukalt forkisid teema!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Sisesta teema pealkiri siia...", "composer.discard": "Katkesta", "composer.submit": "Postita", diff --git a/public/language/et/user.json b/public/language/et/user.json index 848c4daddd..9decf2a23e 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -52,6 +52,7 @@ "digest_daily": "Igapäevaselt", "digest_weekly": "Iga nädal", "digest_monthly": "Iga kuu", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Sellel kasutajal pole ühtegi jälgijat :(", "follows_no_one": "See kasutaja ei jälgi kedagi :(", "has_no_posts": "See kasutaja pole midagi postitanud veel.", diff --git a/public/language/fa_IR/email.json b/public/language/fa_IR/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/fa_IR/email.json +++ b/public/language/fa_IR/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 29caeaa0c1..64f4d35823 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -20,6 +20,7 @@ "username-too-short": "نام کاربری خیلی کوتاه است.", "username-too-long": "Username too long", "user-banned": "کاربر محروم شد.", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "چنین دسته‌ای وجود ندارد.", "no-topic": "چنین جستاری وجود ندارد.", "no-post": "چنین دیدگاهی وجود ندارد.", diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json index 1c525fad8b..47f345fc6d 100644 --- a/public/language/fa_IR/topic.json +++ b/public/language/fa_IR/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "این جستار جابه‌جا خواهد شد به دستهٔ", "fork_topic_instruction": "دیدگاه‌هایی را که می‌خواهید به جستار تازه ببرید، برگزینید", "fork_no_pids": "هیچ دیدگاهی انتخاب نشده!", - "fork_success": "جستار با موفقیت منشعب شد.", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "سرنویس جستارتان را اینجا بنویسید...", "composer.discard": "دور بیانداز", "composer.submit": "بفرست", diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index 76ab51bacd..e6085077d4 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -52,6 +52,7 @@ "digest_daily": "روزانه", "digest_weekly": "هفتگی", "digest_monthly": "ماهانه", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "این کاربر هیچ دنبال‌کننده‌ای ندارد :(", "follows_no_one": "این کاربر هیچ کسی را دنبال نمی‌کند :(", "has_no_posts": "این کاربر هنوز هیچ دیدگاهی نگذاشته است.", diff --git a/public/language/fi/email.json b/public/language/fi/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/fi/email.json +++ b/public/language/fi/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 738eb62699..73febab0c3 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -20,6 +20,7 @@ "username-too-short": "Käyttäjänimi on liian lyhyt", "username-too-long": "Username too long", "user-banned": "Käyttäjä on estetty", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategoriaa ei ole olemassa", "no-topic": "Aihetta ei ole olemassa", "no-post": "Viestiä ei ole olemassa", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index ad1ef0f1a0..bccb155bf4 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Tämä keskustelu siirretään aihealueelle", "fork_topic_instruction": "Napsauta viestejä, jotka haluat haaroittaa", "fork_no_pids": "Ei valittuja viestejä!", - "fork_success": "Keskustelu haaroitettu onnistuneesti!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Syötä aiheesi otsikko tähän...", "composer.discard": "Hylkää", "composer.submit": "Lähetä", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index 64fd3511b2..930f9cffb0 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Kukaan ei seuraa tätä käyttäjää :(", "follows_no_one": "Tämä käyttäjä ei seuraa ketään :(", "has_no_posts": "Tämä käyttäjä ei ole kirjoittanut vielä mitään.", diff --git a/public/language/fr/category.json b/public/language/fr/category.json index c680ae7db9..47b8ebb764 100644 --- a/public/language/fr/category.json +++ b/public/language/fr/category.json @@ -4,5 +4,5 @@ "browsing": "parcouru par", "no_replies": "Personne n'a répondu", "share_this_category": "Partager cette catégorie", - "ignore": "Ignore" + "ignore": "Ignorer" } \ No newline at end of file diff --git a/public/language/fr/email.json b/public/language/fr/email.json index 88cb8c270a..88872a11fb 100644 --- a/public/language/fr/email.json +++ b/public/language/fr/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Derniers sujets de %1 :", "digest.cta": "Cliquez ici pour aller sur %1", "digest.unsub.info": "Ce message vous a été envoyé en raison de vos paramètres d'abonnement.", - "digest.unsub.cta": "Cliquez ici pour modifier ces paramètres", "digest.daily.no_topics": "Il n'y a eu aucun sujet actif ces derniers jours", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Ceci est un email de test pour vérifier que l'emailer est correctement configuré pour NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Merci !" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 821061cee1..bcc272f6d8 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nom d'utilisateur trop court", "username-too-long": "Username too long", "user-banned": "Utilisateur banni", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Cette catégorie n'existe pas", "no-topic": "Ce sujet n'existe pas", "no-post": "Ce message n'existe pas", diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index 47473793ec..f73fad2ed0 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ce sujet sera déplacé vers la catégorie", "fork_topic_instruction": "Cliquez sur les postes à scinder", "fork_no_pids": "Aucun post sélectionné !", - "fork_success": "Sujet scindé !", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Entrer le titre du sujet ici ...", "composer.discard": "Abandonner", "composer.submit": "Envoyer", diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 6d832ee81d..a476ad9f05 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -52,6 +52,7 @@ "digest_daily": "Quotidien", "digest_weekly": "Hebdomadaire", "digest_monthly": "Mensuel", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Cet utilisateur n'est suivi par personne :(", "follows_no_one": "Cet utilisateur ne suit personne :(", "has_no_posts": "Ce membre n'a rien posté pour le moment", diff --git a/public/language/he/email.json b/public/language/he/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/he/email.json +++ b/public/language/he/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/he/error.json b/public/language/he/error.json index 2df9c1b918..28cd04e663 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -20,6 +20,7 @@ "username-too-short": "שם משתמש קצר מדי", "username-too-long": "Username too long", "user-banned": "המשתמש חסום", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "קטגוריה אינה קיימת", "no-topic": "נושא אינו קיים", "no-post": "פוסט אינו קיים", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 8bff5a472c..59d3ab6736 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "נושא זה יועבר לקטגוריה", "fork_topic_instruction": "לחץ על הפוסטים שברצונך לשכפל", "fork_no_pids": "לא בחרת אף פוסט!", - "fork_success": "הנושא שוכפל בהצלחה!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "הכנס את כותרת הנושא כאן...", "composer.discard": "מחק", "composer.submit": "שלח", diff --git a/public/language/he/user.json b/public/language/he/user.json index 7d2b777bfa..58ee8081e5 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -52,6 +52,7 @@ "digest_daily": "יומי", "digest_weekly": "שבועי", "digest_monthly": "חודשי", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "למשתמש זה אין עוקבים :(", "follows_no_one": "משתמש זה אינו עוקב אחרי אחרים :(", "has_no_posts": "המשתמש הזה עוד לא פרסם כלום.", diff --git a/public/language/hu/email.json b/public/language/hu/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/hu/email.json +++ b/public/language/hu/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index 693d3466f5..44e43200be 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ez a téma ebbe a kategóriába lesz mozgatva", "fork_topic_instruction": "Klikkelj azokra a hozzászólásokra, amiket szét akarsz szedni", "fork_no_pids": "Nincs hozzászólás kiválasztva!", - "fork_success": "Sikeresen szétválasztott topik!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Írd be a témanevet...", "composer.discard": "Elvet", "composer.submit": "Küldés", diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 024d83aecc..a7ef8fc8c0 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Ezt a felhasználót nem követi senki :(", "follows_no_one": "Ez a felhasználó nem követ senkit :(", "has_no_posts": "Ennek a felhasználónak még nincsen hozzászólása.", diff --git a/public/language/it/email.json b/public/language/it/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/it/email.json +++ b/public/language/it/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/it/error.json b/public/language/it/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 5f18f9cd39..2efb613bca 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Questa discussione verrà spostata nella categoria", "fork_topic_instruction": "Clicca sui post che vuoi dividere", "fork_no_pids": "Nessun post selezionato!", - "fork_success": "Discussione divisa con successo!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Inserisci qui il titolo della discussione...", "composer.discard": "Annulla", "composer.submit": "Invia", diff --git a/public/language/it/user.json b/public/language/it/user.json index 691a818241..c06f211a6f 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Questo utente non è seguito da nessuno :(", "follows_no_one": "Questo utente non segue nessuno :(", "has_no_posts": "Questo utente non ha ancora postato nulla.", diff --git a/public/language/ja/email.json b/public/language/ja/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ja/email.json +++ b/public/language/ja/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index 3d88578169..0d6aad1afd 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "ユーザーが停止された", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "板が存在しない", "no-topic": "スレッドが存在しない", "no-post": "ポストが存在しない", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 7d24e2e34f..94366f766e 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "スレッドはこちらのカテゴリへ移動", "fork_topic_instruction": "フォークしたいポストをクリックして", "fork_no_pids": "ポストが選択されていません!", - "fork_success": "スレッドをフォークできました!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "スレッドのタイトルを入力して...", "composer.discard": "破棄する", "composer.submit": "保存する", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 8d69a78188..ba3b2c1ed0 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -52,6 +52,7 @@ "digest_daily": "デイリー", "digest_weekly": "ウィークリー", "digest_monthly": "マンスリー", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "フォロワーはまだいません :(", "follows_no_one": "フォロー中のユーザーはまだいません :(", "has_no_posts": "まだポストを投稿したことありません。", diff --git a/public/language/ko/email.json b/public/language/ko/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ko/email.json +++ b/public/language/ko/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index fd74542ee3..be2363f55a 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -20,6 +20,7 @@ "username-too-short": "사용자 이름이 너무 짧습니다.", "username-too-long": "Username too long", "user-banned": "차단된 사용자입니다.", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "존재하지 않는 카테고리입니다.", "no-topic": "존재하지 않는 주제입니다.", "no-post": "존재하지 않는 게시물입니다.", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index 3619cdc736..7e4d14a405 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "이 주제를 지정한 카테고리로 이동합니다.", "fork_topic_instruction": "복제할 게시물을 선택하세요.", "fork_no_pids": "게시물이 선택되지 않았습니다.", - "fork_success": "성공적으로 주제를 복제했습니다.", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "여기에 제목을 입력하세요.", "composer.discard": "취소", "composer.submit": "등록", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index 7e4f4f760a..37ef4e717d 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -52,6 +52,7 @@ "digest_daily": "매일", "digest_weekly": "매주", "digest_monthly": "매월", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "아무도 이 사용자를 팔로우하지 않습니다.", "follows_no_one": "이 사용자는 아무도 팔로우하지 않습니다.", "has_no_posts": "이 사용자는 게시물을 작성하지 않았습니다.", diff --git a/public/language/lt/email.json b/public/language/lt/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/lt/email.json +++ b/public/language/lt/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index f41fc62c24..48edfbb72a 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -20,6 +20,7 @@ "username-too-short": "Slapyvardis per trumpas", "username-too-long": "Username too long", "user-banned": "Vartotojas užblokuotas", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategorija neegzistuoja", "no-topic": "Tema neegzistuoja", "no-post": "Pranešimas neegzistuoja", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 2eb059c363..2d1896a77c 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ši tema bus perkelta į kategoriją", "fork_topic_instruction": "Pažymėkite ant įrašų, kuriuos norite perkelti į naują temą", "fork_no_pids": "Nepasirinktas joks įrašas!", - "fork_success": "Tema sėkmingai išskaidyta!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Įrašykite temos pavadinimą...", "composer.discard": "Atšaukti", "composer.submit": "Patvirtinti", diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 7d502c9c1c..2ef87f0c0c 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -52,6 +52,7 @@ "digest_daily": "Kas dieną", "digest_weekly": "Kas savaitę", "digest_monthly": "Kas mėnesį", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Šis vartotojas neturi jokių sekėjų :(", "follows_no_one": "Šis vartotojas nieko neseka :(", "has_no_posts": "Šis vartotojas dar neparašė nė vieno pranešimo.", diff --git a/public/language/ms/email.json b/public/language/ms/email.json index 3126f0da9d..8d02c3995f 100644 --- a/public/language/ms/email.json +++ b/public/language/ms/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Terima Kasih!" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 6c9f213d94..b6ad0bd586 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nama pengunna terlalu pendek", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index a40da8e224..dde98de2a8 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Topik ini akan dipindahkan kepada kategori", "fork_topic_instruction": "Clik kiriman yang anda hendak salin", "fork_no_pids": "Tiada kiriman yang dipilih", - "fork_success": "Berjaya menyalin topik!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Masukkan tajuk topik disini", "composer.discard": "Abaikan", "composer.submit": "Hantar", diff --git a/public/language/ms/user.json b/public/language/ms/user.json index 7d9a3e2989..366c6ae8fb 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -52,6 +52,7 @@ "digest_daily": "Harian", "digest_weekly": "Mingguan", "digest_monthly": "Bulanan", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Pengguna ini tiada pengikut :(", "follows_no_one": "Pengguna ini tidak mengikuti sesiapa :(", "has_no_posts": "Pengguna ini masih belum mengirim sebarang pos", diff --git a/public/language/nb/email.json b/public/language/nb/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/nb/email.json +++ b/public/language/nb/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 9aeddb6ecf..65f9f09bb5 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dette emnet vil bli flyttet til kategorien", "fork_topic_instruction": "Trykk på innleggene du vil dele", "fork_no_pids": "Ingen innlegg valgt!", - "fork_success": "Innlegg ble delt!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Skriv din tråd-tittel her", "composer.discard": "Forkast", "composer.submit": "Send", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 283348db31..020d50b173 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daglig", "digest_weekly": "Ukentlig", "digest_monthly": "Månedlig", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Denne brukeren har ingen følgere :(", "follows_no_one": "Denne brukeren følger ingen :(", "has_no_posts": "Denne brukeren har ikke skrevet noe enda.", diff --git a/public/language/nl/category.json b/public/language/nl/category.json index 7c6651aa9a..45dbbf4d4d 100644 --- a/public/language/nl/category.json +++ b/public/language/nl/category.json @@ -4,5 +4,5 @@ "browsing": "verkennen", "no_replies": "Niemand heeft gereageerd", "share_this_category": "Deel deze categorie", - "ignore": "Ignore" + "ignore": "Negeren" } \ No newline at end of file diff --git a/public/language/nl/email.json b/public/language/nl/email.json index e662dce397..ee272f19f5 100644 --- a/public/language/nl/email.json +++ b/public/language/nl/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "De laatste onderwerpen van %1", "digest.cta": "Klik hier om deze website te bezoeken %1 ", "digest.unsub.info": "Deze overzicht was verzonden naar jou vanwege je abbonement instellingen", - "digest.unsub.cta": "Klik hier om u instellingen te wijzigen", "digest.daily.no_topics": "Er zijn geen actieve onderwerpen vandaag", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Dit is een test email om te verifiëren dat de email service correct is opgezet voor jou NodeBB", + "unsub.cta": "Click here to alter those settings", "closing": "Bedankt!" } \ No newline at end of file diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 173db65af9..d27566d742 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ongeldige Titel!", "invalid-user-data": "Ongeldig Gebruikersdata", "invalid-password": "Ongeldig wachtwoord", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Geef alsjeblieft een gebruikersnaam en wachtwoord op", "invalid-pagination-value": "Ongeldig pagineringswaarde", "username-taken": "Gebruikersnaam is al bezet", "email-taken": "Email adres is al gebruikt", "email-not-confirmed": "U email adres is niet bevestigd, Klik hier om uw email adres te bevestigen", "username-too-short": "Gebruikersnaam is te kort", - "username-too-long": "Username too long", + "username-too-long": "Gebruikersnaam is te lang", "user-banned": "Gebruiker verbannen", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categorie bestaat niet", "no-topic": "Onderwerp bestaat niet", "no-post": "Bericht bestaat niet", @@ -54,9 +55,9 @@ "upload-error": "Upload Fout : %1", "signature-too-long": "Deze handtekening kan niet groter zijn dan %1 karakters!", "cant-chat-with-yourself": "Je kan niet met jezelf chatten!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Reputatie systeem is uitgeschakeld", + "downvoting-disabled": "Downvoten is uitgeschakeld", "not-enough-reputation-to-downvote": "U heeft niet de benodigde reputatie om dit bericht te downvoten", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "U heeft niet de benodigde reputatie om dit bericht te melden aan de admins", + "reload-failed": "NodeBB heeft een probleem geconstateerd tijdens het laden van: \"%1\".\nNodeBB blijft verder draaien. Het is wel verstandig om de actie wat u daarvoor heeft gedaan ongedaan te maken door te herladen." } \ No newline at end of file diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index 6559d5df3e..4c51c977b5 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -10,14 +10,14 @@ "new_notification": "een nieuwe notificatie", "you_have_unread_notifications": "U heeft ongelezen notificaties", "new_message_from": "Nieuw bericht van %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 heeft uw bericht geupvote in %2.", + "moved_your_post": "%1 heeft uw bericht verplaatst", + "moved_your_topic": "%1 heeft uw onderwerp verplaatst.", + "favourited_your_post_in": "%1 heeft uw bericht gefavoriet in %2.", + "user_flagged_post_in": "%1 rapporteerde een bericht in %2", "user_posted_to": "%1 heeft een reactie op het bericht gegeven aan %2", "user_mentioned_you_in": "%1 heeft u genoemd in %2", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1 volgt u nu.", "email-confirmed": "Email adres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van uw email adres. Uw account is nu volledig actief.", "email-confirm-error": "Een fout vond plaats", diff --git a/public/language/nl/recent.json b/public/language/nl/recent.json index f111931b98..fbbfaaeb52 100644 --- a/public/language/nl/recent.json +++ b/public/language/nl/recent.json @@ -4,6 +4,6 @@ "week": "Week", "month": "Maand", "year": "Jaar", - "alltime": "All Time", + "alltime": "Intussen", "no_recent_topics": "Er zijn geen recente reacties." } \ No newline at end of file diff --git a/public/language/nl/search.json b/public/language/nl/search.json index 7b1aab07ca..cb61065a4e 100644 --- a/public/language/nl/search.json +++ b/public/language/nl/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 resulta(a)ten was een match \"%2\", (%3 seconds)", - "no-matches": "No posts found" + "no-matches": "Geen berichten gevonden" } \ No newline at end of file diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index 9a9d1a2f81..0530a16f5c 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dit onderwerp zal verplaatst worden naar de categorie", "fork_topic_instruction": "Klik op de berichten die je wilt forken", "fork_no_pids": "Geen berichten geselecteerd!", - "fork_success": "Onderwerp is met succes gesplitst!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Vul de titel voor het onderwerp hier in...", "composer.discard": "Annuleren", "composer.submit": "Opslaan", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index 916a856227..e91b069cae 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -4,7 +4,7 @@ "username": "Gebruikersnaam", "email": "Email", "confirm_email": "Bevestig uw email adres", - "delete_account": "Delete Account", + "delete_account": "Account Verwijderen", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Volledige Naam", "website": "Website", @@ -52,6 +52,7 @@ "digest_daily": "Dagelijks", "digest_weekly": "Weekelijks", "digest_monthly": "Maandelijks", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Deze gebruiker heeft geen volgers :(", "follows_no_one": "Deze gebruiker volgt niemand :(", "has_no_posts": "Deze gebruiker heeft nog geen berichten geplaatst", @@ -64,6 +65,6 @@ "notification_sounds": "Speel een geluid af wanneer ik een notificatie ontvang.", "browsing": "Zoek Instellingen", "open_links_in_new_tab": "Open de uitgaande links in een nieuw tabblad?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Volg de onderwerpen waarop u gereageerd heeft.", + "follow_topics_you_create": "Volg de onderwerpen die u gecreëerd heeft." } \ No newline at end of file diff --git a/public/language/pl/email.json b/public/language/pl/email.json index 2e5df85e6a..9a154171c0 100644 --- a/public/language/pl/email.json +++ b/public/language/pl/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Ostatnie tematy z %1", "digest.cta": "Kliknij, by odwiedzić %1", "digest.unsub.info": "To podsumowanie zostało wysłane zgodnie z Twoimi ustawieniami.", - "digest.unsub.cta": "Kliknij tutaj, aby je zmienić", "digest.daily.no_topics": "Wczoraj nie było żadnych aktywnych tematów", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "To jest e-mail testowy, aby sprawdzić, czy poprawnie skonfigurowałeś e-mailer w swoim NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Dziękujemy!" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 75fd7e8daa..c91c2ad906 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -12,14 +12,15 @@ "invalid-title": "Błędny tytuł.", "invalid-user-data": "Błędne dane użytkownika.", "invalid-password": "Błędne hasło", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Proszę podać nazwę użytkownika i hasło", "invalid-pagination-value": "Błędna wartość paginacji", "username-taken": "Login zajęty.", "email-taken": "E-mail zajęty.", "email-not-confirmed": "Twój email nie jest potwierdzony, kliknij tutaj aby go potwierdzić.", "username-too-short": "Nazwa użytkownika za krótka.", - "username-too-long": "Username too long", + "username-too-long": "Zbyt długa nazwa użytkownika", "user-banned": "Użytkownik zbanowany", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategoria nie istnieje.", "no-topic": "Temat nie istnieje", "no-post": "Post nie istnieje", @@ -54,9 +55,9 @@ "upload-error": "Błąd uploadu: %1", "signature-too-long": "Sygnatura nie może mieć więcej niż %1 znaków.", "cant-chat-with-yourself": "Nie możesz czatować ze sobą", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "System reputacji został wyłączony", + "downvoting-disabled": "Ocena postów jest wyłączona", "not-enough-reputation-to-downvote": "Masz za mało reputacji by ocenić ten post.", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "not-enough-reputation-to-flag": "Nie masz wystarczającej reputacji by oflagować ten post", "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 6ef02c02a4..25ae1af224 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ten wątek zostanie przeniesiony do kategorii", "fork_topic_instruction": "Zaznacz posty, które chcesz sklonować", "fork_no_pids": "Nie zaznaczyłeś żadnych postów!", - "fork_success": "Pomyślnie sklonowano wątek", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Wpisz tytuł wątku tutaj", "composer.discard": "Odrzuć", "composer.submit": "Wyślij", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index bcbe736105..82b79963f7 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -4,7 +4,7 @@ "username": "Nazwa użytkownika", "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", - "delete_account": "Delete Account", + "delete_account": "Skasuj konto", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Pełna nazwa", "website": "Strona WWW", @@ -52,6 +52,7 @@ "digest_daily": "Codziennie", "digest_weekly": "Co tydzień", "digest_monthly": "Co miesiąc", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Ten użytkownik nie ma jeszcze żadnych obserwujących", "follows_no_one": "Użytkownik jeszcze nikogo nie obsweruje.", "has_no_posts": "Użytkownik nie napisał jeszcze żadnych postów.", @@ -64,6 +65,6 @@ "notification_sounds": "Odtwórz dźwięk po otrzymaniu powiadomienia.", "browsing": "Ustawienia szukania", "open_links_in_new_tab": "Otwierać linki zewnętrzne w nowych kartach?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Śledź tematy, w których piszesz.", + "follow_topics_you_create": "Śledź swoje tematy." } \ No newline at end of file diff --git a/public/language/pt_BR/email.json b/public/language/pt_BR/email.json index f0b4cce398..2857f112a3 100644 --- a/public/language/pt_BR/email.json +++ b/public/language/pt_BR/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Últimos tópicos de %1", "digest.cta": "Clique aqui para visitar %1", "digest.unsub.info": "Este resumo foi enviado para você devido a configuração de assinatura", - "digest.unsub.cta": "Clique aqui para alterar suas configurações", "digest.daily.no_topics": "Não houve nenhum tópico ativo atualmente", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Este é um e-mail de teste, para verificar que o email está corretamente configurado no seu NodeBB.\n", + "unsub.cta": "Click here to alter those settings", "closing": "Obrigado!" } \ No newline at end of file diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index dd6fe32c3c..d6e0da1ede 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nome de usuário muito curto", "username-too-long": "Username too long", "user-banned": "Usuário banido", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categoria não existe", "no-topic": "Tópico não existe", "no-post": "Post não existe", diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index 8a4912a100..a0f0caea3d 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Este tópico será movido para a categoria", "fork_topic_instruction": "Clique nos posts que você quer clonar", "fork_no_pids": "Nenhum post selecionado!", - "fork_success": "Clone realizado com sucesso!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Digite aqui o título para o seu tópico...", "composer.discard": "Descartar", "composer.submit": "Enviar", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 2ce231dd81..60b2827e2a 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -52,6 +52,7 @@ "digest_daily": "Diariamente", "digest_weekly": "Semanalmente", "digest_monthly": "Mensalmente", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Este usuário não possui seguidores :(", "follows_no_one": "Este usuário não está seguindo ninguém :(", "has_no_posts": "Este usuário não postou nada ainda.", diff --git a/public/language/ro/email.json b/public/language/ro/email.json index 9c7da47629..7dd905af3f 100644 --- a/public/language/ro/email.json +++ b/public/language/ro/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Apasă aici pentru a vizita %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Mulțumesc!" } \ No newline at end of file diff --git a/public/language/ro/error.json b/public/language/ro/error.json index f8346aa636..a40ac5f0d0 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -20,6 +20,7 @@ "username-too-short": "Numele de utilizator este prea scurt", "username-too-long": "Username too long", "user-banned": "Utilizator banat", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categoria nu există", "no-topic": "Subiectul nu există", "no-post": "Mesajul nu există", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 0463625d25..3bcdf5805e 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Acest subiect va fi mutat în categoria", "fork_topic_instruction": "Apasă pe mesajele care vrei sa le bifurci", "fork_no_pids": "Nu a fost selectat nici un mesaj!", - "fork_success": "Ai bifurcat mesajul cu succes!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Introdu numele subiectului aici ...", "composer.discard": "Renunță", "composer.submit": "Trimite", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index a3a45e9731..77a53ae683 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -52,6 +52,7 @@ "digest_daily": "Zilnic", "digest_weekly": "Săptămânal", "digest_monthly": "Lunar", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Pe acest utilizator nu îl urmărește nimeni :(", "follows_no_one": "Acest utilizator nu urmărește pe nimeni :(", "has_no_posts": "Acest utilizator nu a postat nici un mesaj până acuma.", diff --git a/public/language/ru/email.json b/public/language/ru/email.json index d0fc8fabed..5d463a1779 100644 --- a/public/language/ru/email.json +++ b/public/language/ru/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Последние темы %1", "digest.cta": "Кликните здесь для просмотра %1", "digest.unsub.info": "Вам была выслана сводка новостей в соответствии с вашими настройками.", - "digest.unsub.cta": "Кликните здесь для изменения ваших настроек.", "digest.daily.no_topics": "За минувший день новых тем нет.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Это тестовое сообщение для проверки почтового сервиса NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Спасибо!" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 875a018f95..a715f9c9a2 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -20,6 +20,7 @@ "username-too-short": "Слишком короткое имя пользователя", "username-too-long": "Username too long", "user-banned": "Пользователь заблокирован", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Несуществующая категория", "no-topic": "Несуществующая тема", "no-post": "Несуществующая запись", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 9757210077..5a97aa09ea 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Эта тема будет перенесена в категорию", "fork_topic_instruction": "Отметьте сообщения для ответвления", "fork_no_pids": "Сообщения не отмечены!", - "fork_success": "Ответвление темы создано!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Введите название темы...", "composer.discard": "Отменить", "composer.submit": "Подтвердить", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 86e3a3dd9d..e09a5fd425 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -52,6 +52,7 @@ "digest_daily": "За День", "digest_weekly": "За Неделю", "digest_monthly": "За Месяц", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Этого пользователя никто не читает :(", "follows_no_one": "Этот пользователь никого не читает :(", "has_no_posts": "Это пользователь еще ничего не написал.", diff --git a/public/language/sc/email.json b/public/language/sc/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/sc/email.json +++ b/public/language/sc/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index afc9d94ddd..b6eee990c6 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Custa arresonada at a èssere mòvida in sa creze", "fork_topic_instruction": "Sèbera is arresonos chi boles partzire", "fork_no_pids": "Perunu arresonu seberadu!", - "fork_success": "As partzidu s'arresonada!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Pone su tìtulu de s'arresonada inoghe...", "composer.discard": "Lassa a Pèrdere", "composer.submit": "Imbia", diff --git a/public/language/sc/user.json b/public/language/sc/user.json index b8426137bc..298fc5ffa0 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Custu impitadore non tenet perunu sighidore :(", "follows_no_one": "Custu impitadore no est sighende nissunu :(", "has_no_posts": "Custu impitadore no at ancora publicadu nudda.", diff --git a/public/language/sk/email.json b/public/language/sk/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/sk/email.json +++ b/public/language/sk/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 08c0106813..60262ff3e4 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "Užívateľ je zakázaný", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategória neexistuje", "no-topic": "Téme neexistuje", "no-post": "Príspevok neexistuje", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 697c0880ca..72da1a0869 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Táto téma bude presunutá do kategórie", "fork_topic_instruction": "Vyber príspevky, ktoré chceš oddeliť", "fork_no_pids": "Žiadne príspevky neboli vybrané!", - "fork_success": "Téma bola úspešne rozdelená!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Vlož nadpis témy sem...", "composer.discard": "Zahodiť", "composer.submit": "Poslať", diff --git a/public/language/sk/user.json b/public/language/sk/user.json index 8281beeff1..2fa171f6ef 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -52,6 +52,7 @@ "digest_daily": "Denne", "digest_weekly": "Týždenne", "digest_monthly": "Mesačne", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Tohoto užívatela nikto nesleduje :(", "follows_no_one": "Tento užívateľ nikoho nesleduje :(", "has_no_posts": "Tento používateľ ešte nespravil príspevok", diff --git a/public/language/sv/email.json b/public/language/sv/email.json index 2a6f643862..5c84229840 100644 --- a/public/language/sv/email.json +++ b/public/language/sv/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Senaste ämnen från %1", "digest.cta": "Klicka här för att besöka %1", "digest.unsub.info": "Det här meddelandet fick du på grund av dina inställningar för prenumeration. ", - "digest.unsub.cta": "Klicka här för att ändra dom inställningarna", "digest.daily.no_topics": "Det verkar inte varit några aktiva ämnen dom senaste dagarna ", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "\nDet här är ett textmeddelande som verifierar att eposten är korrekt installerat för din NodeBB. ", + "unsub.cta": "Click here to alter those settings", "closing": "Tack!" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 2c10713a84..6f118116f3 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ogiltig rubrik.", "invalid-user-data": "Ogiltig användardata", "invalid-password": "Ogiltigt lösenord", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Specificera både användarnamn och lösenord", "invalid-pagination-value": "Ogiltigt sidnummer", "username-taken": "Användarnamn upptaget", "email-taken": "Epostadress upptagen", "email-not-confirmed": "Din epostadress är ännu inte bekräftad. Klicka här för att bekräfta din epostadress.", "username-too-short": "Användarnamnet är för kort", - "username-too-long": "Username too long", + "username-too-long": "Användarnamnet är för långt", "user-banned": "Användare bannad", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategori hittades inte", "no-topic": "Ämne hittades inte", "no-post": "Inlägget hittades inte", @@ -54,9 +55,9 @@ "upload-error": "Fel vid uppladdning: %1", "signature-too-long": "Signaturer kan inte vara längre än %1 tecken.", "cant-chat-with-yourself": "Du kan inte chatta med dig själv.", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Ryktessystemet är inaktiverat.", + "downvoting-disabled": "Nedröstning är inaktiverat", "not-enough-reputation-to-downvote": "Du har inte tillräckligt förtroende för att rösta ner det här meddelandet", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "Du har inte tillräckligt förtroende för att flagga det här inlägget.", + "reload-failed": "NodeBB stötte på problem med att ladda om: \"%1\". NodeBB kommer fortsätta servera den befintliga resurser till klienten, men du borde återställa det du gjorde alldeles innan du försökte ladda om." } \ No newline at end of file diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json index c4c0efc382..72c9d13e33 100644 --- a/public/language/sv/pages.json +++ b/public/language/sv/pages.json @@ -5,7 +5,7 @@ "recent": "Senaste ämnena", "users": "Registrerade användare", "notifications": "Notiser", - "tags": "Topics tagged under \"%1\"", + "tags": "Ämnen märkta med \"%1\"", "user.edit": "Ändrar \"%1\"", "user.following": "Personer %1 Följer", "user.followers": "Personer som följer %1", diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 8615e49c77..f2f1adb18d 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Detta ämne kommer bli flytta till kategori", "fork_topic_instruction": "Klicka på de inlägg du vill grena", "fork_no_pids": "Inga inlägg valda!", - "fork_success": "Grening av inlägg lyckad!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Skriv in ämnets titel här...", "composer.discard": "Avbryt", "composer.submit": "Spara", @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 fler användare() och %2 gäst(er)", "more_users": "%1 fler användare()", "more_guests": "1% fler gäst(er)", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 och %2 andra", "sort_by": "Sortera på", "oldest_to_newest": "Äldst till nyaste", "newest_to_oldest": "Nyaste till äldst", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index a55c26bb7d..e4e18aafaa 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -4,7 +4,7 @@ "username": "Användarnamn", "email": "Epost", "confirm_email": "Bekräfta epostadress ", - "delete_account": "Delete Account", + "delete_account": "Ta bort ämne", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Hela namnet", "website": "Webbsida", @@ -52,6 +52,7 @@ "digest_daily": "Daligen", "digest_weekly": "Veckovis", "digest_monthly": "Månadsvis", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Denna användare har inga följare :(", "follows_no_one": "Denna användare följer ingen :(", "has_no_posts": "Denna användare har inte gjort några inlägg än.", @@ -64,6 +65,6 @@ "notification_sounds": "Spela ett ljud när du får en notis.", "browsing": "Inställning för bläddring", "open_links_in_new_tab": "Öppna utgående länkar på ny flik?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Följ ämnen so du svarat på.", + "follow_topics_you_create": "Följ ämnen du skapat." } \ No newline at end of file diff --git a/public/language/th/email.json b/public/language/th/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/th/email.json +++ b/public/language/th/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index c72667cb33..0736d6be97 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "กระทู้นี้จะถูกย้ายไปที่หมวดหมู่", "fork_topic_instruction": "คลิกที่โพสที่คุณต้องการที่จะแยก", "fork_no_pids": "ไม่มีโพสต์ที่เลือก!", - "fork_success": "แยกกระทู้สำเร็จ", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "ป้อนชื่อกระทู้ของคุณที่นี่ ...", "composer.discard": "ยกเลิก", "composer.submit": "ส่ง", diff --git a/public/language/th/user.json b/public/language/th/user.json index e5debb6388..ba6a427e10 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "ผู้ใช้รายนี้ไม่มีใครติดตาม :(", "follows_no_one": "ผู้ใช้รายนี้ไม่ติดตามใคร :(", "has_no_posts": "ผู้ใช้รายนี้ไม่ได้โพสต์อะไรเลย", diff --git a/public/language/tr/email.json b/public/language/tr/email.json index 96fd81431e..e1d294f543 100644 --- a/public/language/tr/email.json +++ b/public/language/tr/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "En güncel konular", "digest.cta": "Ziyaret etmek için buraya tıklayın", "digest.unsub.info": "Bu e-posta seçtiğiniz ayarlar nedeniyle gönderildi.", - "digest.unsub.cta": "Bu ayarları değiştirmek için buraya tıklayın", "digest.daily.no_topics": "Geçtiğimiz gün içinde aktif bir konu yok.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.", + "unsub.cta": "Click here to alter those settings", "closing": "Teşekkürler!" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 6c6a37053a..86b7a7567d 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -20,6 +20,7 @@ "username-too-short": "Kullanıcı ismi çok kısa", "username-too-long": "Username too long", "user-banned": "Kullanıcı Yasaklı", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategori Yok", "no-topic": "Başlık Yok", "no-post": "İleti Yok", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index 0716b3e6f1..f4968ca57a 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Bu konu kategorisine taşınacak", "fork_topic_instruction": "Ayırmak istediğiniz iletileri tıklayın", "fork_no_pids": "Hiç bir ileti seçilmedi!", - "fork_success": "Başlık başarıyla ayrıldı!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Başlık ismini buraya girin...", "composer.discard": "Vazgeç", "composer.submit": "Gönder", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 7f4281ce96..8d9584f0bc 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -52,6 +52,7 @@ "digest_daily": "Günlük", "digest_weekly": "Haftalık", "digest_monthly": "Aylık", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Bu kullanıcının hiç takipçisi yok :(", "follows_no_one": "Bu kullanıcı kimseyi takip etmiyor :(", "has_no_posts": "Bu kullanıcı henüz birşey göndermedi.", diff --git a/public/language/vi/email.json b/public/language/vi/email.json index 98e591ab02..60825a08d6 100644 --- a/public/language/vi/email.json +++ b/public/language/vi/email.json @@ -1,20 +1,23 @@ { - "password-reset-requested": "Password Reset Requested - %1!", - "welcome-to": "Welcome to %1", - "greeting_no_name": "Hello", - "greeting_with_name": "Hello %1", - "welcome.text1": "Thank you for registering with %1!", - "welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.", - "welcome.cta": "Click here to confirm your email address", - "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.", - "reset.text2": "To continue with the password reset, please click on the following link:", - "reset.cta": "Click here to reset your password", - "digest.notifications": "You have some unread notifications from %1:", - "digest.latest_topics": "Latest topics from %1", - "digest.cta": "Click here to visit %1", - "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", - "digest.daily.no_topics": "There have been no active topics in the past day", - "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", - "closing": "Thanks!" + "password-reset-requested": "Yêu cầu khởi tạo lại mật khẩu - %1!", + "welcome-to": "Chào mừng đến với %1", + "greeting_no_name": "Xin chào", + "greeting_with_name": "Xin chào %1", + "welcome.text1": "Cảm ơn bạn đã đăng ký tại %1!", + "welcome.text2": "Để kích hoạt đầy đủ tính năng của tài khoản, chúng tôi cần xác định rằng bạn là chủ của địa chỉ email mà bạn đã đăng ký.", + "welcome.cta": "Click vào đây để xác nhận địa chỉ email", + "reset.text1": "Chúng tôi nhận được yêu cầu khởi tạo lại mật khẩu của bạn, rất có thể vì bạn đã quên mất nó. Nếu không đúng, hãy bỏ qua email này.", + "reset.text2": "Để khởi tạo lại mật khẩu, hãy click vào liên kết sau:", + "reset.cta": "Click vào đây để khởi tạo lại mật khẩu", + "digest.notifications": "Bạn có vài thông báo chưa duyệt từ %1:", + "digest.latest_topics": "Chủ đề mới nhất từ %1", + "digest.cta": "Click vào đây để truy cập %1", + "digest.unsub.info": "Tập san này được gửi đến bạn dựa theo cài đặt theo dõi của bạn.", + "digest.daily.no_topics": "Không có chủ đề nào có hoạt động trong ngày hôm qua.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", + "test.text1": "Đây là email kiểm tra để xác nhận rằng trình gửi email đã được cài đặt một cách chính xác cho NodeBB của bạn.", + "unsub.cta": "Click here to alter those settings", + "closing": "Xin cảm ơn!" } \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 189c8de1a5..c375b9fd51 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -1,25 +1,26 @@ { "invalid-data": "Dữ liệu không hợp lệ", "not-logged-in": "Bạn không được đăng nhập.", - "account-locked": "Your account has been locked temporarily", - "search-requires-login": "Searching requires an account! Please login or register!", + "account-locked": "Tài khoản của bạn đã tạm thời bị khóa", + "search-requires-login": "Bạn cần đăng nhập để thực hiện việc tìm kiếm! Xin hãy đăng nhập hoặc đăng ký!", "invalid-cid": "Danh mục ID không hợp lệ", "invalid-tid": "ID chủ đề không hợp lệ", "invalid-pid": "ID bài viết không hợp lệ", "invalid-uid": "ID tài khoản không hợp lệ", "invalid-username": "Tên đăng nhập không hợp lệ", "invalid-email": "Email không hợp lệ", - "invalid-title": "Invalid title!", + "invalid-title": "Tiêu đề không hợp lệ!", "invalid-user-data": "Dữ liệu tài khoản không hợp lệ", "invalid-password": "Mật khẩu không hợp lệ", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Xin hãy nhập cả tên đăng nhập và mật khẩu", "invalid-pagination-value": "Số trang không hợp lệ", "username-taken": "Tên đăng nhập đã tồn tại", "email-taken": "Email đã tồn tại", - "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", - "username-too-short": "Username too short", - "username-too-long": "Username too long", + "email-not-confirmed": "Địa chỉ email của bạn chưa được xác nhận, hãy click vào đây để xác nhận email.", + "username-too-short": "Tên đăng nhập quá ngắn", + "username-too-long": "Tên đăng nhập quá dài", "user-banned": "Tài khoản bị ban", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Phần mục không tồn tại", "no-topic": "Chủ đề không tồn tại", "no-post": "Bài viết không tồn tại", @@ -27,7 +28,7 @@ "no-user": "Tài khoản không tồn tại", "no-teaser": "Teaser không tồn tại", "no-privileges": "Bạn không đủ quyền cho hành động này", - "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", + "no-emailers-configured": "Không có trình cắm email nào được tải, vì vậy email kiểm tra không thể gửi được", "category-disabled": "Danh mục bị disabled", "topic-locked": "Chủ đề bị khóa", "still-uploading": "Vui lòng chờ upload", @@ -38,7 +39,7 @@ "file-too-big": "Kích thước file tối đa %1kb", "cant-vote-self-post": "Bạn không thể vote cho chính bài viết của bạn", "already-favourited": "Bạn đã bấm yêu thích cho bài viết này", - "already-unfavourited": "You already unfavourited this post", + "already-unfavourited": "Bạn đã bỏ thích bài này", "cant-ban-other-admins": "Bạn không thể ban được các admin khác", "invalid-image-type": "Kiểu hình ảnh không hợp lệ", "group-name-too-short": "Tên nhóm quá ngắn", @@ -51,12 +52,12 @@ "topic-thumbnails-are-disabled": "Thumbnails cho chủ đề đã bị tắt", "invalid-file": "File không hợp lệ", "uploads-are-disabled": "Đã khóa lựa chọn tải lên", - "upload-error": "Upload Error : %1", + "upload-error": "Lỗi tải lên : %1", "signature-too-long": "Chứ ký không được dài quá %1 ký tự", "cant-chat-with-yourself": "Bạn không thể chat với chính bạn!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "reputation-system-disabled": "Hệ thống tín nhiệm đã bị vô hiệu hóa.", + "downvoting-disabled": "Downvote đã bị tắt", + "not-enough-reputation-to-downvote": "Bạn không có đủ phiếu tín nhiệm để downvote bài này", + "not-enough-reputation-to-flag": "Bạn không đủ phiếu tín nhiệm để gắn cờ bài này", + "reload-failed": "NodeBB gặp lỗi trong khi tải lại: \"%1\". NodeBB sẽ tiếp tục hoạt động với dữ liệu trước đó, mặc dù vậy bạn nên tháo gỡ những gì bạn đã làm để trước khi tải lại." } \ No newline at end of file diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 5554138a31..33447ba786 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -13,13 +13,13 @@ "please_log_in": "Xin hãy đăng nhập", "logout": "Đăng xuất", "posting_restriction_info": "Hiện giờ chỉ có các thành viên mới được quyền gửi bài viết, hãy nhấn vào đây để đăng nhập", - "welcome_back": "Welcome Back", + "welcome_back": "Chào mừng bạn quay lại", "you_have_successfully_logged_in": "Bạn đã đăng nhập thành công", "save_changes": "Lưu thay đổi", "close": "Đóng lại", "pagination": "Số trang", - "pagination.out_of": "%1 out of %2", - "pagination.enter_index": "Enter index", + "pagination.out_of": "%1 trong số %2", + "pagination.enter_index": "Nhập khóa", "header.admin": "Quản trị viên", "header.recent": "Gần đây", "header.unread": "Chưa đọc", @@ -38,7 +38,7 @@ "alert.success": "Thành công", "alert.error": "Lỗi", "alert.banned": "Bị cấm", - "alert.banned.message": "You have just been banned, you will now be logged out.", + "alert.banned.message": "Bạn vừa bị khóa tài khoản, bạn sẽ tự động thoát ra ngay bây giờ.", "alert.unfollow": "Bạn đã không còn theo dõi %1!", "alert.follow": "Bạn giờ đang theo dõi %1!", "online": "Đang online", @@ -70,6 +70,6 @@ "language": "Ngôn ngữ", "guest": "Khách", "guests": "Số khách", - "updated.title": "Forum Updated", - "updated.message": "This forum has just been updated to the latest version. Click here to refresh the page." + "updated.title": "Cập nhật diễn đàn", + "updated.message": "Diễn đàn đã được cập nhật bản mới nhất. Click vào đây để tải lại trang." } \ No newline at end of file diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json index c00c111e11..2ae6844485 100644 --- a/public/language/vi/groups.json +++ b/public/language/vi/groups.json @@ -1,7 +1,7 @@ { - "view_group": "View Group", - "details.title": "Group Details", - "details.members": "Member List", - "details.has_no_posts": "This group's members have not made any posts.", - "details.latest_posts": "Latest Posts" + "view_group": "Xem nhóm", + "details.title": "Chi tiết nhóm", + "details.members": "Danh sách thành viên", + "details.has_no_posts": "Nhóm thành viên này chưa viết bài viết nào.", + "details.latest_posts": "Bài mới nhất" } \ No newline at end of file diff --git a/public/language/vi/login.json b/public/language/vi/login.json index 25973cd323..0190bd059e 100644 --- a/public/language/vi/login.json +++ b/public/language/vi/login.json @@ -5,5 +5,5 @@ "alternative_logins": "Đăng nhập bằng tên khác", "failed_login_attempt": "Đăng nhập thất bại, xin hãy thử lại", "login_successful": "Bạn đã đăng nhập thành công!", - "dont_have_account": "Don't have an account?" + "dont_have_account": "Chưa có tài khoản?" } \ No newline at end of file diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index 4795f64e74..6afe9ffa9c 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -1,18 +1,18 @@ { "chat.chatting_with": "Chat với ", - "chat.placeholder": "Type chat message here, press enter to send", + "chat.placeholder": "Nhập tin nhắn ở đây, nhấn enter để gửi", "chat.send": "Gửi đi", "chat.no_active": "Bạn hiện giờ không có cuộc chat nào", "chat.user_typing": "%1b đang gõ", - "chat.user_has_messaged_you": "%1 has messaged you.", - "chat.see_all": "See all Chats", - "chat.no-messages": "Please select a recipient to view chat message history", - "chat.recent-chats": "Recent Chats", - "chat.contacts": "Contacts", - "chat.message-history": "Message History", - "chat.pop-out": "Pop out chat", - "chat.maximize": "Maximize", - "composer.user_said_in": "%1 said in %2:", - "composer.user_said": "%1 said:", - "composer.discard": "Are you sure you wish to discard this post?" + "chat.user_has_messaged_you": "%1 đã gửi tin cho bạn.", + "chat.see_all": "Tất cả tin nhắn.", + "chat.no-messages": "Hãy chọn 1 tài khoản để xem lịch sử chat", + "chat.recent-chats": "Vừa chat", + "chat.contacts": "Liên hệ", + "chat.message-history": "Lịch sử tin nhắn", + "chat.pop-out": "Bật cửa sổ chat", + "chat.maximize": "Phóng to", + "composer.user_said_in": "%1 đã nói trong %2:", + "composer.user_said": "%1 đã nói:", + "composer.discard": "Bạn có chắc chắn hủy bỏ bài viết này?" } \ No newline at end of file diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json index a5ee695599..87143000ad 100644 --- a/public/language/vi/pages.json +++ b/public/language/vi/pages.json @@ -5,7 +5,7 @@ "recent": "Chủ đề gần đây", "users": "Số người dùng đã đăng ký", "notifications": "Thông báo", - "tags": "Topics tagged under \"%1\"", + "tags": "Chủ đề được tag theo \"%1\"", "user.edit": "Chỉnh sửa \"%1\"", "user.following": "Người mà %1 theo dõi", "user.followers": "Người đang theo dõi %1", diff --git a/public/language/vi/tags.json b/public/language/vi/tags.json index f065d4bbfa..e309559c94 100644 --- a/public/language/vi/tags.json +++ b/public/language/vi/tags.json @@ -1,6 +1,6 @@ { - "no_tag_topics": "There are no topics with this tag.", - "tags": "Tags", - "enter_tags_here": "Enter tags here. Press enter after each tag.", - "no_tags": "There are no tags yet." + "no_tag_topics": "Không có bài viết nào với nhãn này.", + "tags": "Nhãn", + "enter_tags_here": "Nhập nhãn ở đây. Nhấn enter sau mỗi nhãn.", + "no_tags": "Chưa có nhãn nào." } \ No newline at end of file diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 597c8cd4b9..baeccd2195 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -7,14 +7,14 @@ "post_is_deleted": "Bài gửi này đã bị xóa!", "profile": "Hồ sơ", "posted_by": "Được viết bởi %1", - "posted_by_guest": "Posted by Guest", + "posted_by_guest": "Đăng bởi khách", "chat": "Chat", "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này", "quote": "Trích dẫn", "reply": "Trả lời", "edit": "Chỉnh sửa", "delete": "Xóa", - "purge": "Purge", + "purge": "Xóa hẳn", "restore": "Phục hồi", "move": "Chuyển đi", "fork": "Fork", @@ -23,7 +23,7 @@ "share": "Chia sẻ", "tools": "Công cụ", "flag": "Flag", - "locked": "Locked", + "locked": "Khóa", "bookmark_instructions": "Bấm vào đây để quay về hoặc đóng lại để hủy", "flag_title": "Flag bài viết này để chỉnh sửa", "flag_confirm": "Bạn có chắc là muốn flag bài viết này không?", @@ -49,12 +49,12 @@ "thread_tools.delete_confirm": "Bạn có chắc là muốn hủy thread này không?", "thread_tools.restore": "Phục hồi chủ đề", "thread_tools.restore_confirm": "Bạn có chắc là muốn phục hồi thread này không", - "thread_tools.purge": "Purge Topic", - "thread_tools.purge_confirm": "Are you sure you want to purge this thread?", + "thread_tools.purge": "Xóa hẳn chủ đề", + "thread_tools.purge_confirm": "Bạn có chắc muốn xóa hẳn chủ đề này?", "topic_move_success": "Đã chuyển thành công chủ đề này sang %1", "post_delete_confirm": "Bạn có chắc là muốn xóa bài gửi này không?", "post_restore_confirm": "Bạn có chắc là muốn phục hồi bài gửi này không?", - "post_purge_confirm": "Are you sure you want to purge this post?", + "post_purge_confirm": "Bạn có chắc muốn xóa hẳn bài này?", "load_categories": "Đang tải các phần mục", "disabled_categories_note": "Các phần mục bị khóa đã được đánh xám", "confirm_move": "Chuyển", @@ -64,14 +64,14 @@ "favourites.has_no_favourites": "Bạn đang không có yêu thích nào. Hãy yêu thích một vài bài viết để thấy được chúng tại đây!", "loading_more_posts": "Tải thêm các bài gửi khác", "move_topic": "Chuyển chủ đề", - "move_topics": "Move Topics", + "move_topics": "Di chuyển chủ đề", "move_post": "Chuyển bài gửi", "post_moved": "Đã chuyển bài gửi!", "fork_topic": "Fork chủ đề", "topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục", "fork_topic_instruction": "Nhấp vào bài gửi mà bạn muốn fork", "fork_no_pids": "Chưa chọn bài gửi nào!", - "fork_success": "Đã fork chủ đề thành công!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...", "composer.discard": "Loại bỏ", "composer.submit": "Gửi", @@ -87,9 +87,9 @@ "more_users_and_guests": "%1 người dùng và %2 khách nữa", "more_users": "%1 người dùng nữa", "more_guests": "%1 khách nữa", - "users_and_others": "%1 and %2 others", - "sort_by": "Sort by", - "oldest_to_newest": "Oldest to Newest", - "newest_to_oldest": "Newest to Oldest", - "most_votes": "Most votes" + "users_and_others": "%1 và%2 khác", + "sort_by": "Sắp xếp theo", + "oldest_to_newest": "Cũ đến mới", + "newest_to_oldest": "Mới đến cũ", + "most_votes": "Bình chọn nhiều nhất" } \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json index accaa5bcbd..1aeac40d09 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -3,8 +3,8 @@ "offline": "Offline", "username": "Tên truy cập", "email": "Email", - "confirm_email": "Confirm Email", - "delete_account": "Delete Account", + "confirm_email": "Xác nhận email", + "delete_account": "Xóa tài khoản", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Tên đầy đủ", "website": "Website", @@ -52,6 +52,7 @@ "digest_daily": "Hàng ngày", "digest_weekly": "Hàng tuần", "digest_monthly": "Hàng tháng", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Người dùng này hiện chưa có ai theo dõi :(", "follows_no_one": "Người dùng này hiện chưa theo dõi ai :(", "has_no_posts": "Người dùng này chưa viết bài nào", @@ -62,8 +63,8 @@ "topics_per_page": "Số chủ đề trong một trang", "posts_per_page": "Số bài viết trong một trang", "notification_sounds": "Xuất hiện âm thanh khi bạn nhận được một thông báo", - "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "browsing": "Đang xem cài đặt", + "open_links_in_new_tab": "Mở liên kết trong tab mới?", + "follow_topics_you_reply_to": "Theo dõi chủ đề mà bạn trả lời", + "follow_topics_you_create": "Theo dõi chủ đề bạn tạo" } \ No newline at end of file diff --git a/public/language/vi/users.json b/public/language/vi/users.json index 244b26154b..cda4f0b017 100644 --- a/public/language/vi/users.json +++ b/public/language/vi/users.json @@ -5,6 +5,6 @@ "search": "Tìm kiếm", "enter_username": "Gõ tên người dùng để tìm kiếm", "load_more": "Tải thêm", - "user-not-found": "User not found!", - "users-found-search-took": "%1 user(s) found! Search took %2 ms." + "user-not-found": "Tài khoản không tìm thấy!", + "users-found-search-took": "%1 tài khoản(s) tìm thấy! Tìm trong %2 mili giây." } \ No newline at end of file diff --git a/public/language/zh_CN/category.json b/public/language/zh_CN/category.json index 2f776788d1..933e12e3e1 100644 --- a/public/language/zh_CN/category.json +++ b/public/language/zh_CN/category.json @@ -4,5 +4,5 @@ "browsing": "正在浏览", "no_replies": "尚无回复", "share_this_category": "分享此版块", - "ignore": "Ignore" + "ignore": "忽略" } \ No newline at end of file diff --git a/public/language/zh_CN/email.json b/public/language/zh_CN/email.json index ae359e04fb..77b5e00903 100644 --- a/public/language/zh_CN/email.json +++ b/public/language/zh_CN/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "来自 %1 的最新主题", "digest.cta": "点击这里访问 %1", "digest.unsub.info": "根据您的订阅设置,为您发送此摘要。", - "digest.unsub.cta": "点击这里修改这些设置", "digest.daily.no_topics": "最近几天有一些未激活的主题", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。", + "unsub.cta": "Click here to alter those settings", "closing": "谢谢!" } \ No newline at end of file diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index 561c57a0da..56c19a26b2 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -12,14 +12,15 @@ "invalid-title": "无效标题!", "invalid-user-data": "无效用户数据", "invalid-password": "无效密码", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "请确认用户名和密码", "invalid-pagination-value": "无效页码", "username-taken": "用户名已注册", "email-taken": "电子邮箱已注册", "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。", "username-too-short": "用户名太短", - "username-too-long": "Username too long", + "username-too-long": "用户名太长", "user-banned": "用户已禁止", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "版块不存在", "no-topic": "主题不存在", "no-post": "帖子不存在", @@ -54,9 +55,9 @@ "upload-error": "上传错误:%1", "signature-too-long": "签名档不能超过 %1 字!", "cant-chat-with-yourself": "您不能和自己聊天!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "威望系统已禁用。", + "downvoting-disabled": "反对功能已禁用", "not-enough-reputation-to-downvote": "您还没有足够的威望为此帖扣分", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "您没有足够的威望标记此帖", + "reload-failed": "NodeBB 重新加载时遇到问题: \"%1\"。NodeBB 会继续给已存在的客户端组件服务,虽然您应该撤销在重新加载前执行的操作。" } \ No newline at end of file diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json index 6e4faf216f..13d533bc53 100644 --- a/public/language/zh_CN/notifications.json +++ b/public/language/zh_CN/notifications.json @@ -10,14 +10,14 @@ "new_notification": "新通知", "you_have_unread_notifications": "您有未读的通知。", "new_message_from": "来自 %1 的新消息", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1%2 点赞了您的帖子。", + "moved_your_post": "%1 移动了您的帖子。", + "moved_your_topic": "%1 移动了您的主题帖。", + "favourited_your_post_in": "%1%2 收藏了您的帖子。", + "user_flagged_post_in": "%1%2 标记了一个帖子", "user_posted_to": "%1 回复了:%2", "user_mentioned_you_in": "%1%2 中提到了您", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1关注了您。", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全面激活。", "email-confirm-error": "出错了.……", diff --git a/public/language/zh_CN/recent.json b/public/language/zh_CN/recent.json index c363d4921c..6b6df56f05 100644 --- a/public/language/zh_CN/recent.json +++ b/public/language/zh_CN/recent.json @@ -4,6 +4,6 @@ "week": "本周", "month": "本月", "year": "本年", - "alltime": "All Time", + "alltime": "有史以来", "no_recent_topics": "无最新主题。" } \ No newline at end of file diff --git a/public/language/zh_CN/search.json b/public/language/zh_CN/search.json index 484acb3626..3d8460e57b 100644 --- a/public/language/zh_CN/search.json +++ b/public/language/zh_CN/search.json @@ -1,4 +1,4 @@ { "results_matching": "共 %1 条结果匹配 \"%2\",(耗时 %3 秒)", - "no-matches": "No posts found" + "no-matches": "没有找到帖子" } \ No newline at end of file diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json index 183cbfcc23..098584ec36 100644 --- a/public/language/zh_CN/topic.json +++ b/public/language/zh_CN/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "此主题将被移动到版块", "fork_topic_instruction": "点击将分割的帖子", "fork_no_pids": "未选中帖子!", - "fork_success": "已成功分割主题!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "在此输入您主题的标题...", "composer.discard": "撤销", "composer.submit": "提交", diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index 4e22cd19b5..a1c9e34de2 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -4,7 +4,7 @@ "username": "用户名", "email": "电子邮件", "confirm_email": "确认电子邮箱", - "delete_account": "Delete Account", + "delete_account": "删除帐号", "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "姓名", "website": "网站", @@ -52,6 +52,7 @@ "digest_daily": "每天", "digest_weekly": "每周", "digest_monthly": "每月", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "此用户还没有粉丝 :(", "follows_no_one": "此用户尚未关注任何人 :(", "has_no_posts": "此用户尚未发布任何帖子。", @@ -64,6 +65,6 @@ "notification_sounds": "收到通知时播放提示音。", "browsing": "浏览设置", "open_links_in_new_tab": "在新标签中打开外部链接?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "关注您回复的主题。", + "follow_topics_you_create": "关注您创建的主题。" } \ No newline at end of file diff --git a/public/language/zh_TW/email.json b/public/language/zh_TW/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/zh_TW/email.json +++ b/public/language/zh_TW/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index d965ca117e..544d11f42e 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "該使用者已被停用", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "類別並不存在", "no-topic": "主題並不存在", "no-post": "文章並不存在", diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json index 696de46b27..bba85af9e6 100644 --- a/public/language/zh_TW/topic.json +++ b/public/language/zh_TW/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "這個主題將會被移動到", "fork_topic_instruction": "點擊要作為主題的文章", "fork_no_pids": "尚未選擇文章!", - "fork_success": "成功將文章作為主題!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "輸入標題...", "composer.discard": "放棄", "composer.submit": "發表", diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index ed42254f3b..46def28d26 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -52,6 +52,7 @@ "digest_daily": "每日", "digest_weekly": "每週", "digest_monthly": "每月", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "該用戶還沒有被任何人關注。", "follows_no_one": "該用戶還沒有關注過任何人。", "has_no_posts": "尚未有任何貼文.", From e9b373438d216913cf742b547c1fa2f0f0dbd1e1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 09:21:22 -0400 Subject: [PATCH 282/372] latest translations and fallbacks Conflicts: public/language/de/user.json public/language/nl/user.json public/language/pl/user.json public/language/sv/user.json public/language/vi/user.json public/language/zh_CN/user.json --- public/language/ar/email.json | 5 ++- public/language/ar/error.json | 1 + public/language/ar/topic.json | 2 +- public/language/ar/user.json | 1 + public/language/cs/email.json | 5 ++- public/language/cs/error.json | 1 + public/language/cs/topic.json | 2 +- public/language/cs/user.json | 1 + public/language/de/email.json | 5 ++- public/language/de/error.json | 13 ++++---- public/language/de/topic.json | 2 +- public/language/de/user.json | 9 +++--- public/language/en@pirate/email.json | 5 ++- public/language/en@pirate/error.json | 1 + public/language/en@pirate/topic.json | 2 +- public/language/en@pirate/user.json | 1 + public/language/en_US/email.json | 5 ++- public/language/en_US/error.json | 1 + public/language/en_US/topic.json | 2 +- public/language/en_US/user.json | 1 + public/language/es/email.json | 5 ++- public/language/es/error.json | 1 + public/language/es/topic.json | 2 +- public/language/es/user.json | 1 + public/language/et/email.json | 5 ++- public/language/et/error.json | 1 + public/language/et/topic.json | 2 +- public/language/et/user.json | 1 + public/language/fa_IR/email.json | 5 ++- public/language/fa_IR/error.json | 1 + public/language/fa_IR/topic.json | 2 +- public/language/fa_IR/user.json | 1 + public/language/fi/email.json | 5 ++- public/language/fi/error.json | 1 + public/language/fi/topic.json | 2 +- public/language/fi/user.json | 1 + public/language/fr/category.json | 2 +- public/language/fr/email.json | 5 ++- public/language/fr/error.json | 1 + public/language/fr/topic.json | 2 +- public/language/fr/user.json | 1 + public/language/he/email.json | 5 ++- public/language/he/error.json | 1 + public/language/he/topic.json | 2 +- public/language/he/user.json | 1 + public/language/hu/email.json | 5 ++- public/language/hu/error.json | 1 + public/language/hu/topic.json | 2 +- public/language/hu/user.json | 1 + public/language/it/email.json | 5 ++- public/language/it/error.json | 1 + public/language/it/topic.json | 2 +- public/language/it/user.json | 1 + public/language/ja/email.json | 5 ++- public/language/ja/error.json | 1 + public/language/ja/topic.json | 2 +- public/language/ja/user.json | 1 + public/language/ko/email.json | 5 ++- public/language/ko/error.json | 1 + public/language/ko/topic.json | 2 +- public/language/ko/user.json | 1 + public/language/lt/email.json | 5 ++- public/language/lt/error.json | 1 + public/language/lt/topic.json | 2 +- public/language/lt/user.json | 1 + public/language/ms/email.json | 5 ++- public/language/ms/error.json | 1 + public/language/ms/topic.json | 2 +- public/language/ms/user.json | 1 + public/language/nb/email.json | 5 ++- public/language/nb/error.json | 1 + public/language/nb/topic.json | 2 +- public/language/nb/user.json | 1 + public/language/nl/category.json | 2 +- public/language/nl/email.json | 5 ++- public/language/nl/error.json | 13 ++++---- public/language/nl/notifications.json | 12 ++++---- public/language/nl/recent.json | 2 +- public/language/nl/search.json | 2 +- public/language/nl/topic.json | 2 +- public/language/nl/user.json | 9 +++--- public/language/pl/email.json | 5 ++- public/language/pl/error.json | 11 ++++--- public/language/pl/topic.json | 2 +- public/language/pl/user.json | 9 +++--- public/language/pt_BR/email.json | 5 ++- public/language/pt_BR/error.json | 1 + public/language/pt_BR/topic.json | 2 +- public/language/pt_BR/user.json | 1 + public/language/ro/email.json | 5 ++- public/language/ro/error.json | 1 + public/language/ro/topic.json | 2 +- public/language/ro/user.json | 1 + public/language/ru/email.json | 5 ++- public/language/ru/error.json | 1 + public/language/ru/topic.json | 2 +- public/language/ru/user.json | 1 + public/language/sc/email.json | 5 ++- public/language/sc/error.json | 1 + public/language/sc/topic.json | 2 +- public/language/sc/user.json | 1 + public/language/sk/email.json | 5 ++- public/language/sk/error.json | 1 + public/language/sk/topic.json | 2 +- public/language/sk/user.json | 1 + public/language/sv/email.json | 5 ++- public/language/sv/error.json | 13 ++++---- public/language/sv/pages.json | 2 +- public/language/sv/topic.json | 4 +-- public/language/sv/user.json | 9 +++--- public/language/th/email.json | 5 ++- public/language/th/error.json | 1 + public/language/th/topic.json | 2 +- public/language/th/user.json | 1 + public/language/tr/email.json | 5 ++- public/language/tr/error.json | 1 + public/language/tr/topic.json | 2 +- public/language/tr/user.json | 1 + public/language/vi/email.json | 39 +++++++++++++----------- public/language/vi/error.json | 31 ++++++++++--------- public/language/vi/global.json | 12 ++++---- public/language/vi/groups.json | 10 +++--- public/language/vi/login.json | 2 +- public/language/vi/modules.json | 24 +++++++-------- public/language/vi/pages.json | 2 +- public/language/vi/tags.json | 8 ++--- public/language/vi/topic.json | 26 ++++++++-------- public/language/vi/user.json | 15 ++++----- public/language/vi/users.json | 4 +-- public/language/zh_CN/category.json | 2 +- public/language/zh_CN/email.json | 5 ++- public/language/zh_CN/error.json | 13 ++++---- public/language/zh_CN/notifications.json | 12 ++++---- public/language/zh_CN/recent.json | 2 +- public/language/zh_CN/search.json | 2 +- public/language/zh_CN/topic.json | 2 +- public/language/zh_CN/user.json | 9 +++--- public/language/zh_TW/email.json | 5 ++- public/language/zh_TW/error.json | 1 + public/language/zh_TW/topic.json | 2 +- public/language/zh_TW/user.json | 1 + 141 files changed, 369 insertions(+), 214 deletions(-) diff --git a/public/language/ar/email.json b/public/language/ar/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ar/email.json +++ b/public/language/ar/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ar/error.json b/public/language/ar/error.json index feb589fd8f..6e6956b0ec 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "المستخدم محظور", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index 930f642748..eed42e4f01 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "هذا الموضوع سوف ينقل إلى فئة", "fork_topic_instruction": "إضغط على الردود لتفريعهم", "fork_no_pids": "لم تختار أي رد", - "fork_success": "تفريع الموضوع بنجاح!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/ar/user.json b/public/language/ar/user.json index a1aa9dc80f..f5a6be3c14 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "هذا المستخدم ليس لديه أي أتباع :(", "follows_no_one": "هذا المستخدم لا يتبع أحد :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/cs/email.json b/public/language/cs/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/cs/email.json +++ b/public/language/cs/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index fd1e316089..b3a0eb1aeb 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Toto téma bude přesunuto do kategorie", "fork_topic_instruction": "Vyber příspěvky, které chceš oddělit", "fork_no_pids": "Žádné příspěvky nebyly vybrány!", - "fork_success": "Téma bylo úspěšně rozděleno!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 13be6ba018..48e9c3655b 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Tohoto uživatele nikdo nesleduje :(", "follows_no_one": "Tento uživatel nikoho nesleduje :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/de/email.json b/public/language/de/email.json index e3c67dc47b..6936a71750 100644 --- a/public/language/de/email.json +++ b/public/language/de/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Aktuellste Themen vom %1", "digest.cta": "Klicke hier, um %1 zu besuchen", "digest.unsub.info": "Diese Zusammenfassung wurde dir aufgrund deiner Abonnement-Einstellungen gesendet.", - "digest.unsub.cta": "Klicke hier, um diese Einstellungen zu ändern", "digest.daily.no_topics": "Es gab heute keine aktiven Themen", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Dies ist eine Test-E-Mail, um zu überprüfen, ob der E-Mailer deines NodeBB korrekt eingestellt wurde.", + "unsub.cta": "Click here to alter those settings", "closing": "Danke!" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 4217b44a31..ddf1509f60 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ungültiger Titel", "invalid-user-data": "Ungültige Benutzerdaten", "invalid-password": "Ungültiges Passwort", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Bitte gebe einen Benutzernamen und ein Passwort an", "invalid-pagination-value": "Die Nummerierung ist ungültig", "username-taken": "Der Benutzername ist bereits vergeben", "email-taken": "Die E-Mail-Adresse ist bereits vergeben", "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt. Bitte klicke hier, um deine E-Mail zu bestätigen.", "username-too-short": "Benutzername ist zu kurz", - "username-too-long": "Username too long", + "username-too-long": "Der Benutzername ist zu lang", "user-banned": "Der Benutzer ist gesperrt", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Die Kategorie existiert nicht", "no-topic": "Das Thema existiert nicht", "no-post": "Der Beitrag existiert nicht", @@ -54,9 +55,9 @@ "upload-error": "Upload-Fehler: %1", "signature-too-long": "Die Signatur darf maximal %1 Zeichen enthalten!", "cant-chat-with-yourself": "Du kannst nicht mit dir selber chatten!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.", + "downvoting-disabled": "Downvotes sind deaktiviert.", "not-enough-reputation-to-downvote": "Deine Reputation ist zu niedrig, um diesen Beitrag negativ zu bewerten.", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "Deine Reputation ist nicht gut genug, um diesen Beitrag zu melden.", + "reload-failed": "Es ist ein Problem während des Reloads von NodeBB aufgetreten: \"%1\". NodeBB wird weiterhin clientseitige Assets bereitstellen, allerdings solltest du das, was du vor dem Reload gemacht hast, rückgängig machen." } \ No newline at end of file diff --git a/public/language/de/topic.json b/public/language/de/topic.json index c3642ea0bd..77fbaf000f 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dieses Thema wird verschoben nach", "fork_topic_instruction": "Klicke auf die Beiträge, die du aufspalten willst", "fork_no_pids": "Keine Beiträge ausgewählt!", - "fork_success": "Thema erfolgreich aufgespalten!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Hier den Titel des Themas eingeben...", "composer.discard": "Verwerfen", "composer.submit": "Absenden", diff --git a/public/language/de/user.json b/public/language/de/user.json index c264b87190..d3b4e12d45 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -4,8 +4,8 @@ "username": "Nutzername", "email": "E-Mail", "confirm_email": "E-Mail bestätigen", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account": "Konto löschen", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Kompletter Name", "website": "Homepage", "location": "Wohnort", @@ -52,6 +52,7 @@ "digest_daily": "Täglich", "digest_weekly": "Wöchentlich", "digest_monthly": "Monatlich", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Dieser User hat noch keine Follower.", "follows_no_one": "Dieser User folgt noch niemandem :(", "has_no_posts": "Dieser Nutzer hat noch nichts gepostet.", @@ -64,6 +65,6 @@ "notification_sounds": "Ton abspielen, wenn ich eine Benachrichtigung erhalte.", "browsing": "Browser Einstellungen", "open_links_in_new_tab": "Externe Links in neuem Tab öffnen?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Folge Themen, auf die du antwortest.", + "follow_topics_you_create": "Folge Themen, die du erstellst." } \ No newline at end of file diff --git a/public/language/en@pirate/email.json b/public/language/en@pirate/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/en@pirate/email.json +++ b/public/language/en@pirate/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/en@pirate/topic.json b/public/language/en@pirate/topic.json index aa69261f90..6093dc2302 100644 --- a/public/language/en@pirate/topic.json +++ b/public/language/en@pirate/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", - "fork_success": "Succesfully forked topic!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index a8efc3d857..fb50580f31 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/en_US/email.json b/public/language/en_US/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/en_US/email.json +++ b/public/language/en_US/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 200ce83066..c63073194a 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/en_US/topic.json b/public/language/en_US/topic.json index 8ffca481ab..c64d0c9881 100644 --- a/public/language/en_US/topic.json +++ b/public/language/en_US/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", - "fork_success": "Succesfully forked topic!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", "composer.discard": "Discard", "composer.submit": "Submit", diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index 50848c7f83..58ea366320 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(", "has_no_posts": "This user didn't post anything yet.", diff --git a/public/language/es/email.json b/public/language/es/email.json index b791157e63..c83494b565 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Últimos temas de %1", "digest.cta": "Cliquea aquí para visitar %1", "digest.unsub.info": "Este compendio te fue enviado debido a tus ajustes de subscripción.", - "digest.unsub.cta": "Cliquea aquí para alterar estos ajustes", "digest.daily.no_topics": "No han habido temas activos en el día pasado", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Este es un email de prueba para verificar que el envío de email está ajustado correctamente para tu NodeBB", + "unsub.cta": "Click here to alter those settings", "closing": "¡Gracias!" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index d3fa14fe91..79fd819d2f 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -20,6 +20,7 @@ "username-too-short": "El nombre de usuario es demasiado corto", "username-too-long": "Username too long", "user-banned": "Usuario expulsado", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "La categoría no existe", "no-topic": "El tema no existe.", "no-post": "La publicación no existe", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 1522291a09..d8eafd749c 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Este tema será movido a la categoría", "fork_topic_instruction": "Click en las publicaciones que quieres bifurcar", "fork_no_pids": "¡No seleccionaste publicaciones!", - "fork_success": "¡Bifurcado con exito!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Ingresa el titulo de tu tema", "composer.discard": "Descartar", "composer.submit": "Enviar", diff --git a/public/language/es/user.json b/public/language/es/user.json index 3d711138d2..32a7fe7340 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -52,6 +52,7 @@ "digest_daily": "Diariamente", "digest_weekly": "Semanalmente", "digest_monthly": "Mensualmente", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Este miembro no tiene seguidores. :(", "follows_no_one": "Este miembro no sigue a nadie. :(", "has_no_posts": "Este usuario aún no ha publicado nada.", diff --git a/public/language/et/email.json b/public/language/et/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/et/email.json +++ b/public/language/et/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/et/error.json b/public/language/et/error.json index 459b749ca0..51926c86b5 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -20,6 +20,7 @@ "username-too-short": "Kasutajanimi on liiga lühike", "username-too-long": "Username too long", "user-banned": "Kasutaja bannitud", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategooriat ei eksisteeri", "no-topic": "Teemat ei eksisteeri", "no-post": "Postitust ei eksisteeri", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index dc4dcc62cb..8b57bef58d 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "See teema liigutatakse antud kategooriasse", "fork_topic_instruction": "Vajuta postitustele, mida soovid forkida", "fork_no_pids": "Sa ei ole postitusi valinud!", - "fork_success": "Edukalt forkisid teema!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Sisesta teema pealkiri siia...", "composer.discard": "Katkesta", "composer.submit": "Postita", diff --git a/public/language/et/user.json b/public/language/et/user.json index 4ecb16dac4..edde90c4cf 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -52,6 +52,7 @@ "digest_daily": "Igapäevaselt", "digest_weekly": "Iga nädal", "digest_monthly": "Iga kuu", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Sellel kasutajal pole ühtegi jälgijat :(", "follows_no_one": "See kasutaja ei jälgi kedagi :(", "has_no_posts": "See kasutaja pole midagi postitanud veel.", diff --git a/public/language/fa_IR/email.json b/public/language/fa_IR/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/fa_IR/email.json +++ b/public/language/fa_IR/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 29caeaa0c1..64f4d35823 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -20,6 +20,7 @@ "username-too-short": "نام کاربری خیلی کوتاه است.", "username-too-long": "Username too long", "user-banned": "کاربر محروم شد.", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "چنین دسته‌ای وجود ندارد.", "no-topic": "چنین جستاری وجود ندارد.", "no-post": "چنین دیدگاهی وجود ندارد.", diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json index 1c525fad8b..47f345fc6d 100644 --- a/public/language/fa_IR/topic.json +++ b/public/language/fa_IR/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "این جستار جابه‌جا خواهد شد به دستهٔ", "fork_topic_instruction": "دیدگاه‌هایی را که می‌خواهید به جستار تازه ببرید، برگزینید", "fork_no_pids": "هیچ دیدگاهی انتخاب نشده!", - "fork_success": "جستار با موفقیت منشعب شد.", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "سرنویس جستارتان را اینجا بنویسید...", "composer.discard": "دور بیانداز", "composer.submit": "بفرست", diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index f0212723e2..f460e31c7b 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -52,6 +52,7 @@ "digest_daily": "روزانه", "digest_weekly": "هفتگی", "digest_monthly": "ماهانه", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "این کاربر هیچ دنبال‌کننده‌ای ندارد :(", "follows_no_one": "این کاربر هیچ کسی را دنبال نمی‌کند :(", "has_no_posts": "این کاربر هنوز هیچ دیدگاهی نگذاشته است.", diff --git a/public/language/fi/email.json b/public/language/fi/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/fi/email.json +++ b/public/language/fi/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 738eb62699..73febab0c3 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -20,6 +20,7 @@ "username-too-short": "Käyttäjänimi on liian lyhyt", "username-too-long": "Username too long", "user-banned": "Käyttäjä on estetty", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategoriaa ei ole olemassa", "no-topic": "Aihetta ei ole olemassa", "no-post": "Viestiä ei ole olemassa", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index ad1ef0f1a0..bccb155bf4 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Tämä keskustelu siirretään aihealueelle", "fork_topic_instruction": "Napsauta viestejä, jotka haluat haaroittaa", "fork_no_pids": "Ei valittuja viestejä!", - "fork_success": "Keskustelu haaroitettu onnistuneesti!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Syötä aiheesi otsikko tähän...", "composer.discard": "Hylkää", "composer.submit": "Lähetä", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index d008e94b62..d8aecf0e3d 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Kukaan ei seuraa tätä käyttäjää :(", "follows_no_one": "Tämä käyttäjä ei seuraa ketään :(", "has_no_posts": "Tämä käyttäjä ei ole kirjoittanut vielä mitään.", diff --git a/public/language/fr/category.json b/public/language/fr/category.json index c680ae7db9..47b8ebb764 100644 --- a/public/language/fr/category.json +++ b/public/language/fr/category.json @@ -4,5 +4,5 @@ "browsing": "parcouru par", "no_replies": "Personne n'a répondu", "share_this_category": "Partager cette catégorie", - "ignore": "Ignore" + "ignore": "Ignorer" } \ No newline at end of file diff --git a/public/language/fr/email.json b/public/language/fr/email.json index 88cb8c270a..88872a11fb 100644 --- a/public/language/fr/email.json +++ b/public/language/fr/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Derniers sujets de %1 :", "digest.cta": "Cliquez ici pour aller sur %1", "digest.unsub.info": "Ce message vous a été envoyé en raison de vos paramètres d'abonnement.", - "digest.unsub.cta": "Cliquez ici pour modifier ces paramètres", "digest.daily.no_topics": "Il n'y a eu aucun sujet actif ces derniers jours", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Ceci est un email de test pour vérifier que l'emailer est correctement configuré pour NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Merci !" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 821061cee1..bcc272f6d8 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nom d'utilisateur trop court", "username-too-long": "Username too long", "user-banned": "Utilisateur banni", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Cette catégorie n'existe pas", "no-topic": "Ce sujet n'existe pas", "no-post": "Ce message n'existe pas", diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index 47473793ec..f73fad2ed0 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ce sujet sera déplacé vers la catégorie", "fork_topic_instruction": "Cliquez sur les postes à scinder", "fork_no_pids": "Aucun post sélectionné !", - "fork_success": "Sujet scindé !", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Entrer le titre du sujet ici ...", "composer.discard": "Abandonner", "composer.submit": "Envoyer", diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 68a99a577e..76667e98ed 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -52,6 +52,7 @@ "digest_daily": "Quotidien", "digest_weekly": "Hebdomadaire", "digest_monthly": "Mensuel", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Cet utilisateur n'est suivi par personne :(", "follows_no_one": "Cet utilisateur ne suit personne :(", "has_no_posts": "Ce membre n'a rien posté pour le moment", diff --git a/public/language/he/email.json b/public/language/he/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/he/email.json +++ b/public/language/he/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/he/error.json b/public/language/he/error.json index 2df9c1b918..28cd04e663 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -20,6 +20,7 @@ "username-too-short": "שם משתמש קצר מדי", "username-too-long": "Username too long", "user-banned": "המשתמש חסום", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "קטגוריה אינה קיימת", "no-topic": "נושא אינו קיים", "no-post": "פוסט אינו קיים", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 8bff5a472c..59d3ab6736 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "נושא זה יועבר לקטגוריה", "fork_topic_instruction": "לחץ על הפוסטים שברצונך לשכפל", "fork_no_pids": "לא בחרת אף פוסט!", - "fork_success": "הנושא שוכפל בהצלחה!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "הכנס את כותרת הנושא כאן...", "composer.discard": "מחק", "composer.submit": "שלח", diff --git a/public/language/he/user.json b/public/language/he/user.json index 292d31f90b..badb2cf544 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -52,6 +52,7 @@ "digest_daily": "יומי", "digest_weekly": "שבועי", "digest_monthly": "חודשי", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "למשתמש זה אין עוקבים :(", "follows_no_one": "משתמש זה אינו עוקב אחרי אחרים :(", "has_no_posts": "המשתמש הזה עוד לא פרסם כלום.", diff --git a/public/language/hu/email.json b/public/language/hu/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/hu/email.json +++ b/public/language/hu/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index 693d3466f5..44e43200be 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ez a téma ebbe a kategóriába lesz mozgatva", "fork_topic_instruction": "Klikkelj azokra a hozzászólásokra, amiket szét akarsz szedni", "fork_no_pids": "Nincs hozzászólás kiválasztva!", - "fork_success": "Sikeresen szétválasztott topik!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Írd be a témanevet...", "composer.discard": "Elvet", "composer.submit": "Küldés", diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 909e4ad553..fd14fd271c 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Ezt a felhasználót nem követi senki :(", "follows_no_one": "Ez a felhasználó nem követ senkit :(", "has_no_posts": "Ennek a felhasználónak még nincsen hozzászólása.", diff --git a/public/language/it/email.json b/public/language/it/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/it/email.json +++ b/public/language/it/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/it/error.json b/public/language/it/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 5f18f9cd39..2efb613bca 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Questa discussione verrà spostata nella categoria", "fork_topic_instruction": "Clicca sui post che vuoi dividere", "fork_no_pids": "Nessun post selezionato!", - "fork_success": "Discussione divisa con successo!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Inserisci qui il titolo della discussione...", "composer.discard": "Annulla", "composer.submit": "Invia", diff --git a/public/language/it/user.json b/public/language/it/user.json index d490e3e1ef..18acd1c232 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Questo utente non è seguito da nessuno :(", "follows_no_one": "Questo utente non segue nessuno :(", "has_no_posts": "Questo utente non ha ancora postato nulla.", diff --git a/public/language/ja/email.json b/public/language/ja/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ja/email.json +++ b/public/language/ja/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index 3d88578169..0d6aad1afd 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "ユーザーが停止された", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "板が存在しない", "no-topic": "スレッドが存在しない", "no-post": "ポストが存在しない", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 7d24e2e34f..94366f766e 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "スレッドはこちらのカテゴリへ移動", "fork_topic_instruction": "フォークしたいポストをクリックして", "fork_no_pids": "ポストが選択されていません!", - "fork_success": "スレッドをフォークできました!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "スレッドのタイトルを入力して...", "composer.discard": "破棄する", "composer.submit": "保存する", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index ddb43c4dd5..0c683c1b86 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -52,6 +52,7 @@ "digest_daily": "デイリー", "digest_weekly": "ウィークリー", "digest_monthly": "マンスリー", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "フォロワーはまだいません :(", "follows_no_one": "フォロー中のユーザーはまだいません :(", "has_no_posts": "まだポストを投稿したことありません。", diff --git a/public/language/ko/email.json b/public/language/ko/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/ko/email.json +++ b/public/language/ko/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index fd74542ee3..be2363f55a 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -20,6 +20,7 @@ "username-too-short": "사용자 이름이 너무 짧습니다.", "username-too-long": "Username too long", "user-banned": "차단된 사용자입니다.", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "존재하지 않는 카테고리입니다.", "no-topic": "존재하지 않는 주제입니다.", "no-post": "존재하지 않는 게시물입니다.", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index 3619cdc736..7e4d14a405 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "이 주제를 지정한 카테고리로 이동합니다.", "fork_topic_instruction": "복제할 게시물을 선택하세요.", "fork_no_pids": "게시물이 선택되지 않았습니다.", - "fork_success": "성공적으로 주제를 복제했습니다.", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "여기에 제목을 입력하세요.", "composer.discard": "취소", "composer.submit": "등록", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index e3b7b29e83..fb0625f04f 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -52,6 +52,7 @@ "digest_daily": "매일", "digest_weekly": "매주", "digest_monthly": "매월", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "아무도 이 사용자를 팔로우하지 않습니다.", "follows_no_one": "이 사용자는 아무도 팔로우하지 않습니다.", "has_no_posts": "이 사용자는 게시물을 작성하지 않았습니다.", diff --git a/public/language/lt/email.json b/public/language/lt/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/lt/email.json +++ b/public/language/lt/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index f41fc62c24..48edfbb72a 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -20,6 +20,7 @@ "username-too-short": "Slapyvardis per trumpas", "username-too-long": "Username too long", "user-banned": "Vartotojas užblokuotas", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategorija neegzistuoja", "no-topic": "Tema neegzistuoja", "no-post": "Pranešimas neegzistuoja", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 2eb059c363..2d1896a77c 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ši tema bus perkelta į kategoriją", "fork_topic_instruction": "Pažymėkite ant įrašų, kuriuos norite perkelti į naują temą", "fork_no_pids": "Nepasirinktas joks įrašas!", - "fork_success": "Tema sėkmingai išskaidyta!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Įrašykite temos pavadinimą...", "composer.discard": "Atšaukti", "composer.submit": "Patvirtinti", diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 1902cdd399..2823f89e95 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -52,6 +52,7 @@ "digest_daily": "Kas dieną", "digest_weekly": "Kas savaitę", "digest_monthly": "Kas mėnesį", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Šis vartotojas neturi jokių sekėjų :(", "follows_no_one": "Šis vartotojas nieko neseka :(", "has_no_posts": "Šis vartotojas dar neparašė nė vieno pranešimo.", diff --git a/public/language/ms/email.json b/public/language/ms/email.json index 3126f0da9d..8d02c3995f 100644 --- a/public/language/ms/email.json +++ b/public/language/ms/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Terima Kasih!" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 6c9f213d94..b6ad0bd586 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nama pengunna terlalu pendek", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index a40da8e224..dde98de2a8 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Topik ini akan dipindahkan kepada kategori", "fork_topic_instruction": "Clik kiriman yang anda hendak salin", "fork_no_pids": "Tiada kiriman yang dipilih", - "fork_success": "Berjaya menyalin topik!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Masukkan tajuk topik disini", "composer.discard": "Abaikan", "composer.submit": "Hantar", diff --git a/public/language/ms/user.json b/public/language/ms/user.json index 74fffdbd04..cff395f87a 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -52,6 +52,7 @@ "digest_daily": "Harian", "digest_weekly": "Mingguan", "digest_monthly": "Bulanan", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Pengguna ini tiada pengikut :(", "follows_no_one": "Pengguna ini tidak mengikuti sesiapa :(", "has_no_posts": "Pengguna ini masih belum mengirim sebarang pos", diff --git a/public/language/nb/email.json b/public/language/nb/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/nb/email.json +++ b/public/language/nb/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 9aeddb6ecf..65f9f09bb5 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dette emnet vil bli flyttet til kategorien", "fork_topic_instruction": "Trykk på innleggene du vil dele", "fork_no_pids": "Ingen innlegg valgt!", - "fork_success": "Innlegg ble delt!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Skriv din tråd-tittel her", "composer.discard": "Forkast", "composer.submit": "Send", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 5f3f3db9ab..7575e7f77f 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daglig", "digest_weekly": "Ukentlig", "digest_monthly": "Månedlig", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Denne brukeren har ingen følgere :(", "follows_no_one": "Denne brukeren følger ingen :(", "has_no_posts": "Denne brukeren har ikke skrevet noe enda.", diff --git a/public/language/nl/category.json b/public/language/nl/category.json index 7c6651aa9a..45dbbf4d4d 100644 --- a/public/language/nl/category.json +++ b/public/language/nl/category.json @@ -4,5 +4,5 @@ "browsing": "verkennen", "no_replies": "Niemand heeft gereageerd", "share_this_category": "Deel deze categorie", - "ignore": "Ignore" + "ignore": "Negeren" } \ No newline at end of file diff --git a/public/language/nl/email.json b/public/language/nl/email.json index e662dce397..ee272f19f5 100644 --- a/public/language/nl/email.json +++ b/public/language/nl/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "De laatste onderwerpen van %1", "digest.cta": "Klik hier om deze website te bezoeken %1 ", "digest.unsub.info": "Deze overzicht was verzonden naar jou vanwege je abbonement instellingen", - "digest.unsub.cta": "Klik hier om u instellingen te wijzigen", "digest.daily.no_topics": "Er zijn geen actieve onderwerpen vandaag", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Dit is een test email om te verifiëren dat de email service correct is opgezet voor jou NodeBB", + "unsub.cta": "Click here to alter those settings", "closing": "Bedankt!" } \ No newline at end of file diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 173db65af9..d27566d742 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ongeldige Titel!", "invalid-user-data": "Ongeldig Gebruikersdata", "invalid-password": "Ongeldig wachtwoord", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Geef alsjeblieft een gebruikersnaam en wachtwoord op", "invalid-pagination-value": "Ongeldig pagineringswaarde", "username-taken": "Gebruikersnaam is al bezet", "email-taken": "Email adres is al gebruikt", "email-not-confirmed": "U email adres is niet bevestigd, Klik hier om uw email adres te bevestigen", "username-too-short": "Gebruikersnaam is te kort", - "username-too-long": "Username too long", + "username-too-long": "Gebruikersnaam is te lang", "user-banned": "Gebruiker verbannen", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categorie bestaat niet", "no-topic": "Onderwerp bestaat niet", "no-post": "Bericht bestaat niet", @@ -54,9 +55,9 @@ "upload-error": "Upload Fout : %1", "signature-too-long": "Deze handtekening kan niet groter zijn dan %1 karakters!", "cant-chat-with-yourself": "Je kan niet met jezelf chatten!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Reputatie systeem is uitgeschakeld", + "downvoting-disabled": "Downvoten is uitgeschakeld", "not-enough-reputation-to-downvote": "U heeft niet de benodigde reputatie om dit bericht te downvoten", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "U heeft niet de benodigde reputatie om dit bericht te melden aan de admins", + "reload-failed": "NodeBB heeft een probleem geconstateerd tijdens het laden van: \"%1\".\nNodeBB blijft verder draaien. Het is wel verstandig om de actie wat u daarvoor heeft gedaan ongedaan te maken door te herladen." } \ No newline at end of file diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index 6559d5df3e..4c51c977b5 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -10,14 +10,14 @@ "new_notification": "een nieuwe notificatie", "you_have_unread_notifications": "U heeft ongelezen notificaties", "new_message_from": "Nieuw bericht van %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 heeft uw bericht geupvote in %2.", + "moved_your_post": "%1 heeft uw bericht verplaatst", + "moved_your_topic": "%1 heeft uw onderwerp verplaatst.", + "favourited_your_post_in": "%1 heeft uw bericht gefavoriet in %2.", + "user_flagged_post_in": "%1 rapporteerde een bericht in %2", "user_posted_to": "%1 heeft een reactie op het bericht gegeven aan %2", "user_mentioned_you_in": "%1 heeft u genoemd in %2", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1 volgt u nu.", "email-confirmed": "Email adres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van uw email adres. Uw account is nu volledig actief.", "email-confirm-error": "Een fout vond plaats", diff --git a/public/language/nl/recent.json b/public/language/nl/recent.json index f111931b98..fbbfaaeb52 100644 --- a/public/language/nl/recent.json +++ b/public/language/nl/recent.json @@ -4,6 +4,6 @@ "week": "Week", "month": "Maand", "year": "Jaar", - "alltime": "All Time", + "alltime": "Intussen", "no_recent_topics": "Er zijn geen recente reacties." } \ No newline at end of file diff --git a/public/language/nl/search.json b/public/language/nl/search.json index 7b1aab07ca..cb61065a4e 100644 --- a/public/language/nl/search.json +++ b/public/language/nl/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 resulta(a)ten was een match \"%2\", (%3 seconds)", - "no-matches": "No posts found" + "no-matches": "Geen berichten gevonden" } \ No newline at end of file diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index 9a9d1a2f81..0530a16f5c 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Dit onderwerp zal verplaatst worden naar de categorie", "fork_topic_instruction": "Klik op de berichten die je wilt forken", "fork_no_pids": "Geen berichten geselecteerd!", - "fork_success": "Onderwerp is met succes gesplitst!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Vul de titel voor het onderwerp hier in...", "composer.discard": "Annuleren", "composer.submit": "Opslaan", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index df0f112c6f..e91b069cae 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -4,8 +4,8 @@ "username": "Gebruikersnaam", "email": "Email", "confirm_email": "Bevestig uw email adres", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account": "Account Verwijderen", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Volledige Naam", "website": "Website", "location": "Locatie", @@ -52,6 +52,7 @@ "digest_daily": "Dagelijks", "digest_weekly": "Weekelijks", "digest_monthly": "Maandelijks", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Deze gebruiker heeft geen volgers :(", "follows_no_one": "Deze gebruiker volgt niemand :(", "has_no_posts": "Deze gebruiker heeft nog geen berichten geplaatst", @@ -64,6 +65,6 @@ "notification_sounds": "Speel een geluid af wanneer ik een notificatie ontvang.", "browsing": "Zoek Instellingen", "open_links_in_new_tab": "Open de uitgaande links in een nieuw tabblad?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Volg de onderwerpen waarop u gereageerd heeft.", + "follow_topics_you_create": "Volg de onderwerpen die u gecreëerd heeft." } \ No newline at end of file diff --git a/public/language/pl/email.json b/public/language/pl/email.json index 2e5df85e6a..9a154171c0 100644 --- a/public/language/pl/email.json +++ b/public/language/pl/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Ostatnie tematy z %1", "digest.cta": "Kliknij, by odwiedzić %1", "digest.unsub.info": "To podsumowanie zostało wysłane zgodnie z Twoimi ustawieniami.", - "digest.unsub.cta": "Kliknij tutaj, aby je zmienić", "digest.daily.no_topics": "Wczoraj nie było żadnych aktywnych tematów", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "To jest e-mail testowy, aby sprawdzić, czy poprawnie skonfigurowałeś e-mailer w swoim NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Dziękujemy!" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 75fd7e8daa..c91c2ad906 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -12,14 +12,15 @@ "invalid-title": "Błędny tytuł.", "invalid-user-data": "Błędne dane użytkownika.", "invalid-password": "Błędne hasło", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Proszę podać nazwę użytkownika i hasło", "invalid-pagination-value": "Błędna wartość paginacji", "username-taken": "Login zajęty.", "email-taken": "E-mail zajęty.", "email-not-confirmed": "Twój email nie jest potwierdzony, kliknij tutaj aby go potwierdzić.", "username-too-short": "Nazwa użytkownika za krótka.", - "username-too-long": "Username too long", + "username-too-long": "Zbyt długa nazwa użytkownika", "user-banned": "Użytkownik zbanowany", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategoria nie istnieje.", "no-topic": "Temat nie istnieje", "no-post": "Post nie istnieje", @@ -54,9 +55,9 @@ "upload-error": "Błąd uploadu: %1", "signature-too-long": "Sygnatura nie może mieć więcej niż %1 znaków.", "cant-chat-with-yourself": "Nie możesz czatować ze sobą", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "System reputacji został wyłączony", + "downvoting-disabled": "Ocena postów jest wyłączona", "not-enough-reputation-to-downvote": "Masz za mało reputacji by ocenić ten post.", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", + "not-enough-reputation-to-flag": "Nie masz wystarczającej reputacji by oflagować ten post", "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." } \ No newline at end of file diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 6ef02c02a4..25ae1af224 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Ten wątek zostanie przeniesiony do kategorii", "fork_topic_instruction": "Zaznacz posty, które chcesz sklonować", "fork_no_pids": "Nie zaznaczyłeś żadnych postów!", - "fork_success": "Pomyślnie sklonowano wątek", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Wpisz tytuł wątku tutaj", "composer.discard": "Odrzuć", "composer.submit": "Wyślij", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 65a0258933..82b79963f7 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -4,8 +4,8 @@ "username": "Nazwa użytkownika", "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account": "Skasuj konto", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Pełna nazwa", "website": "Strona WWW", "location": "Położenie", @@ -52,6 +52,7 @@ "digest_daily": "Codziennie", "digest_weekly": "Co tydzień", "digest_monthly": "Co miesiąc", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Ten użytkownik nie ma jeszcze żadnych obserwujących", "follows_no_one": "Użytkownik jeszcze nikogo nie obsweruje.", "has_no_posts": "Użytkownik nie napisał jeszcze żadnych postów.", @@ -64,6 +65,6 @@ "notification_sounds": "Odtwórz dźwięk po otrzymaniu powiadomienia.", "browsing": "Ustawienia szukania", "open_links_in_new_tab": "Otwierać linki zewnętrzne w nowych kartach?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Śledź tematy, w których piszesz.", + "follow_topics_you_create": "Śledź swoje tematy." } \ No newline at end of file diff --git a/public/language/pt_BR/email.json b/public/language/pt_BR/email.json index f0b4cce398..2857f112a3 100644 --- a/public/language/pt_BR/email.json +++ b/public/language/pt_BR/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Últimos tópicos de %1", "digest.cta": "Clique aqui para visitar %1", "digest.unsub.info": "Este resumo foi enviado para você devido a configuração de assinatura", - "digest.unsub.cta": "Clique aqui para alterar suas configurações", "digest.daily.no_topics": "Não houve nenhum tópico ativo atualmente", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Este é um e-mail de teste, para verificar que o email está corretamente configurado no seu NodeBB.\n", + "unsub.cta": "Click here to alter those settings", "closing": "Obrigado!" } \ No newline at end of file diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index dd6fe32c3c..d6e0da1ede 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -20,6 +20,7 @@ "username-too-short": "Nome de usuário muito curto", "username-too-long": "Username too long", "user-banned": "Usuário banido", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categoria não existe", "no-topic": "Tópico não existe", "no-post": "Post não existe", diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index 8a4912a100..a0f0caea3d 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Este tópico será movido para a categoria", "fork_topic_instruction": "Clique nos posts que você quer clonar", "fork_no_pids": "Nenhum post selecionado!", - "fork_success": "Clone realizado com sucesso!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Digite aqui o título para o seu tópico...", "composer.discard": "Descartar", "composer.submit": "Enviar", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 366c291a5d..78084398dd 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -52,6 +52,7 @@ "digest_daily": "Diariamente", "digest_weekly": "Semanalmente", "digest_monthly": "Mensalmente", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Este usuário não possui seguidores :(", "follows_no_one": "Este usuário não está seguindo ninguém :(", "has_no_posts": "Este usuário não postou nada ainda.", diff --git a/public/language/ro/email.json b/public/language/ro/email.json index 9c7da47629..7dd905af3f 100644 --- a/public/language/ro/email.json +++ b/public/language/ro/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Apasă aici pentru a vizita %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Mulțumesc!" } \ No newline at end of file diff --git a/public/language/ro/error.json b/public/language/ro/error.json index f8346aa636..a40ac5f0d0 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -20,6 +20,7 @@ "username-too-short": "Numele de utilizator este prea scurt", "username-too-long": "Username too long", "user-banned": "Utilizator banat", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Categoria nu există", "no-topic": "Subiectul nu există", "no-post": "Mesajul nu există", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 0463625d25..3bcdf5805e 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Acest subiect va fi mutat în categoria", "fork_topic_instruction": "Apasă pe mesajele care vrei sa le bifurci", "fork_no_pids": "Nu a fost selectat nici un mesaj!", - "fork_success": "Ai bifurcat mesajul cu succes!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Introdu numele subiectului aici ...", "composer.discard": "Renunță", "composer.submit": "Trimite", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 124f9076b6..1719ecce2e 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -52,6 +52,7 @@ "digest_daily": "Zilnic", "digest_weekly": "Săptămânal", "digest_monthly": "Lunar", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Pe acest utilizator nu îl urmărește nimeni :(", "follows_no_one": "Acest utilizator nu urmărește pe nimeni :(", "has_no_posts": "Acest utilizator nu a postat nici un mesaj până acuma.", diff --git a/public/language/ru/email.json b/public/language/ru/email.json index d0fc8fabed..5d463a1779 100644 --- a/public/language/ru/email.json +++ b/public/language/ru/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Последние темы %1", "digest.cta": "Кликните здесь для просмотра %1", "digest.unsub.info": "Вам была выслана сводка новостей в соответствии с вашими настройками.", - "digest.unsub.cta": "Кликните здесь для изменения ваших настроек.", "digest.daily.no_topics": "За минувший день новых тем нет.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Это тестовое сообщение для проверки почтового сервиса NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Спасибо!" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 875a018f95..a715f9c9a2 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -20,6 +20,7 @@ "username-too-short": "Слишком короткое имя пользователя", "username-too-long": "Username too long", "user-banned": "Пользователь заблокирован", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Несуществующая категория", "no-topic": "Несуществующая тема", "no-post": "Несуществующая запись", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 9757210077..5a97aa09ea 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Эта тема будет перенесена в категорию", "fork_topic_instruction": "Отметьте сообщения для ответвления", "fork_no_pids": "Сообщения не отмечены!", - "fork_success": "Ответвление темы создано!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Введите название темы...", "composer.discard": "Отменить", "composer.submit": "Подтвердить", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 6da3902849..e1902b30ee 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -52,6 +52,7 @@ "digest_daily": "За День", "digest_weekly": "За Неделю", "digest_monthly": "За Месяц", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Этого пользователя никто не читает :(", "follows_no_one": "Этот пользователь никого не читает :(", "has_no_posts": "Это пользователь еще ничего не написал.", diff --git a/public/language/sc/email.json b/public/language/sc/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/sc/email.json +++ b/public/language/sc/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index afc9d94ddd..b6eee990c6 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Custa arresonada at a èssere mòvida in sa creze", "fork_topic_instruction": "Sèbera is arresonos chi boles partzire", "fork_no_pids": "Perunu arresonu seberadu!", - "fork_success": "As partzidu s'arresonada!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Pone su tìtulu de s'arresonada inoghe...", "composer.discard": "Lassa a Pèrdere", "composer.submit": "Imbia", diff --git a/public/language/sc/user.json b/public/language/sc/user.json index 7c8781dfb8..826a3a82be 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Custu impitadore non tenet perunu sighidore :(", "follows_no_one": "Custu impitadore no est sighende nissunu :(", "has_no_posts": "Custu impitadore no at ancora publicadu nudda.", diff --git a/public/language/sk/email.json b/public/language/sk/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/sk/email.json +++ b/public/language/sk/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 08c0106813..60262ff3e4 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "Užívateľ je zakázaný", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategória neexistuje", "no-topic": "Téme neexistuje", "no-post": "Príspevok neexistuje", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 697c0880ca..72da1a0869 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Táto téma bude presunutá do kategórie", "fork_topic_instruction": "Vyber príspevky, ktoré chceš oddeliť", "fork_no_pids": "Žiadne príspevky neboli vybrané!", - "fork_success": "Téma bola úspešne rozdelená!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Vlož nadpis témy sem...", "composer.discard": "Zahodiť", "composer.submit": "Poslať", diff --git a/public/language/sk/user.json b/public/language/sk/user.json index cefdf45dfe..7b10ab4040 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -52,6 +52,7 @@ "digest_daily": "Denne", "digest_weekly": "Týždenne", "digest_monthly": "Mesačne", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Tohoto užívatela nikto nesleduje :(", "follows_no_one": "Tento užívateľ nikoho nesleduje :(", "has_no_posts": "Tento používateľ ešte nespravil príspevok", diff --git a/public/language/sv/email.json b/public/language/sv/email.json index 2a6f643862..5c84229840 100644 --- a/public/language/sv/email.json +++ b/public/language/sv/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Senaste ämnen från %1", "digest.cta": "Klicka här för att besöka %1", "digest.unsub.info": "Det här meddelandet fick du på grund av dina inställningar för prenumeration. ", - "digest.unsub.cta": "Klicka här för att ändra dom inställningarna", "digest.daily.no_topics": "Det verkar inte varit några aktiva ämnen dom senaste dagarna ", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "\nDet här är ett textmeddelande som verifierar att eposten är korrekt installerat för din NodeBB. ", + "unsub.cta": "Click here to alter those settings", "closing": "Tack!" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 2c10713a84..6f118116f3 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -12,14 +12,15 @@ "invalid-title": "Ogiltig rubrik.", "invalid-user-data": "Ogiltig användardata", "invalid-password": "Ogiltigt lösenord", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Specificera både användarnamn och lösenord", "invalid-pagination-value": "Ogiltigt sidnummer", "username-taken": "Användarnamn upptaget", "email-taken": "Epostadress upptagen", "email-not-confirmed": "Din epostadress är ännu inte bekräftad. Klicka här för att bekräfta din epostadress.", "username-too-short": "Användarnamnet är för kort", - "username-too-long": "Username too long", + "username-too-long": "Användarnamnet är för långt", "user-banned": "Användare bannad", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategori hittades inte", "no-topic": "Ämne hittades inte", "no-post": "Inlägget hittades inte", @@ -54,9 +55,9 @@ "upload-error": "Fel vid uppladdning: %1", "signature-too-long": "Signaturer kan inte vara längre än %1 tecken.", "cant-chat-with-yourself": "Du kan inte chatta med dig själv.", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "Ryktessystemet är inaktiverat.", + "downvoting-disabled": "Nedröstning är inaktiverat", "not-enough-reputation-to-downvote": "Du har inte tillräckligt förtroende för att rösta ner det här meddelandet", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "Du har inte tillräckligt förtroende för att flagga det här inlägget.", + "reload-failed": "NodeBB stötte på problem med att ladda om: \"%1\". NodeBB kommer fortsätta servera den befintliga resurser till klienten, men du borde återställa det du gjorde alldeles innan du försökte ladda om." } \ No newline at end of file diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json index c4c0efc382..72c9d13e33 100644 --- a/public/language/sv/pages.json +++ b/public/language/sv/pages.json @@ -5,7 +5,7 @@ "recent": "Senaste ämnena", "users": "Registrerade användare", "notifications": "Notiser", - "tags": "Topics tagged under \"%1\"", + "tags": "Ämnen märkta med \"%1\"", "user.edit": "Ändrar \"%1\"", "user.following": "Personer %1 Följer", "user.followers": "Personer som följer %1", diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 8615e49c77..f2f1adb18d 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Detta ämne kommer bli flytta till kategori", "fork_topic_instruction": "Klicka på de inlägg du vill grena", "fork_no_pids": "Inga inlägg valda!", - "fork_success": "Grening av inlägg lyckad!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Skriv in ämnets titel här...", "composer.discard": "Avbryt", "composer.submit": "Spara", @@ -87,7 +87,7 @@ "more_users_and_guests": "%1 fler användare() och %2 gäst(er)", "more_users": "%1 fler användare()", "more_guests": "1% fler gäst(er)", - "users_and_others": "%1 and %2 others", + "users_and_others": "%1 och %2 andra", "sort_by": "Sortera på", "oldest_to_newest": "Äldst till nyaste", "newest_to_oldest": "Nyaste till äldst", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index e4e4c01dcb..e4e18aafaa 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -4,8 +4,8 @@ "username": "Användarnamn", "email": "Epost", "confirm_email": "Bekräfta epostadress ", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account": "Ta bort ämne", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Hela namnet", "website": "Webbsida", "location": "Plats", @@ -52,6 +52,7 @@ "digest_daily": "Daligen", "digest_weekly": "Veckovis", "digest_monthly": "Månadsvis", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Denna användare har inga följare :(", "follows_no_one": "Denna användare följer ingen :(", "has_no_posts": "Denna användare har inte gjort några inlägg än.", @@ -64,6 +65,6 @@ "notification_sounds": "Spela ett ljud när du får en notis.", "browsing": "Inställning för bläddring", "open_links_in_new_tab": "Öppna utgående länkar på ny flik?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Följ ämnen so du svarat på.", + "follow_topics_you_create": "Följ ämnen du skapat." } \ No newline at end of file diff --git a/public/language/th/email.json b/public/language/th/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/th/email.json +++ b/public/language/th/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 26e3c9d80c..f90d459cce 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "User banned", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Category doesn't exist", "no-topic": "Topic doesn't exist", "no-post": "Post doesn't exist", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index c72667cb33..0736d6be97 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "กระทู้นี้จะถูกย้ายไปที่หมวดหมู่", "fork_topic_instruction": "คลิกที่โพสที่คุณต้องการที่จะแยก", "fork_no_pids": "ไม่มีโพสต์ที่เลือก!", - "fork_success": "แยกกระทู้สำเร็จ", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "ป้อนชื่อกระทู้ของคุณที่นี่ ...", "composer.discard": "ยกเลิก", "composer.submit": "ส่ง", diff --git a/public/language/th/user.json b/public/language/th/user.json index f6d6861e31..7f75a2bcbb 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -52,6 +52,7 @@ "digest_daily": "Daily", "digest_weekly": "Weekly", "digest_monthly": "Monthly", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "ผู้ใช้รายนี้ไม่มีใครติดตาม :(", "follows_no_one": "ผู้ใช้รายนี้ไม่ติดตามใคร :(", "has_no_posts": "ผู้ใช้รายนี้ไม่ได้โพสต์อะไรเลย", diff --git a/public/language/tr/email.json b/public/language/tr/email.json index 96fd81431e..e1d294f543 100644 --- a/public/language/tr/email.json +++ b/public/language/tr/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "En güncel konular", "digest.cta": "Ziyaret etmek için buraya tıklayın", "digest.unsub.info": "Bu e-posta seçtiğiniz ayarlar nedeniyle gönderildi.", - "digest.unsub.cta": "Bu ayarları değiştirmek için buraya tıklayın", "digest.daily.no_topics": "Geçtiğimiz gün içinde aktif bir konu yok.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.", + "unsub.cta": "Click here to alter those settings", "closing": "Teşekkürler!" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 6c6a37053a..86b7a7567d 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -20,6 +20,7 @@ "username-too-short": "Kullanıcı ismi çok kısa", "username-too-long": "Username too long", "user-banned": "Kullanıcı Yasaklı", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Kategori Yok", "no-topic": "Başlık Yok", "no-post": "İleti Yok", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index 0716b3e6f1..f4968ca57a 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "Bu konu kategorisine taşınacak", "fork_topic_instruction": "Ayırmak istediğiniz iletileri tıklayın", "fork_no_pids": "Hiç bir ileti seçilmedi!", - "fork_success": "Başlık başarıyla ayrıldı!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Başlık ismini buraya girin...", "composer.discard": "Vazgeç", "composer.submit": "Gönder", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index fb1316fd7e..1327933256 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -52,6 +52,7 @@ "digest_daily": "Günlük", "digest_weekly": "Haftalık", "digest_monthly": "Aylık", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Bu kullanıcının hiç takipçisi yok :(", "follows_no_one": "Bu kullanıcı kimseyi takip etmiyor :(", "has_no_posts": "Bu kullanıcı henüz birşey göndermedi.", diff --git a/public/language/vi/email.json b/public/language/vi/email.json index 98e591ab02..60825a08d6 100644 --- a/public/language/vi/email.json +++ b/public/language/vi/email.json @@ -1,20 +1,23 @@ { - "password-reset-requested": "Password Reset Requested - %1!", - "welcome-to": "Welcome to %1", - "greeting_no_name": "Hello", - "greeting_with_name": "Hello %1", - "welcome.text1": "Thank you for registering with %1!", - "welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.", - "welcome.cta": "Click here to confirm your email address", - "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.", - "reset.text2": "To continue with the password reset, please click on the following link:", - "reset.cta": "Click here to reset your password", - "digest.notifications": "You have some unread notifications from %1:", - "digest.latest_topics": "Latest topics from %1", - "digest.cta": "Click here to visit %1", - "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", - "digest.daily.no_topics": "There have been no active topics in the past day", - "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", - "closing": "Thanks!" + "password-reset-requested": "Yêu cầu khởi tạo lại mật khẩu - %1!", + "welcome-to": "Chào mừng đến với %1", + "greeting_no_name": "Xin chào", + "greeting_with_name": "Xin chào %1", + "welcome.text1": "Cảm ơn bạn đã đăng ký tại %1!", + "welcome.text2": "Để kích hoạt đầy đủ tính năng của tài khoản, chúng tôi cần xác định rằng bạn là chủ của địa chỉ email mà bạn đã đăng ký.", + "welcome.cta": "Click vào đây để xác nhận địa chỉ email", + "reset.text1": "Chúng tôi nhận được yêu cầu khởi tạo lại mật khẩu của bạn, rất có thể vì bạn đã quên mất nó. Nếu không đúng, hãy bỏ qua email này.", + "reset.text2": "Để khởi tạo lại mật khẩu, hãy click vào liên kết sau:", + "reset.cta": "Click vào đây để khởi tạo lại mật khẩu", + "digest.notifications": "Bạn có vài thông báo chưa duyệt từ %1:", + "digest.latest_topics": "Chủ đề mới nhất từ %1", + "digest.cta": "Click vào đây để truy cập %1", + "digest.unsub.info": "Tập san này được gửi đến bạn dựa theo cài đặt theo dõi của bạn.", + "digest.daily.no_topics": "Không có chủ đề nào có hoạt động trong ngày hôm qua.", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", + "test.text1": "Đây là email kiểm tra để xác nhận rằng trình gửi email đã được cài đặt một cách chính xác cho NodeBB của bạn.", + "unsub.cta": "Click here to alter those settings", + "closing": "Xin cảm ơn!" } \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 189c8de1a5..c375b9fd51 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -1,25 +1,26 @@ { "invalid-data": "Dữ liệu không hợp lệ", "not-logged-in": "Bạn không được đăng nhập.", - "account-locked": "Your account has been locked temporarily", - "search-requires-login": "Searching requires an account! Please login or register!", + "account-locked": "Tài khoản của bạn đã tạm thời bị khóa", + "search-requires-login": "Bạn cần đăng nhập để thực hiện việc tìm kiếm! Xin hãy đăng nhập hoặc đăng ký!", "invalid-cid": "Danh mục ID không hợp lệ", "invalid-tid": "ID chủ đề không hợp lệ", "invalid-pid": "ID bài viết không hợp lệ", "invalid-uid": "ID tài khoản không hợp lệ", "invalid-username": "Tên đăng nhập không hợp lệ", "invalid-email": "Email không hợp lệ", - "invalid-title": "Invalid title!", + "invalid-title": "Tiêu đề không hợp lệ!", "invalid-user-data": "Dữ liệu tài khoản không hợp lệ", "invalid-password": "Mật khẩu không hợp lệ", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Xin hãy nhập cả tên đăng nhập và mật khẩu", "invalid-pagination-value": "Số trang không hợp lệ", "username-taken": "Tên đăng nhập đã tồn tại", "email-taken": "Email đã tồn tại", - "email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.", - "username-too-short": "Username too short", - "username-too-long": "Username too long", + "email-not-confirmed": "Địa chỉ email của bạn chưa được xác nhận, hãy click vào đây để xác nhận email.", + "username-too-short": "Tên đăng nhập quá ngắn", + "username-too-long": "Tên đăng nhập quá dài", "user-banned": "Tài khoản bị ban", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "Phần mục không tồn tại", "no-topic": "Chủ đề không tồn tại", "no-post": "Bài viết không tồn tại", @@ -27,7 +28,7 @@ "no-user": "Tài khoản không tồn tại", "no-teaser": "Teaser không tồn tại", "no-privileges": "Bạn không đủ quyền cho hành động này", - "no-emailers-configured": "No email plugins were loaded, so a test email could not be sent", + "no-emailers-configured": "Không có trình cắm email nào được tải, vì vậy email kiểm tra không thể gửi được", "category-disabled": "Danh mục bị disabled", "topic-locked": "Chủ đề bị khóa", "still-uploading": "Vui lòng chờ upload", @@ -38,7 +39,7 @@ "file-too-big": "Kích thước file tối đa %1kb", "cant-vote-self-post": "Bạn không thể vote cho chính bài viết của bạn", "already-favourited": "Bạn đã bấm yêu thích cho bài viết này", - "already-unfavourited": "You already unfavourited this post", + "already-unfavourited": "Bạn đã bỏ thích bài này", "cant-ban-other-admins": "Bạn không thể ban được các admin khác", "invalid-image-type": "Kiểu hình ảnh không hợp lệ", "group-name-too-short": "Tên nhóm quá ngắn", @@ -51,12 +52,12 @@ "topic-thumbnails-are-disabled": "Thumbnails cho chủ đề đã bị tắt", "invalid-file": "File không hợp lệ", "uploads-are-disabled": "Đã khóa lựa chọn tải lên", - "upload-error": "Upload Error : %1", + "upload-error": "Lỗi tải lên : %1", "signature-too-long": "Chứ ký không được dài quá %1 ký tự", "cant-chat-with-yourself": "Bạn không thể chat với chính bạn!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", - "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "reputation-system-disabled": "Hệ thống tín nhiệm đã bị vô hiệu hóa.", + "downvoting-disabled": "Downvote đã bị tắt", + "not-enough-reputation-to-downvote": "Bạn không có đủ phiếu tín nhiệm để downvote bài này", + "not-enough-reputation-to-flag": "Bạn không đủ phiếu tín nhiệm để gắn cờ bài này", + "reload-failed": "NodeBB gặp lỗi trong khi tải lại: \"%1\". NodeBB sẽ tiếp tục hoạt động với dữ liệu trước đó, mặc dù vậy bạn nên tháo gỡ những gì bạn đã làm để trước khi tải lại." } \ No newline at end of file diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 5554138a31..33447ba786 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -13,13 +13,13 @@ "please_log_in": "Xin hãy đăng nhập", "logout": "Đăng xuất", "posting_restriction_info": "Hiện giờ chỉ có các thành viên mới được quyền gửi bài viết, hãy nhấn vào đây để đăng nhập", - "welcome_back": "Welcome Back", + "welcome_back": "Chào mừng bạn quay lại", "you_have_successfully_logged_in": "Bạn đã đăng nhập thành công", "save_changes": "Lưu thay đổi", "close": "Đóng lại", "pagination": "Số trang", - "pagination.out_of": "%1 out of %2", - "pagination.enter_index": "Enter index", + "pagination.out_of": "%1 trong số %2", + "pagination.enter_index": "Nhập khóa", "header.admin": "Quản trị viên", "header.recent": "Gần đây", "header.unread": "Chưa đọc", @@ -38,7 +38,7 @@ "alert.success": "Thành công", "alert.error": "Lỗi", "alert.banned": "Bị cấm", - "alert.banned.message": "You have just been banned, you will now be logged out.", + "alert.banned.message": "Bạn vừa bị khóa tài khoản, bạn sẽ tự động thoát ra ngay bây giờ.", "alert.unfollow": "Bạn đã không còn theo dõi %1!", "alert.follow": "Bạn giờ đang theo dõi %1!", "online": "Đang online", @@ -70,6 +70,6 @@ "language": "Ngôn ngữ", "guest": "Khách", "guests": "Số khách", - "updated.title": "Forum Updated", - "updated.message": "This forum has just been updated to the latest version. Click here to refresh the page." + "updated.title": "Cập nhật diễn đàn", + "updated.message": "Diễn đàn đã được cập nhật bản mới nhất. Click vào đây để tải lại trang." } \ No newline at end of file diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json index c00c111e11..2ae6844485 100644 --- a/public/language/vi/groups.json +++ b/public/language/vi/groups.json @@ -1,7 +1,7 @@ { - "view_group": "View Group", - "details.title": "Group Details", - "details.members": "Member List", - "details.has_no_posts": "This group's members have not made any posts.", - "details.latest_posts": "Latest Posts" + "view_group": "Xem nhóm", + "details.title": "Chi tiết nhóm", + "details.members": "Danh sách thành viên", + "details.has_no_posts": "Nhóm thành viên này chưa viết bài viết nào.", + "details.latest_posts": "Bài mới nhất" } \ No newline at end of file diff --git a/public/language/vi/login.json b/public/language/vi/login.json index 25973cd323..0190bd059e 100644 --- a/public/language/vi/login.json +++ b/public/language/vi/login.json @@ -5,5 +5,5 @@ "alternative_logins": "Đăng nhập bằng tên khác", "failed_login_attempt": "Đăng nhập thất bại, xin hãy thử lại", "login_successful": "Bạn đã đăng nhập thành công!", - "dont_have_account": "Don't have an account?" + "dont_have_account": "Chưa có tài khoản?" } \ No newline at end of file diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index 4795f64e74..6afe9ffa9c 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -1,18 +1,18 @@ { "chat.chatting_with": "Chat với ", - "chat.placeholder": "Type chat message here, press enter to send", + "chat.placeholder": "Nhập tin nhắn ở đây, nhấn enter để gửi", "chat.send": "Gửi đi", "chat.no_active": "Bạn hiện giờ không có cuộc chat nào", "chat.user_typing": "%1b đang gõ", - "chat.user_has_messaged_you": "%1 has messaged you.", - "chat.see_all": "See all Chats", - "chat.no-messages": "Please select a recipient to view chat message history", - "chat.recent-chats": "Recent Chats", - "chat.contacts": "Contacts", - "chat.message-history": "Message History", - "chat.pop-out": "Pop out chat", - "chat.maximize": "Maximize", - "composer.user_said_in": "%1 said in %2:", - "composer.user_said": "%1 said:", - "composer.discard": "Are you sure you wish to discard this post?" + "chat.user_has_messaged_you": "%1 đã gửi tin cho bạn.", + "chat.see_all": "Tất cả tin nhắn.", + "chat.no-messages": "Hãy chọn 1 tài khoản để xem lịch sử chat", + "chat.recent-chats": "Vừa chat", + "chat.contacts": "Liên hệ", + "chat.message-history": "Lịch sử tin nhắn", + "chat.pop-out": "Bật cửa sổ chat", + "chat.maximize": "Phóng to", + "composer.user_said_in": "%1 đã nói trong %2:", + "composer.user_said": "%1 đã nói:", + "composer.discard": "Bạn có chắc chắn hủy bỏ bài viết này?" } \ No newline at end of file diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json index a5ee695599..87143000ad 100644 --- a/public/language/vi/pages.json +++ b/public/language/vi/pages.json @@ -5,7 +5,7 @@ "recent": "Chủ đề gần đây", "users": "Số người dùng đã đăng ký", "notifications": "Thông báo", - "tags": "Topics tagged under \"%1\"", + "tags": "Chủ đề được tag theo \"%1\"", "user.edit": "Chỉnh sửa \"%1\"", "user.following": "Người mà %1 theo dõi", "user.followers": "Người đang theo dõi %1", diff --git a/public/language/vi/tags.json b/public/language/vi/tags.json index f065d4bbfa..e309559c94 100644 --- a/public/language/vi/tags.json +++ b/public/language/vi/tags.json @@ -1,6 +1,6 @@ { - "no_tag_topics": "There are no topics with this tag.", - "tags": "Tags", - "enter_tags_here": "Enter tags here. Press enter after each tag.", - "no_tags": "There are no tags yet." + "no_tag_topics": "Không có bài viết nào với nhãn này.", + "tags": "Nhãn", + "enter_tags_here": "Nhập nhãn ở đây. Nhấn enter sau mỗi nhãn.", + "no_tags": "Chưa có nhãn nào." } \ No newline at end of file diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 597c8cd4b9..baeccd2195 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -7,14 +7,14 @@ "post_is_deleted": "Bài gửi này đã bị xóa!", "profile": "Hồ sơ", "posted_by": "Được viết bởi %1", - "posted_by_guest": "Posted by Guest", + "posted_by_guest": "Đăng bởi khách", "chat": "Chat", "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này", "quote": "Trích dẫn", "reply": "Trả lời", "edit": "Chỉnh sửa", "delete": "Xóa", - "purge": "Purge", + "purge": "Xóa hẳn", "restore": "Phục hồi", "move": "Chuyển đi", "fork": "Fork", @@ -23,7 +23,7 @@ "share": "Chia sẻ", "tools": "Công cụ", "flag": "Flag", - "locked": "Locked", + "locked": "Khóa", "bookmark_instructions": "Bấm vào đây để quay về hoặc đóng lại để hủy", "flag_title": "Flag bài viết này để chỉnh sửa", "flag_confirm": "Bạn có chắc là muốn flag bài viết này không?", @@ -49,12 +49,12 @@ "thread_tools.delete_confirm": "Bạn có chắc là muốn hủy thread này không?", "thread_tools.restore": "Phục hồi chủ đề", "thread_tools.restore_confirm": "Bạn có chắc là muốn phục hồi thread này không", - "thread_tools.purge": "Purge Topic", - "thread_tools.purge_confirm": "Are you sure you want to purge this thread?", + "thread_tools.purge": "Xóa hẳn chủ đề", + "thread_tools.purge_confirm": "Bạn có chắc muốn xóa hẳn chủ đề này?", "topic_move_success": "Đã chuyển thành công chủ đề này sang %1", "post_delete_confirm": "Bạn có chắc là muốn xóa bài gửi này không?", "post_restore_confirm": "Bạn có chắc là muốn phục hồi bài gửi này không?", - "post_purge_confirm": "Are you sure you want to purge this post?", + "post_purge_confirm": "Bạn có chắc muốn xóa hẳn bài này?", "load_categories": "Đang tải các phần mục", "disabled_categories_note": "Các phần mục bị khóa đã được đánh xám", "confirm_move": "Chuyển", @@ -64,14 +64,14 @@ "favourites.has_no_favourites": "Bạn đang không có yêu thích nào. Hãy yêu thích một vài bài viết để thấy được chúng tại đây!", "loading_more_posts": "Tải thêm các bài gửi khác", "move_topic": "Chuyển chủ đề", - "move_topics": "Move Topics", + "move_topics": "Di chuyển chủ đề", "move_post": "Chuyển bài gửi", "post_moved": "Đã chuyển bài gửi!", "fork_topic": "Fork chủ đề", "topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục", "fork_topic_instruction": "Nhấp vào bài gửi mà bạn muốn fork", "fork_no_pids": "Chưa chọn bài gửi nào!", - "fork_success": "Đã fork chủ đề thành công!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...", "composer.discard": "Loại bỏ", "composer.submit": "Gửi", @@ -87,9 +87,9 @@ "more_users_and_guests": "%1 người dùng và %2 khách nữa", "more_users": "%1 người dùng nữa", "more_guests": "%1 khách nữa", - "users_and_others": "%1 and %2 others", - "sort_by": "Sort by", - "oldest_to_newest": "Oldest to Newest", - "newest_to_oldest": "Newest to Oldest", - "most_votes": "Most votes" + "users_and_others": "%1 và%2 khác", + "sort_by": "Sắp xếp theo", + "oldest_to_newest": "Cũ đến mới", + "newest_to_oldest": "Mới đến cũ", + "most_votes": "Bình chọn nhiều nhất" } \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json index 1f5cb24b9a..1aeac40d09 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -3,9 +3,9 @@ "offline": "Offline", "username": "Tên truy cập", "email": "Email", - "confirm_email": "Confirm Email", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "confirm_email": "Xác nhận email", + "delete_account": "Xóa tài khoản", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "Tên đầy đủ", "website": "Website", "location": "Địa điểm", @@ -52,6 +52,7 @@ "digest_daily": "Hàng ngày", "digest_weekly": "Hàng tuần", "digest_monthly": "Hàng tháng", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "Người dùng này hiện chưa có ai theo dõi :(", "follows_no_one": "Người dùng này hiện chưa theo dõi ai :(", "has_no_posts": "Người dùng này chưa viết bài nào", @@ -62,8 +63,8 @@ "topics_per_page": "Số chủ đề trong một trang", "posts_per_page": "Số bài viết trong một trang", "notification_sounds": "Xuất hiện âm thanh khi bạn nhận được một thông báo", - "browsing": "Browsing Settings", - "open_links_in_new_tab": "Open outgoing links in new tab?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "browsing": "Đang xem cài đặt", + "open_links_in_new_tab": "Mở liên kết trong tab mới?", + "follow_topics_you_reply_to": "Theo dõi chủ đề mà bạn trả lời", + "follow_topics_you_create": "Theo dõi chủ đề bạn tạo" } \ No newline at end of file diff --git a/public/language/vi/users.json b/public/language/vi/users.json index 244b26154b..cda4f0b017 100644 --- a/public/language/vi/users.json +++ b/public/language/vi/users.json @@ -5,6 +5,6 @@ "search": "Tìm kiếm", "enter_username": "Gõ tên người dùng để tìm kiếm", "load_more": "Tải thêm", - "user-not-found": "User not found!", - "users-found-search-took": "%1 user(s) found! Search took %2 ms." + "user-not-found": "Tài khoản không tìm thấy!", + "users-found-search-took": "%1 tài khoản(s) tìm thấy! Tìm trong %2 mili giây." } \ No newline at end of file diff --git a/public/language/zh_CN/category.json b/public/language/zh_CN/category.json index 2f776788d1..933e12e3e1 100644 --- a/public/language/zh_CN/category.json +++ b/public/language/zh_CN/category.json @@ -4,5 +4,5 @@ "browsing": "正在浏览", "no_replies": "尚无回复", "share_this_category": "分享此版块", - "ignore": "Ignore" + "ignore": "忽略" } \ No newline at end of file diff --git a/public/language/zh_CN/email.json b/public/language/zh_CN/email.json index ae359e04fb..77b5e00903 100644 --- a/public/language/zh_CN/email.json +++ b/public/language/zh_CN/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "来自 %1 的最新主题", "digest.cta": "点击这里访问 %1", "digest.unsub.info": "根据您的订阅设置,为您发送此摘要。", - "digest.unsub.cta": "点击这里修改这些设置", "digest.daily.no_topics": "最近几天有一些未激活的主题", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。", + "unsub.cta": "Click here to alter those settings", "closing": "谢谢!" } \ No newline at end of file diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index 561c57a0da..56c19a26b2 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -12,14 +12,15 @@ "invalid-title": "无效标题!", "invalid-user-data": "无效用户数据", "invalid-password": "无效密码", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "请确认用户名和密码", "invalid-pagination-value": "无效页码", "username-taken": "用户名已注册", "email-taken": "电子邮箱已注册", "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。", "username-too-short": "用户名太短", - "username-too-long": "Username too long", + "username-too-long": "用户名太长", "user-banned": "用户已禁止", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "版块不存在", "no-topic": "主题不存在", "no-post": "帖子不存在", @@ -54,9 +55,9 @@ "upload-error": "上传错误:%1", "signature-too-long": "签名档不能超过 %1 字!", "cant-chat-with-yourself": "您不能和自己聊天!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "威望系统已禁用。", + "downvoting-disabled": "反对功能已禁用", "not-enough-reputation-to-downvote": "您还没有足够的威望为此帖扣分", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "您没有足够的威望标记此帖", + "reload-failed": "NodeBB 重新加载时遇到问题: \"%1\"。NodeBB 会继续给已存在的客户端组件服务,虽然您应该撤销在重新加载前执行的操作。" } \ No newline at end of file diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json index 6e4faf216f..13d533bc53 100644 --- a/public/language/zh_CN/notifications.json +++ b/public/language/zh_CN/notifications.json @@ -10,14 +10,14 @@ "new_notification": "新通知", "you_have_unread_notifications": "您有未读的通知。", "new_message_from": "来自 %1 的新消息", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1%2 点赞了您的帖子。", + "moved_your_post": "%1 移动了您的帖子。", + "moved_your_topic": "%1 移动了您的主题帖。", + "favourited_your_post_in": "%1%2 收藏了您的帖子。", + "user_flagged_post_in": "%1%2 标记了一个帖子", "user_posted_to": "%1 回复了:%2", "user_mentioned_you_in": "%1%2 中提到了您", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1关注了您。", "email-confirmed": "电子邮箱已确认", "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全面激活。", "email-confirm-error": "出错了.……", diff --git a/public/language/zh_CN/recent.json b/public/language/zh_CN/recent.json index c363d4921c..6b6df56f05 100644 --- a/public/language/zh_CN/recent.json +++ b/public/language/zh_CN/recent.json @@ -4,6 +4,6 @@ "week": "本周", "month": "本月", "year": "本年", - "alltime": "All Time", + "alltime": "有史以来", "no_recent_topics": "无最新主题。" } \ No newline at end of file diff --git a/public/language/zh_CN/search.json b/public/language/zh_CN/search.json index 484acb3626..3d8460e57b 100644 --- a/public/language/zh_CN/search.json +++ b/public/language/zh_CN/search.json @@ -1,4 +1,4 @@ { "results_matching": "共 %1 条结果匹配 \"%2\",(耗时 %3 秒)", - "no-matches": "No posts found" + "no-matches": "没有找到帖子" } \ No newline at end of file diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json index 183cbfcc23..098584ec36 100644 --- a/public/language/zh_CN/topic.json +++ b/public/language/zh_CN/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "此主题将被移动到版块", "fork_topic_instruction": "点击将分割的帖子", "fork_no_pids": "未选中帖子!", - "fork_success": "已成功分割主题!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "在此输入您主题的标题...", "composer.discard": "撤销", "composer.submit": "提交", diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index 51d0b25817..a1c9e34de2 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -4,8 +4,8 @@ "username": "用户名", "email": "电子邮件", "confirm_email": "确认电子邮箱", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?", + "delete_account": "删除帐号", + "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", "fullname": "姓名", "website": "网站", "location": "位置", @@ -52,6 +52,7 @@ "digest_daily": "每天", "digest_weekly": "每周", "digest_monthly": "每月", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "此用户还没有粉丝 :(", "follows_no_one": "此用户尚未关注任何人 :(", "has_no_posts": "此用户尚未发布任何帖子。", @@ -64,6 +65,6 @@ "notification_sounds": "收到通知时播放提示音。", "browsing": "浏览设置", "open_links_in_new_tab": "在新标签中打开外部链接?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "关注您回复的主题。", + "follow_topics_you_create": "关注您创建的主题。" } \ No newline at end of file diff --git a/public/language/zh_TW/email.json b/public/language/zh_TW/email.json index 98e591ab02..fa93bc213e 100644 --- a/public/language/zh_TW/email.json +++ b/public/language/zh_TW/email.json @@ -13,8 +13,11 @@ "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.unsub.cta": "Click here to alter those settings", "digest.daily.no_topics": "There have been no active topics in the past day", + "notif.chat.subject": "New chat message received from %1", + "notif.chat.cta": "Click here to continue the conversation", + "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", + "unsub.cta": "Click here to alter those settings", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index d965ca117e..544d11f42e 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -20,6 +20,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "user-banned": "該使用者已被停用", + "user-too-new": "You need to wait %1 seconds before making your first post!", "no-category": "類別並不存在", "no-topic": "主題並不存在", "no-post": "文章並不存在", diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json index 696de46b27..bba85af9e6 100644 --- a/public/language/zh_TW/topic.json +++ b/public/language/zh_TW/topic.json @@ -71,7 +71,7 @@ "topic_will_be_moved_to": "這個主題將會被移動到", "fork_topic_instruction": "點擊要作為主題的文章", "fork_no_pids": "尚未選擇文章!", - "fork_success": "成功將文章作為主題!", + "fork_success": "Succesfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "輸入標題...", "composer.discard": "放棄", "composer.submit": "發表", diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index f73d573c02..d344fd7153 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -52,6 +52,7 @@ "digest_daily": "每日", "digest_weekly": "每週", "digest_monthly": "每月", + "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", "has_no_follower": "該用戶還沒有被任何人關注。", "follows_no_one": "該用戶還沒有關注過任何人。", "has_no_posts": "尚未有任何貼文.", From d6e0be52cebd50f460001092d3140c2d456ef98c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 12:59:28 -0400 Subject: [PATCH 283/372] companion to 48c123592e49b8fdc6e0bd0e0e8c66ac517d83f4 - prevent deleting your own account if you're admin --- src/user/delete.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/user/delete.js b/src/user/delete.js index 8c34fe6542..ac4ba1251d 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -25,6 +25,16 @@ module.exports = function(User) { ], callback); }; + User.deleteAccount = function(uid, callback) { + user.isAdministrator(uid, function(err, isAdmin) { + if (err || isAdmin) { + return callback(err || new Error('[[error:cant-ban-other-admins]]')); + } + + deleteAccount(uid, callback); + }); + }; + function deletePosts(uid, callback) { deleteSortedSetElements('uid:' + uid + ':posts', posts.purge, callback); } @@ -43,7 +53,7 @@ module.exports = function(User) { }, {alwaysStartAt: 0}, callback); } - User.deleteAccount = function(uid, callback) { + function deleteAccount(uid, callback) { user.getUserFields(uid, ['username', 'userslug', 'email'], function(err, userData) { if (err) { return callback(err); @@ -97,7 +107,7 @@ module.exports = function(User) { ], callback); }); }); - }; + } function deleteUserFromFollowers(uid, callback) { db.getSetMembers('followers:' + uid, function(err, uids) { From 91293350b5b5148fc3255d18b5b7337349eb4f6c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 14:47:35 -0400 Subject: [PATCH 284/372] closes https://github.com/NodeBB/NodeBB/issues/2101 --- public/src/forum/footer.js | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index 94c07caef8..c5db90936a 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -24,17 +24,41 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) .attr('data-content', count > 20 ? '20+' : count); } - function onNewPost(data) { - if (parseInt(app.uid, 10)) { - socket.emit('user.getUnreadCount', updateUnreadTopicCount); + function prepareUnreadButton() { + var unreadTopics = {}; + + function onNewPost(data) { + if (data && data.posts && data.posts.length) { + var post = data.posts[0]; + + if (parseInt(post.uid, 10) !== parseInt(app.uid, 10) && !unreadTopics[post.topic.tid]) { + increaseUnreadCount(); + unreadTopics[post.topic.tid] = true; + } + } + } + + function increaseUnreadCount() { + var count = parseInt($('#unread-count').attr('data-content'), 10) + 1; + updateUnreadTopicCount(null, count); } - } - socket.on('event:new_post', onNewPost); + $(window).on('action:ajaxify.end', function(ev, data) { + var tid = data.url.match(/^topic\/(\d+)/); + + if (tid && tid[1]) { + delete unreadTopics[tid[1]]; + } + }); + + socket.on('event:new_post', onNewPost); + } socket.on('event:unread.updateCount', updateUnreadTopicCount); socket.emit('user.getUnreadCount', updateUnreadTopicCount); socket.on('event:unread.updateChatCount', updateUnreadChatCount); socket.emit('user.getUnreadChatCount', updateUnreadChatCount); + + prepareUnreadButton(); }); From 004758f5bdae9ab7df75e2346d81c71d05c53a72 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 14:53:01 -0400 Subject: [PATCH 285/372] closes https://github.com/NodeBB/NodeBB/issues/2104 --- public/src/forum/footer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index c5db90936a..775686aaa2 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -24,7 +24,7 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) .attr('data-content', count > 20 ? '20+' : count); } - function prepareUnreadButton() { + function initUnreadTopics() { var unreadTopics = {}; function onNewPost(data) { @@ -33,6 +33,7 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) if (parseInt(post.uid, 10) !== parseInt(app.uid, 10) && !unreadTopics[post.topic.tid]) { increaseUnreadCount(); + markTopicsUnread(post.topic.tid); unreadTopics[post.topic.tid] = true; } } @@ -43,6 +44,10 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) updateUnreadTopicCount(null, count); } + function markTopicsUnread(tid) { + $('[data-tid="' + tid + '"]').addClass('unread'); + } + $(window).on('action:ajaxify.end', function(ev, data) { var tid = data.url.match(/^topic\/(\d+)/); @@ -60,5 +65,5 @@ define('forum/footer', ['notifications', 'chat'], function(Notifications, Chat) socket.on('event:unread.updateChatCount', updateUnreadChatCount); socket.emit('user.getUnreadChatCount', updateUnreadChatCount); - prepareUnreadButton(); + initUnreadTopics(); }); From 1caf4806e84ef83f707065c0e4c7d424e8f5567c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 14:58:16 -0400 Subject: [PATCH 286/372] closes https://github.com/NodeBB/NodeBB/issues/2103 --- src/controllers/accounts.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 1e21ecc62a..c180b9bf69 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -136,7 +136,16 @@ accountsController.getUserByUID = function(req, res, next) { }; accountsController.getAccount = function(req, res, next) { - var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; + var lowercaseSlug = req.params.userslug.toLowerCase(), + callerUID = req.user ? parseInt(req.user.uid, 10) : 0; + + if (req.params.userslug !== lowercaseSlug) { + if (res.locals.isAPI) { + req.params.userslug = lowercaseSlug; + } else { + res.redirect(nconf.get('relative_path') + '/user/' + lowercaseSlug); + } + } getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) { if(err) { From a0610799957af000f93a6aba305003054ab0283c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 16:07:26 -0400 Subject: [PATCH 287/372] fixed all POST routes to use new csrf middleware --- public/src/forum/admin/categories.js | 2 +- public/src/modules/composer/uploads.js | 6 +++--- public/src/modules/uploader.js | 1 + src/controllers/accounts.js | 2 ++ src/controllers/admin.js | 5 ++++- src/controllers/categories.js | 1 + src/controllers/topics.js | 1 + src/routes/admin.js | 8 ++++---- src/routes/api.js | 6 +++--- src/routes/index.js | 20 ++++++++++---------- 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index 37a551ffef..c91f2bf706 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -203,7 +203,7 @@ define('forum/admin/categories', ['uploader', 'forum/admin/iconSelect'], functio var inputEl = $(this), cid = inputEl.parents('li[data-cid]').attr('data-cid'); - uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', {cid: cid}, 0, function(imageUrlOnServer) { + uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', { cid: cid }, 0, function(imageUrlOnServer) { inputEl.val(imageUrlOnServer); var previewBox = inputEl.parents('li[data-cid]').find('.preview-box'); previewBox.css('background', 'url(' + imageUrlOnServer + '?' + new Date().getTime() + ')') diff --git a/public/src/modules/composer/uploads.js b/public/src/modules/composer/uploads.js index 008970a3ca..54822aa681 100644 --- a/public/src/modules/composer/uploads.js +++ b/public/src/modules/composer/uploads.js @@ -235,10 +235,10 @@ define('composer/uploads', ['composer/preview'], function(preview) { textarea.val(current.replace(re, filename + '](' + text + ')')); } - $(this).find('#postUploadCsrf').val($('#csrf_token').val()); + $(this).find('#postUploadCsrf').val($('#csrf').attr('data-csrf')); if (formData) { - formData.append('_csrf', $('#csrf_token').val()); + formData.append('_csrf', $('#csrf').attr('data-csrf')); } uploads.inProgress[post_uuid] = uploads.inProgress[post_uuid] || []; @@ -291,7 +291,7 @@ define('composer/uploads', ['composer/preview'], function(preview) { thumbForm.attr('action', params.route); thumbForm.off('submit').submit(function() { - var csrf = $('#csrf_token').val(); + var csrf = $('#csrf').attr('data-csrf'); $(this).find('#thumbUploadCsrf').val(csrf); if(formData) { diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js index 2d5aa120f3..e44d59d4e2 100644 --- a/public/src/modules/uploader.js +++ b/public/src/modules/uploader.js @@ -19,6 +19,7 @@ define('uploader', function() { uploadForm[0].reset(); uploadForm.attr('action', route); uploadForm.find('#params').val(JSON.stringify(params)); + uploadForm.find('#csrfToken').val($('#csrf').attr('data-csrf')); if(fileSize) { uploadForm.find('#upload-file-size').html(fileSize); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index a9466e4569..a91197a069 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -336,6 +336,8 @@ accountsController.accountEdit = function(req, res, next) { return next(err); } + userData.csrf = req.csrfToken(); + res.render('account/edit', userData); }); }; diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 84834edef0..af1a419152 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -127,7 +127,10 @@ function filterAndRenderCategories(req, res, next, active) { return active ? !category.disabled : category.disabled; }); - res.render('admin/categories', {categories: categoryData}); + res.render('admin/categories', { + categories: categoryData, + csrf: req.csrfToken() + }); }); } diff --git a/src/controllers/categories.js b/src/controllers/categories.js index f7557210fb..e663db6ddc 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -176,6 +176,7 @@ categoriesController.get = function(req, res, next) { data.currentPage = page; data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false; + data.csrf = req.csrfToken(); // Paginator for noscript data.pages = []; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index bfc2b4e396..ef388d183e 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -196,6 +196,7 @@ topicsController.get = function(req, res, next) { data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; + data.csrf = req.csrfToken(); var topic_url = tid + (req.params.slug ? '/' + req.params.slug : ''); var queryString = qs.stringify(req.query); diff --git a/src/routes/admin.js b/src/routes/admin.js index 008acfde60..c18cc8665c 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -43,11 +43,11 @@ function userRoutes(app, middleware, controllers) { } function forumRoutes(app, middleware, controllers) { - app.get('/admin/categories/active', middleware.admin.buildHeader, controllers.admin.categories.active); - app.get('/api/admin/categories/active', controllers.admin.categories.active); + app.get('/admin/categories/active', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.categories.active); + app.get('/api/admin/categories/active', middleware.requireCSRF, controllers.admin.categories.active); - app.get('/admin/categories/disabled', middleware.admin.buildHeader, controllers.admin.categories.disabled); - app.get('/api/admin/categories/disabled', controllers.admin.categories.disabled); + app.get('/admin/categories/disabled', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.categories.disabled); + app.get('/api/admin/categories/disabled', middleware.requireCSRF, controllers.admin.categories.disabled); app.get('/admin/tags', middleware.admin.buildHeader, controllers.admin.tags.get); app.get('/api/admin/tags', controllers.admin.tags.get); diff --git a/src/routes/api.js b/src/routes/api.js index e152715beb..5a59a2387d 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -203,8 +203,8 @@ module.exports = function(app, middleware, controllers) { router.get('/categories/:cid/moderators', getModerators); router.get('/recent/posts/:term?', getRecentPosts); - router.post('/post/upload', uploadPost); - router.post('/topic/thumb/upload', uploadThumb); - router.post('/user/:userslug/uploadpicture', middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.uploadPicture); + router.post('/post/upload', middleware.requireCSRF, uploadPost); + router.post('/topic/thumb/upload', middleware.requireCSRF, uploadThumb); + router.post('/user/:userslug/uploadpicture', middleware.requireCSRF, middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.uploadPicture); }; diff --git a/src/routes/index.js b/src/routes/index.js index 1e3124a6ea..f53043eb17 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -54,11 +54,11 @@ function staticRoutes(app, middleware, controllers) { function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); - app.get('/topic/:topic_id/:slug/:post_index?', middleware.buildHeader, middleware.checkPostIndex, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.checkPostIndex, controllers.topics.get); + app.get('/topic/:topic_id/:slug/:post_index?', middleware.requireCSRF, middleware.buildHeader, middleware.checkPostIndex, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.requireCSRF, middleware.checkPostIndex, controllers.topics.get); - app.get('/topic/:topic_id/:slug?', middleware.buildHeader, middleware.addSlug, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug?', middleware.addSlug, controllers.topics.get); + app.get('/topic/:topic_id/:slug?', middleware.requireCSRF, middleware.buildHeader, middleware.addSlug, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug?', middleware.requireCSRF, middleware.addSlug, controllers.topics.get); } function tagRoutes(app, middleware, controllers) { @@ -82,11 +82,11 @@ function categoryRoutes(app, middleware, controllers) { app.get('/api/unread/total', middleware.authenticate, controllers.categories.unreadTotal); - app.get('/category/:category_id/:slug/:topic_index', middleware.buildHeader, middleware.checkTopicIndex, controllers.categories.get); - app.get('/api/category/:category_id/:slug/:topic_index', middleware.checkTopicIndex, controllers.categories.get); + app.get('/category/:category_id/:slug/:topic_index', middleware.requireCSRF, middleware.buildHeader, middleware.checkTopicIndex, controllers.categories.get); + app.get('/api/category/:category_id/:slug/:topic_index', middleware.requireCSRF, middleware.checkTopicIndex, controllers.categories.get); - app.get('/category/:category_id/:slug?', middleware.buildHeader, middleware.addSlug, controllers.categories.get); - app.get('/api/category/:category_id/:slug?', controllers.categories.get); + app.get('/category/:category_id/:slug?', middleware.requireCSRF, middleware.buildHeader, middleware.addSlug, controllers.categories.get); + app.get('/api/category/:category_id/:slug?', middleware.requireCSRF, controllers.categories.get); } function accountRoutes(app, middleware, controllers) { @@ -108,8 +108,8 @@ function accountRoutes(app, middleware, controllers) { app.get('/user/:userslug/topics', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); app.get('/api/user/:userslug/topics', middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); - app.get('/user/:userslug/edit', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); - app.get('/api/user/:userslug/edit', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); + app.get('/user/:userslug/edit', middleware.requireCSRF, middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); + app.get('/api/user/:userslug/edit', middleware.requireCSRF, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); app.get('/user/:userslug/settings', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); app.get('/api/user/:userslug/settings', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); From cd135dda1361103e4bf2691776a9d892c49bd567 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 16:11:11 -0400 Subject: [PATCH 288/372] renamed middleware to applyCSRF, re: #2082 --- src/middleware/middleware.js | 2 +- src/routes/admin.js | 20 ++++++++++---------- src/routes/api.js | 6 +++--- src/routes/authentication.js | 4 ++-- src/routes/index.js | 28 ++++++++++++++-------------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 806be9d50b..42d5471d75 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -34,7 +34,7 @@ middleware.authenticate = function(req, res, next) { } }; -middleware.requireCSRF = csrf(); +middleware.applyCSRF = csrf(); middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(); diff --git a/src/routes/admin.js b/src/routes/admin.js index c18cc8665c..04c6169489 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -9,8 +9,8 @@ function mainRoutes(app, middleware, controllers) { app.get('/admin/plugins', middleware.admin.buildHeader, controllers.admin.plugins.get); app.get('/api/admin/plugins', controllers.admin.plugins.get); - app.get('/admin/settings', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.settings.get); - app.get('/api/admin/settings', middleware.requireCSRF, controllers.admin.settings.get); + app.get('/admin/settings', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.settings.get); + app.get('/api/admin/settings', middleware.applyCSRF, controllers.admin.settings.get); app.get('/admin/themes', middleware.admin.buildHeader, controllers.admin.themes.get); app.get('/api/admin/themes', controllers.admin.themes.get); @@ -43,11 +43,11 @@ function userRoutes(app, middleware, controllers) { } function forumRoutes(app, middleware, controllers) { - app.get('/admin/categories/active', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.categories.active); - app.get('/api/admin/categories/active', middleware.requireCSRF, controllers.admin.categories.active); + app.get('/admin/categories/active', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.categories.active); + app.get('/api/admin/categories/active', middleware.applyCSRF, controllers.admin.categories.active); - app.get('/admin/categories/disabled', middleware.requireCSRF, middleware.admin.buildHeader, controllers.admin.categories.disabled); - app.get('/api/admin/categories/disabled', middleware.requireCSRF, controllers.admin.categories.disabled); + app.get('/admin/categories/disabled', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.categories.disabled); + app.get('/api/admin/categories/disabled', middleware.applyCSRF, controllers.admin.categories.disabled); app.get('/admin/tags', middleware.admin.buildHeader, controllers.admin.tags.get); app.get('/api/admin/tags', controllers.admin.tags.get); @@ -57,10 +57,10 @@ function apiRoutes(app, middleware, controllers) { // todo, needs to be in api namespace app.get('/admin/users/csv', middleware.authenticate, controllers.admin.users.getCSV); - app.post('/admin/category/uploadpicture', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); - app.post('/admin/uploadfavicon', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadFavicon); - app.post('/admin/uploadlogo', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadLogo); - app.post('/admin/uploadgravatardefault', middleware.requireCSRF, middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); + app.post('/admin/category/uploadpicture', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); + app.post('/admin/uploadfavicon', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadFavicon); + app.post('/admin/uploadlogo', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadLogo); + app.post('/admin/uploadgravatardefault', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); } function miscRoutes(app, middleware, controllers) { diff --git a/src/routes/api.js b/src/routes/api.js index 5a59a2387d..aff69efe9e 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -203,8 +203,8 @@ module.exports = function(app, middleware, controllers) { router.get('/categories/:cid/moderators', getModerators); router.get('/recent/posts/:term?', getRecentPosts); - router.post('/post/upload', middleware.requireCSRF, uploadPost); - router.post('/topic/thumb/upload', middleware.requireCSRF, uploadThumb); - router.post('/user/:userslug/uploadpicture', middleware.requireCSRF, middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.uploadPicture); + router.post('/post/upload', middleware.applyCSRF, uploadPost); + router.post('/topic/thumb/upload', middleware.applyCSRF, uploadThumb); + router.post('/user/:userslug/uploadpicture', middleware.applyCSRF, middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.uploadPicture); }; diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 6471814ad5..8925d6a539 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -197,8 +197,8 @@ /* End backwards compatibility block */ app.post('/logout', logout); - app.post('/register', middleware.requireCSRF, register); - app.post('/login', middleware.requireCSRF, login); + app.post('/register', middleware.applyCSRF, register); + app.post('/login', middleware.applyCSRF, login); }); }); }; diff --git a/src/routes/index.js b/src/routes/index.js index 0789b2474c..85090aaf2e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -21,11 +21,11 @@ function mainRoutes(app, middleware, controllers) { app.get('/', middleware.buildHeader, controllers.home); app.get('/api', controllers.home); - app.get('/login', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login); - app.get('/api/login', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, controllers.login); + app.get('/login', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login); + app.get('/api/login', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, controllers.login); - app.get('/register', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.register); - app.get('/api/register', middleware.requireCSRF, middleware.redirectToAccountIfLoggedIn, controllers.register); + app.get('/register', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.register); + app.get('/api/register', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, controllers.register); app.get('/confirm/:code', middleware.buildHeader, controllers.confirmEmail); app.get('/api/confirm/:code', controllers.confirmEmail); @@ -54,11 +54,11 @@ function staticRoutes(app, middleware, controllers) { function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); - app.get('/topic/:topic_id/:slug/:post_index?', middleware.requireCSRF, middleware.buildHeader, middleware.checkPostIndex, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.requireCSRF, middleware.checkPostIndex, controllers.topics.get); + app.get('/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.buildHeader, middleware.checkPostIndex, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.checkPostIndex, controllers.topics.get); - app.get('/topic/:topic_id/:slug?', middleware.requireCSRF, middleware.buildHeader, middleware.addSlug, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug?', middleware.requireCSRF, middleware.addSlug, controllers.topics.get); + app.get('/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.buildHeader, middleware.addSlug, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.addSlug, controllers.topics.get); } function tagRoutes(app, middleware, controllers) { @@ -82,11 +82,11 @@ function categoryRoutes(app, middleware, controllers) { app.get('/api/unread/total', middleware.authenticate, controllers.categories.unreadTotal); - app.get('/category/:category_id/:slug/:topic_index', middleware.requireCSRF, middleware.buildHeader, middleware.checkTopicIndex, controllers.categories.get); - app.get('/api/category/:category_id/:slug/:topic_index', middleware.requireCSRF, middleware.checkTopicIndex, controllers.categories.get); + app.get('/category/:category_id/:slug/:topic_index', middleware.applyCSRF, middleware.buildHeader, middleware.checkTopicIndex, controllers.categories.get); + app.get('/api/category/:category_id/:slug/:topic_index', middleware.applyCSRF, middleware.checkTopicIndex, controllers.categories.get); - app.get('/category/:category_id/:slug?', middleware.requireCSRF, middleware.buildHeader, middleware.addSlug, controllers.categories.get); - app.get('/api/category/:category_id/:slug?', middleware.requireCSRF, controllers.categories.get); + app.get('/category/:category_id/:slug?', middleware.applyCSRF, middleware.buildHeader, middleware.addSlug, controllers.categories.get); + app.get('/api/category/:category_id/:slug?', middleware.applyCSRF, controllers.categories.get); } function accountRoutes(app, middleware, controllers) { @@ -108,8 +108,8 @@ function accountRoutes(app, middleware, controllers) { app.get('/user/:userslug/topics', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); app.get('/api/user/:userslug/topics', middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); - app.get('/user/:userslug/edit', middleware.requireCSRF, middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); - app.get('/api/user/:userslug/edit', middleware.requireCSRF, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); + app.get('/user/:userslug/edit', middleware.applyCSRF, middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); + app.get('/api/user/:userslug/edit', middleware.applyCSRF, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); app.get('/user/:userslug/settings', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); app.get('/api/user/:userslug/settings', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); From 406660738889f54413f3b06f9f99f3291fa9357e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 16:29:03 -0400 Subject: [PATCH 289/372] closes #2052 --- install/data/defaults.json | 4 ---- public/language/en_GB/modules.json | 4 ++++ public/src/forum/chats.js | 36 ++++++++++++++++++++++++------ public/src/modules/chat.js | 21 +++++++++++++++-- src/controllers/accounts.js | 2 +- src/messaging.js | 12 ++++++++-- src/socket.io/modules.js | 2 +- 7 files changed, 64 insertions(+), 17 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 00138af35c..24918285d0 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -75,10 +75,6 @@ "field": "profileImageDimension", "value": 128 }, - { - "field": "chatMessagesToDisplay", - "value": 50 - }, { "field": "requireEmailConfirmation", "value": 0 diff --git a/public/language/en_GB/modules.json b/public/language/en_GB/modules.json index 9bf107bec6..12e6d7457f 100644 --- a/public/language/en_GB/modules.json +++ b/public/language/en_GB/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", diff --git a/public/src/forum/chats.js b/public/src/forum/chats.js index 110afa4473..84bf4fd4cc 100644 --- a/public/src/forum/chats.js +++ b/public/src/forum/chats.js @@ -78,8 +78,28 @@ define('forum/chats', ['string', 'sounds', 'forum/infinitescroll'], function(S, loadMoreRecentChats(); } }); + + $('.expanded-chat [data-since]').on('click', function() { + var since = $(this).attr('data-since'); + $('.expanded-chat [data-since]').removeClass('selected'); + $(this).addClass('selected'); + loadChatSince(since); + return false; + }); }; + function loadChatSince(since) { + var uid = Chats.getRecipientUid(); + if (!uid) { + return; + } + socket.emit('modules.chats.get', {touid: uid, since: since}, function(err, messages) { + var chatContent = $('.expanded-chat .chat-content'); + chatContent.find('.chat-message').remove(); + Chats.parseMessage(messages, onMessagesParsed); + }); + } + Chats.addGlobalEventListeners = function() { $(window).on('resize', Chats.resizeMainWindow); $(window).on('mousemove keypress click', function() { @@ -93,6 +113,14 @@ define('forum/chats', ['string', 'sounds', 'forum/infinitescroll'], function(S, }); }; + function onMessagesParsed(html) { + var newMessage = $(html); + newMessage.insertBefore($('.user-typing')); + newMessage.find('span.timeago').timeago(); + newMessage.find('img:not(".chat-user-image")').addClass('img-responsive'); + Chats.scrollToBottom($('.expanded-chat .chat-content')); + } + Chats.addSocketListeners = function() { socket.on('event:chats.receive', function(data) { var typingNotifEl = $('.user-typing'), @@ -101,13 +129,7 @@ define('forum/chats', ['string', 'sounds', 'forum/infinitescroll'], function(S, if (Chats.isCurrentChat(data.withUid)) { newMessage = data.self === 0; data.message.self = data.self; - Chats.parseMessage(data.message, function(html) { - var newMessage = $(html); - newMessage.insertBefore(typingNotifEl); - newMessage.find('span.timeago').timeago(); - newMessage.find('img:not(".chat-user-image")').addClass('img-responsive'); - Chats.scrollToBottom(containerEl); - }); + Chats.parseMessage(data.message, onMessagesParsed); } else { $('.chats-list li[data-uid="' + data.withUid + '"]').addClass('unread'); app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + data.message.fromUser.username + ']]'); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 24bc182716..f40dda09a2 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -193,9 +193,10 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, var totalHeight = chatModal.find('.modal-content').outerHeight() - chatModal.find('.modal-header').outerHeight(); var padding = parseInt(chatModal.find('.modal-body').css('padding-top'), 10) + parseInt(chatModal.find('.modal-body').css('padding-bottom'), 10); var contentMargin = parseInt(chatModal.find('#chat-content').css('margin-top'), 10) + parseInt(chatModal.find('#chat-content').css('margin-bottom'), 10); + var sinceHeight = chatModal.find('.since-bar').outerHeight(true); var inputGroupHeight = chatModal.find('.input-group').outerHeight(); - chatModal.find('#chat-content').css('height', totalHeight - padding - contentMargin - inputGroupHeight); + chatModal.find('#chat-content').css('height', totalHeight - padding - contentMargin - sinceHeight - inputGroupHeight); }); chatModal.find('#chat-with-name').html(username); @@ -220,6 +221,14 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, } }); + chatModal.find('[data-since]').on('click', function() { + var since = $(this).attr('data-since'); + chatModal.find('[data-since]').removeClass('selected'); + $(this).addClass('selected'); + loadChatSince(chatModal, since); + return false; + }); + addSendHandler(chatModal); getChatMessages(chatModal, function() { @@ -279,7 +288,15 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, }; function getChatMessages(chatModal, callback) { - socket.emit('modules.chats.get', {touid: chatModal.attr('touid')}, function(err, messages) { + socket.emit('modules.chats.get', {touid: chatModal.attr('touid'), since: 'day'}, function(err, messages) { + module.appendChatMessage(chatModal, messages, callback); + }); + } + + function loadChatSince(chatModal, since, callback) { + socket.emit('modules.chats.get', {touid: chatModal.attr('touid'), since: since}, function(err, messages) { + var chatContent = chatModal.find('#chat-content'); + chatContent.find('.chat-message').remove(); module.appendChatMessage(chatModal, messages, callback); }); } diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 6ce15eb073..a81f7fcd07 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -535,7 +535,7 @@ accountsController.getChats = function(req, res, next) { function(toUid, next) { async.parallel({ toUser: async.apply(user.getUserFields, toUid, ['uid', 'username']), - messages: async.apply(messaging.getMessages, req.user.uid, toUid, false) + messages: async.apply(messaging.getMessages, req.user.uid, toUid, 'day', false) }, next); } ], function(err, data) { diff --git a/src/messaging.js b/src/messaging.js index 0d9d9ca3fb..e47270d5da 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -88,10 +88,18 @@ var db = require('./database'), }); } - Messaging.getMessages = function(fromuid, touid, isNew, callback) { + Messaging.getMessages = function(fromuid, touid, since, isNew, callback) { var uids = sortUids(fromuid, touid); - db.getSortedSetRevRange('messages:uid:' + uids[0] + ':to:' + uids[1], 0, (meta.config.chatMessagesToDisplay || 50) - 1, function(err, mids) { + var terms = { + day: 86400000, + week: 604800000, + month: 2592000000, + threemonths: 7776000000 + }; + since = terms[since] || terms['day']; + var count = parseInt(meta.config.chatMessageInboxSize, 10) || 250; + db.getSortedSetRevRangeByScore('messages:uid:' + uids[0] + ':to:' + uids[1], 0, count, Infinity, Date.now() - since, function(err, mids) { if (err) { return callback(err); } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 399adae0b9..a96c3ffd4b 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -166,7 +166,7 @@ SocketModules.chats.get = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - Messaging.getMessages(socket.uid, data.touid, false, callback); + Messaging.getMessages(socket.uid, data.touid, data.since, false, callback); }; SocketModules.chats.send = function(socket, data, callback) { From 2e16306dc21ec276d06854d8e4dac7f394b6ff30 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 16:31:27 -0400 Subject: [PATCH 290/372] updated modules translations --- public/language/ar/modules.json | 4 ++++ public/language/cs/modules.json | 4 ++++ public/language/de/modules.json | 4 ++++ public/language/en@pirate/modules.json | 4 ++++ public/language/en_US/modules.json | 4 ++++ public/language/es/modules.json | 4 ++++ public/language/et/modules.json | 4 ++++ public/language/fa_IR/modules.json | 4 ++++ public/language/fi/modules.json | 4 ++++ public/language/fr/modules.json | 4 ++++ public/language/he/modules.json | 4 ++++ public/language/hu/modules.json | 4 ++++ public/language/it/modules.json | 4 ++++ public/language/ja/modules.json | 4 ++++ public/language/ko/modules.json | 4 ++++ public/language/lt/modules.json | 4 ++++ public/language/ms/modules.json | 4 ++++ public/language/nb/modules.json | 4 ++++ public/language/nl/modules.json | 4 ++++ public/language/pl/modules.json | 4 ++++ public/language/pt_BR/modules.json | 4 ++++ public/language/ro/modules.json | 4 ++++ public/language/ru/modules.json | 4 ++++ public/language/sc/modules.json | 4 ++++ public/language/sk/modules.json | 4 ++++ public/language/sv/modules.json | 4 ++++ public/language/th/modules.json | 4 ++++ public/language/tr/modules.json | 4 ++++ public/language/vi/modules.json | 4 ++++ public/language/zh_CN/modules.json | 4 ++++ public/language/zh_TW/modules.json | 4 ++++ 31 files changed, 124 insertions(+) diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index 9f6267d3a4..1b740fb922 100644 --- a/public/language/ar/modules.json +++ b/public/language/ar/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 33299f001b..cb79a5a156 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/de/modules.json b/public/language/de/modules.json index 4a19291085..215f0838cf 100644 --- a/public/language/de/modules.json +++ b/public/language/de/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Nachrichtenverlauf", "chat.pop-out": "Chat als Pop-out anzeigen", "chat.maximize": "Maximieren", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 sagte in %2:", "composer.user_said": "%1 sagte:", "composer.discard": "Bist du sicher, dass du diesen Post verwerfen möchtest?" diff --git a/public/language/en@pirate/modules.json b/public/language/en@pirate/modules.json index b0f32fba74..661d150639 100644 --- a/public/language/en@pirate/modules.json +++ b/public/language/en@pirate/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/en_US/modules.json b/public/language/en_US/modules.json index 0567700071..9fbc634fef 100644 --- a/public/language/en_US/modules.json +++ b/public/language/en_US/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/es/modules.json b/public/language/es/modules.json index ea478bb904..94a1481fff 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Historial de mensajes", "chat.pop-out": "Mostrar en ventana independiente", "chat.maximize": "Maximizar", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 dijo en %2:", "composer.user_said": "%1 dijo:", "composer.discard": "¿Estás seguro de que deseas descargar este post?" diff --git a/public/language/et/modules.json b/public/language/et/modules.json index 18ff7ecfff..ad67cbb689 100644 --- a/public/language/et/modules.json +++ b/public/language/et/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Sõnumite ajalugu", "chat.pop-out": "Pop-out vestlus", "chat.maximize": "Suurenda", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 ütles %2:", "composer.user_said": "%1 ütles:", "composer.discard": "Oled kindel, et soovid selle postituse tühistada?" diff --git a/public/language/fa_IR/modules.json b/public/language/fa_IR/modules.json index 4d22d32319..fdb9473d36 100644 --- a/public/language/fa_IR/modules.json +++ b/public/language/fa_IR/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "تاریخچه پیام‌ها", "chat.pop-out": "Pop out chat", "chat.maximize": "تمام صفحه", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 در %2 گفته است:", "composer.user_said": "%1 گفته است:", "composer.discard": "آیا از دور انداختن این دیدگاه اطمینان دارید؟" diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index 2493032dca..bc64dfc9a3 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index 1b2adba55d..9b20cbd5e8 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Historique des messages", "chat.pop-out": "Afficher la discussion", "chat.maximize": "Agrandir", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 a dit dans %2 :", "composer.user_said": "%1 a dit :", "composer.discard": "Êtes-vous sûr de bien vouloir supprimer ce message ?" diff --git a/public/language/he/modules.json b/public/language/he/modules.json index 918929e838..072459c69b 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 אמר ב%2:", "composer.user_said": "%1 אמר:", "composer.discard": "האם למחוק פוסט זה?" diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index 646de2be03..556ee5812c 100644 --- a/public/language/hu/modules.json +++ b/public/language/hu/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/it/modules.json b/public/language/it/modules.json index b29af37317..6ad72bc915 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index 8f4d5713da..57f8c05db1 100644 --- a/public/language/ja/modules.json +++ b/public/language/ja/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index afadc4a16a..4b8f13b37f 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index b64357c69d..872951166b 100644 --- a/public/language/lt/modules.json +++ b/public/language/lt/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Žinučių istorija", "chat.pop-out": "Iššokančio lango pokalbiai", "chat.maximize": "Padininti", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 parašė į %2:", "composer.user_said": "%1 parašė:", "composer.discard": "Ar tikrai norite sunaikinti šį pranešimą?" diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index 15ed55ba53..360bc5b729 100644 --- a/public/language/ms/modules.json +++ b/public/language/ms/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index d6c4c7516d..17e695de33 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Meldingshistorikk", "chat.pop-out": "Pop-out chat", "chat.maximize": "Maksimer", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 sa i %2: ", "composer.user_said": "%1 sa: ", "composer.discard": "Er du sikker på at du vil forkaste dette innlegget?" diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index caaae8472f..f7d98f303e 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Berichten Geschiedenis", "chat.pop-out": "tevoorschijn halen gesprek", "chat.maximize": "Maximaliseren", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 zegt in %2:", "composer.user_said": "%1 zegt:", "composer.discard": "Weet u het zeker dat u dit bericht niet wilt plaatsen?" diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 154ae27501..678e131cea 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Historia wiadomości", "chat.pop-out": "Pokaż czat", "chat.maximize": "Maksymalizuj", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 powiedział w %2:", "composer.user_said": "%1 powiedział:", "composer.discard": "Na pewno chcesz porzucić ten post?" diff --git a/public/language/pt_BR/modules.json b/public/language/pt_BR/modules.json index 84f94a3de5..be9126b20d 100644 --- a/public/language/pt_BR/modules.json +++ b/public/language/pt_BR/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Histórico de Conversas", "chat.pop-out": "Minimizar o chat", "chat.maximize": "Maximizar", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 disse em %2:", "composer.user_said": "%1 disse:", "composer.discard": "Deseja mesmo remover essa postagem?" diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index 0c4ad4d148..3310439f13 100644 --- a/public/language/ro/modules.json +++ b/public/language/ro/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Istorie Mesaje", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximizează", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 a spus în %2:", "composer.user_said": "%1 a spus:", "composer.discard": "Ești sigur că vrei să renunți la acest mesaj?" diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index 5cd840d3d0..ec2079f009 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "История сообщений", "chat.pop-out": "Покинуть диалог", "chat.maximize": "Развернуть", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 сказал %2:", "composer.user_said": "%1 сказал:", "composer.discard": "Вы уверены, что хотите отказаться от этого поста?" diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 04d1143a2c..f2ca01ab31 100644 --- a/public/language/sc/modules.json +++ b/public/language/sc/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 9c661800d4..6c21458605 100644 --- a/public/language/sk/modules.json +++ b/public/language/sk/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index a996e30676..aea650c3a8 100644 --- a/public/language/sv/modules.json +++ b/public/language/sv/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Historik för meddelande", "chat.pop-out": "Utskjutande chatt", "chat.maximize": "Maximera", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 sa i %2:", "composer.user_said": "%1 sa:", "composer.discard": "Är du säker på att du vill förkasta det här inlägget?" diff --git a/public/language/th/modules.json b/public/language/th/modules.json index 90494a58ee..2f8e7734aa 100644 --- a/public/language/th/modules.json +++ b/public/language/th/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index fb44fb8324..0e4d6ba82c 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Mesaj Geçmişi", "chat.pop-out": "Sohbeti Pencereye Çevir", "chat.maximize": "Büyüt", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 %2 içinde söyledi:", "composer.user_said": "%1 söyledi:", "composer.discard": "Bu iletiyi iptal etmek istediğinizden eminmisiniz?" diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index 6afe9ffa9c..f0c69eef1c 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Lịch sử tin nhắn", "chat.pop-out": "Bật cửa sổ chat", "chat.maximize": "Phóng to", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 đã nói trong %2:", "composer.user_said": "%1 đã nói:", "composer.discard": "Bạn có chắc chắn hủy bỏ bài viết này?" diff --git a/public/language/zh_CN/modules.json b/public/language/zh_CN/modules.json index ec5334cdc8..e4a7c146f6 100644 --- a/public/language/zh_CN/modules.json +++ b/public/language/zh_CN/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "消息历史", "chat.pop-out": "弹出聊天窗口", "chat.maximize": "最大化", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 在 %2 中说:", "composer.user_said": "%1 说:", "composer.discard": "确定想要取消此帖?" diff --git a/public/language/zh_TW/modules.json b/public/language/zh_TW/modules.json index 7efe5fbdd2..f5a71c026a 100644 --- a/public/language/zh_TW/modules.json +++ b/public/language/zh_TW/modules.json @@ -12,6 +12,10 @@ "chat.message-history": "Message History", "chat.pop-out": "Pop out chat", "chat.maximize": "Maximize", + "chat.yesterday": "Yesterday", + "chat.seven_days": "7 Days", + "chat.thirty_days": "30 Days", + "chat.three_months": "3 Months", "composer.user_said_in": "%1 said in %2:", "composer.user_said": "%1 said:", "composer.discard": "Are you sure you wish to discard this post?" From 3abbeb8795e582f107ff950b31ff833d5033029a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 16:55:30 -0400 Subject: [PATCH 291/372] show recent posts of child categories --- src/controllers/categories.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index febaae4ba3..b6951f3e15 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -126,8 +126,13 @@ categoriesController.get = function(req, res, next) { return next(err); } - categoryData.privileges = privileges; - next(err, categoryData); + categories.getRecentTopicReplies(categoryData.children, uid, function(err) { + if (err) { + return next(err); + } + categoryData.privileges = privileges; + next(err, categoryData); + }); }); }); }, From a0cc2ee6a94e300920dd87973655d006007a8f90 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 16:57:10 -0400 Subject: [PATCH 292/372] duh --- src/controllers/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index b6951f3e15..6810e2ea7e 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -131,7 +131,7 @@ categoriesController.get = function(req, res, next) { return next(err); } categoryData.privileges = privileges; - next(err, categoryData); + next(null, categoryData); }); }); }); From c1f3b94d67db5014856bc556f30ea3a5dc122f46 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 17:16:03 -0400 Subject: [PATCH 293/372] closes #2073 --- install/data/defaults.json | 4 ++++ src/controllers/api.js | 1 + 2 files changed, 5 insertions(+) diff --git a/install/data/defaults.json b/install/data/defaults.json index 24918285d0..15267aeee1 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -35,6 +35,10 @@ "field": "allowLocalLogin", "value": 1 }, + { + "field": "allowAccountDelete", + "value": 1, + }, { "field": "allowFileUploads", "value": 0 diff --git a/src/controllers/api.js b/src/controllers/api.js index e6c3b820a9..b15422c157 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -30,6 +30,7 @@ apiController.getConfig = function(req, res, next) { config.allowGuestSearching = parseInt(meta.config.allowGuestSearching, 10) === 1; config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1; config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1; + config.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1; config.privateUserInfo = parseInt(meta.config.privateUserInfo, 10) === 1; config.usePagination = parseInt(meta.config.usePagination, 10) === 1; config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1; From 45383ce21ecf7480ac23fc79cee55d2cf4aa4c73 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 18:09:53 -0400 Subject: [PATCH 294/372] posts.getPostsByTid and posts.getPostsByPids now require a uid passed in --- src/posts.js | 6 +++--- src/topics.js | 4 ++-- src/topics/posts.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/posts.js b/src/posts.js index c0f0d91cb8..6ba5350b34 100644 --- a/src/posts.js +++ b/src/posts.js @@ -94,7 +94,7 @@ var async = require('async'), ], callback); }; - Posts.getPostsByTid = function(tid, set, start, end, reverse, callback) { + Posts.getPostsByTid = function(tid, set, start, end, uid, reverse, callback) { Posts.getPidsFromSet(set, start, end, reverse, function(err, pids) { if(err) { return callback(err); @@ -104,7 +104,7 @@ var async = require('async'), return callback(null, []); } - Posts.getPostsByPids(pids, callback); + Posts.getPostsByPids(pids, uid, callback); }); }; @@ -115,7 +115,7 @@ var async = require('async'), db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, callback); }; - Posts.getPostsByPids = function(pids, callback) { + Posts.getPostsByPids = function(pids, uid, callback) { var keys = []; for(var x=0, numPids=pids.length; x Date: Wed, 17 Sep 2014 18:10:19 -0400 Subject: [PATCH 295/372] filter:post.getPosts now passes in uid --- src/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 6ba5350b34..06216f0d07 100644 --- a/src/posts.js +++ b/src/posts.js @@ -149,7 +149,7 @@ var async = require('async'), return callback(err); } - plugins.fireHook('filter:post.getPosts', {posts: posts}, function(err, data) { + plugins.fireHook('filter:post.getPosts', {posts: posts, uid: uid}, function(err, data) { if (err) { return callback(err); } From 6f3cb55709d0a02bffe18f3601821aab314dd588 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 18:31:26 -0400 Subject: [PATCH 296/372] closes #2071 --- public/language/en_GB/topic.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 1fa24dbe23..de892fb800 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -33,7 +33,7 @@ "flag_title": "Flag this post for moderation", "flag_confirm": "Are you sure you want to flag this post?", "flag_success": "This post has been flagged for moderation.", - "deleted_message": "This thread has been deleted. Only users with thread management privileges can see it.", + "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.", "not_following_topic.message": "You will no longer receive notifications from this topic.", @@ -46,7 +46,7 @@ "watch.title": "Be notified of new replies in this topic", "share_this_post": "Share this Post", - "thread_tools.title": "Thread Tools", + "thread_tools.title": "Topic Tools", "thread_tools.markAsUnreadForAll": "Mark Unread", "thread_tools.pin": "Pin Topic", "thread_tools.unpin": "Unpin Topic", @@ -56,11 +56,11 @@ "thread_tools.move_all": "Move All", "thread_tools.fork": "Fork Topic", "thread_tools.delete": "Delete Topic", - "thread_tools.delete_confirm": "Are you sure you want to delete this thread?", + "thread_tools.delete_confirm": "Are you sure you want to delete this topic?", "thread_tools.restore": "Restore Topic", - "thread_tools.restore_confirm": "Are you sure you want to restore this thread?", + "thread_tools.restore_confirm": "Are you sure you want to restore this topic?", "thread_tools.purge": "Purge Topic", - "thread_tools.purge_confirm" : "Are you sure you want to purge this thread?", + "thread_tools.purge_confirm" : "Are you sure you want to purge this topic?", "topic_move_success": "This topic has been successfully moved to %1", From 16accf6cd04eb5fba6b96e9ee742857fbd0f59ed Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Sep 2014 18:32:32 -0400 Subject: [PATCH 297/372] fixing groups.leave --- src/groups.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/groups.js b/src/groups.js index 2a1587fd98..49dd25e4a2 100644 --- a/src/groups.js +++ b/src/groups.js @@ -417,6 +417,8 @@ }; Groups.leave = function(groupName, uid, callback) { + callback = callback || function() {}; + db.setRemove('group:' + groupName + ':members', uid, function(err) { if (err) { return callback(err); From 7a61e9d0f8a78dddd0d66498e456fe583f5ccb78 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 18:38:29 -0400 Subject: [PATCH 298/372] latest translations --- public/language/de/category.json | 2 +- public/language/de/notifications.json | 12 +++++----- public/language/de/recent.json | 2 +- public/language/de/search.json | 2 +- public/language/es/category.json | 2 +- public/language/es/email.json | 8 +++---- public/language/es/error.json | 14 ++++++------ public/language/es/search.json | 2 +- public/language/es/user.json | 10 ++++----- public/language/pl/category.json | 2 +- public/language/pl/email.json | 8 +++---- public/language/pl/error.json | 4 ++-- public/language/pl/notifications.json | 12 +++++----- public/language/pl/recent.json | 2 +- public/language/pl/search.json | 2 +- public/language/pl/topic.json | 2 +- public/language/pl/user.json | 6 ++--- public/language/ro/search.json | 2 +- public/language/sv/category.json | 2 +- public/language/sv/notifications.json | 12 +++++----- public/language/sv/recent.json | 2 +- public/language/vi/category.json | 2 +- public/language/vi/notifications.json | 32 +++++++++++++-------------- public/language/vi/recent.json | 4 ++-- public/language/vi/search.json | 4 ++-- 25 files changed, 76 insertions(+), 76 deletions(-) diff --git a/public/language/de/category.json b/public/language/de/category.json index 185540948a..f455b1604b 100644 --- a/public/language/de/category.json +++ b/public/language/de/category.json @@ -4,5 +4,5 @@ "browsing": "Aktiv", "no_replies": "Niemand hat geantwortet", "share_this_category": "Teile diese Kategorie", - "ignore": "Ignore" + "ignore": "Ignorieren" } \ No newline at end of file diff --git a/public/language/de/notifications.json b/public/language/de/notifications.json index a12ad1694c..7140887186 100644 --- a/public/language/de/notifications.json +++ b/public/language/de/notifications.json @@ -10,14 +10,14 @@ "new_notification": "Neue Benachrichtigung", "you_have_unread_notifications": "Du hast ungelesene Benachrichtigungen.", "new_message_from": "Neue Nachricht von %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 hat deinen Beitrag in %2 positiv bewertet.", + "moved_your_post": "%1 hat deinen Beitrag verschoben.", + "moved_your_topic": "%1 hat dein Thema verschoben.", + "favourited_your_post_in": "%1 hat deinen Beitrag in %2 favorisiert.", + "user_flagged_post_in": "%1 hat einen Beitrag in %2 gemeldet", "user_posted_to": "%1 hat auf %2 geantwortet.", "user_mentioned_you_in": "%1 erwähnte dich in %2", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1 folgt dir jetzt.", "email-confirmed": "E-Mail bestätigt", "email-confirmed-message": "Vielen Dank für Ihre E-Mail-Validierung. Ihr Konto ist nun vollständig aktiviert.", "email-confirm-error": "Es ist ein Fehler aufgetreten ...", diff --git a/public/language/de/recent.json b/public/language/de/recent.json index 7e5cc47101..dfed232f64 100644 --- a/public/language/de/recent.json +++ b/public/language/de/recent.json @@ -4,6 +4,6 @@ "week": "Woche", "month": "Monat", "year": "Jahr", - "alltime": "All Time", + "alltime": "Gesamter Zeitraum", "no_recent_topics": "Es gibt keine aktuellen Themen." } \ No newline at end of file diff --git a/public/language/de/search.json b/public/language/de/search.json index 7176039ead..4141710537 100644 --- a/public/language/de/search.json +++ b/public/language/de/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 Ergebniss(e) stimmen mit \"%2\" überein, (%3 Sekunden)", - "no-matches": "No posts found" + "no-matches": "Keine Beiträge gefunden" } \ No newline at end of file diff --git a/public/language/es/category.json b/public/language/es/category.json index ff01be1a31..7146b3eed2 100644 --- a/public/language/es/category.json +++ b/public/language/es/category.json @@ -4,5 +4,5 @@ "browsing": "viendo ahora", "no_replies": "Nadie ha respondido aún", "share_this_category": "Compartir esta categoría", - "ignore": "Ignore" + "ignore": "Ignorar" } \ No newline at end of file diff --git a/public/language/es/email.json b/public/language/es/email.json index c83494b565..bc973d1e34 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -14,10 +14,10 @@ "digest.cta": "Cliquea aquí para visitar %1", "digest.unsub.info": "Este compendio te fue enviado debido a tus ajustes de subscripción.", "digest.daily.no_topics": "No han habido temas activos en el día pasado", - "notif.chat.subject": "New chat message received from %1", - "notif.chat.cta": "Click here to continue the conversation", - "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", + "notif.chat.subject": "Nuevo mensaje de chat recibido de %1", + "notif.chat.cta": "Haz click aquí para continuar la conversación", + "notif.chat.unsub.info": "Esta notificación de chat se te envió debido a tus ajustes de suscripción.", "test.text1": "Este es un email de prueba para verificar que el envío de email está ajustado correctamente para tu NodeBB", - "unsub.cta": "Click here to alter those settings", + "unsub.cta": "Haz click aquí para modificar los ajustes.", "closing": "¡Gracias!" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index 79fd819d2f..d4c30e6d91 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -12,15 +12,15 @@ "invalid-title": "Título no válido!", "invalid-user-data": "Datos de Usuario no válidos", "invalid-password": "Contraseña no válida", - "invalid-username-or-password": "Please specify both a username and password", + "invalid-username-or-password": "Por favor especifica tanto un usuario como contraseña", "invalid-pagination-value": "Valor de paginación no válido.", "username-taken": "Nombre de usuario ya escogido", "email-taken": "El correo electrónico ya está escogido.", "email-not-confirmed": "Tu correo electrónico está sin confirmar, por favor haz click aquí para confirmar tu email.", "username-too-short": "El nombre de usuario es demasiado corto", - "username-too-long": "Username too long", + "username-too-long": "Nombre de usuario demasiado largo", "user-banned": "Usuario expulsado", - "user-too-new": "You need to wait %1 seconds before making your first post!", + "user-too-new": "Necesitas esperar %1 segundos antes de hacer tu primera publicación.", "no-category": "La categoría no existe", "no-topic": "El tema no existe.", "no-post": "La publicación no existe", @@ -55,9 +55,9 @@ "upload-error": "Error de subida: %1", "signature-too-long": "Las firmas no pueden ser más largas de %1 caracteres!", "cant-chat-with-yourself": "No puedes conversar contigo mismo!", - "reputation-system-disabled": "Reputation system is disabled.", - "downvoting-disabled": "Downvoting is disabled", + "reputation-system-disabled": "El sistema de reputación está deshabilitado.", + "downvoting-disabled": "La votación negativa está deshabilitada.", "not-enough-reputation-to-downvote": "No tienes suficiente reputación para votar negativo este post", - "not-enough-reputation-to-flag": "Yo do not have enough reputation to flag this post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "not-enough-reputation-to-flag": "No tienes suficiente reputación para marcar esta publicación", + "reload-failed": "NodeBB encontró un problema mientras refrescar: \"%1\". NodeBB intentará cargar el resto de contenido, aunque deberías deshacer lo que hiciste antes de refrescar." } \ No newline at end of file diff --git a/public/language/es/search.json b/public/language/es/search.json index 8ac377c932..5cd357b2e9 100644 --- a/public/language/es/search.json +++ b/public/language/es/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 resuldado(s) coinciden con \"%2\". (%3 segundos)", - "no-matches": "No posts found" + "no-matches": "No se encontraron publicaciones" } \ No newline at end of file diff --git a/public/language/es/user.json b/public/language/es/user.json index f244ffc9ff..4c2f89a501 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -4,8 +4,8 @@ "username": "Nombre de usuario", "email": "Correo Electrónico", "confirm_email": "Repetir correo electrónico", - "delete_account": "Delete Account", - "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", + "delete_account": "Eliminar cuenta", + "delete_account_confirm": "Estás seguro de que quieres eliminar tu cuenta?
    Esta acción es irreversible y no podrás recuperar tus datos

    Introduce tu nombre de usuario para confirmar la eliminación de la cuenta.", "fullname": "Nombre completo", "website": "Sitio Web", "location": "Ubicación", @@ -52,7 +52,7 @@ "digest_daily": "Diariamente", "digest_weekly": "Semanalmente", "digest_monthly": "Mensualmente", - "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", + "send_chat_notifications": "Envía un correo electrónico si recibes un mensaje de chat cuando no estás en línea.", "has_no_follower": "Este miembro no tiene seguidores. :(", "follows_no_one": "Este miembro no sigue a nadie. :(", "has_no_posts": "Este usuario aún no ha publicado nada.", @@ -65,6 +65,6 @@ "notification_sounds": "Reproducir un sonido al recibir una notificación", "browsing": "Preferencias de navegación.", "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña?", - "follow_topics_you_reply_to": "Follow topics that you reply to.", - "follow_topics_you_create": "Follow topics you create." + "follow_topics_you_reply_to": "Seguir publicaciones en las que respondes.", + "follow_topics_you_create": "Seguir publicaciones que creas." } \ No newline at end of file diff --git a/public/language/pl/category.json b/public/language/pl/category.json index 997aae19ae..fe8b77fe54 100644 --- a/public/language/pl/category.json +++ b/public/language/pl/category.json @@ -4,5 +4,5 @@ "browsing": "przegląda", "no_replies": "Nikt jeszcze nie odpowiedział", "share_this_category": "Udostępnij tę kategorię", - "ignore": "Ignore" + "ignore": "Ignoruj" } \ No newline at end of file diff --git a/public/language/pl/email.json b/public/language/pl/email.json index 9a154171c0..2498036436 100644 --- a/public/language/pl/email.json +++ b/public/language/pl/email.json @@ -14,10 +14,10 @@ "digest.cta": "Kliknij, by odwiedzić %1", "digest.unsub.info": "To podsumowanie zostało wysłane zgodnie z Twoimi ustawieniami.", "digest.daily.no_topics": "Wczoraj nie było żadnych aktywnych tematów", - "notif.chat.subject": "New chat message received from %1", - "notif.chat.cta": "Click here to continue the conversation", - "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", + "notif.chat.subject": "Nowa wiadomość chatu od %1", + "notif.chat.cta": "Kliknij tutaj, by kontynuować konwersację", + "notif.chat.unsub.info": "To powiadomienie o chacie zostało Ci wysłane zgodnie z ustawieniami Twojego konta.", "test.text1": "To jest e-mail testowy, aby sprawdzić, czy poprawnie skonfigurowałeś e-mailer w swoim NodeBB.", - "unsub.cta": "Click here to alter those settings", + "unsub.cta": "Kliknij tutaj, by zmienić te ustawienia", "closing": "Dziękujemy!" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index c91c2ad906..19b93b1951 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -20,7 +20,7 @@ "username-too-short": "Nazwa użytkownika za krótka.", "username-too-long": "Zbyt długa nazwa użytkownika", "user-banned": "Użytkownik zbanowany", - "user-too-new": "You need to wait %1 seconds before making your first post!", + "user-too-new": "Musisz odczekać %1 sekund przed napisaniem pierwszego posta!", "no-category": "Kategoria nie istnieje.", "no-topic": "Temat nie istnieje", "no-post": "Post nie istnieje", @@ -59,5 +59,5 @@ "downvoting-disabled": "Ocena postów jest wyłączona", "not-enough-reputation-to-downvote": "Masz za mało reputacji by ocenić ten post.", "not-enough-reputation-to-flag": "Nie masz wystarczającej reputacji by oflagować ten post", - "reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading." + "reload-failed": "NodeBB napotkał problem w czasie ładowania \"%1\". Forum będzie nadal dostarczać zasoby dostępne w kliencie, jednak powinieneś cofnąć ostatnią akcję." } \ No newline at end of file diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json index c715ae9567..2bc2824686 100644 --- a/public/language/pl/notifications.json +++ b/public/language/pl/notifications.json @@ -10,14 +10,14 @@ "new_notification": "Nowe powiadomienie", "you_have_unread_notifications": "Masz nieprzeczytane powiadomienia.", "new_message_from": "Nowa wiadomość od %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 zagłosował na Twój post w %2", + "moved_your_post": "%1 przesunął Twój post.", + "moved_your_topic": "%1 przesunął Twój temat.", + "favourited_your_post_in": "%1 polubił Twój post w %2.", + "user_flagged_post_in": "%1 oflagował Twój post w %2", "user_posted_to": "%1 dodał odpowiedź do %2", "user_mentioned_you_in": "%1 wspomniał cię w %2", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1 zaczął Cię śledzić.", "email-confirmed": "E-mail potwierdzony", "email-confirmed-message": "Dziękujemy za potwierdzenie maila. Twoje konto zostało aktywowane.", "email-confirm-error": "Wystąpił błąd.", diff --git a/public/language/pl/recent.json b/public/language/pl/recent.json index 8f92e35c6a..207d22a1d8 100644 --- a/public/language/pl/recent.json +++ b/public/language/pl/recent.json @@ -4,6 +4,6 @@ "week": "Tydzień", "month": "Miesiąc", "year": "Rok", - "alltime": "All Time", + "alltime": "Od początku", "no_recent_topics": "Brak ostatnich wątków." } \ No newline at end of file diff --git a/public/language/pl/search.json b/public/language/pl/search.json index 2a9252fdab..ed42681f15 100644 --- a/public/language/pl/search.json +++ b/public/language/pl/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 wyników pasujących do \"%2\", (%3 sekund)", - "no-matches": "No posts found" + "no-matches": "Nie znaleziono postów" } \ No newline at end of file diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 25ae1af224..08af14906e 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -44,7 +44,7 @@ "thread_tools.unlock": "Otwórz wątek", "thread_tools.move": "Przenieś wątek", "thread_tools.move_all": "Przenieś wszystko", - "thread_tools.fork": "Skopiuj wątek", + "thread_tools.fork": "Podziel wątek", "thread_tools.delete": "Usuń wątek", "thread_tools.delete_confirm": "Na pewno chcesz usunąć ten wątek?", "thread_tools.restore": "Przywróć wątek", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 82b79963f7..957954f1a2 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -5,7 +5,7 @@ "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", "delete_account": "Skasuj konto", - "delete_account_confirm": "Are you sure you want to delete your account?
    This action is irreversible and you will not be able to recover any of your data

    Enter your username to confirm that you wish to destroy this account.", + "delete_account_confirm": "Jesteś pewny, że chcesz skasować swoje konto?
    Tej operacji nie można cofnąć i utracisz wszystkie swoje dane

    Podaj swoją nazwę użytkownika, by potwierdzić skasowanie konta.", "fullname": "Pełna nazwa", "website": "Strona WWW", "location": "Położenie", @@ -46,13 +46,13 @@ "max": "maks.", "settings": "Ustawienia", "show_email": "Wyświetlaj mój adres e-mail", - "digest_label": "Subskrybuj dla Digestów!", + "digest_label": "Przysyłaj okresowe podsumowanie wiadomości na forum", "digest_description": "Subskrybuj by dostawać maile z tego forum (nowe powiadomienia i tematy) zgodnie z ustalonym harmonogramem.", "digest_off": "Wyłączone", "digest_daily": "Codziennie", "digest_weekly": "Co tydzień", "digest_monthly": "Co miesiąc", - "send_chat_notifications": "Send an email if a new chat message arrives and I am not online", + "send_chat_notifications": "Wyślij e-maila, jeśli dostanę nową wiadomość z chatu, a nie jestem on-line", "has_no_follower": "Ten użytkownik nie ma jeszcze żadnych obserwujących", "follows_no_one": "Użytkownik jeszcze nikogo nie obsweruje.", "has_no_posts": "Użytkownik nie napisał jeszcze żadnych postów.", diff --git a/public/language/ro/search.json b/public/language/ro/search.json index e9eef4632a..7d1d5330d4 100644 --- a/public/language/ro/search.json +++ b/public/language/ro/search.json @@ -1,4 +1,4 @@ { "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", - "no-matches": "No posts found" + "no-matches": "Nu a fost găsit nici un mesaj!" } \ No newline at end of file diff --git a/public/language/sv/category.json b/public/language/sv/category.json index c9662de9c0..ff00a051b5 100644 --- a/public/language/sv/category.json +++ b/public/language/sv/category.json @@ -4,5 +4,5 @@ "browsing": "läser", "no_replies": "Ingen har svarat", "share_this_category": "Dela den här kategorin", - "ignore": "Ignore" + "ignore": "Ignorera" } \ No newline at end of file diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json index 267c761ba5..2e8e5a3f2a 100644 --- a/public/language/sv/notifications.json +++ b/public/language/sv/notifications.json @@ -10,14 +10,14 @@ "new_notification": "Ny notis", "you_have_unread_notifications": "Du har olästa notiser.", "new_message_from": "Nytt medelande från %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 har röstat upp ditt inlägg i %2", + "moved_your_post": "%1 har flyttat dit inlägg.", + "moved_your_topic": "%1 har flyttat ditt ämne.", + "favourited_your_post_in": "%1 har favoriserat ditt inlägg i %2.", + "user_flagged_post_in": "%1 flaggade ett inlägg i %2", "user_posted_to": "%1 har skrivit ett svar på: %2", "user_mentioned_you_in": "%1 nämnde dig i %2", - "user_started_following_you": "%1 started following you.", + "user_started_following_you": "%1 började följa dig.", "email-confirmed": "Epost bekräftad", "email-confirmed-message": "Tack för att du bekräftat din epostadress. Ditt konto är nu fullt ut aktiverat.", "email-confirm-error": "Ett fel uppstod...", diff --git a/public/language/sv/recent.json b/public/language/sv/recent.json index f9e04c9081..dc59877cf6 100644 --- a/public/language/sv/recent.json +++ b/public/language/sv/recent.json @@ -4,6 +4,6 @@ "week": "Vecka", "month": "Månad", "year": "År", - "alltime": "All Time", + "alltime": "Alltid", "no_recent_topics": "Det finns inga olästa ämnen." } \ No newline at end of file diff --git a/public/language/vi/category.json b/public/language/vi/category.json index 70ab036818..a1074bb468 100644 --- a/public/language/vi/category.json +++ b/public/language/vi/category.json @@ -4,5 +4,5 @@ "browsing": "đang duyệt", "no_replies": "Chưa có ai bình luận", "share_this_category": "Chia sẻ phần mục này", - "ignore": "Ignore" + "ignore": "Bỏ qua" } \ No newline at end of file diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json index 029eb60475..48f6174299 100644 --- a/public/language/vi/notifications.json +++ b/public/language/vi/notifications.json @@ -2,25 +2,25 @@ "title": "Thông báo", "no_notifs": "Bạn không có thông báo nào mới", "see_all": "Xem tất cả thông báo", - "back_to_home": "Back to %1", + "back_to_home": "Quay lại %1", "outgoing_link": "Liên kết ngoài", - "outgoing_link_message": "You are now leaving %1.", - "continue_to": "Continue to %1", - "return_to": "Return to %1", + "outgoing_link_message": "Bạn đang rời khỏi %1.", + "continue_to": "Tiếp tục tới %1", + "return_to": "Quay lại %1", "new_notification": "Thông báo mới", "you_have_unread_notifications": "Bạn có thông báo chưa đọc", "new_message_from": "Tin nhắn mới từ %1", - "upvoted_your_post_in": "%1 has upvoted your post in %2.", - "moved_your_post": "%1 has moved your post.", - "moved_your_topic": "%1 has moved your topic.", - "favourited_your_post_in": "%1 has favourited your post in %2.", - "user_flagged_post_in": "%1 flagged a post in %2", + "upvoted_your_post_in": "%1 đã bình chọn bài của bạn trong %2.", + "moved_your_post": "%1 đã di chuyển bài của bạn.", + "moved_your_topic": "%1 đã di chuyển chủ đề của bạn.", + "favourited_your_post_in": "%1 đã thích bài của bạn trong %2.", + "user_flagged_post_in": "%1 gắn cờ 1 bài trong %2", "user_posted_to": "%1 đã trả lời %2", - "user_mentioned_you_in": "%1 mentioned you in %2", - "user_started_following_you": "%1 started following you.", - "email-confirmed": "Email Confirmed", - "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", - "email-confirm-error": "An error occurred...", - "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." + "user_mentioned_you_in": "%1 nhắc đến bạn trong %2", + "user_started_following_you": "%1 đã theo dõi bạn.", + "email-confirmed": "Đã xác nhận email", + "email-confirmed-message": "Cảm ơn bạn đã xác nhận địa chỉ email của bạn. Tài khoản của bạn đã được kích hoạt đầy đủ.", + "email-confirm-error": "Đã có lỗi xảy ra...", + "email-confirm-error-message": "Đã có lỗi khi xác nhận địa chỉ email. Có thể đoạn mã không đúng hoặc đã hết hạn.", + "email-confirm-sent": "Email xác nhận đã gửi." } \ No newline at end of file diff --git a/public/language/vi/recent.json b/public/language/vi/recent.json index 544c3cefb2..32dc489353 100644 --- a/public/language/vi/recent.json +++ b/public/language/vi/recent.json @@ -3,7 +3,7 @@ "day": "Ngày", "week": "Tuần", "month": "Tháng", - "year": "Year", - "alltime": "All Time", + "year": "Năm", + "alltime": "Tất cả thời gian", "no_recent_topics": "Không có chủ đề nào gần đây" } \ No newline at end of file diff --git a/public/language/vi/search.json b/public/language/vi/search.json index e9eef4632a..323dea6a5e 100644 --- a/public/language/vi/search.json +++ b/public/language/vi/search.json @@ -1,4 +1,4 @@ { - "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", - "no-matches": "No posts found" + "results_matching": "%1 kết quả(s) trùng với \"%2\", (%3 giây)", + "no-matches": "Không tìm thấy bài viết nào" } \ No newline at end of file From 309ac6fec5394b23984695201d51e9075ded6e2c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 17 Sep 2014 18:41:03 -0400 Subject: [PATCH 299/372] adding semver lib to scripts --- src/meta/js.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meta/js.js b/src/meta/js.js index c51b5ec402..07a9e3c107 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -38,6 +38,7 @@ module.exports = function(Meta) { 'public/vendor/xregexp/unicode/unicode-base.js', 'public/vendor/buzz/buzz.min.js', 'public/vendor/mousetrap/mousetrap.js', + 'public/vendor/semver/semver.browser.js', './node_modules/templates.js/lib/templates.js', 'public/src/utils.js', 'public/src/app.js', From 0f258859a71555d35c1feb9df4dce4cb77ccac36 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 19:01:00 -0400 Subject: [PATCH 300/372] closes #2067 --- public/language/en_GB/error.json | 1 + public/src/modules/search.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 62908a30db..7c4baf4cc4 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -16,6 +16,7 @@ "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", "invalid-username-or-password": "Please specify both a username and password", + "invalid-search-term": "Invalid search term", "invalid-pagination-value": "Invalid pagination value", diff --git a/public/src/modules/search.js b/public/src/modules/search.js index aa035367a8..177e532c38 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -7,6 +7,12 @@ define('search', ['navigator'], function(nav) { }; Search.query = function(term, callback) { + try { + term = encodeURIComponent(term); + } catch(e) { + return app.alertError('[[error:invalid-search-term]]'); + } + // Detect if a tid was specified var topicSearch = term.match(/in:topic-([\d]+)/); From 63a7766ac1a868d0103a55ea3d40e85b1f4f39a1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Sep 2014 19:39:59 -0400 Subject: [PATCH 301/372] closes #2027 --- public/src/forum/category.js | 74 ++++++++++++++++++++---------------- public/src/modules/alerts.js | 2 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/public/src/forum/category.js b/public/src/forum/category.js index 646582abaf..30bcf629f1 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -42,8 +42,8 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' var clickedTid = $(this).parents('li.category-item[data-tid]').attr('data-tid'); $('#topics-container li.category-item').each(function(index, el) { if($(el).offset().top - $(window).scrollTop() > 0) { - localStorage.setItem('category:bookmark', $(el).attr('data-tid')); - localStorage.setItem('category:bookmark:clicked', clickedTid); + localStorage.setItem('category:' + cid + ':bookmark', $(el).attr('data-tid')); + localStorage.setItem('category:' + cid + ':bookmark:clicked', clickedTid); return false; } }); @@ -84,45 +84,53 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' $(window).on('action:popstate', function(ev, data) { if(data.url.indexOf('category/') === 0) { - var bookmark = localStorage.getItem('category:bookmark'); - var clicked = localStorage.getItem('category:bookmark:clicked'); + var cid = data.url.match(/^category\/(\d+)/); + if (cid && cid[1]) { + cid = cid[1]; + } + if (!cid) { + return; + } - if (bookmark) { + var bookmark = localStorage.getItem('category:' + cid + ':bookmark'); + var clicked = localStorage.getItem('category:' + cid + ':bookmark:clicked'); - if(config.usePagination) { - socket.emit('topics.getTidPage', bookmark, function(err, page) { - if(err) { - return; - } - if(parseInt(page, 10) !== pagination.currentPage) { - pagination.loadPage(page); - } else { - Category.scrollToTopic(bookmark, clicked, 400); - } - }); - } else { + if (!bookmark) { + return; + } - socket.emit('topics.getTidIndex', bookmark, function(err, index) { - if(err) { - return; - } + if(config.usePagination) { + socket.emit('topics.getTidPage', bookmark, function(err, page) { + if (err) { + return; + } + if(parseInt(page, 10) !== pagination.currentPage) { + pagination.loadPage(page); + } else { + Category.scrollToTopic(bookmark, clicked, 400); + } + }); + } else { + socket.emit('topics.getTidIndex', bookmark, function(err, index) { + if (err) { + return; + } - if(index === 0) { - Category.highlightTopic(clicked); - return; - } + if (index === 0) { + Category.highlightTopic(clicked); + return; + } - if (index < 0) { - index = 0; - } + if (index < 0) { + index = 0; + } - $('#topics-container').empty(); + $('#topics-container').empty(); - loadTopicsAfter(index, function() { - Category.scrollToTopic(bookmark, clicked, 0); - }); + loadTopicsAfter(index, function() { + Category.scrollToTopic(bookmark, clicked, 0); }); - } + }); } } }); diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index bdf55ae917..60128e848f 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -6,7 +6,7 @@ define('alerts', function() { module.alert = function (params) { params.alert_id = 'alert_button_' + (params.alert_id ? params.alert_id : new Date().getTime()); - params.title = params.title.trim() || ''; + params.title = params.title ? params.title.trim() || '' : ''; params.message = params.message.trim(); params.location = params.location || 'right-top'; From f3778687642e207e39753a7082ed622b148ad2bb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 12:01:39 -0400 Subject: [PATCH 302/372] closes #2064 --- public/src/modules/alerts.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/src/modules/alerts.js b/public/src/modules/alerts.js index 60128e848f..ccbec68d0a 100644 --- a/public/src/modules/alerts.js +++ b/public/src/modules/alerts.js @@ -23,7 +23,11 @@ define('alerts', function() { function createNew(params) { templates.parse('alert', params, function(alertTpl) { translator.translate(alertTpl, function(translatedHTML) { - var alert = $(translatedHTML); + var alert = $('#' + params.alert_id); + if (alert.length) { + return updateAlert(alert, params); + } + alert = $(translatedHTML); alert.fadeIn(200); $('.alert-' + params.location).prepend(alert); From 926acd16b2d0c30ea80ac646fecdcd35533c406a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 14:04:22 -0400 Subject: [PATCH 303/372] closes #2032 --- src/controllers/categories.js | 89 ++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 6810e2ea7e..91f36139e6 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -91,48 +91,54 @@ categoriesController.get = function(req, res, next) { async.waterfall([ function(next) { - categories.getCategoryField(cid, 'disabled', next); + async.parallel({ + exists: function(next) { + categories.exists(cid, next); + }, + disabled: function(next) { + categories.getCategoryField(cid, 'disabled', next); + }, + privileges: function(next) { + privileges.categories.get(cid, uid, next); + }, + userSettings: function(next) { + user.getSettings(uid, next); + } + }, next); }, - function(disabled, next) { - if (parseInt(disabled, 10) === 1) { - return next(new Error('[[error:category-disabled]]')); + function(results, next) { + if (!results.exists || parseInt(results.disabled, 10) === 1) { + return notFound(req, res); } - privileges.categories.get(cid, uid, next); - }, - function (privileges, next) { - if (!privileges.read) { - return next(new Error('[[error:no-privileges]]')); + if (!results.privileges.read) { + return notAllowed(req, res); } - user.getSettings(uid, function(err, settings) { + var settings = results.userSettings; + + var topicIndex = 0; + if (!settings.usePagination) { + topicIndex = Math.max((req.params.topic_index || 1) - (settings.topicsPerPage - 1), 0); + } else if (!req.query.page) { + var index = Math.max(parseInt((req.params.topic_index || 0), 10), 0); + page = Math.ceil((index + 1) / settings.topicsPerPage); + } + + var start = (page - 1) * settings.topicsPerPage + topicIndex, + end = start + settings.topicsPerPage - 1; + + categories.getCategoryById(cid, start, end, uid, function (err, categoryData) { if (err) { return next(err); } - var topicIndex = 0; - if (!settings.usePagination) { - topicIndex = Math.max((req.params.topic_index || 1) - (settings.topicsPerPage - 1), 0); - } else if (!req.query.page) { - var index = Math.max(parseInt((req.params.topic_index || 0), 10), 0); - page = Math.ceil((index + 1) / settings.topicsPerPage); - } - - var start = (page - 1) * settings.topicsPerPage + topicIndex, - end = start + settings.topicsPerPage - 1; - - categories.getCategoryById(cid, start, end, uid, function (err, categoryData) { + categories.getRecentTopicReplies(categoryData.children, uid, function(err) { if (err) { return next(err); } - - categories.getRecentTopicReplies(categoryData.children, uid, function(err) { - if (err) { - return next(err); - } - categoryData.privileges = privileges; - next(null, categoryData); - }); + categoryData.privileges = results.privileges; + next(null, categoryData); }); }); }, @@ -179,7 +185,7 @@ categoriesController.get = function(req, res, next) { } ], function (err, data) { if (err) { - return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect(nconf.get('relative_path') + '/404'); + return next(err); } if (data.link) { @@ -193,7 +199,7 @@ categoriesController.get = function(req, res, next) { } data.currentPage = page; - data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false; + data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data.csrf = req.csrfToken(); // Paginator for noscript @@ -204,8 +210,27 @@ categoriesController.get = function(req, res, next) { active: x === parseInt(page, 10) }); } + res.render('category', data); }); }; +function notFound(req, res) { + res.locals.isAPI ? res.json(404, 'not-found') : res.redirect(nconf.get('relative_path') + '/404'); +} + +function notAllowed(req, res) { + var uid = req.user ? req.user.uid : 0; + if (uid) { + res.locals.isAPI ? res.json(403, 'not-allowed') : res.redirect(nconf.get('relative_path') + '/403'); + } else { + if (res.locals.isAPI) { + res.json(401, 'not-authorized'); + } else { + req.session.returnTo = req.url; + res.redirect(nconf.get('relative_path') + '/login'); + } + } +} + module.exports = categoriesController; From 6844c828cac0770a6e785f6523d920eb4ffb389c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 18 Sep 2014 17:05:22 -0400 Subject: [PATCH 304/372] added a hook I didn't end up needing, but here it is anyway `filter:posts.modifyUserInfo` --- src/posts.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 06216f0d07..a31661066d 100644 --- a/src/posts.js +++ b/src/posts.js @@ -211,7 +211,8 @@ var async = require('async'), } userData.signature = results.signature; userData.custom_profile_info = results.customProfileInfo.profile; - next(null, userData); + + plugins.fireHook('filter:posts.modifyUserInfo', userData, next); }); }, callback); }); From 8f916ed06696c642b2f5415fbc549b4635c81a6f Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 18 Sep 2014 17:09:40 -0400 Subject: [PATCH 305/372] filter:sockets.sendNewPostsToUids --- src/socket.io/posts.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index e7d5e330d3..d3b772052e 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -5,6 +5,7 @@ var async = require('async'), db = require('../database'), posts = require('../posts'), + plugins = require('../plugins'), privileges = require('../privileges'), meta = require('../meta'), topics = require('../topics'), @@ -50,11 +51,14 @@ SocketPosts.reply = function(socket, data, callback) { if (err) { return; } - for(var i=0; i Date: Thu, 18 Sep 2014 17:22:20 -0400 Subject: [PATCH 306/372] #2032 --- src/controllers/categories.js | 12 ++++---- src/controllers/topics.js | 58 +++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 91f36139e6..b23e4d4afb 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -108,11 +108,11 @@ categoriesController.get = function(req, res, next) { }, function(results, next) { if (!results.exists || parseInt(results.disabled, 10) === 1) { - return notFound(req, res); + return categoriesController.notFound(req, res); } if (!results.privileges.read) { - return notAllowed(req, res); + return categoriesController.notAllowed(req, res); } var settings = results.userSettings; @@ -215,11 +215,11 @@ categoriesController.get = function(req, res, next) { }); }; -function notFound(req, res) { +categoriesController.notFound = function(req, res) { res.locals.isAPI ? res.json(404, 'not-found') : res.redirect(nconf.get('relative_path') + '/404'); -} +}; -function notAllowed(req, res) { +categoriesController.notAllowed = function(req, res) { var uid = req.user ? req.user.uid : 0; if (uid) { res.locals.isAPI ? res.json(403, 'not-allowed') : res.redirect(nconf.get('relative_path') + '/403'); @@ -231,6 +231,6 @@ function notAllowed(req, res) { res.redirect(nconf.get('relative_path') + '/login'); } } -} +}; module.exports = categoriesController; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index d0857b862a..de91eb75dd 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -6,12 +6,13 @@ var topicsController = {}, validator = require('validator'), nconf = require('nconf'), qs = require('querystring'), - user = require('./../user'), - meta = require('./../meta'), - topics = require('./../topics'), + user = require('../user'), + meta = require('../meta'), + topics = require('../topics'), posts = require('../posts'), privileges = require('../privileges'), - utils = require('./../../public/src/utils'); + categoriesController = require('./categories'), + utils = require('../../public/src/utils'); topicsController.get = function(req, res, next) { var tid = req.params.topic_id, @@ -22,16 +23,10 @@ topicsController.get = function(req, res, next) { async.waterfall([ function (next) { - privileges.topics.get(tid, uid, next); - }, - function (privileges, next) { - if (!privileges.read || privileges.disabled) { - return next(new Error('[[error:no-privileges]]')); - } - - userPrivileges = privileges; - async.parallel({ + privileges: function(next) { + privileges.topics.get(tid, uid, next); + }, postCount: function(next) { topics.getPostCount(tid, next); }, @@ -41,6 +36,16 @@ topicsController.get = function(req, res, next) { }, next); }, function (results, next) { + userPrivileges = results.privileges; + + if (userPrivileges.disabled) { + return categoriesController.notFound(req, res); + } + + if (!userPrivileges.read) { + return categoriesController.notAllowed(req, res); + } + var settings = results.settings; var postCount = parseInt(results.postCount, 10) + 1; var set = 'tid:' + tid + ':posts', @@ -82,16 +87,22 @@ topicsController.get = function(req, res, next) { end = start + settings.postsPerPage - 1; topics.getTopicWithPosts(tid, set, uid, start, end, reverse, function (err, topicData) { - if (topicData) { - if (topicData.deleted && !userPrivileges.view_deleted) { - return next(new Error('[[error:no-topic]]')); - } - topicData.currentPage = page; - if(page > 1) { - topicData.posts.splice(0, 1); - } + if (err && err.message === '[[error:no-topic]]' && !topicData) { + return categoriesController.notFound(req, res); + } + if (err && !topicData) { + return next(err); } - next(err, topicData); + if (topicData.deleted && !userPrivileges.view_deleted) { + return categoriesController.notAllowed(req, res); + } + + topicData.currentPage = page; + if(page > 1) { + topicData.posts.splice(0, 1); + } + + next(null, topicData); }); }, function (topicData, next) { @@ -191,7 +202,7 @@ topicsController.get = function(req, res, next) { } ], function (err, data) { if (err) { - return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect(nconf.get('relative_path') + '/404'); + return next(err); } data.privileges = userPrivileges; @@ -214,6 +225,7 @@ topicsController.get = function(req, res, next) { active: x === parseInt(page, 10) }); } + res.render('topic', data); }); }; From 9e7f929029691dad7f3fceb7c84e9551d1a8b4ba Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 18 Sep 2014 17:36:23 -0400 Subject: [PATCH 307/372] updated filter:sockets.sendNewPostToUids and added type --- src/socket.io/posts.js | 4 +++- src/socket.io/topics.js | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index d3b772052e..aaee8a1500 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -52,7 +52,9 @@ SocketPosts.reply = function(socket, data, callback) { return; } - plugins.fireHook('filter:sockets.sendNewPostsToUids', {uidsTo: uids, uidFrom: data.uid}, function(err, uids) { + plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: data.uid, type: "newPost"}, function(err, data) { + uids = data.uids; + for(var i=0; i Date: Thu, 18 Sep 2014 17:40:15 -0400 Subject: [PATCH 308/372] woops, wrong property name --- src/socket.io/posts.js | 2 +- src/socket.io/topics.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index aaee8a1500..3fa11dfa40 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -53,7 +53,7 @@ SocketPosts.reply = function(socket, data, callback) { } plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: data.uid, type: "newPost"}, function(err, data) { - uids = data.uids; + uids = data.uidsTo; for(var i=0; i Date: Thu, 18 Sep 2014 17:44:24 -0400 Subject: [PATCH 309/372] forgot to require plugins --- src/socket.io/topics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 0f923f76b6..8d495ee239 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -7,6 +7,7 @@ var nconf = require('nconf'), topics = require('../topics'), categories = require('../categories'), privileges = require('../privileges'), + plugins = require('../plugins'), notifications = require('../notifications'), threadTools = require('../threadTools'), websockets = require('./index'), From 93b83060bea67fd23e2299724909d08938c39e02 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 17:55:59 -0400 Subject: [PATCH 310/372] fix expireAt using millis --- src/middleware/middleware.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 42d5471d75..f2cb140bb8 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -60,8 +60,8 @@ middleware.incrementPageViews = function(req, res, next) { db.increment('pageviews:monthly'); db.increment('pageviews:daily'); - db.expireAt('pageviews:monthly', nextMonth.getTime()); - db.expireAt('pageviews:daily', nextDay.getTime()); + db.pexpireAt('pageviews:monthly', nextMonth.getTime()); + db.pexpireAt('pageviews:daily', nextDay.getTime()); next(); }; From 18442509670f6af15ad49c4b00e9efe00d9db520 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 17:59:09 -0400 Subject: [PATCH 311/372] check err --- src/middleware/middleware.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index f2cb140bb8..c97067173e 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -67,17 +67,20 @@ middleware.incrementPageViews = function(req, res, next) { }; middleware.redirectToAccountIfLoggedIn = function(req, res, next) { - if (req.user) { - user.getUserField(req.user.uid, 'userslug', function (err, userslug) { - if (res.locals.isAPI) { - return res.json(302, '/user/' + userslug); - } else { - res.redirect('/user/' + userslug); - } - }); - } else { - next(); + if (!req.user) { + return next(); } + user.getUserField(req.user.uid, 'userslug', function (err, userslug) { + if (err) { + return next(err); + } + + if (res.locals.isAPI) { + res.json(302, '/user/' + userslug); + } else { + res.redirect('/user/' + userslug); + } + }); }; middleware.redirectToLoginIfGuest = function(req, res, next) { From 803a2436de9f78f39e4e2e43b7b1a0a2247e4404 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 18:12:16 -0400 Subject: [PATCH 312/372] wait for increment to complete before expire --- src/middleware/middleware.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index c97067173e..7ebd9d83d1 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -58,10 +58,19 @@ middleware.incrementPageViews = function(req, res, next) { nextDay.setDate(nextDay.getDate() + 1); nextDay.setHours(0, 0, 0, 0); - db.increment('pageviews:monthly'); - db.increment('pageviews:daily'); - db.pexpireAt('pageviews:monthly', nextMonth.getTime()); - db.pexpireAt('pageviews:daily', nextDay.getTime()); + db.increment('pageviews:monthly', function(err) { + if (err) { + return; + } + db.pexpireAt('pageviews:monthly', nextMonth.getTime()); + }); + + db.increment('pageviews:daily', function(err) { + if (err) { + return; + } + db.pexpireAt('pageviews:daily', nextDay.getTime()); + }); next(); }; From c32fdabc436b339bc6807c205e7b81a00cd29b1a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Sep 2014 21:20:05 -0400 Subject: [PATCH 313/372] minor refactor --- src/meta.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/meta.js b/src/meta.js index c923670c5f..09e9a6d134 100644 --- a/src/meta.js +++ b/src/meta.js @@ -30,18 +30,21 @@ var async = require('async'), }; Meta.reload = function(callback) { - plugins.reload(function() { - async.parallel([ - async.apply(Meta.js.minify, false), - async.apply(Meta.css.minify), - async.apply(Meta.templates.compile) - ], function(err) { - if (!err) { - emitter.emit('nodebb:ready'); - } + async.series([ + async.apply(plugins.reload), + function(next) { + async.parallel([ + async.apply(Meta.js.minify, false), + async.apply(Meta.css.minify), + async.apply(Meta.templates.compile) + ], next); + } + ], function(err) { + if (!err) { + emitter.emit('nodebb:ready'); + } - if (callback) callback.apply(null, arguments); - }); + if (callback) callback.apply(null, arguments); }); }; From 7652f2bf399d19951e397c31777258eb275c2a6a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 21:48:49 -0400 Subject: [PATCH 314/372] escape input tag --- src/controllers/tags.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 6d3d18c1f0..755ede4b91 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -3,11 +3,12 @@ var tagsController = {}, async = require('async'), nconf = require('nconf'), + validator = require('validator'), meta = require('../meta'), topics = require('../topics'); tagsController.getTag = function(req, res, next) { - var tag = req.params.tag; + var tag = validator.escape(req.params.tag); var uid = req.user ? req.user.uid : 0; var end = (parseInt(meta.config.topicsPerList, 10) || 20) - 1; topics.getTagTids(tag, 0, end, function(err, tids) { From 1bad2f79eecf11c9ffc5394b6c99852937b9b1e6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Sep 2014 22:24:14 -0400 Subject: [PATCH 315/372] show megabyte values --- src/database/mongo/main.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 2382acd28e..d9a1b4a9be 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -56,6 +56,10 @@ module.exports = function(db, module) { } stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2); + stats.dataSize = (stats.dataSize / 1024).toFixed(2); + stats.storageSize = (stats.storageSize / 1024).toFixed(2); + stats.fileSize = (stats.fileSize / 1024).toFixed(2); + stats.indexSize = (stats.indexSize / 1024).toFixed(2); stats.raw = JSON.stringify(stats, null, 4); stats.mongo = true; From bae634e1249f51ae57a6f5838ff63cf1b524f3da Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 08:27:27 -0400 Subject: [PATCH 316/372] closed #2112 --- src/routes/authentication.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 8925d6a539..aa59419a6a 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -61,6 +61,8 @@ }, function() { if (userData.uid) { user.logIP(userData.uid, req.ip); + + plugins.fireHook('action:user.loggedIn', userData.uid); } if (!req.session.returnTo) { From 4393907ff6436955810ce52fe19756aa5cf40080 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 08:27:27 -0400 Subject: [PATCH 317/372] closed #2112 --- src/routes/authentication.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 9b30ae08a9..ee6c04122e 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -61,6 +61,8 @@ }, function() { if (userData.uid) { user.logIP(userData.uid, req.ip); + + plugins.fireHook('action:user.loggedIn', userData.uid); } if (!req.session.returnTo) { From 2bcd43531aae5d5141ade349f39cf649429224eb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 09:11:55 -0400 Subject: [PATCH 318/372] updated package.json to use 0.5.1-ready themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9a2d3760fc..2714de3f5a 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "nodebb-plugin-markdown": "~0.5.0", "nodebb-plugin-mentions": "~0.6.0", "nodebb-plugin-soundpack-default": "~0.1.1", - "nodebb-theme-lavender": "~0.0.74", - "nodebb-theme-vanilla": "~0.0.111", + "nodebb-theme-lavender": "~0.1.0", + "nodebb-theme-vanilla": "~0.1.0", "nodebb-widget-essentials": "~0.1.0", "npm": "^1.4.6", "passport": "~0.2.0", From f0d3b5dd00b2f813ec673807af9a95b2b22224e6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 09:11:55 -0400 Subject: [PATCH 319/372] updated package.json to use 0.5.1-ready themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b9ab598728..2b53707b32 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "nodebb-plugin-markdown": "~0.5.0", "nodebb-plugin-mentions": "~0.6.0", "nodebb-plugin-soundpack-default": "~0.1.1", - "nodebb-theme-lavender": "~0.0.74", - "nodebb-theme-vanilla": "~0.0.111", + "nodebb-theme-lavender": "~0.1.0", + "nodebb-theme-vanilla": "~0.1.0", "nodebb-widget-essentials": "~0.1.0", "npm": "^1.4.6", "passport": "~0.2.0", From 327d1c1bec72c6d9e48331c750ac62dd78d96e01 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 14:25:43 -0400 Subject: [PATCH 320/372] some cleanup and callbacks for post --- src/posts.js | 25 +++++++---- src/socket.io/posts.js | 2 +- src/topics/create.js | 99 +++++++++++++++++++++++++----------------- src/user.js | 8 ++-- 4 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/posts.js b/src/posts.js index a31661066d..ed3d5efa9b 100644 --- a/src/posts.js +++ b/src/posts.js @@ -11,7 +11,7 @@ var async = require('async'), db = require('./database'), - utils = require('./../public/src/utils'), + utils = require('../public/src/utils'), user = require('./user'), groups = require('./groups'), topics = require('./topics'), @@ -70,17 +70,24 @@ var async = require('async'), db.setObject('post:' + postData.pid, postData, next); }, function(next) { - db.sortedSetAdd('posts:pid', timestamp, postData.pid); - - db.incrObjectField('global', 'postCount'); - emitter.emit('event:newpost', postData); - - plugins.fireHook('filter:post.get', postData, next); + async.parallel([ + function(next) { + db.sortedSetAdd('posts:pid', timestamp, postData.pid, next); + }, + function(next) { + db.incrObjectField('global', 'postCount', next); + } + ], function(err) { + if (err) { + return next(err); + } + plugins.fireHook('filter:post.get', postData, next); + }); }, function(postData, next) { postTools.parse(postData.content, function(err, content) { - if(err) { + if (err) { return next(err); } @@ -430,7 +437,7 @@ var async = require('async'), var cids = posts.map(function(post) { return map[post.tid]; - }) + }); callback(null, cids); }); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 3fa11dfa40..5cf5fadd29 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -54,7 +54,7 @@ SocketPosts.reply = function(socket, data, callback) { plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: data.uid, type: "newPost"}, function(err, data) { uids = data.uidsTo; - + for(var i=0; i Date: Fri, 19 Sep 2014 14:39:27 -0400 Subject: [PATCH 321/372] composer tweaks, hiding navbar when mobile composer is open --- public/language/en_GB/tags.json | 1 + public/src/app.js | 7 +++++++ public/src/modules/composer.js | 1 + public/src/modules/composer/resize.js | 3 ++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/public/language/en_GB/tags.json b/public/language/en_GB/tags.json index 49adc42b2c..f67b2ca7b5 100644 --- a/public/language/en_GB/tags.json +++ b/public/language/en_GB/tags.json @@ -2,5 +2,6 @@ "no_tag_topics": "There are no topics with this tag.", "tags": "Tags", "enter_tags_here": "Enter tags here. Press enter after each tag.", + "enter_tags_here_short": "Enter tags...", "no_tags": "There are no tags yet." } \ No newline at end of file diff --git a/public/src/app.js b/public/src/app.js index 411e5f568d..8138f956f2 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -375,6 +375,13 @@ var socket, }); }; + app.toggleNavbar = function(state) { + var navbarEl = $('.navbar'); + if (navbarEl) { + navbarEl.toggleClass('hidden', !!!state); + } + }; + function exposeConfigToTemplates() { $(document).ready(function() { templates.setGlobal('relative_path', RELATIVE_PATH); diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 8ba252b125..90a9311987 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -460,6 +460,7 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting $('body').css({'margin-bottom': 0}); $('.action-bar button').removeAttr('disabled'); + app.toggleNavbar(true); socket.emit('modules.composer.unregister', post_uuid); } } diff --git a/public/src/modules/composer/resize.js b/public/src/modules/composer/resize.js index 75382b2b6e..fb452b838a 100644 --- a/public/src/modules/composer/resize.js +++ b/public/src/modules/composer/resize.js @@ -22,7 +22,8 @@ define('composer/resize', function() { } if (env === 'sm' || env === 'xs') { - postContainer.css('height', $(window).height() - $('#header-menu').height()); + app.toggleNavbar(false); + postContainer.css('height', $(window).height()); } if (config.hasImageUploadPlugin) { From f3f4711a607c4187a75a7d4a3bac20f0baa02b4c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 15:54:13 -0400 Subject: [PATCH 322/372] wait for callback --- src/categories.js | 29 ++++++++++++++++++----------- src/postTools.js | 2 +- src/posts.js | 10 +++++++++- src/topics/fork.js | 8 ++++++-- src/topics/posts.js | 18 ++++++++++++------ src/topics/recent.js | 17 +++++++++++++---- src/user.js | 24 +++++++++++++----------- 7 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/categories.js b/src/categories.js index 1a0f159f2d..75ec29705b 100644 --- a/src/categories.js +++ b/src/categories.js @@ -9,7 +9,6 @@ var db = require('./database'), topics = require('./topics'), plugins = require('./plugins'), meta = require('./meta'), - emitter = require('./emitter'), validator = require('validator'), privileges = require('./privileges'), @@ -418,27 +417,35 @@ var db = require('./database'), ], callback); }; - Categories.onNewPostMade = function(postData) { + Categories.onNewPostMade = function(postData, callback) { topics.getTopicFields(postData.tid, ['cid', 'pinned'], function(err, topicData) { if (err) { - return winston.error(err.message); + return callback(err); } if (!topicData) { - return; + return callback(); } var cid = topicData.cid; - db.sortedSetAdd('categories:recent_posts:cid:' + cid, postData.timestamp, postData.pid); - db.incrObjectField('category:' + cid, 'post_count'); - - if(parseInt(topicData.pinned, 10) === 0) { - db.sortedSetAdd('categories:' + cid + ':tid', postData.timestamp, postData.tid); - } + async.parallel([ + function(next) { + db.sortedSetAdd('categories:recent_posts:cid:' + cid, postData.timestamp, postData.pid, next); + }, + function(next) { + db.incrObjectField('category:' + cid, 'post_count', next); + }, + function(next) { + if(parseInt(topicData.pinned, 10) === 0) { + db.sortedSetAdd('categories:' + cid + ':tid', postData.timestamp, postData.tid, next); + } else { + next(); + } + } + ], callback); }); }; - emitter.on('event:newpost', Categories.onNewPostMade); }(exports)); diff --git a/src/postTools.js b/src/postTools.js index 595941fb09..cfbd9d504f 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -211,7 +211,7 @@ var winston = require('winston'), } if (timestamp) { - topics.updateTimestamp(tid, timestamp); + return topics.updateTimestamp(tid, timestamp, callback); } callback(); }); diff --git a/src/posts.js b/src/posts.js index ed3d5efa9b..4f9bbf9488 100644 --- a/src/posts.js +++ b/src/posts.js @@ -70,8 +70,16 @@ var async = require('async'), db.setObject('post:' + postData.pid, postData, next); }, function(next) { - emitter.emit('event:newpost', postData); async.parallel([ + function(next) { + user.onNewPostMade(postData, next); + }, + function(next) { + topics.onNewPostMade(postData, next); + }, + function(next) { + categories.onNewPostMade(postData, next); + }, function(next) { db.sortedSetAdd('posts:pid', timestamp, postData.pid, next); }, diff --git a/src/topics/fork.js b/src/topics/fork.js index 4d276f16fb..4e562781bb 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -48,8 +48,12 @@ module.exports = function(Topics) { return callback(err); } - Topics.updateTimestamp(tid, Date.now()); - Topics.getTopicData(tid, callback); + Topics.updateTimestamp(tid, Date.now(), function(err) { + if (err) { + return callback(err); + } + Topics.getTopicData(tid, callback); + }); }); function move(pid, next) { diff --git a/src/topics/posts.js b/src/topics/posts.js index 0b29527939..ee769add0e 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -7,20 +7,26 @@ var async = require('async'), db = require('../database'), user = require('../user'), - emitter = require('../emitter'), favourites = require('../favourites'), posts = require('../posts'), privileges = require('../privileges'); module.exports = function(Topics) { - Topics.onNewPostMade = function(postData) { - Topics.increasePostCount(postData.tid); - Topics.updateTimestamp(postData.tid, postData.timestamp); - Topics.addPostToTopic(postData.tid, postData.pid, postData.timestamp, 0); + Topics.onNewPostMade = function(postData, callback) { + async.parallel([ + function(next) { + Topics.increasePostCount(postData.tid, next); + }, + function(next) { + Topics.updateTimestamp(postData.tid, postData.timestamp, next); + }, + function(next) { + Topics.addPostToTopic(postData.tid, postData.pid, postData.timestamp, 0, next); + } + ], callback); }; - emitter.on('event:newpost', Topics.onNewPostMade); Topics.getTopicPosts = function(tid, set, start, end, uid, reverse, callback) { callback = callback || function() {}; diff --git a/src/topics/recent.js b/src/topics/recent.js index 77d8023d32..e94dbe23a8 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -2,7 +2,10 @@ 'use strict'; -var db = require('./../database'); +var async = require('async'), + db = require('../database'); + + module.exports = function(Topics) { var terms = { @@ -33,9 +36,15 @@ module.exports = function(Topics) { db.getSortedSetRevRangeByScore('topics:recent', start, count, Infinity, Date.now() - since, callback); }; - Topics.updateTimestamp = function(tid, timestamp) { - Topics.updateRecent(tid, timestamp); - Topics.setTopicField(tid, 'lastposttime', timestamp); + Topics.updateTimestamp = function(tid, timestamp, callback) { + async.parallel([ + function(next) { + Topics.updateRecent(tid, timestamp, next); + }, + function(next) { + Topics.setTopicField(tid, 'lastposttime', timestamp, next); + } + ], callback); }; Topics.updateRecent = function(tid, timestamp, callback) { diff --git a/src/user.js b/src/user.js index 2271fc3c77..ad2d5852f9 100644 --- a/src/user.js +++ b/src/user.js @@ -1,7 +1,6 @@ 'use strict'; -var - async = require('async'), +var async = require('async'), nconf = require('nconf'), gravatar = require('gravatar'), @@ -9,7 +8,6 @@ var db = require('./database'), meta = require('./meta'), groups = require('./groups'), - emitter = require('./emitter'), Password = require('./password'); (function(User) { @@ -298,16 +296,20 @@ var Password.hash(nconf.get('bcrypt_rounds'), password, callback); }; - User.onNewPostMade = function(postData) { - User.addPostIdToUser(postData.uid, postData.pid, postData.timestamp); - - User.incrementUserPostCountBy(postData.uid, 1); - - User.setUserField(postData.uid, 'lastposttime', postData.timestamp); + User.onNewPostMade = function(postData, callback) { + async.parallel([ + function(next) { + User.addPostIdToUser(postData.uid, postData.pid, postData.timestamp, next); + }, + function(next) { + User.incrementUserPostCountBy(postData.uid, 1, next); + }, + function(next) { + User.setUserField(postData.uid, 'lastposttime', postData.timestamp, next); + } + ], callback); }; - emitter.on('event:newpost', User.onNewPostMade); - User.incrementUserPostCountBy = function(uid, value, callback) { callback = callback || function() {}; User.incrementUserFieldBy(uid, 'postcount', value, function(err, newpostcount) { From d13fcd3529f3c9c4ea22ca6aa42ac77ccab0d62d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 16:17:45 -0400 Subject: [PATCH 323/372] closed #2109 --- public/src/forum/admin/categories.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index c91f2bf706..7c5dcf56b3 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -89,14 +89,8 @@ define('forum/admin/categories', ['uploader', 'forum/admin/iconSelect'], functio timeout: 2000 }); - ajaxify.loadTemplate('admin/categories', function(adminCategories) { - var html = $(templates.parse(templates.getBlock(adminCategories, 'categories'), {categories: [data]})); - - html.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker); - - $('#entry-container').append(html); - $('#new-category-modal').modal('hide'); - }); + $('#new-category-modal').modal('hide'); + ajaxify.refresh(); }); } From 89e56794e8f89916983b2b52e133effa772c919f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 18:40:48 -0400 Subject: [PATCH 324/372] check cid --- src/categories.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/categories.js b/src/categories.js index 75ec29705b..bf9e59aefe 100644 --- a/src/categories.js +++ b/src/categories.js @@ -307,7 +307,7 @@ var db = require('./database'), category.name = validator.escape(category.name); category.description = validator.escape(category.description); category.backgroundImage = category.image ? nconf.get('relative_path') + category.image : ''; - category.disabled = category.disabled ? parseInt(category.disabled, 10) !== 0 : false; + category.disabled = parseInt(category.disabled, 10) === 1; next(null, category); }, callback); @@ -423,7 +423,7 @@ var db = require('./database'), return callback(err); } - if (!topicData) { + if (!topicData || !topicData.cid) { return callback(); } From 630977fb99f16e9f1855bb29bd6323e3b20e772f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 18:42:26 -0400 Subject: [PATCH 325/372] added parentCid 0 to new categories --- src/categories.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/categories.js b/src/categories.js index bf9e59aefe..5303eda1b9 100644 --- a/src/categories.js +++ b/src/categories.js @@ -39,6 +39,7 @@ var db = require('./database'), bgColor: data.bgColor, color: data.color, slug: slug, + parentCid: 0, topic_count: 0, post_count: 0, disabled: 0, From 59120df71ca84e2799e805fdf5d1a77d91433c30 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 18:59:11 -0400 Subject: [PATCH 326/372] bulk fix --- src/controllers/index.js | 2 +- src/database/mongo/sets.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 872eedd9d8..0dabd3dcab 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -76,7 +76,7 @@ Controllers.home = function(req, res, next) { childCategories.push.apply(childCategories, categoryData[i].children); } - if (categoryData[i].parent) { + if (categoryData[i].parent && categoryData[i].parent.cid) { categoryData.splice(i, 1); } } diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index a48b51cded..78e501cbd9 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -33,6 +33,11 @@ module.exports = function(db, module) { module.setsAdd = function(keys, value, callback) { callback = callback || helpers.noop; + + if (!Array.isArray(keys) || !keys.length) { + return callback(); + } + if(!Array.isArray(value)) { value = [value]; } From 3d4dad226e8e8527c1319a9ff7ed704ffa259324 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 19:24:28 -0400 Subject: [PATCH 327/372] crash fix --- src/user/notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index 08703d6237..fa61fce32a 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -229,9 +229,9 @@ var async = require('async'), if (err) { return callback(err); } - + value = value ? value.toString() : ''; nids = notifications.filter(function(notification) { - return notification && notification[field] !== value.toString(); + return notification && notification[field] !== value; }).map(function(notification) { return notification.nid; }); From 4de658e0964873f621f811a862fe9bec7263f63f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 19:45:16 -0400 Subject: [PATCH 328/372] checks --- src/socket.io/topics.js | 4 +++- src/topics/unread.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 8d495ee239..7b7b0fb7f2 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -89,7 +89,9 @@ SocketTopics.markAsRead = function(socket, tids, callback) { if (!tids.length) { return callback(); } - + tids = tids.filter(function(tid) { + return tid && utils.isNumber(tid); + }); topics.markAsRead(tids, socket.uid, function(err) { if (err) { return callback(err); diff --git a/src/topics/unread.js b/src/topics/unread.js index fd1849cfdf..0951a0f3aa 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -206,6 +206,9 @@ module.exports = function(Topics) { }; Topics.markTopicNotificationsRead = function(tid, uid) { + if (!tid) { + return; + } user.notifications.getUnreadByField(uid, 'tid', tid, function(err, nids) { if (err) { return winston.error(err.stack); From ac301eaabe1823c9c5690bd8fc6bfa1b53da155f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Sep 2014 20:14:20 -0400 Subject: [PATCH 329/372] changed default to 1 --- src/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories.js b/src/categories.js index 5303eda1b9..db0729c288 100644 --- a/src/categories.js +++ b/src/categories.js @@ -45,7 +45,7 @@ var db = require('./database'), disabled: 0, order: data.order, link: '', - numRecentReplies: 2, + numRecentReplies: 1, class: 'col-md-3 col-xs-6', imageClass: 'auto' }; From dab3667773c50d5b97368c9db1df21e53004c5f2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 22:53:57 -0400 Subject: [PATCH 330/372] re: #2108, auth re-routing --- src/meta.js | 6 ++++-- src/routes/authentication.js | 31 ++++++++++++++++++++++--------- src/routes/index.js | 7 ++++++- src/webserver.js | 2 +- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/meta.js b/src/meta.js index 09e9a6d134..be696ca98f 100644 --- a/src/meta.js +++ b/src/meta.js @@ -5,7 +5,8 @@ var async = require('async'), user = require('./user'), groups = require('./groups'), plugins = require('./plugins'), - emitter = require('./emitter'); + emitter = require('./emitter'), + auth = require('./routes/authentication'); (function (Meta) { Meta.restartRequired = false; @@ -36,7 +37,8 @@ var async = require('async'), async.parallel([ async.apply(Meta.js.minify, false), async.apply(Meta.css.minify), - async.apply(Meta.templates.compile) + async.apply(Meta.templates.compile), + async.apply(auth.reloadRoutes) ], next); } ], function(err) { diff --git a/src/routes/authentication.js b/src/routes/authentication.js index aa59419a6a..4f3e497748 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -7,14 +7,17 @@ Password = require('../password'), winston = require('winston'), async = require('async'), + express = require('express'), meta = require('../meta'), user = require('../user'), plugins = require('../plugins'), db = require('../database'), + hotswap = require('../hotswap'), utils = require('../../public/src/utils'), - login_strategies = []; + login_strategies = [], + controllers = require('../controllers'); function logout(req, res) { if (req.user && parseInt(req.user.uid, 10) > 0) { @@ -140,17 +143,21 @@ app.use(passport.session()); }; - Auth.get_login_strategies = function() { return login_strategies; }; - Auth.registerApp = function(app) { + Auth.registerApp = function(app, middleware) { Auth.app = app; + Auth.middleware = middleware; }; - Auth.createRoutes = function(app, middleware, controllers) { + Auth.reloadRoutes = function(callback) { + var router = express.Router(); + router.hotswapId = 'auth'; + plugins.ready(function() { + console.log('reloading auth routes!'); plugins.fireHook('filter:auth.init', login_strategies, function(err) { if (err) { winston.error('filter:auth.init - plugin failure'); @@ -173,12 +180,12 @@ /* End backwards compatibility block */ if (strategy.url) { - app.get(strategy.url, passport.authenticate(strategy.name, { + router.get(strategy.url, passport.authenticate(strategy.name, { scope: strategy.scope })); } - app.get(strategy.callbackURL, passport.authenticate(strategy.name, { + router.get(strategy.callbackURL, passport.authenticate(strategy.name, { successReturnToOrRedirect: nconf.get('relative_path') + '/', failureRedirect: nconf.get('relative_path') + '/login' })); @@ -198,9 +205,15 @@ } /* End backwards compatibility block */ - app.post('/logout', logout); - app.post('/register', middleware.applyCSRF, register); - app.post('/login', middleware.applyCSRF, login); + router.post('/logout', logout); + router.post('/register', Auth.middleware.applyCSRF, register); + router.post('/login', Auth.middleware.applyCSRF, login); + + hotswap.replace('auth', router); + console.log('now I\m here,', typeof callback); + if (typeof callback === 'function') { + callback(); + } }); }); }; diff --git a/src/routes/index.js b/src/routes/index.js index 85090aaf2e..0cd9a65d9e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -154,12 +154,16 @@ module.exports = function(app, middleware) { var router = express.Router(), pageRouter = express.Router(), pluginRouter = express.Router(), + authRouter = express.Router(), relativePath = nconf.get('relative_path'); pluginRouter.render = function() { app.render.apply(app, arguments); }; + + // Set-up for hotswapping (when NodeBB reloads) pluginRouter.hotswapId = 'plugins'; + authRouter.hotswapId = 'auth'; app.all(relativePath + '/api/?*', middleware.prepareAPI); app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI); @@ -170,7 +174,6 @@ module.exports = function(app, middleware) { apiRoutes(router, middleware, controllers); feedRoutes(router, middleware, controllers); pluginRoutes(router, middleware, controllers); - authRoutes.createRoutes(router, middleware, controllers); app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { maxAge: app.enabled('cache') ? 5184000000 : 0 @@ -195,6 +198,7 @@ module.exports = function(app, middleware) { app.use(relativePath, router); app.use(relativePath, pluginRouter); app.use(relativePath, pageRouter); + app.use(relativePath, authRouter); if (process.env.NODE_ENV === 'development') { require('./debug')(app, middleware, controllers); @@ -204,6 +208,7 @@ module.exports = function(app, middleware) { // Add plugin routes plugins.reloadRoutes(); + authRoutes.reloadRoutes(); }; function handleErrors(err, req, res, next) { diff --git a/src/webserver.js b/src/webserver.js index fa4b1e7fdc..e51ffe1da9 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -37,7 +37,6 @@ if(nconf.get('ssl')) { var port = nconf.get('PORT') || nconf.get('port'); logger.init(app); - auth.registerApp(app); emailer.registerApp(app); if (cluster.isWorker && process.env.handle_jobs === 'true') { @@ -64,6 +63,7 @@ if(nconf.get('ssl')) { middleware = middleware(app, data); routes(app, middleware); plugins.prepareApp(app, middleware); + auth.registerApp(app, middleware); if (err) { winston.error('Errors were encountered while attempting to initialise NodeBB.'); From f891f493390ccac9de47b91bcfcccac4db09c2a3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Sep 2014 22:58:01 -0400 Subject: [PATCH 331/372] removing console.logs --- src/routes/authentication.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 4f3e497748..e17289ba5c 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -157,7 +157,6 @@ router.hotswapId = 'auth'; plugins.ready(function() { - console.log('reloading auth routes!'); plugins.fireHook('filter:auth.init', login_strategies, function(err) { if (err) { winston.error('filter:auth.init - plugin failure'); @@ -210,7 +209,6 @@ router.post('/login', Auth.middleware.applyCSRF, login); hotswap.replace('auth', router); - console.log('now I\m here,', typeof callback); if (typeof callback === 'function') { callback(); } From a52c69d2a158e47e01423f6fe376777c6ca4f459 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Sat, 20 Sep 2014 15:04:14 -0400 Subject: [PATCH 332/372] only send noscript paginator data on cold loads --- src/controllers/categories.js | 16 +++++++++------- src/controllers/topics.js | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index b23e4d4afb..d3b4291cd8 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -202,13 +202,15 @@ categoriesController.get = function(req, res, next) { data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data.csrf = req.csrfToken(); - // Paginator for noscript - data.pages = []; - for(var x=1;x<=data.pageCount;x++) { - data.pages.push({ - page: x, - active: x === parseInt(page, 10) - }); + if (!res.locals.isAPI) { + // Paginator for noscript + data.pages = []; + for(var x=1;x<=data.pageCount;x++) { + data.pages.push({ + page: x, + active: x === parseInt(page, 10) + }); + } } res.render('category', data); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index de91eb75dd..5bae5b3e42 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -217,13 +217,15 @@ topicsController.get = function(req, res, next) { topic_url += '?' + queryString; } - // Paginator for noscript - data.pages = []; - for(var x=1; x<=data.pageCount; x++) { - data.pages.push({ - page: x, - active: x === parseInt(page, 10) - }); + if (!res.locals.isAPI) { + // Paginator for noscript + data.pages = []; + for(var x=1; x<=data.pageCount; x++) { + data.pages.push({ + page: x, + active: x === parseInt(page, 10) + }); + } } res.render('topic', data); From e2707353e996a8185fc099e336b0392627d03eaa Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 20 Sep 2014 16:05:52 -0400 Subject: [PATCH 333/372] dont return unnecessary data --- src/database/mongo/sets.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 78e501cbd9..ae9275db50 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -98,7 +98,7 @@ module.exports = function(db, module) { module.isSetMember = function(key, value, callback) { value = helpers.valueToString(value); - db.collection('objects').findOne({_key:key, members: value}, function(err, item) { + db.collection('objects').findOne({_key:key, members: value}, {_id: 0, members: 0},function(err, item) { callback(err, item !== null && item !== undefined); }); }; @@ -108,7 +108,7 @@ module.exports = function(db, module) { values[i] = helpers.valueToString(values[i]); } - db.collection('objects').findOne({_key:key, members: {$in : values}}, function(err, items) { + db.collection('objects').findOne({_key: key, members: {$in : values}}, {_id: 0, _key: 0}, function(err, items) { if (err) { return callback(err); } @@ -125,7 +125,7 @@ module.exports = function(db, module) { value = helpers.valueToString(value); - db.collection('objects').find({_key: {$in : sets}, members: value}).toArray(function(err, result) { + db.collection('objects').find({_key: {$in : sets}, members: value}, {_id:0, members: 0}).toArray(function(err, result) { if(err) { return callback(err); } @@ -143,13 +143,13 @@ module.exports = function(db, module) { }; module.getSetMembers = function(key, callback) { - db.collection('objects').findOne({_key:key}, {members:1}, function(err, data) { + db.collection('objects').findOne({_key: key}, {members: 1}, {_id: 0, _key: 0}, function(err, data) { callback(err, data ? data.members : []); }); }; module.getSetsMembers = function(keys, callback) { - db.collection('objects').find({_key: {$in: keys}}, {_key: 1, members: 1}).toArray(function(err, data) { + db.collection('objects').find({_key: {$in: keys}}, {_id: 0, _key: 1, members: 1}).toArray(function(err, data) { if (err) { return callback(err); } @@ -167,7 +167,7 @@ module.exports = function(db, module) { }; module.setCount = function(key, callback) { - db.collection('objects').findOne({_key:key}, function(err, data) { + db.collection('objects').findOne({_key: key}, {_id: 0}, function(err, data) { return callback(err, data ? data.members.length : 0); }); }; From a19c306f491d2df7e8f734f56896c70908fb072f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 20 Sep 2014 18:07:46 -0400 Subject: [PATCH 334/372] guest fixes --- src/routes/index.js | 2 +- src/topics.js | 2 +- src/user.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 0cd9a65d9e..da96a2757d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -199,7 +199,7 @@ module.exports = function(app, middleware) { app.use(relativePath, pluginRouter); app.use(relativePath, pageRouter); app.use(relativePath, authRouter); - + if (process.env.NODE_ENV === 'development') { require('./debug')(app, middleware, controllers); } diff --git a/src/topics.js b/src/topics.js index 8aa69d268b..24535805e3 100644 --- a/src/topics.js +++ b/src/topics.js @@ -176,7 +176,7 @@ var async = require('async'), return array.map(function(topic) { return topic && topic[field]; }).filter(function(value, index, array) { - return value && array.indexOf(value) === index; + return utils.isNumber(value) && array.indexOf(value) === index; }); } diff --git a/src/user.js b/src/user.js index ad2d5852f9..6cd8a125ea 100644 --- a/src/user.js +++ b/src/user.js @@ -109,6 +109,7 @@ var async = require('async'), } if (!parseInt(user.uid, 10)) { + user.uid = 0; user.username = '[[global:guest]]'; user.userslug = ''; } From b7fd4c22d4886409ebba2b3cb72a30721a9b3130 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 20 Sep 2014 18:51:32 -0400 Subject: [PATCH 335/372] removed checkPostIndex middleware 1 less call to get topic post count --- src/controllers/topics.js | 14 +++++++++++++- src/middleware/middleware.js | 19 ------------------- src/routes/index.js | 4 ++-- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 5bae5b3e42..4d94db705d 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -36,6 +36,19 @@ topicsController.get = function(req, res, next) { }, next); }, function (results, next) { + + var postCount = parseInt(results.postCount, 10) + 1; + if (utils.isNumber(req.params.post_index)) { + var url = ''; + if (req.params.post_index > postCount) { + url = '/topic/' + req.params.topic_id + '/' + req.params.slug + '/' + postCount; + return res.locals.isAPI ? res.json(302, url) : res.redirect(url); + } else if (req.params.post_index < 1) { + url = '/topic/' + req.params.topic_id + '/' + req.params.slug; + return res.locals.isAPI ? res.json(302, url) : res.redirect(url); + } + } + userPrivileges = results.privileges; if (userPrivileges.disabled) { @@ -47,7 +60,6 @@ topicsController.get = function(req, res, next) { } var settings = results.settings; - var postCount = parseInt(results.postCount, 10) + 1; var set = 'tid:' + tid + ':posts', reverse = false; diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 7ebd9d83d1..555d5f8573 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -125,25 +125,6 @@ middleware.addSlug = function(req, res, next) { next(); }; -middleware.checkPostIndex = function(req, res, next) { - topics.getPostCount(req.params.topic_id, function(err, postCount) { - if (err) { - return next(err); - } - var postIndex = parseInt(req.params.post_index, 10); - postCount = parseInt(postCount, 10) + 1; - var url = ''; - if (postIndex > postCount) { - url = '/topic/' + req.params.topic_id + '/' + req.params.slug + '/' + postCount; - return res.locals.isAPI ? res.json(302, url) : res.redirect(url); - } else if (postIndex < 1) { - url = '/topic/' + req.params.topic_id + '/' + req.params.slug; - return res.locals.isAPI ? res.json(302, url) : res.redirect(url); - } - next(); - }); -}; - middleware.checkTopicIndex = function(req, res, next) { db.sortedSetCard('categories:' + req.params.category_id + ':tid', function(err, topicCount) { if (err) { diff --git a/src/routes/index.js b/src/routes/index.js index da96a2757d..642b8c1b9d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -54,8 +54,8 @@ function staticRoutes(app, middleware, controllers) { function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); - app.get('/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.buildHeader, middleware.checkPostIndex, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.checkPostIndex, controllers.topics.get); + app.get('/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.buildHeader, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, controllers.topics.get); app.get('/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.buildHeader, middleware.addSlug, controllers.topics.get); app.get('/api/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.addSlug, controllers.topics.get); From 5323934cc234f494e9440864473976373ef5f8ef Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 20 Sep 2014 19:09:45 -0400 Subject: [PATCH 336/372] calculate page count in controller --- src/controllers/topics.js | 2 ++ src/topics.js | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 4d94db705d..518e766aa0 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -109,6 +109,8 @@ topicsController.get = function(req, res, next) { return categoriesController.notAllowed(req, res); } + topicData.pageCount = Math.ceil(parseInt(postCount, 10) / settings.postsPerPage); + topicData.currentPage = page; if(page > 1) { topicData.posts.splice(0, 1); diff --git a/src/topics.js b/src/topics.js index 24535805e3..8016da727d 100644 --- a/src/topics.js +++ b/src/topics.js @@ -26,8 +26,13 @@ var async = require('async'), require('./topics/tags')(Topics); Topics.getTopicData = function(tid, callback) { - Topics.getTopicsData([tid], function(err, topics) { - callback(err, Array.isArray(topics) && topics.length ? topics[0] : null); + db.getObject('topic:' + tid, function(err, topic) { + if (err || !topic) { + return callback(err); + } + topic.title = validator.escape(topic.title); + topic.relativeTime = utils.toISOString(topic.timestamp); + callback(null, topic); }); }; @@ -274,9 +279,6 @@ var async = require('async'), category: function(next) { Topics.getCategoryData(tid, next); }, - pageCount: function(next) { - Topics.getPageCount(tid, uid, next); - }, threadTools: function(next) { plugins.fireHook('filter:topic.thread_tools', [], next); }, @@ -470,7 +472,7 @@ var async = require('async'), Topics.isOwner = function(tid, uid, callback) { uid = parseInt(uid, 10); - if (uid === 0) { + if (!uid) { return callback(null, false); } Topics.getTopicField(tid, 'uid', function(err, author) { From 7d93926ef7ea83415f8ac0b938022d1c8d736af9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 20 Sep 2014 23:04:49 -0400 Subject: [PATCH 337/372] removed async.map --- src/database/mongo/sets.js | 3 +- src/groups.js | 61 +++++++++++++++++++++++++++++++------- src/privileges/helpers.js | 5 +--- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index ae9275db50..795a2b45bb 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -57,7 +57,7 @@ module.exports = function(db, module) { } bulk.execute(function(err) { - return callback(err); + callback(err); }); }; @@ -153,6 +153,7 @@ module.exports = function(db, module) { if (err) { return callback(err); } + var sets = {}; data.forEach(function(set) { sets[set._key] = set.members || []; diff --git a/src/groups.js b/src/groups.js index 49dd25e4a2..2a04cfd69d 100644 --- a/src/groups.js +++ b/src/groups.js @@ -1,15 +1,18 @@ 'use strict'; +var async = require('async'), + winston = require('winston'), + _ = require('underscore'), + user = require('./user'), + meta = require('./meta'), + db = require('./database'), + posts = require('./posts'), + utils = require('../public/src/utils'); + + (function(Groups) { - var async = require('async'), - winston = require('winston'), - user = require('./user'), - meta = require('./meta'), - db = require('./database'), - posts = require('./posts'), - utils = require('../public/src/utils'), - ephemeralGroups = ['guests'], + var ephemeralGroups = ['guests'], internals = { filterGroups: function(groups, options) { @@ -186,7 +189,45 @@ return callback(err); } - callback(null, isMembers.indexOf(true) !== -1) + callback(null, isMembers.indexOf(true) !== -1); + }); + }); + }; + + Groups.isMemberOfGroupsList = function(uid, groupListKeys, callback) { + var sets = groupListKeys.map(function(groupName) { + return 'group:' + groupName + ':members'; + }); + + db.getSetsMembers(sets, function(err, members) { + if (err) { + return callback(err); + } + + var uniqueGroups = _.unique(_.flatten(members)); + uniqueGroups = internals.removeEphemeralGroups(uniqueGroups); + + Groups.isMemberOfGroups(uid, uniqueGroups, function(err, isMembers) { + if (err) { + return callback(err); + } + + var map = {}; + + uniqueGroups.forEach(function(groupName, index) { + map[groupName] = isMembers[index]; + }); + + var result = members.map(function(groupNames) { + for (var i=0; i Date: Sat, 20 Sep 2014 23:57:33 -0400 Subject: [PATCH 338/372] derp --- src/favourites.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/favourites.js b/src/favourites.js index ae55417f5e..a678b613b4 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -111,11 +111,14 @@ var async = require('async'), } toggleVote('upvote', pid, uid, function(err, votes) { - if (err) return callback(err); + if (err) { + return callback(err); + } plugins.fireHook('action:post.upvote', { pid: pid, uid: uid }); + callback(null, votes); }); }; @@ -139,7 +142,9 @@ var async = require('async'), } toggleVote('downvote', pid, uid, function(err, votes) { - if (err) return callback(err); + if (err) { + return callback(err); + } plugins.fireHook('action:post.downvote', { pid: pid, uid: uid @@ -174,14 +179,13 @@ var async = require('async'), }; Favourites.hasVoted = function(pid, uid, callback) { - async.parallel({ - upvoted: function(next) { - db.isSetMember('pid:' + pid + ':upvote', uid, next); - }, - downvoted: function(next) { - db.isSetMember('pid:' + pid + ':downvote', uid, next); + db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid, function(err, hasVoted) { + if (err) { + return callback(err); } - }, callback); + + callback (null, {upvoted: hasVoted[0], downvoted: hasVoted[1]}); + }); }; Favourites.getVoteStatusByPostIDs = function(pids, uid, callback) { From d1273516b54f02eb55fc8ff4977a7bbe055b0114 Mon Sep 17 00:00:00 2001 From: Moritz Schmidt Date: Sun, 21 Sep 2014 10:12:20 +0200 Subject: [PATCH 339/372] default for missing post_index parameter Avoid getting NaN if post_index param is not set (NaN is passed through to redis, redis complains, and the user ends up at /404) --- src/controllers/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index bfc2b4e396..f710e1119d 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -72,7 +72,7 @@ topicsController.get = function(req, res, next) { postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage + 1), 0); } } else if (!req.query.page) { - var index = Math.max(parseInt(req.params.post_index, 10), 0); + var index = Math.max(parseInt(req.params.post_index, 10), 0) || 0; page = Math.ceil((index + 1) / settings.postsPerPage); } From 0914004e120bfbf25a5a70535cd3ef3c2161f748 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 21 Sep 2014 11:29:09 -0400 Subject: [PATCH 340/372] crash fix --- src/controllers/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 518e766aa0..283a1bcc7c 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -192,7 +192,7 @@ topicsController.get = function(req, res, next) { }, { property: 'article:section', - content: topicData.category.name + content: topicData.category ? topicData.category.name : '' } ]; From 5f2a66295c2db897f3f9ae12c2e3057952a8fc52 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 21 Sep 2014 13:30:20 -0400 Subject: [PATCH 341/372] check keys --- src/database/mongo/hash.js | 73 ++++++++++++++++++++++++++++++------ src/database/mongo/list.js | 27 +++++++++++-- src/database/mongo/main.js | 25 ++++++++++-- src/database/mongo/sets.js | 24 ++++++++++-- src/database/mongo/sorted.js | 58 ++++++++++++++++++++++++++-- src/posts.js | 3 ++ 6 files changed, 184 insertions(+), 26 deletions(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 791cbc12cc..d887143d89 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -7,6 +7,9 @@ module.exports = function(db, module) { module.setObject = function(key, data, callback) { callback = callback || helpers.noop; + if (!key) { + return callback(); + } data._key = key; db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err) { callback(err); @@ -21,10 +24,16 @@ module.exports = function(db, module) { }; module.getObject = function(key, callback) { - db.collection('objects').findOne({_key:key}, {_id:0, _key:0}, callback); + if (!key) { + return callback(); + } + db.collection('objects').findOne({_key: key}, {_id:0, _key:0}, callback); }; module.getObjects = function(keys, callback) { + if (!Array.isArray(keys) || !keys.length) { + return callback(); + } db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function(err, data) { if(err) { return callback(err); @@ -42,19 +51,52 @@ module.exports = function(db, module) { }; module.getObjectField = function(key, field, callback) { + if (!key) { + return callback(); + } field = helpers.fieldToString(field); - module.getObjectFields(key, [field], function(err, data) { - callback(err, data ? data[field] : null); + var _fields = { + _id: 0 + }; + _fields[field] = 1; + db.collection('objects').findOne({_key: key}, _fields, function(err, item) { + if (err || !item) { + return callback(err, null); + } + + callback(null, item[field] || null); }); }; module.getObjectFields = function(key, fields, callback) { - module.getObjectsFields([key], fields, function(err, items) { - callback(err, items ? items[0] : null); + if (!key) { + return callback(); + } + var _fields = { + _id: 0 + }; + + for(var i=0; i Date: Sun, 21 Sep 2014 14:29:27 -0400 Subject: [PATCH 342/372] refactored plugin and auth init a bit, and fixed issue where successive reloads caused old login strategies to be inadvertently preserved --- app.js | 1 - src/middleware/index.js | 2 +- src/plugins.js | 44 ++++++++++++++++-------------------- src/routes/authentication.js | 13 ++++++----- src/routes/index.js | 2 +- src/webserver.js | 2 -- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/app.js b/app.js index 52e3e000db..b47935376d 100644 --- a/app.js +++ b/app.js @@ -137,7 +137,6 @@ function start() { upgrade.check(function(schema_ok) { if (schema_ok || nconf.get('check-schema') === false) { sockets.init(webserver.server); - plugins.init(); nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path')); diff --git a/src/middleware/index.js b/src/middleware/index.js index 986d5ffe91..f9ee6e4c01 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -129,7 +129,7 @@ module.exports = function(app, data) { app.use(middleware.processRender); - auth.initialize(app); + auth.initialize(app, middleware); routeCurrentTheme(app, data.currentThemeId, data.themesData); routeThemeScreenshots(app, data.themesData); diff --git a/src/plugins.js b/src/plugins.js index 427816c7bc..41f38dfd3c 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -31,11 +31,15 @@ var fs = require('fs'), Plugins.initialized = false; - Plugins.init = function() { + Plugins.init = function(nbbApp, nbbMiddleware) { if (Plugins.initialized) { return; } + app = nbbApp; + middleware = nbbMiddleware; + hotswap.prepare(nbbApp); + if (global.env === 'development') { winston.info('[plugins] Initializing plugins system'); } @@ -62,12 +66,6 @@ var fs = require('fs'), }); }; - Plugins.prepareApp = function(nbbApp, nbbMiddleware) { - app = nbbApp; - middleware = nbbMiddleware; - hotswap.prepare(nbbApp); - }; - Plugins.ready = function(callback) { if (!Plugins.initialized) { emitter.once('plugins:loaded', callback); @@ -124,24 +122,20 @@ var fs = require('fs'), }; Plugins.reloadRoutes = function(callback) { - if (!app || !middleware || !controllers) { - return; - } else { - var router = express.Router(); - router.hotswapId = 'plugins'; - router.render = function() { - app.render.apply(app, arguments); - }; - - // Deprecated as of v0.5.0, remove this hook call for NodeBB v0.6.0-1 - Plugins.fireHook('action:app.load', router, middleware, controllers); - - Plugins.fireHook('static:app.load', router, middleware, controllers, function() { - hotswap.replace('plugins', router); - winston.info('[plugins] All plugins reloaded and rerouted'); - callback(); - }); - } + var router = express.Router(); + router.hotswapId = 'plugins'; + router.render = function() { + app.render.apply(app, arguments); + }; + + // Deprecated as of v0.5.0, remove this hook call for NodeBB v0.6.0-1 + Plugins.fireHook('action:app.load', router, middleware, controllers); + + Plugins.fireHook('static:app.load', router, middleware, controllers, function() { + hotswap.replace('plugins', router); + winston.info('[plugins] All plugins reloaded and rerouted'); + callback(); + }); }; Plugins.loadPlugin = function(pluginPath, callback) { diff --git a/src/routes/authentication.js b/src/routes/authentication.js index e17289ba5c..cfe9e71462 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -138,25 +138,26 @@ }); } - Auth.initialize = function(app) { + Auth.initialize = function(app, middleware) { app.use(passport.initialize()); app.use(passport.session()); + + Auth.app = app; + Auth.middleware = middleware; }; Auth.get_login_strategies = function() { return login_strategies; }; - Auth.registerApp = function(app, middleware) { - Auth.app = app; - Auth.middleware = middleware; - }; - Auth.reloadRoutes = function(callback) { var router = express.Router(); router.hotswapId = 'auth'; plugins.ready(function() { + // Reset the registered login strategies + login_strategies.length = 0; + plugins.fireHook('filter:auth.init', login_strategies, function(err) { if (err) { winston.error('filter:auth.init - plugin failure'); diff --git a/src/routes/index.js b/src/routes/index.js index 642b8c1b9d..253f00404a 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -207,7 +207,7 @@ module.exports = function(app, middleware) { app.use(handleErrors); // Add plugin routes - plugins.reloadRoutes(); + plugins.init(app, middleware); authRoutes.reloadRoutes(); }; diff --git a/src/webserver.js b/src/webserver.js index e51ffe1da9..d9d47d7f3c 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -62,8 +62,6 @@ if(nconf.get('ssl')) { }, function(err, data) { middleware = middleware(app, data); routes(app, middleware); - plugins.prepareApp(app, middleware); - auth.registerApp(app, middleware); if (err) { winston.error('Errors were encountered while attempting to initialise NodeBB.'); From ba90b5d113e21624a8a29a3a217b53476a968ab7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 21 Sep 2014 18:56:36 -0400 Subject: [PATCH 343/372] widget render change --- src/categories.js | 3 ++ src/controllers/api.js | 24 +++++-------- src/widgets.js | 81 +++++++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/categories.js b/src/categories.js index db0729c288..2a80c4a67b 100644 --- a/src/categories.js +++ b/src/categories.js @@ -238,6 +238,9 @@ var db = require('./database'), Categories.getModerators = function(cid, callback) { Groups.get('cid:' + cid + ':privileges:mods', {}, function(err, groupObj) { + if (err && err === 'group-not-found') { + return callback(null, []); + } if (err) { return callback(err); } diff --git a/src/controllers/api.js b/src/controllers/api.js index b15422c157..a43678d22a 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -98,21 +98,15 @@ apiController.renderWidgets = function(req, res, next) { return res.json(200, {}); } - async.each(areas.locations, function(location, next) { - widgets.render(uid, { - template: areas.template, - url: areas.url, - location: location - }, function(err, widgets) { - renderedWidgets.push({ - location: location, - widgets: widgets - }); - - next(); - }); - }, function(err) { - res.json(200, renderedWidgets); + widgets.render(uid, { + template: areas.template, + url: areas.url, + locations: areas.locations + }, function(err, widgets) { + if (err) { + return next(err); + } + res.json(200, widgets); }); }; diff --git a/src/widgets.js b/src/widgets.js index efcc768646..1755ab9cb5 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -9,52 +9,75 @@ var async = require('async'), (function(Widgets) { + Widgets.render = function(uid, area, callback) { - if (!area.location || !area.template) { + if (!area.locations || !area.template) { callback({ error: 'Missing location and template data' }); } - var rendered = []; + Widgets.getAreas(['global', area.template], area.locations, function(err, data) { - async.parallel({ - global: function(next) { - Widgets.getArea('global', area.location, next); - }, - local: function(next) { - Widgets.getArea(area.template, area.location, next); - } - }, function(err, data) { - var widgets = data.global.concat(data.local); + var widgetsByLocation = {}; - async.eachSeries(widgets, function(widget, next) { + async.map(area.locations, function(location, done) { + widgetsByLocation[location] = data.global[location].concat(data[area.template][location]); - if (!widget || !widget.data || (!!widget.data['registered-only'] && uid === 0)) { - return next(); + if (!widgetsByLocation[location].length) { + return done(null, {location: location, widgets: []}); } - plugins.fireHook('filter:widget.render:' + widget.widget, { - uid: uid, - area: area, - data: widget.data - }, function(err, html){ - if (widget.data.container && widget.data.container.match('{body}')) { - html = templates.parse(widget.data.container, { - title: widget.data.title, - body: html - }); + async.map(widgetsByLocation[location], function(widget, next) { + + if (!widget || !widget.data || (!!widget.data['registered-only'] && uid === 0)) { + return next(); } - rendered.push({ - html: html + plugins.fireHook('filter:widget.render:' + widget.widget, { + uid: uid, + area: area, + data: widget.data + }, function(err, html) { + if (widget.data.container && widget.data.container.match('{body}')) { + html = templates.parse(widget.data.container, { + title: widget.data.title, + body: html + }); + } + + next(err, {html: html}); }); + }, function(err, widgets) { + done(err, {location: location, widgets: widgets.filter(Boolean)}); + }); + }, callback); + }); + }; - next(err); + Widgets.getAreas = function(templates, locations, callback) { + var keys = templates.map(function(tpl) { + return 'widgets:' + tpl; + }); + db.getObjectsFields(keys, locations, function(err, data) { + if (err) { + return callback(err); + } + + var returnData = {}; + + templates.forEach(function(template, index) { + returnData[template] = returnData[template] || {}; + locations.forEach(function(location) { + if (data && data[index] && data[index][location]) { + returnData[template][location] = JSON.parse(data[index][location]); + } else { + returnData[template][location] = []; + } }); - }, function(err) { - callback(err, rendered); }); + + callback(null, returnData); }); }; From 8c600ae15dee4ac56859a9389cdfaf145b1ffea1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 21 Sep 2014 19:24:51 -0400 Subject: [PATCH 344/372] stop tag autocomplete ajaxify --- public/src/modules/composer/tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/composer/tags.js b/public/src/modules/composer/tags.js index 3930376680..93b33fbb38 100644 --- a/public/src/modules/composer/tags.js +++ b/public/src/modules/composer/tags.js @@ -33,8 +33,8 @@ define('composer/tags', function() { } if (tags) { response(tags); - $('.ui-autocomplete a').attr('href', '#'); } + $('.ui-autocomplete a').attr('data-ajaxify', 'false'); }); }, select: function(event, ui) { From c640e0196972a22814fbd374699e826e9f3ed358 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 21 Sep 2014 22:07:08 -0400 Subject: [PATCH 345/372] show stack --- src/database/mongo.js | 4 ++-- src/meta/configs.js | 2 +- src/meta/templates.js | 2 +- src/user/email.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 4f38b9fe3e..dc65e3ce0d 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -75,8 +75,8 @@ if(nconf.get('mongo:password') && nconf.get('mongo:username')) { db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) { - if(err) { - winston.error(err.message); + if (err) { + winston.error(err.stack); process.exit(); } createIndices(); diff --git a/src/meta/configs.js b/src/meta/configs.js index ea8e17dc3a..1a542e8e9c 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -14,7 +14,7 @@ module.exports = function(Meta) { Meta.configs.list(function (err, config) { if (err) { - winston.error(err); + winston.error(err.stack); return callback(err); } diff --git a/src/meta/templates.js b/src/meta/templates.js index ccc95c7ab7..4851d19016 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -83,7 +83,7 @@ Templates.compile = function(callback) { fs.writeFile(path.join(viewsPath, relativePath), file, next); }, function(err) { if (err) { - winston.error(err); + winston.error('[themes] ' + err.stack); } else { winston.info('[themes] Successfully compiled templates.'); emitter.emit('templates:compiled'); diff --git a/src/user/email.js b/src/user/email.js index 892d740e0f..fe79771fbb 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -46,7 +46,7 @@ var async = require('async'), user.getUserField(uid, 'username', function(err, username) { if (err) { - return winston.error(err.message); + return winston.error(err.stack); } var title = meta.config.title || meta.config.browserTitle || 'NodeBB'; From 21f65cbb6ae96d070a420678c9800f3530836cad Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 12:39:25 -0400 Subject: [PATCH 346/372] crash fix --- src/topics/unread.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index 0951a0f3aa..846e1b34d9 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -125,8 +125,10 @@ module.exports = function(Topics) { return callback(err); } - var topicCids = topics.map(function(topic) { - return topic && topic.cid.toString(); + var topicCids = topics.filter(function(topic) { + return topic && topic.cid; + }).map(function(topic) { + return topic.cid.toString(); }); topicCids = topicCids.filter(function(cid) { @@ -139,7 +141,7 @@ module.exports = function(Topics) { } topics = topics.filter(function(topic) { - return topic && readableCids.indexOf(topic.cid.toString()) !== -1; + return readableCids.indexOf(topic.cid.toString()) !== -1; }).map(function(topic) { return topic.tid; }); From 8b64e65095002f323c781dc8c432f2620879fd94 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 15:43:41 -0400 Subject: [PATCH 347/372] cleaned up admin routes --- src/routes/admin.js | 110 +++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 62 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index 04c6169489..b64208a3ba 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1,83 +1,69 @@ "use strict"; +var express = require('express'); -function mainRoutes(app, middleware, controllers) { - app.get('/admin', middleware.admin.buildHeader, controllers.admin.home); - app.get('/admin/index', middleware.admin.buildHeader, controllers.admin.home); - app.get('/api/admin/index', controllers.admin.home); - app.get('/admin/plugins', middleware.admin.buildHeader, controllers.admin.plugins.get); - app.get('/api/admin/plugins', controllers.admin.plugins.get); - - app.get('/admin/settings', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.settings.get); - app.get('/api/admin/settings', middleware.applyCSRF, controllers.admin.settings.get); - - app.get('/admin/themes', middleware.admin.buildHeader, controllers.admin.themes.get); - app.get('/api/admin/themes', controllers.admin.themes.get); - - app.get('/admin/languages', middleware.admin.buildHeader, controllers.admin.languages.get); - app.get('/api/admin/languages', controllers.admin.languages.get); - - app.get('/admin/groups', middleware.admin.buildHeader, controllers.admin.groups.get); - app.get('/api/admin/groups', controllers.admin.groups.get); +function apiRoutes(app, middleware, controllers) { + // todo, needs to be in api namespace + app.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV); - app.get('/admin/sounds', middleware.admin.buildHeader, controllers.admin.sounds.get); - app.get('/api/admin/sounds', controllers.admin.sounds.get); + app.post('/category/uploadpicture', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); + app.post('/uploadfavicon', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadFavicon); + app.post('/uploadlogo', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadLogo); + app.post('/uploadgravatardefault', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); } -function userRoutes(app, middleware, controllers) { - app.get('/admin/users/search', middleware.admin.buildHeader, controllers.admin.users.search); - app.get('/api/admin/users/search', controllers.admin.users.search); - - app.get('/admin/users/latest', middleware.admin.buildHeader, controllers.admin.users.sortByJoinDate); - app.get('/api/admin/users/latest', controllers.admin.users.sortByJoinDate); +function adminRouter(middleware, controllers) { + var router = express.Router(); - app.get('/admin/users/sort-posts', middleware.admin.buildHeader, controllers.admin.users.sortByPosts); - app.get('/api/admin/users/sort-posts', controllers.admin.users.sortByPosts); + router.use(middleware.admin.buildHeader); - app.get('/admin/users/sort-reputation', middleware.admin.buildHeader, controllers.admin.users.sortByReputation); - app.get('/api/admin/users/sort-reputation', controllers.admin.users.sortByReputation); + router.get('/', controllers.admin.home); - app.get('/admin/users', middleware.admin.buildHeader, controllers.admin.users.search); - app.get('/api/admin/users', controllers.admin.users.search); -} - -function forumRoutes(app, middleware, controllers) { - app.get('/admin/categories/active', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.categories.active); - app.get('/api/admin/categories/active', middleware.applyCSRF, controllers.admin.categories.active); + addRoutes(router, middleware, controllers); - app.get('/admin/categories/disabled', middleware.applyCSRF, middleware.admin.buildHeader, controllers.admin.categories.disabled); - app.get('/api/admin/categories/disabled', middleware.applyCSRF, controllers.admin.categories.disabled); + apiRoutes(router, middleware, controllers); - app.get('/admin/tags', middleware.admin.buildHeader, controllers.admin.tags.get); - app.get('/api/admin/tags', controllers.admin.tags.get); + return router; } -function apiRoutes(app, middleware, controllers) { - // todo, needs to be in api namespace - app.get('/admin/users/csv', middleware.authenticate, controllers.admin.users.getCSV); +function apiRouter(middleware, controllers) { + var router = express.Router(); - app.post('/admin/category/uploadpicture', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture); - app.post('/admin/uploadfavicon', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadFavicon); - app.post('/admin/uploadlogo', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadLogo); - app.post('/admin/uploadgravatardefault', middleware.applyCSRF, middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault); -} - -function miscRoutes(app, middleware, controllers) { - app.get('/admin/database', middleware.admin.buildHeader, controllers.admin.database.get); - app.get('/api/admin/database', controllers.admin.database.get); + addRoutes(router, middleware, controllers); - app.get('/admin/events', middleware.admin.buildHeader, controllers.admin.events.get); - app.get('/api/admin/events', controllers.admin.events.get); + return router; +} - app.get('/admin/logger', middleware.admin.buildHeader, controllers.admin.logger.get); - app.get('/api/admin/logger', controllers.admin.logger.get); +function addRoutes(router, middleware, controllers) { + //main + router.get('/index', controllers.admin.home); + router.get('/plugins', controllers.admin.plugins.get); + router.get('/settings', middleware.applyCSRF, controllers.admin.settings.get); + router.get('/themes', controllers.admin.themes.get); + router.get('/languages', controllers.admin.languages.get); + router.get('/groups', controllers.admin.groups.get); + router.get('/sounds', controllers.admin.sounds.get); + + //user + router.get('/users', controllers.admin.users.search); + router.get('/users/search', controllers.admin.users.search); + router.get('/users/latest', controllers.admin.users.sortByJoinDate); + router.get('/users/sort-posts', controllers.admin.users.sortByPosts); + router.get('/users/sort-reputation', controllers.admin.users.sortByReputation); + + //forum + router.get('/categories/active', middleware.applyCSRF, controllers.admin.categories.active); + router.get('/categories/disabled', middleware.applyCSRF, controllers.admin.categories.disabled); + router.get('/tags', controllers.admin.tags.get); + + //misc + router.get('/database', controllers.admin.database.get); + router.get('/events', controllers.admin.events.get); + router.get('/logger', controllers.admin.logger.get); } module.exports = function(app, middleware, controllers) { - mainRoutes(app, middleware, controllers); - userRoutes(app, middleware, controllers); - forumRoutes(app, middleware, controllers); - apiRoutes(app, middleware, controllers); - miscRoutes(app, middleware, controllers); + app.use('/admin/', adminRouter(middleware, controllers)); + app.use('/api/admin/', apiRouter(middleware, controllers)); }; From 2b90add5607c614039dab60d43805d56693de37e Mon Sep 17 00:00:00 2001 From: psychobunny Date: Mon, 22 Sep 2014 16:08:40 -0400 Subject: [PATCH 348/372] fix for topics without category data (?) @barisusakli lemme know if this is legit, if not, see line 195 as well --- src/controllers/topics.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 283a1bcc7c..fa463ab703 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -201,17 +201,20 @@ topicsController.get = function(req, res, next) { rel: 'alternate', type: 'application/rss+xml', href: nconf.get('url') + '/topic/' + tid + '.rss' - }, - { - rel: 'up', - href: nconf.get('url') + '/category/' + topicData.category.slug - }, + } { rel: 'canonical', href: nconf.get('url') + '/topic/' + topicData.slug } ]; + if (topicData.category) { + res.locals.linkTags.push({ + rel: 'up', + href: nconf.get('url') + '/category/' + topicData.category.slug + }); + } + next(null, topicData); } ], function (err, data) { From 0db93504ceb98a8ac50ec66f5bb7537da463b78c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Mon, 22 Sep 2014 16:09:27 -0400 Subject: [PATCH 349/372] forgot comma --- src/controllers/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index fa463ab703..f9ba17f743 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -201,7 +201,7 @@ topicsController.get = function(req, res, next) { rel: 'alternate', type: 'application/rss+xml', href: nconf.get('url') + '/topic/' + tid + '.rss' - } + }, { rel: 'canonical', href: nconf.get('url') + '/topic/' + topicData.slug From bfdc32a9c3f80cf4535d66215d7c684dfd374ba6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Sep 2014 19:03:59 -0400 Subject: [PATCH 350/372] typo --- src/hotswap.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotswap.js b/src/hotswap.js index 717c099052..b56beae22e 100644 --- a/src/hotswap.js +++ b/src/hotswap.js @@ -19,14 +19,14 @@ HotSwap.find = function(id) { } }; -HotSwap.replace = function(id, middleware) { +HotSwap.replace = function(id, router) { var idx = HotSwap.find(id); if (idx) { - delete stack[idx].handle; // Destroy the old middleware - stack[idx].handle = middleware; // Replace with the new one - winston.info('[hotswap] Middleware with id `' + id + '` replaced successfully'); + delete stack[idx].handle; // Destroy the old router + stack[idx].handle = router; // Replace with the new one + winston.info('[hotswap] Router with id `' + id + '` replaced successfully'); } else { - winston.warn('[hotswap] Could not find middleware in stack with hotswapId `' + id + '`'); + winston.warn('[hotswap] Could not find router in stack with hotswapId `' + id + '`'); } }; From 9b9e79ef271e18093ee1c97721179e9b4e4f4e5d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Sep 2014 20:31:57 -0400 Subject: [PATCH 351/372] re: #2099 --- src/middleware/middleware.js | 24 ++++++++++++++++++++++++ src/routes/index.js | 2 ++ 2 files changed, 26 insertions(+) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 555d5f8573..508abcab39 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -454,6 +454,30 @@ middleware.addExpiresHeaders = function(req, res, next) { next(); }; +middleware.maintenanceMode = function(req, res, next) { + var render = function() { + res.render('maintenance', { + site_title: meta.config.site_title || 'NodeBB' + }); + } + + if (meta.config.maintenanceMode === '1') { + if (!req.user) { + return render(); + } else { + user.isAdministrator(req.user.uid, function(err, isAdmin) { + if (!isAdmin) { + return render(); + } else { + return next(); + } + }); + } + } else { + return next(); + } +}; + module.exports = function(webserver) { app = webserver; middleware.admin = require('./admin')(webserver); diff --git a/src/routes/index.js b/src/routes/index.js index 253f00404a..98902835c2 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -165,6 +165,8 @@ module.exports = function(app, middleware) { pluginRouter.hotswapId = 'plugins'; authRouter.hotswapId = 'auth'; + app.use(middleware.maintenanceMode); + app.all(relativePath + '/api/?*', middleware.prepareAPI); app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI); app.all(relativePath + '/admin/?*', middleware.ensureLoggedIn, middleware.admin.isAdmin); From 39392672a27602a42d2e88b530faaf88466f99ad Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 21:38:56 -0400 Subject: [PATCH 352/372] moved static removed pageRouter middleware into routes, helper method to setup page routes --- src/routes/index.js | 176 ++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 114 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index 98902835c2..ed76d01fe3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -18,141 +18,91 @@ var nconf = require('nconf'), function mainRoutes(app, middleware, controllers) { - app.get('/', middleware.buildHeader, controllers.home); - app.get('/api', controllers.home); + setupPageRoute(app, '/', middleware, [], controllers.home); - app.get('/login', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login); - app.get('/api/login', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, controllers.login); + var loginRegisterMiddleware = [middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn]; - app.get('/register', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.register); - app.get('/api/register', middleware.applyCSRF, middleware.redirectToAccountIfLoggedIn, controllers.register); - - app.get('/confirm/:code', middleware.buildHeader, controllers.confirmEmail); - app.get('/api/confirm/:code', controllers.confirmEmail); - - app.get('/outgoing', middleware.buildHeader, controllers.outgoing); - app.get('/api/outgoing', controllers.outgoing); - - app.get('/search/:term?', middleware.buildHeader, middleware.guestSearchingAllowed, controllers.search); - app.get('/api/search/:term?', middleware.guestSearchingAllowed, controllers.search); - - app.get('/reset/:code?', middleware.buildHeader, controllers.reset); - app.get('/api/reset/:code?', controllers.reset); + setupPageRoute(app, '/login', middleware, loginRegisterMiddleware, controllers.login); + setupPageRoute(app, '/register', middleware, loginRegisterMiddleware, controllers.register); + setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); + setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); + setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search); + setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); } function staticRoutes(app, middleware, controllers) { - app.get('/404', middleware.buildHeader, controllers.static['404']); - app.get('/api/404', controllers.static['404']); - - app.get('/403', middleware.buildHeader, controllers.static['403']); - app.get('/api/403', controllers.static['403']); - - app.get('/500', middleware.buildHeader, controllers.static['500']); - app.get('/api/500', controllers.static['500']); + setupPageRoute(app, '/404', middleware, [], controllers.static['404']); + setupPageRoute(app, '/403', middleware, [], controllers.static['403']); + setupPageRoute(app, '/500', middleware, [], controllers.static['500']); } function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); - app.get('/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, middleware.buildHeader, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug/:post_index?', middleware.applyCSRF, controllers.topics.get); - - app.get('/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.buildHeader, middleware.addSlug, controllers.topics.get); - app.get('/api/topic/:topic_id/:slug?', middleware.applyCSRF, middleware.addSlug, controllers.topics.get); + setupPageRoute(app, '/topic/:topic_id/:slug/:post_index?', middleware, [middleware.applyCSRF], controllers.topics.get); + setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [middleware.applyCSRF, middleware.addSlug], controllers.topics.get); } function tagRoutes(app, middleware, controllers) { - - app.get('/tags/:tag', middleware.buildHeader, controllers.tags.getTag); - app.get('/api/tags/:tag', controllers.tags.getTag); - - app.get('/tags', middleware.buildHeader, controllers.tags.getTags); - app.get('/api/tags', controllers.tags.getTags); + setupPageRoute(app, '/tags/:tag', middleware, [], controllers.tags.getTag); + setupPageRoute(app, '/tags', middleware, [], controllers.tags.getTags); } function categoryRoutes(app, middleware, controllers) { - app.get('/popular/:term?', middleware.buildHeader, controllers.categories.popular); - app.get('/api/popular/:term?', controllers.categories.popular); - - app.get('/recent/:term?', middleware.buildHeader, controllers.categories.recent); - app.get('/api/recent/:term?', controllers.categories.recent); - - app.get('/unread', middleware.buildHeader, middleware.authenticate, controllers.categories.unread); - app.get('/api/unread', middleware.authenticate, controllers.categories.unread); - + setupPageRoute(app, '/popular/:term?', middleware, [], controllers.categories.popular); + setupPageRoute(app, '/recent/:term?', middleware, [], controllers.categories.recent); + setupPageRoute(app, '/unread', middleware, [middleware.authenticate], controllers.categories.unread); app.get('/api/unread/total', middleware.authenticate, controllers.categories.unreadTotal); - app.get('/category/:category_id/:slug/:topic_index', middleware.applyCSRF, middleware.buildHeader, middleware.checkTopicIndex, controllers.categories.get); - app.get('/api/category/:category_id/:slug/:topic_index', middleware.applyCSRF, middleware.checkTopicIndex, controllers.categories.get); - - app.get('/category/:category_id/:slug?', middleware.applyCSRF, middleware.buildHeader, middleware.addSlug, controllers.categories.get); - app.get('/api/category/:category_id/:slug?', middleware.applyCSRF, controllers.categories.get); + setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [middleware.applyCSRF, middleware.checkTopicIndex], controllers.categories.get); + setupPageRoute(app, '/category/:category_id/:slug?', middleware, [middleware.applyCSRF, middleware.addSlug], controllers.categories.get); } function accountRoutes(app, middleware, controllers) { - app.get('/user/:userslug', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount); - app.get('/api/user/:userslug', middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount); - - app.get('/user/:userslug/following', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowing); - app.get('/api/user/:userslug/following', middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowing); - - app.get('/user/:userslug/followers', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowers); - app.get('/api/user/:userslug/followers', middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowers); - - app.get('/user/:userslug/favourites', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.getFavourites); - app.get('/api/user/:userslug/favourites', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.getFavourites); - - app.get('/user/:userslug/posts', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getPosts); - app.get('/api/user/:userslug/posts', middleware.checkGlobalPrivacySettings, controllers.accounts.getPosts); + var middlewares = [middleware.checkGlobalPrivacySettings]; + var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]; - app.get('/user/:userslug/topics', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); - app.get('/api/user/:userslug/topics', middleware.checkGlobalPrivacySettings, controllers.accounts.getTopics); + setupPageRoute(app, '/user/:userslug', middleware, middlewares, controllers.accounts.getAccount); + setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.getFollowing); + setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.getFollowers); + setupPageRoute(app, '/user/:userslug/posts', middleware, middlewares, controllers.accounts.getPosts); + setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.getTopics); - app.get('/user/:userslug/edit', middleware.applyCSRF, middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); - app.get('/api/user/:userslug/edit', middleware.applyCSRF, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit); + setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.getFavourites); + setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.accountEdit); + setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.accountSettings); - app.get('/user/:userslug/settings', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); - app.get('/api/user/:userslug/settings', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings); - - app.get('/notifications', middleware.buildHeader, middleware.authenticate, controllers.accounts.getNotifications); - app.get('/api/notifications', middleware.authenticate, controllers.accounts.getNotifications); - - app.get('/chats/:userslug?', middleware.buildHeader, middleware.redirectToLoginIfGuest, controllers.accounts.getChats); - app.get('/api/chats/:userslug?', middleware.redirectToLoginIfGuest, controllers.accounts.getChats); + setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.getNotifications); + setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.redirectToLoginIfGuest], controllers.accounts.getChats); } function userRoutes(app, middleware, controllers) { - app.get('/user(s)?', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); - app.get('/api/users', middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); - - app.get('/users/online', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); - app.get('/api/users/online', middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers); + var middlewares = [middleware.checkGlobalPrivacySettings]; - app.get('/users/sort-posts', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByPosts); - app.get('/api/users/sort-posts', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByPosts); + setupPageRoute(app, '/users', middleware, middlewares, controllers.users.getOnlineUsers); + setupPageRoute(app, '/users/online', middleware, middlewares, controllers.users.getOnlineUsers); + setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); + setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); + setupPageRoute(app, '/users/latest', middleware, middlewares, controllers.users.getUsersSortedByJoinDate); + setupPageRoute(app, '/users/search', middleware, middlewares, controllers.users.getUsersForSearch); + } - app.get('/users/sort-reputation', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByReputation); - app.get('/api/users/sort-reputation', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByReputation); - - app.get('/users/latest', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByJoinDate); - app.get('/api/users/latest', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByJoinDate); +function groupRoutes(app, middleware, controllers) { + var middlewares = [middleware.checkGlobalPrivacySettings]; - app.get('/users/search', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersForSearch); - app.get('/api/users/search', middleware.checkGlobalPrivacySettings, controllers.users.getUsersForSearch); + setupPageRoute(app, '/groups', middleware, middlewares, controllers.groups.list); + setupPageRoute(app, '/groups/:name', middleware, middlewares, controllers.groups.details); } -function groupRoutes(app, middleware, controllers) { - app.get('/groups', middleware.buildHeader, controllers.groups.list); - app.get('/api/groups', controllers.groups.list); +function setupPageRoute(router, name, middleware, middlewares, controller) { + middlewares = middlewares.concat([middleware.incrementPageViews, middleware.updateLastOnlineTime]); - app.get('/groups/:name', middleware.buildHeader, controllers.groups.details); - app.get('/api/groups/:name', controllers.groups.details); + router.get(name, middleware.buildHeader, middlewares, controller); + router.get('/api' + name, middlewares, controller); } - module.exports = function(app, middleware) { var router = express.Router(), - pageRouter = express.Router(), pluginRouter = express.Router(), authRouter = express.Router(), relativePath = nconf.get('relative_path'); @@ -177,34 +127,32 @@ module.exports = function(app, middleware) { feedRoutes(router, middleware, controllers); pluginRoutes(router, middleware, controllers); - app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { - maxAge: app.enabled('cache') ? 5184000000 : 0 - })); - /** * Every view has an associated API route. * */ - pageRouter.use(middleware.incrementPageViews); - pageRouter.use(middleware.updateLastOnlineTime); - - mainRoutes(pageRouter, middleware, controllers); - staticRoutes(pageRouter, middleware, controllers); - topicRoutes(pageRouter, middleware, controllers); - tagRoutes(pageRouter, middleware, controllers); - categoryRoutes(pageRouter, middleware, controllers); - accountRoutes(pageRouter, middleware, controllers); - userRoutes(pageRouter, middleware, controllers); - groupRoutes(pageRouter, middleware, controllers); + + mainRoutes(router, middleware, controllers); + staticRoutes(router, middleware, controllers); + topicRoutes(router, middleware, controllers); + tagRoutes(router, middleware, controllers); + categoryRoutes(router, middleware, controllers); + accountRoutes(router, middleware, controllers); + userRoutes(router, middleware, controllers); + groupRoutes(router, middleware, controllers); app.use(relativePath, router); app.use(relativePath, pluginRouter); - app.use(relativePath, pageRouter); app.use(relativePath, authRouter); if (process.env.NODE_ENV === 'development') { require('./debug')(app, middleware, controllers); } + + app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { + maxAge: app.enabled('cache') ? 5184000000 : 0 + })); + app.use(catch404); app.use(handleErrors); From 515e663f8512c1dc4b42b5aceb1fe90efea3a2e4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 21:47:20 -0400 Subject: [PATCH 353/372] getObjectsFix return empty array if keys is empty or not an array --- src/database/mongo/hash.js | 2 +- src/notifications.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index d887143d89..2308892a42 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -32,7 +32,7 @@ module.exports = function(db, module) { module.getObjects = function(keys, callback) { if (!Array.isArray(keys) || !keys.length) { - return callback(); + return callback(null, []); } db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function(err, data) { if(err) { diff --git a/src/notifications.js b/src/notifications.js index 0e38a3eec7..3671ad1613 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -40,6 +40,10 @@ var async = require('async'), return callback(err); } + if (!Array.isArray(notifications) || !notifications.length) { + return callback(null, []); + } + async.map(notifications, function(notification, next) { if (!notification) { return next(null, null); From 2d5dc44ce59cb79eabfa9fef4c9edde28d3ce615 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 21:54:28 -0400 Subject: [PATCH 354/372] closes #2123 --- src/controllers/topics.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index f9ba17f743..64db92ec08 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -252,25 +252,40 @@ topicsController.get = function(req, res, next) { topicsController.teaser = function(req, res, next) { var tid = req.params.topic_id; var uid = req.user ? parseInt(req.user.uid, 10) : 0; - topics.getLatestUndeletedPid(tid, function(err, pid) { + + if (!utils.isNumber(tid)) { + return next(new Error('[[error:invalid-tid]]')); + } + + privileges.topics.can('read', tid, uid, function(err, canRead) { if (err) { return next(err); } - if (!pid) { - return res.json(404, 'not-found'); + if (!canRead) { + return res.json(403, '[[error:no-priveges]]'); } - posts.getPostSummaryByPids([pid], uid, {stripTags: false}, function(err, posts) { + topics.getLatestUndeletedPid(tid, function(err, pid) { if (err) { return next(err); } - if (!Array.isArray(posts) || !posts.length) { + if (!pid) { return res.json(404, 'not-found'); } - res.json(posts[0]); + posts.getPostSummaryByPids([pid], uid, {stripTags: false}, function(err, posts) { + if (err) { + return next(err); + } + + if (!Array.isArray(posts) || !posts.length) { + return res.json(404, 'not-found'); + } + + res.json(posts[0]); + }); }); }); }; From 48c3619028680b01b40be3e66a83b109886fd7fe Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Sep 2014 22:17:50 -0400 Subject: [PATCH 355/372] lowered chat zindex --- public/src/modules/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index f40dda09a2..452610c245 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -262,7 +262,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar, module.center = function(chatModal) { chatModal.css("left", Math.max(0, (($(window).width() - $(chatModal).outerWidth()) / 2) + $(window).scrollLeft()) + "px"); chatModal.css("top", Math.max(0, $(window).height() / 4 - $(chatModal).outerHeight() / 2)); - chatModal.css("zIndex", 2000); + chatModal.css("zIndex", 100); chatModal.find('#chat-message-input').focus(); return chatModal; }; From a6abde2a2bf68b9abfa2f2da1fca53241f986f09 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2014 12:58:29 -0400 Subject: [PATCH 356/372] fixed upward infinite scrolling so that it is less jarring --- public/src/forum/topic.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 6acbcc7354..bcfdc0187c 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -333,7 +333,19 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT if(after) { html.insertAfter(after); } else if(before) { + // Save document height and position for future reference (about 5 lines down) + var height = $(document).height(), + scrollTop = $(document).scrollTop(), + originalPostEl = $('li[data-index="0"]'); + + // Insert the new post html.insertBefore(before); + + // If the user is not at the top of the page... (or reasonably so...) + if (scrollTop > originalPostEl.offset().top) { + // Now restore the relative position the user was on prior to new post insertion + $(document).scrollTop(scrollTop + ($(document).height() - height)); + } } else { $('#post-container').append(html); } @@ -397,15 +409,11 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT var reverse = config.topicPostSort === 'newest_to_oldest' || config.topicPostSort === 'most_votes'; infinitescroll.calculateAfter(direction, '#post-container .post-row[data-index!="0"]', config.postsPerPage, reverse, function(after, offset, el) { - loadPostsAfter(after, function() { - if (direction < 0 && el) { - navigator.scrollToPost(el.attr('data-index'), false, 0, offset); - } - }); + loadPostsAfter(after); }); } - function loadPostsAfter(after, callback) { + function loadPostsAfter(after) { var tid = ajaxify.variables.get('topic_id'); if (!utils.isNumber(tid) || !utils.isNumber(after) || (after === 0 && $('#post-container li.post-row[data-index="1"]').length)) { return; @@ -426,9 +434,6 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT if (data && data.posts && data.posts.length) { createNewPosts(data, function(postsCreated) { done(); - if (postsCreated) { - callback(); - } }); hidePostToolsForDeletedPosts(); } else { From b6f7014a9df678850c0048deebe941c1bb8cfcbd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2014 13:04:54 -0400 Subject: [PATCH 357/372] fixed infinite scroller so that it takes the height of OP into account when detecting when to load more posts while scrolling up --- public/src/forum/infinitescroll.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/public/src/forum/infinitescroll.js b/public/src/forum/infinitescroll.js index 870f555a8c..ac0787a023 100644 --- a/public/src/forum/infinitescroll.js +++ b/public/src/forum/infinitescroll.js @@ -17,9 +17,10 @@ define('forum/infinitescroll', function() { }; function onScroll() { - var top = $(window).height() * 0.1 + topOffset; - var bottom = ($(document).height() - $(window).height()) * 0.9; - var currentScrollTop = $(window).scrollTop(); + var originalPostEl = $('li[data-index="0"]'), + top = $(window).height() * 0.15 + topOffset + (originalPostEl ? originalPostEl.outerHeight() : 0), + bottom = ($(document).height() - $(window).height()) * 0.85, + currentScrollTop = $(window).scrollTop(); if(currentScrollTop < top && currentScrollTop < previousScrollTop) { callback(-1); From 7f2254c9daad66b4acd4dfc04942ed8721cafe69 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 13:08:47 -0400 Subject: [PATCH 358/372] closes https://github.com/NodeBB/NodeBB/issues/2126 --- src/topics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics.js b/src/topics.js index 8016da727d..323f501a5b 100644 --- a/src/topics.js +++ b/src/topics.js @@ -245,7 +245,7 @@ var async = require('async'), parseInt(topic.uid, 10) === parseInt(uid, 10)); }); - plugins.fireHook('filter:topics.get', topics, callback); + plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, callback); }); }); }; From 99bacf910cd48830c60420ffae2ac043c69e80e8 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 13:33:36 -0400 Subject: [PATCH 359/372] oops - #2126 --- src/topics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/topics.js b/src/topics.js index 323f501a5b..74f9cbd945 100644 --- a/src/topics.js +++ b/src/topics.js @@ -245,7 +245,9 @@ var async = require('async'), parseInt(topic.uid, 10) === parseInt(uid, 10)); }); - plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, callback); + plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, function(err, topicData) { + callback(err, topicData.topics) + }); }); }); }; From 07679980229313bf1df0d455d99364734a17e697 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Sep 2014 13:34:49 -0400 Subject: [PATCH 360/372] closes #2124 --- src/controllers/admin.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/admin.js b/src/controllers/admin.js index ab28005db7..8ba54380d8 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -140,6 +140,9 @@ function filterAndRenderCategories(req, res, next, active) { } categoryData = categoryData.filter(function (category) { + if (!category) { + return false; + } return active ? !category.disabled : category.disabled; }); From 901470ce058d0ba06298218af3fe4a5f9fcc4652 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2014 13:44:26 -0400 Subject: [PATCH 361/372] always pipe child output to parent --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 8b7afb6827..777be064d8 100644 --- a/loader.js +++ b/loader.js @@ -20,7 +20,7 @@ var nconf = require('nconf'), Loader.init = function() { cluster.setupMaster({ exec: "app.js", - silent: process.env.NODE_ENV !== 'development' ? true : false + silent: false }); cluster.on('fork', function(worker) { From fb764472dd305850637de38fea74ac9ebccb012d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Sep 2014 13:48:15 -0400 Subject: [PATCH 362/372] closes #2125 --- public/src/forum/topic/postTools.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/public/src/forum/topic/postTools.js b/public/src/forum/topic/postTools.js index 2bc22cb06a..a4b7708e2a 100644 --- a/public/src/forum/topic/postTools.js +++ b/public/src/forum/topic/postTools.js @@ -131,7 +131,9 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com } var username = getUserName(selectionText ? $(selection.baseNode) : button); - + if (getData(button, 'data-uid') === '0') { + username = ''; + } if (selectionText.length) { composer.addQuote(tid, ajaxify.variables.get('topic_slug'), getData(button, 'data-index'), getData(button, 'data-pid'), topicName, username, selectionText); } else { @@ -201,8 +203,12 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com post = button.parents('li[data-pid]'); if (post.length) { - username = '@' + post.attr('data-username').replace(/\s/g, '-'); + username = post.attr('data-username').replace(/\s/g, '-'); } + if (post.attr('data-uid') !== '0') { + username = '@' + username; + } + return username; } From 0eb4fd89ef0d77632f2b349fc58c34bd58a88e31 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 13:57:59 -0400 Subject: [PATCH 363/372] don't blow up nodebb if plugin.json is broken --- src/plugins.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins.js b/src/plugins.js index 41f38dfd3c..ea47125b9c 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -144,8 +144,16 @@ var fs = require('fs'), return callback(pluginPath.match('nodebb-theme') ? null : err); } - var pluginData = JSON.parse(data), - libraryPath, staticDir; + try { + var pluginData = JSON.parse(data), + libraryPath, staticDir; + } catch (err) { + var pluginDir = pluginPath.split(path.sep); + pluginDir = pluginDir[pluginDir.length -1]; + + winston.error('[plugins/' + pluginDir + '] Plugin not loaded - please check its plugin.json for errors'); + return callback(err); + } /* Starting v0.5.0, `minver` is deprecated in favour of `compatibility`. From 77dfc7378b53d9b5b21e2e323350f62d7f6b9702 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 13:58:03 -0400 Subject: [PATCH 364/372] filter:post.getPostSummaryByPids --- src/posts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 1cb277fabb..745e7f0d2f 100644 --- a/src/posts.js +++ b/src/posts.js @@ -343,7 +343,9 @@ var async = require('async'), next(null, post); }); - }, callback); + }, function(err, posts) { + plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, callback); + }); }); }); }; From 180625ece34b05058c5b8662d6c80071446965e7 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 14:00:28 -0400 Subject: [PATCH 365/372] forgot to return just posts array --- src/posts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/posts.js b/src/posts.js index 745e7f0d2f..dcedf4383d 100644 --- a/src/posts.js +++ b/src/posts.js @@ -344,7 +344,9 @@ var async = require('async'), next(null, post); }); }, function(err, posts) { - plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, callback); + plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, function(err, postData) { + callback(err, postData.posts); + }); }); }); }); From a0da73dabda958b631891c8f3e1a613a52ae5418 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 23 Sep 2014 15:03:13 -0400 Subject: [PATCH 366/372] don't pass back err if plugin.json is corrupted --- src/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins.js b/src/plugins.js index ea47125b9c..2cc51316d1 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -152,7 +152,7 @@ var fs = require('fs'), pluginDir = pluginDir[pluginDir.length -1]; winston.error('[plugins/' + pluginDir + '] Plugin not loaded - please check its plugin.json for errors'); - return callback(err); + return callback(); } /* From 64c51d5cba9ae54345639ab2bd07263ab2d054d5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Sep 2014 16:02:19 -0400 Subject: [PATCH 367/372] check nid before pusing and marking read --- src/notifications.js | 7 ++++++- src/user/notifications.js | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/notifications.js b/src/notifications.js index 3671ad1613..3a10a5da37 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -120,6 +120,11 @@ var async = require('async'), Notifications.push = function(notification, uids, callback) { callback = callback || function() {}; + + if (!notification.nid) { + return callback(); + } + var websockets = require('./socket.io'); if (!Array.isArray(uids)) { uids = [uids]; @@ -183,7 +188,7 @@ var async = require('async'), return callback(); } - var notificationKeys = nids.map(function(nid) { + var notificationKeys = nids.filter(Boolean).map(function(nid) { return 'notifications:' + nid; }); diff --git a/src/user/notifications.js b/src/user/notifications.js index fa61fce32a..63d1ccc82f 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -71,9 +71,7 @@ var async = require('async'), winston.info('[notifications.get] nid ' + nids[index] + ' not found. Removing.'); } - if (nids[index]) { - deletedNids.push(nids[index]); - } + deletedNids.push(nids[index]); } }); From b1f492dec3e61e3f413d428fa932139d1ca8332b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Sep 2014 17:07:17 -0400 Subject: [PATCH 368/372] fix redis bulk zadd --- src/database/redis/sorted.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 900497c087..2df108cf9b 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -15,11 +15,14 @@ module.exports = function(redisClient, module) { if (scores.length !== values.length) { return callback(new Error('[[error:invalid-data]]')); } - var multi = redisClient.multi(); - for(var i=0; i Date: Tue, 23 Sep 2014 17:08:30 -0400 Subject: [PATCH 369/372] exposed redis' "connect" method, so socket.io can call it from outside database/redis.js, fixed NodeBB requiring Redis as a socket.io store. It will now go back to using in-memory store per process, if no redis config is found in the NodeBB configuration. re: #2097 --- src/database/redis.js | 49 +++++++++++++++++++++++++----------------- src/socket.io/index.js | 43 +++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/database/redis.js b/src/database/redis.js index 0becf2ba9e..09d13d6cf8 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -47,26 +47,7 @@ process.exit(); } - var redis_socket_or_host = nconf.get('redis:host'); - - if (redis_socket_or_host && redis_socket_or_host.indexOf('/')>=0) { - /* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */ - redisClient = redis.createClient(nconf.get('redis:host')); - } else { - /* Else, connect over tcp/ip */ - redisClient = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')); - } - - if (nconf.get('redis:password')) { - redisClient.auth(nconf.get('redis:password')); - } else { - winston.warn('You have no redis password setup!'); - } - - redisClient.on('error', function (err) { - winston.error(err.stack); - process.exit(1); - }); + redisClient = module.connect(); module.client = redisClient; @@ -104,6 +85,34 @@ } }; + module.connect = function() { + var redis_socket_or_host = nconf.get('redis:host'), + cxn; + + if (!redis) redis = require('redis'); + + if (redis_socket_or_host && redis_socket_or_host.indexOf('/') >= 0) { + /* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */ + cxn = redis.createClient(nconf.get('redis:host')); + } else { + /* Else, connect over tcp/ip */ + cxn = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')); + } + + if (nconf.get('redis:password')) { + cxn.auth(nconf.get('redis:password')); + } else { + winston.warn('You have no redis password setup!'); + } + + cxn.on('error', function (err) { + winston.error(err.stack); + process.exit(1); + }); + + return cxn; + }; + module.close = function() { redisClient.quit(); }; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 6f949177d1..354e72516e 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -66,23 +66,32 @@ function onUserDisconnect(uid, socketid, socketCount) { } Sockets.init = function(server) { - var RedisStore = require('socket.io/lib/stores/redis'), - redis = require('redis'), - pub = redis.createClient(), - sub = redis.createClient(), - client = redis.createClient(); - - io = socketioWildcard(SocketIO).listen(server, { - log: false, - transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], - 'browser client minification': true, - resource: nconf.get('relative_path') + '/socket.io', - 'store' : new RedisStore({ - redisPub : pub, - redisSub : sub, - redisClient : client - }), - }); + // Default socket.io config + var config = { + log: false, + transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], + 'browser client minification': true, + resource: nconf.get('relative_path') + '/socket.io' + }; + + // If a redis server is configured, use it as a socket.io store, otherwise, fall back to in-memory store + if (nconf.get('redis')) { + var RedisStore = require('socket.io/lib/stores/redis'), + database = require('../database/redis'), + pub = database.connect(), + sub = database.connect(), + client = database.connect(); + + config.store = new RedisStore({ + redisPub : pub, + redisSub : sub, + redisClient : client + }); + } else if (nconf.get('cluster')) { + winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.') + } + + io = socketioWildcard(SocketIO).listen(server, config); Sockets.server = io; From 8abb247870da20d6a676b74f9997ab02d9ec97a5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Sep 2014 17:12:34 -0400 Subject: [PATCH 370/372] fix mention regression --- public/src/forum/topic/postTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/forum/topic/postTools.js b/public/src/forum/topic/postTools.js index a4b7708e2a..96edf0720c 100644 --- a/public/src/forum/topic/postTools.js +++ b/public/src/forum/topic/postTools.js @@ -205,7 +205,7 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com if (post.length) { username = post.attr('data-username').replace(/\s/g, '-'); } - if (post.attr('data-uid') !== '0') { + if (post.length && post.attr('data-uid') !== '0') { username = '@' + username; } From 20819e3dfd81e643ab12eadc3e87790293e2e195 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2014 17:12:36 -0400 Subject: [PATCH 371/372] Revert "use redis session storage instead of connect-mongo" This reverts commit 056f89d6cf69aa52abe88f235ec0e90caabbc46a. --- src/database/mongo.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index dc65e3ce0d..d173693451 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -9,9 +9,7 @@ session = require('express-session'), db, mongoClient, - redis = require('redis'), - redisClient = redis.createClient(), - connectRedis; + mongoStore; module.questions = [ { @@ -46,7 +44,7 @@ module.init = function(callback) { try { mongoClient = require('mongodb').MongoClient; - connectRedis = require('connect-redis')(session); + mongoStore = require('connect-mongo')({session: session}); } catch (err) { winston.error('Unable to initialize MongoDB! Is MongoDB installed? Error :' + err.message); process.exit(); @@ -62,9 +60,8 @@ module.client = db; - module.sessionStore = new connectRedis({ - client: redisClient, - ttl: 60 * 60 * 24 * 14 + module.sessionStore = new mongoStore({ + db: db }); require('./mongo/main')(db, module); From ecea917d11df07308fcf4df79112d454d48b9f26 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Sep 2014 17:18:20 -0400 Subject: [PATCH 372/372] closed #2097 -- NodeBB will prefer using Redis as session store if at all possible --- src/database/mongo.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index d173693451..e67419d3e4 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -43,8 +43,14 @@ module.init = function(callback) { try { + var sessionStore; mongoClient = require('mongodb').MongoClient; - mongoStore = require('connect-mongo')({session: session}); + + if (!nconf.get('redis')) { + sessionStore = require('connect-mongo')({session: session}); + } else { + sessionStore = require('connect-redis')(session); + } } catch (err) { winston.error('Unable to initialize MongoDB! Is MongoDB installed? Error :' + err.message); process.exit(); @@ -60,9 +66,16 @@ module.client = db; - module.sessionStore = new mongoStore({ - db: db - }); + if (!nconf.get('redis')) { + module.sessionStore = new sessionStore({ + db: db + }); + } else { + module.sessionStore = new sessionStore({ + client: require('./redis').connect(), + ttl: 60 * 60 * 24 * 14 + }); + } require('./mongo/main')(db, module); require('./mongo/hash')(db, module);