From 2d96cdba1c4466be7f4dc29e2d8f1500430100d6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Oct 2015 16:13:37 -0400 Subject: [PATCH 01/91] closes #3720 --- public/src/app.js | 4 +++- public/src/client/category.js | 7 ++----- public/src/client/pagination.js | 18 ++++++++---------- public/src/client/topic.js | 8 ++------ public/src/client/topic/posts.js | 6 +++--- public/src/modules/navigator.js | 2 +- src/controllers/categories.js | 12 +++++------- src/controllers/topics.js | 16 +++++++--------- src/pagination.js | 6 ++++-- src/user/search.js | 1 - 10 files changed, 35 insertions(+), 45 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 6d724f3f49..f77c860b02 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -71,12 +71,14 @@ app.cacheBuster = null; } }); - require(['taskbar', 'helpers'], function(taskbar, helpers) { + require(['taskbar', 'helpers', 'forum/pagination'], function(taskbar, helpers, pagination) { taskbar.init(); // templates.js helpers helpers.register(); + pagination.init(); + $(window).trigger('action:app.load'); }); }); diff --git a/public/src/client/category.js b/public/src/client/category.js index 7a295c1b28..cfb7de08d1 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -2,7 +2,6 @@ /* global define, config, templates, app, utils, ajaxify, socket */ define('forum/category', [ - 'forum/pagination', 'forum/infinitescroll', 'share', 'navigator', @@ -10,8 +9,7 @@ define('forum/category', [ 'sort', 'components', 'translator' - -], function(pagination, infinitescroll, share, navigator, categoryTools, sort, components, translator) { +], function(infinitescroll, share, navigator, categoryTools, sort, components, translator) { var Category = {}; $(window).on('action:ajaxify.start', function(ev, data) { @@ -112,7 +110,7 @@ define('forum/category', [ if (config.usePagination) { var page = Math.ceil((parseInt(bookmarkIndex, 10) + 1) / config.topicsPerPage); - if (parseInt(page, 10) !== pagination.currentPage) { + if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) { pagination.loadPage(page, function() { Category.scrollToTopic(bookmarkIndex, clickedIndex, 400); }); @@ -176,7 +174,6 @@ define('forum/category', [ infinitescroll.init($('[component="category"]'), Category.loadMoreTopics); } else { navigator.hide(); - pagination.init(ajaxify.data.currentPage, ajaxify.data.pageCount); } } diff --git a/public/src/client/pagination.js b/public/src/client/pagination.js index a94967c7c5..534b4e27f2 100644 --- a/public/src/client/pagination.js +++ b/public/src/client/pagination.js @@ -4,14 +4,8 @@ define('forum/pagination', function() { var pagination = {}; - pagination.currentPage = 0; - pagination.pageCount = 0; - - pagination.init = function(currentPage, pageCount) { - pagination.currentPage = parseInt(currentPage, 10); - pagination.pageCount = parseInt(pageCount, 10); - - $('.pagination').on('click', '.select-page', function(e) { + pagination.init = function() { + $('body').on('click', '.pagination .select-page', function(e) { e.preventDefault(); bootbox.prompt('Enter page number:', function(pageNum) { pagination.loadPage(pageNum); @@ -22,10 +16,14 @@ define('forum/pagination', function() { pagination.loadPage = function(page, callback) { callback = callback || function() {}; page = parseInt(page, 10); - if (!utils.isNumber(page) || page < 1 || page > pagination.pageCount) { + if (!utils.isNumber(page) || page < 1 || page > ajaxify.data.pagination.pageCount) { return; } - var url = window.location.pathname.slice(1).split('/').slice(0, 3).join('/') + '?page=' + page; + + var query = utils.params(); + query.page = page; + + var url = window.location.pathname + '?' + $.param(query); ajaxify.go(url, callback); }; diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 89b6e30a1d..116dc62a1b 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -4,7 +4,6 @@ /* globals define, app, socket, config, ajaxify, RELATIVE_PATH, utils */ define('forum/topic', [ - 'forum/pagination', 'forum/infinitescroll', 'forum/topic/threadTools', 'forum/topic/postTools', @@ -14,7 +13,7 @@ define('forum/topic', [ 'navigator', 'sort', 'components' -], function(pagination, infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort, components) { +], function(infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort, components) { var Topic = {}, currentUrl = ''; @@ -147,7 +146,7 @@ define('forum/topic', [ if (components.get('post/anchor', postIndex).length) { return navigator.scrollToPostIndex(postIndex, true); } - } else if (bookmark && (!config.usePagination || (config.usePagination && pagination.currentPage === 1)) && ajaxify.data.postcount > 10) { + } else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > 10) { app.alert({ alert_id: 'bookmark', message: '[[topic:bookmark_instructions]]', @@ -218,12 +217,9 @@ define('forum/topic', [ infinitescroll.init($('[component="topic"]'), posts.loadMorePosts); } else { navigator.hide(); - - pagination.init(parseInt(ajaxify.data.currentPage, 10), parseInt(ajaxify.data.pageCount, 10)); } } - function updateTopicTitle() { if ($(window).scrollTop() > 50) { components.get('navbar/title').find('span').text(ajaxify.data.title).show(); diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index fd5f310b59..222c07f98b 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -51,15 +51,15 @@ define('forum/topic/posts', [ var posts = data.posts; - pagination.pageCount = Math.max(1, Math.ceil((posts[0].topic.postcount - 1) / config.postsPerPage)); + ajaxify.data.pagination.pageCount = Math.max(1, Math.ceil((posts[0].topic.postcount - 1) / config.postsPerPage)); var direction = config.topicPostSort === 'oldest_to_newest' || config.topicPostSort === 'most_votes' ? 1 : -1; - var isPostVisible = (pagination.currentPage === pagination.pageCount && direction === 1) || (pagination.currentPage === 1 && direction === -1); + var isPostVisible = (ajaxify.data.pagination.currentPage === ajaxify.data.pagination.pageCount && direction === 1) || (ajaxify.data.pagination.currentPage === 1 && direction === -1); if (isPostVisible) { createNewPosts(data, components.get('post').not('[data-index=0]'), direction, scrollToPost); } else if (parseInt(posts[0].uid, 10) === parseInt(app.user.uid, 10)) { - pagination.loadPage(pagination.pageCount, scrollToPost); + pagination.loadPage(ajaxify.data.pagination.pageCount, scrollToPost); } } diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 251cc78182..c9275348f3 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -162,7 +162,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com if (config.usePagination) { var page = Math.max(1, Math.ceil(postIndex / config.postsPerPage)); - if (parseInt(page, 10) !== pagination.currentPage) { + if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) { pagination.loadPage(page, function() { navigator.scrollToPostIndex(postIndex, highlight, duration); }); diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 220779c524..b72921c5c8 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -81,7 +81,7 @@ categoriesController.list = function(req, res, next) { categoriesController.get = function(req, res, callback) { var cid = req.params.category_id, - page = parseInt(req.query.page, 10) || 1, + currentPage = parseInt(req.query.page, 10) || 1, pageCount = 1, userPrivileges; @@ -127,7 +127,7 @@ categoriesController.get = function(req, res, callback) { return helpers.redirect(res, '/category/' + cid + '/' + req.params.slug + (topicIndex > topicCount ? '/' + topicCount : '')); } - if (settings.usePagination && (page < 1 || page > pageCount)) { + if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) { return callback(); } @@ -135,7 +135,7 @@ categoriesController.get = function(req, res, callback) { topicIndex = Math.max(topicIndex - (settings.topicsPerPage - 1), 0); } else if (!req.query.page) { var index = Math.max(parseInt((topicIndex || 0), 10), 0); - page = Math.ceil((index + 1) / settings.topicsPerPage); + currentPage = Math.ceil((index + 1) / settings.topicsPerPage); topicIndex = 0; } @@ -149,7 +149,7 @@ categoriesController.get = function(req, res, callback) { set = 'cid:' + cid + ':tids:posts'; } - var start = (page - 1) * settings.topicsPerPage + topicIndex, + var start = (currentPage - 1) * settings.topicsPerPage + topicIndex, stop = start + settings.topicsPerPage - 1; next(null, { @@ -249,12 +249,10 @@ categoriesController.get = function(req, res, callback) { return callback(err); } - data.currentPage = page; - data.pageCount = pageCount; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data.rssFeedUrl = nconf.get('relative_path') + '/category/' + data.cid + '.rss'; data.title = data.name; - data.pagination = pagination.create(data.currentPage, data.pageCount); + data.pagination = pagination.create(currentPage, pageCount); data.pagination.rel.forEach(function(rel) { rel.href = nconf.get('url') + '/category/' + data.slug + rel.href; res.locals.linkTags.push(rel); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index abbb245f28..7f386c5af8 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -18,6 +18,8 @@ var topicsController = {}, topicsController.get = function(req, res, callback) { var tid = req.params.topic_id, sort = req.query.sort, + currentPage = parseInt(req.query.page, 10) || 1, + pageCount = 1, userPrivileges; if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) { @@ -56,14 +58,13 @@ topicsController.get = function(req, res, callback) { var settings = results.settings; var postCount = parseInt(results.topic.postcount, 10); - var pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage)); - var page = parseInt(req.query.page, 10) || 1; + pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage)); if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) { return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : '')); } - if (settings.usePagination && (page < 1 || page > pageCount)) { + if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) { return callback(); } @@ -105,10 +106,10 @@ topicsController.get = function(req, res, callback) { index = Math.max(0, req.params.post_index - 1) || 0; } - page = Math.max(1, Math.ceil(index / settings.postsPerPage)); + currentPage = Math.max(1, Math.ceil(index / settings.postsPerPage)); } - var start = (page - 1) * settings.postsPerPage + postIndex, + var start = (currentPage - 1) * settings.postsPerPage + postIndex, stop = start + settings.postsPerPage - 1; topics.getTopicWithPosts(tid, set, req.uid, start, stop, reverse, function (err, topicData) { @@ -120,9 +121,6 @@ topicsController.get = function(req, res, callback) { return next(err); } - topicData.pageCount = pageCount; - topicData.currentPage = page; - topics.modifyByPrivilege(topicData.posts, results.privileges); plugins.fireHook('filter:controllers.topic.get', topicData, next); @@ -261,7 +259,7 @@ topicsController.get = function(req, res, callback) { data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; - data.pagination = pagination.create(data.currentPage, data.pageCount); + data.pagination = pagination.create(currentPage, pageCount); data.pagination.rel.forEach(function(rel) { rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href; res.locals.linkTags.push(rel); diff --git a/src/pagination.js b/src/pagination.js index 58813c391b..15b8aa6eb3 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -10,7 +10,9 @@ pagination.create = function(currentPage, pageCount, queryObj) { prev: {page: 1, active: currentPage > 1}, next: {page: 1, active: currentPage < pageCount}, rel: [], - pages: [] + pages: [], + currentPage: 1, + pageCount: 1 }; } pageCount = parseInt(pageCount, 10); @@ -44,7 +46,7 @@ pagination.create = function(currentPage, pageCount, queryObj) { } } - var data = {rel: [], pages: pages}; + var data = {rel: [], pages: pages, currentPage: currentPage, pageCount: pageCount}; queryObj.page = previous; data.prev = {page: previous, active: currentPage > 1, qs: qs.stringify(queryObj)}; queryObj.page = next; diff --git a/src/user/search.js b/src/user/search.js index 60f125096a..ae91ba1615 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -45,7 +45,6 @@ module.exports = function(User) { var pagination = User.paginate(page, uids); uids = pagination.data; searchResult.pagination = pagination.pagination; - searchResult.pageCount = pagination.pageCount; } User.getUsers(uids, uid, next); From ff7b6e4e91f8b760f0658805c05d4325c0be6f0e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Oct 2015 16:20:19 -0400 Subject: [PATCH 02/91] added search title --- src/controllers/search.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/search.js b/src/controllers/search.js index 329f599783..82257f1a64 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -51,6 +51,7 @@ searchController.search = function(req, res, next) { searchData.pagination = pagination.create(page, searchData.pageCount, req.query); searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; searchData.showAsTopics = req.query.showAs === 'topics'; + searchData.title = '[[global:header.search]]'; searchData.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]); searchData.expandSearch = !req.params.term; From fa4875d78dd9bfe72c9f12b58c8c9f71694fdb5d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Oct 2015 17:36:27 -0400 Subject: [PATCH 03/91] category teaser --- src/controllers/categories.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index b72921c5c8..59068434ad 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -65,6 +65,15 @@ categoriesController.list = function(req, res, next) { return next(err); } + data.categories.forEach(function(category) { + if (category && Array.isArray(category.posts) && category.posts.length) { + category.teaser = { + url: nconf.get('relative_path') + '/topic/' + category.posts[0].topic.slug + '/' + category.posts[0].index, + timestampISO: category.posts[0].relativeTime + }; + } + }); + data.title = '[[pages:categories]]'; if (req.path.startsWith('/api/categories') || req.path.startsWith('/categories')) { data.breadcrumbs = helpers.buildBreadcrumbs([{text: data.title}]); From c268493d2b422f5915c7d992392803cd12d7e889 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Oct 2015 15:29:00 -0400 Subject: [PATCH 04/91] closes #3723 --- public/src/admin/manage/categories.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 1d346ccd8a..d8ae5bfc52 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -1,5 +1,5 @@ "use strict"; -/*global define, socket, app, bootbox, templates, ajaxify, RELATIVE_PATH, Sortable */ +/*global define, socket, app, bootbox, templates, ajaxify, Sortable */ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-serializeobject.min'], function() { var Categories = {}, newCategoryId = -1, sortables; @@ -17,12 +17,12 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri // Enable/Disable toggle events $('.categories').on('click', 'button[data-action="toggle"]', function() { - var self = $(this), - rowEl = self.parents('li'), + var rowEl = $(this).parents('li'), cid = rowEl.attr('data-cid'), disabled = rowEl.hasClass('disabled'); - Categories.toggle(cid, disabled); + Categories.toggle(cid, !disabled); + return false; }); }; @@ -101,7 +101,7 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri disabled: disabled ? 1 : 0 }; - socket.emit('admin.categories.update', payload, function(err, result) { + socket.emit('admin.categories.update', payload, function(err) { if (err) { return app.alertError(err.message); } From 0ce1e666e755ff60b6e6a81189408b882c5967e5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Oct 2015 16:05:05 -0400 Subject: [PATCH 05/91] disabling/enabling parent will disable/enable children --- public/src/admin/manage/categories.js | 23 ++++++++++++------- .../partials/categories/category-rows.tpl | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index d8ae5bfc52..a57fc06701 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -17,11 +17,16 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri // Enable/Disable toggle events $('.categories').on('click', 'button[data-action="toggle"]', function() { - var rowEl = $(this).parents('li'), - cid = rowEl.attr('data-cid'), - disabled = rowEl.hasClass('disabled'); + var $this = $(this), + cid = $this.attr('data-cid'), + parentEl = $this.parents('li[data-cid="' + cid + '"]'), + disabled = parentEl.hasClass('disabled'); - Categories.toggle(cid, !disabled); + var children = parentEl.find('li[data-cid]').map(function() { + return $(this).attr('data-cid'); + }).get(); + + Categories.toggle([cid].concat(children), !disabled); return false; }); }; @@ -94,12 +99,14 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri } }; - Categories.toggle = function(cid, disabled) { + Categories.toggle = function(cids, disabled) { var payload = {}; - payload[cid] = { - disabled: disabled ? 1 : 0 - }; + cids.forEach(function(cid) { + payload[cid] = { + disabled: disabled ? 1 : 0 + }; + }); socket.emit('admin.categories.update', payload, function(err) { if (err) { diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl index 715427c34e..3f16bedd42 100644 --- a/src/views/admin/partials/categories/category-rows.tpl +++ b/src/views/admin/partials/categories/category-rows.tpl @@ -16,7 +16,7 @@
- Edit From 7ecb036310276e1f78adbb074ac8a28ab9b21271 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Oct 2015 16:38:26 -0400 Subject: [PATCH 06/91] up emoji --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06cc7fb64e..172a820869 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "nconf": "~0.7.1", "nodebb-plugin-composer-default": "1.0.16", "nodebb-plugin-dbsearch": "0.2.17", - "nodebb-plugin-emoji-extended": "0.4.14", + "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", "nodebb-plugin-mentions": "1.0.6", "nodebb-plugin-soundpack-default": "0.1.4", From 78fe1da00c576ec41e5e0ec126f04d19610275fc Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 8 Oct 2015 18:29:57 -0400 Subject: [PATCH 07/91] don't mark all chats as read when just reading the list from dropdown/slidemenu/chats page strangely enough it was doing it twice --- src/messaging.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/messaging.js b/src/messaging.js index 415ab5a12c..99b01fec2f 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -111,7 +111,8 @@ var db = require('./database'), touid = params.touid, since = params.since, isNew = params.isNew, - count = params.count || parseInt(meta.config.chatMessageInboxSize, 10) || 250; + count = params.count || parseInt(meta.config.chatMessageInboxSize, 10) || 250, + markRead = params.markRead || true; var uids = sortUids(fromuid, touid), min = params.count ? 0 : Date.now() - (terms[since] || terms.day); @@ -135,13 +136,15 @@ var db = require('./database'), getMessages(mids, fromuid, touid, isNew, callback); }); - notifications.markRead('chat_' + touid + '_' + fromuid, fromuid, function(err) { - if (err) { - winston.error('[messaging] Could not mark notifications related to this chat as read: ' + err.message); - } + if (markRead) { + notifications.markRead('chat_' + touid + '_' + fromuid, fromuid, function(err) { + if (err) { + winston.error('[messaging] Could not mark notifications related to this chat as read: ' + err.message); + } - userNotifications.pushCount(fromuid); - }); + userNotifications.pushCount(fromuid); + }); + } }; function getMessages(mids, fromuid, touid, isNew, callback) { @@ -271,7 +274,8 @@ var db = require('./database'), fromuid: fromuid, touid: uid, isNew: false, - count: 1 + count: 1, + markRead: false }, function(err, teaser) { var teaser = teaser[0]; teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; @@ -286,7 +290,6 @@ var db = require('./database'), results.users.forEach(function(user, index) { if (user && parseInt(user.uid, 10)) { - Messaging.markRead(uid, uids[index]); user.unread = results.unread[index]; user.status = sockets.isUserOnline(user.uid) ? user.status : 'offline'; user.teaser = results.teasers[index]; From 4a9b2a2a9a41f4cac3b8b68642b53f374c81819b Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 8 Oct 2015 18:30:12 -0400 Subject: [PATCH 08/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 172a820869..48fd470bca 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.45", + "nodebb-theme-persona": "3.0.46", "nodebb-theme-vanilla": "4.0.20", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From 7b84696c7f4d2572425f64cbbc54ef0661979678 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 8 Oct 2015 18:34:25 -0400 Subject: [PATCH 09/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48fd470bca..71bc376bf7 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.46", + "nodebb-theme-persona": "3.0.47", "nodebb-theme-vanilla": "4.0.20", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From 1dcdba9b2a4361110a3fee8316db54d6c604fc24 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Oct 2015 19:51:05 -0400 Subject: [PATCH 10/91] moved api route to api.js --- src/routes/api.js | 3 +-- src/routes/index.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/routes/api.js b/src/routes/api.js index e0f1684f05..a888c20747 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -2,8 +2,6 @@ var express = require('express'), - posts = require('../posts'), - categories = require('../categories'), uploadsController = require('../controllers/uploads'); module.exports = function(app, middleware, controllers) { @@ -22,6 +20,7 @@ module.exports = function(app, middleware, controllers) { router.get('/categories/:cid/moderators', controllers.api.getModerators); router.get('/recent/posts/:term?', controllers.api.getRecentPosts); router.get('/unread/total', middleware.authenticate, controllers.unread.unreadTotal); + router.get('/topic/teaser/:topic_id', controllers.topics.teaser); var multipart = require('connect-multiparty'); var multipartMiddleware = multipart(); diff --git a/src/routes/index.js b/src/routes/index.js index 31573544d9..16a6084cc2 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -36,8 +36,6 @@ function mainRoutes(app, middleware, controllers) { } function topicRoutes(app, middleware, controllers) { - app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); - setupPageRoute(app, '/topic/:topic_id/:slug/:post_index?', middleware, [], controllers.topics.get); setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [], controllers.topics.get); } From 1ddcb3f11cebdd91a2ea67cc0d78e8b25bfda64d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Oct 2015 12:59:06 -0400 Subject: [PATCH 11/91] closes #3740 --- src/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications.js b/src/notifications.js index 7327ae7b87..93c4f1f124 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -229,7 +229,7 @@ var async = require('async'), return callback(); } - db.getObject('notification:' + nid, function(err, notification) { + db.getObject('notifications:' + nid, function(err, notification) { if (err || !notification) { return callback(err || new Error('[[error:no-notification]]')); } From 36bfe30425d598c5ff0be5868ddedcd25b33b889 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Oct 2015 14:23:02 -0400 Subject: [PATCH 12/91] closes #3736 --- src/controllers/users.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/users.js b/src/controllers/users.js index c4b7916502..bbe4e9da5d 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -59,6 +59,9 @@ usersController.getUsersSortedByPosts = function(req, res, next) { }; usersController.getUsersSortedByReputation = function(req, res, next) { + if (parseInt(meta.config['reputation:disabled'], 10) === 1) { + return next(); + } usersController.getUsers('users:reputation', 0, 49, req, res, next); }; @@ -218,6 +221,7 @@ usersController.getMap = function(req, res, next) { res.render('usersMap', { rooms: data, + 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1, title: '[[pages:users/map]]', breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:users]]', url: '/users'}, {text: '[[global:map]]'}]) }); @@ -231,6 +235,7 @@ function render(req, res, data, next) { } data.templateData.inviteOnly = meta.config.registrationType === 'invite-only'; + data.templateData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; res.render('users', data.templateData); }); } From 7854e67b7bdf0754fda61b6a364ffe345437a731 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Oct 2015 17:52:55 -0400 Subject: [PATCH 13/91] closes #1637 --- public/language/en_GB/pages.json | 3 + public/language/en_GB/user.json | 2 + public/src/client/account/edit.js | 133 +-------------------- public/src/client/account/edit/email.js | 39 ++++++ public/src/client/account/edit/password.js | 116 ++++++++++++++++++ public/src/client/account/edit/username.js | 43 +++++++ src/controllers/accounts/edit.js | 52 ++++++-- src/routes/accounts.js | 3 + src/socket.io/user.js | 15 +-- src/socket.io/user/picture.js | 6 +- src/socket.io/user/profile.js | 45 ++++++- src/user.js | 26 ++-- src/user/password.js | 28 +++++ src/user/profile.js | 21 +--- 14 files changed, 350 insertions(+), 182 deletions(-) create mode 100644 public/src/client/account/edit/email.js create mode 100644 public/src/client/account/edit/password.js create mode 100644 public/src/client/account/edit/username.js create mode 100644 src/user/password.js diff --git a/public/language/en_GB/pages.json b/public/language/en_GB/pages.json index 6d6164ef9e..f398d5b1ff 100644 --- a/public/language/en_GB/pages.json +++ b/public/language/en_GB/pages.json @@ -29,6 +29,9 @@ "chat": "Chatting with %1", "account/edit": "Editing \"%1\"", + "account/edit/password": "Editing password of \"%1\"", + "account/edit/username": "Editing username of \"%1\"", + "account/edit/email": "Editing email of \"%1\"", "account/following": "People %1 follows", "account/followers": "People who follow %1", "account/posts": "Posts made by %1", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 083e887a24..097a562e3f 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -39,6 +39,8 @@ "profile_update_success": "Profile has been updated successfully!", "change_picture": "Change Picture", + "change_username": "Change Username", + "change_email": "Change Email", "edit": "Edit", "uploaded_picture": "Uploaded Picture", "upload_new_picture": "Upload New Picture", diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index 907c52acf0..b3cdfe4966 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -1,13 +1,12 @@ 'use strict'; -/* globals define, ajaxify, socket, app, config, utils, bootbox */ +/* globals define, ajaxify, socket, app, config, templates, bootbox */ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function(header, uploader, translator) { var AccountEdit = {}, gravatarPicture = '', uploadedPicture = '', - selectedImageType = '', - currentEmail; + selectedImageType = ''; AccountEdit.init = function() { gravatarPicture = ajaxify.data.gravatarpicture; @@ -25,12 +24,9 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], }); }); - currentEmail = $('#inputEmail').val(); - handleImageChange(); handleAccountDelete(); handleEmailConfirm(); - handlePasswordChange(); updateSignature(); updateAboutMe(); }; @@ -38,8 +34,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function updateProfile() { var userData = { uid: $('#inputUID').val(), - username: $('#inputUsername').val(), - email: $('#inputEmail').val(), fullname: $('#inputFullname').val(), website: $('#inputWebsite').val(), birthday: $('#inputBirthday').val(), @@ -64,27 +58,13 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], gravatarPicture = data.gravatarpicture; } - if (data.userslug) { - var oldslug = $('.account-username-box').attr('data-userslug'); - $('.account-username-box a').each(function() { - $(this).attr('href', $(this).attr('href').replace(oldslug, data.userslug)); - }); - - $('.account-username-box').attr('data-userslug', data.userslug); - } - - if (currentEmail !== data.email) { - currentEmail = data.email; - $('#confirm-email').removeClass('hide'); - } - - updateHeader(data.picture, userData.username, data.userslug); + updateHeader(data.picture); }); return false; } - function updateHeader(picture, username, userslug) { + function updateHeader(picture) { require(['components'], function(components) { if (parseInt(ajaxify.data.theirid, 10) !== parseInt(ajaxify.data.yourid, 10)) { return; @@ -93,11 +73,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], if (picture) { components.get('header/userpicture').attr('src', picture); } - - if (username && userslug) { - components.get('header/profilelink').attr('href', config.relative_path + '/user/' + userslug); - components.get('header/username').text(username); - } }); } @@ -282,88 +257,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], }); } - function handlePasswordChange() { - var currentPassword = $('#inputCurrentPassword'); - var password_notify = $('#password-notify'); - var password_confirm_notify = $('#password-confirm-notify'); - var password = $('#inputNewPassword'); - var password_confirm = $('#inputNewPasswordAgain'); - var passwordvalid = false; - var passwordsmatch = false; - - function onPasswordChanged() { - if (password.val().length < config.minimumPasswordLength) { - showError(password_notify, '[[user:change_password_error_length]]'); - passwordvalid = false; - } else if (!utils.isPasswordValid(password.val())) { - showError(password_notify, '[[user:change_password_error]]'); - passwordvalid = false; - } else { - showSuccess(password_notify); - passwordvalid = true; - } - } - - function onPasswordConfirmChanged() { - if (password.val() !== password_confirm.val()) { - showError(password_confirm_notify, '[[user:change_password_error_match]]'); - passwordsmatch = false; - } else { - if (password.val()) { - showSuccess(password_confirm_notify); - } else { - password_confirm_notify.parent().removeClass('alert-success alert-danger'); - password_confirm_notify.children().show(); - password_confirm_notify.find('.msg').html(''); - } - - passwordsmatch = true; - } - } - - password.on('blur', onPasswordChanged); - password_confirm.on('blur', onPasswordConfirmChanged); - - $('#changePasswordBtn').on('click', function() { - onPasswordChanged(); - onPasswordConfirmChanged(); - - var btn = $(this); - if ((passwordvalid && passwordsmatch) || app.user.isAdmin) { - btn.addClass('disabled').find('i').removeClass('hide'); - socket.emit('user.changePassword', { - 'currentPassword': currentPassword.val(), - 'newPassword': password.val(), - 'uid': ajaxify.data.theirid - }, function(err) { - btn.removeClass('disabled').find('i').addClass('hide'); - currentPassword.val(''); - password.val(''); - password_confirm.val(''); - passwordsmatch = false; - passwordvalid = false; - - if (err) { - onPasswordChanged(); - onPasswordConfirmChanged(); - return app.alertError(err.message); - } - - app.alertSuccess('[[user:change_password_success]]'); - }); - } else { - if (!passwordsmatch) { - app.alertError('[[user:change_password_error_match]]'); - } - - if (!passwordvalid) { - app.alertError('[[user:change_password_error]]'); - } - } - return false; - }); - } - function changeUserPicture(type, callback) { socket.emit('user.changePicture', { type: type, @@ -393,24 +286,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], }); } - function showError(element, msg) { - translator.translate(msg, function(msg) { - element.find('.error').html(msg).removeClass('hide').siblings().addClass('hide'); - - element.parent() - .removeClass('alert-success') - .addClass('alert-danger'); - element.show(); - }); - } - - function showSuccess(element) { - element.find('.success').removeClass('hide').siblings().addClass('hide'); - element.parent() - .removeClass('alert-danger') - .addClass('alert-success'); - element.show(); - } return AccountEdit; }); diff --git a/public/src/client/account/edit/email.js b/public/src/client/account/edit/email.js new file mode 100644 index 0000000000..4014039740 --- /dev/null +++ b/public/src/client/account/edit/email.js @@ -0,0 +1,39 @@ +'use strict'; + +/* globals define, ajaxify, socket, app */ + +define('forum/account/edit/email', ['forum/account/header'], function(header) { + var AccountEditEmail = {}; + + AccountEditEmail.init = function() { + header.init(); + + $('#submitBtn').on('click', function () { + var userData = { + uid: $('#inputUID').val(), + email: $('#inputNewEmail').val(), + password: $('#inputCurrentPassword').val() + }; + + if (!userData.email) { + return; + } + + var btn = $(this); + btn.addClass('disabled').find('i').removeClass('hide'); + + socket.emit('user.changeUsernameEmail', userData, function(err) { + btn.removeClass('disabled').find('i').addClass('hide'); + if (err) { + return app.alertError(err.message); + } + + ajaxify.go('user/' + ajaxify.data.userslug); + }); + + return false; + }); + }; + + return AccountEditEmail; +}); diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js new file mode 100644 index 0000000000..3e67045925 --- /dev/null +++ b/public/src/client/account/edit/password.js @@ -0,0 +1,116 @@ +'use strict'; + +/* globals define, ajaxify, socket, app, config, utils */ + +define('forum/account/edit/password', ['forum/account/header', 'translator'], function(header, translator) { + var AccountEditPassword = {}; + + AccountEditPassword.init = function() { + header.init(); + + handlePasswordChange(); + }; + + function handlePasswordChange() { + var currentPassword = $('#inputCurrentPassword'); + var password_notify = $('#password-notify'); + var password_confirm_notify = $('#password-confirm-notify'); + var password = $('#inputNewPassword'); + var password_confirm = $('#inputNewPasswordAgain'); + var passwordvalid = false; + var passwordsmatch = false; + + function onPasswordChanged() { + if (password.val().length < config.minimumPasswordLength) { + showError(password_notify, '[[user:change_password_error_length]]'); + passwordvalid = false; + } else if (!utils.isPasswordValid(password.val())) { + showError(password_notify, '[[user:change_password_error]]'); + passwordvalid = false; + } else { + showSuccess(password_notify); + passwordvalid = true; + } + } + + function onPasswordConfirmChanged() { + if (password.val() !== password_confirm.val()) { + showError(password_confirm_notify, '[[user:change_password_error_match]]'); + passwordsmatch = false; + } else { + if (password.val()) { + showSuccess(password_confirm_notify); + } else { + password_confirm_notify.parent().removeClass('alert-success alert-danger'); + password_confirm_notify.children().show(); + password_confirm_notify.find('.msg').html(''); + } + + passwordsmatch = true; + } + } + + password.on('blur', onPasswordChanged); + password_confirm.on('blur', onPasswordConfirmChanged); + + $('#changePasswordBtn').on('click', function() { + onPasswordChanged(); + onPasswordConfirmChanged(); + + var btn = $(this); + if ((passwordvalid && passwordsmatch) || app.user.isAdmin) { + btn.addClass('disabled').find('i').removeClass('hide'); + socket.emit('user.changePassword', { + 'currentPassword': currentPassword.val(), + 'newPassword': password.val(), + 'uid': ajaxify.data.theirid + }, function(err) { + btn.removeClass('disabled').find('i').addClass('hide'); + currentPassword.val(''); + password.val(''); + password_confirm.val(''); + passwordsmatch = false; + passwordvalid = false; + + if (err) { + onPasswordChanged(); + onPasswordConfirmChanged(); + return app.alertError(err.message); + } + ajaxify.go('user/' + ajaxify.data.userslug); + app.alertSuccess('[[user:change_password_success]]'); + }); + } else { + if (!passwordsmatch) { + app.alertError('[[user:change_password_error_match]]'); + } + + if (!passwordvalid) { + app.alertError('[[user:change_password_error]]'); + } + } + return false; + }); + } + + function showError(element, msg) { + translator.translate(msg, function(msg) { + element.find('.error').html(msg).removeClass('hide').siblings().addClass('hide'); + + element.parent() + .removeClass('alert-success') + .addClass('alert-danger'); + element.show(); + }); + } + + function showSuccess(element) { + element.find('.success').removeClass('hide').siblings().addClass('hide'); + element.parent() + .removeClass('alert-danger') + .addClass('alert-success'); + element.show(); + } + + return AccountEditPassword; +}); diff --git a/public/src/client/account/edit/username.js b/public/src/client/account/edit/username.js new file mode 100644 index 0000000000..4fcddf81f5 --- /dev/null +++ b/public/src/client/account/edit/username.js @@ -0,0 +1,43 @@ +'use strict'; + +/* globals define, ajaxify, socket, app, utils, config */ + +define('forum/account/edit/username', ['forum/account/header'], function(header) { + var AccountEditUsername = {}; + + AccountEditUsername.init = function() { + header.init(); + + $('#submitBtn').on('click', function updateUsername() { + var userData = { + uid: $('#inputUID').val(), + username: $('#inputNewUsername').val(), + password: $('#inputCurrentPassword').val() + }; + + if (!userData.username) { + return; + } + var btn = $(this); + btn.addClass('disabled').find('i').removeClass('hide'); + socket.emit('user.changeUsernameEmail', userData, function(err) { + btn.removeClass('disabled').find('i').addClass('hide'); + if (err) { + return app.alertError(err.message); + } + + var userslug = utils.slugify(userData.username); + if (userData.username && userslug && parseInt(userData.uid, 10) === parseInt(app.user.uid, 10)) { + $('[component="header/profilelink"]').attr('href', config.relative_path + '/user/' + userslug); + $('[component="header/username"]').text(userData.username); + } + + ajaxify.go('user/' + userslug); + }); + + return false; + }); + }; + + return AccountEditUsername; +}); diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index 1120ad65e3..a84dc7c991 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -14,6 +14,48 @@ var async = require('async'), var editController = {}; editController.get = function(req, res, callback) { + accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, function(err, userData) { + if (err || !userData) { + return callback(err); + } + + userData.title = '[[pages:account/edit, ' + userData.username + ']]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]); + + res.render('account/edit', userData); + }); +}; + +editController.password = function(req, res, next) { + renderRoute('password', req, res, next); +}; + +editController.username = function(req, res, next) { + renderRoute('username', req, res, next); +}; + +editController.email = function(req, res, next) { + renderRoute('email', req, res, next); +}; + +function renderRoute(name, req, res, next) { + getUserData(req, next, function(err, userData) { + if (err) { + return next(err); + } + + userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([ + {text: userData.username, url: '/user/' + userData.userslug}, + {text: '[[user:edit]]', url: '/user/' + userData.userslug + '/edit'}, + {text: '[[user:' + name + ']]'} + ]); + + res.render('account/edit/' + name, userData); + }); +} + +function getUserData(req, next, callback) { var userData; async.waterfall([ function(next) { @@ -22,7 +64,7 @@ editController.get = function(req, res, callback) { function(data, next) { userData = data; if (!userData) { - return callback(); + return next(); } db.getObjectField('user:' + userData.uid, 'password', next); } @@ -33,13 +75,9 @@ editController.get = function(req, res, callback) { userData['username:disableEdit'] = parseInt(meta.config['username:disableEdit'], 10) === 1; userData.hasPassword = !!password; - userData.title = '[[pages:account/edit, ' + userData.username + ']]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]); - - res.render('account/edit', userData); + callback(null, userData); }); -}; - +} editController.uploadPicture = function (req, res, next) { var userPhoto = req.files.files[0]; diff --git a/src/routes/accounts.js b/src/routes/accounts.js index 40163e095c..3b486ef5e5 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -17,6 +17,9 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.posts.getFavourites); setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.posts.getWatchedTopics); setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.edit.get); + setupPageRoute(app, '/user/:userslug/edit/username', middleware, accountMiddlewares, controllers.accounts.edit.username); + setupPageRoute(app, '/user/:userslug/edit/email', middleware, accountMiddlewares, controllers.accounts.edit.email); + setupPageRoute(app, '/user/:userslug/edit/password', middleware, accountMiddlewares, controllers.accounts.edit.password); setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 99b4299be1..ad7e4d3b52 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -116,19 +116,6 @@ SocketUser.reset.commit = function(socket, data, callback) { }); }; - -SocketUser.isAdminOrSelf = function(socket, uid, callback) { - if (socket.uid === parseInt(uid, 10)) { - return callback(); - } - user.isAdministrator(socket.uid, function(err, isAdmin) { - if (err || !isAdmin) { - return callback(err || new Error('[[error:no-privileges]]')); - } - callback(); - }); -}; - SocketUser.follow = function(socket, data, callback) { if (!socket.uid || !data) { return; @@ -182,7 +169,7 @@ SocketUser.saveSettings = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - SocketUser.isAdminOrSelf(socket, data.uid, function(err) { + user.isAdminOrSelf(socket.uid, data.uid, function(err) { if (err) { return callback(err); } diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index cc424f2b74..08d6263f3d 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -28,7 +28,7 @@ module.exports = function(SocketUser) { async.waterfall([ function (next) { - SocketUser.isAdminOrSelf(socket, data.uid, next); + user.isAdminOrSelf(socket.uid, data.uid, next); }, function (next) { user.getUserField(data.uid, type, next); @@ -44,7 +44,7 @@ module.exports = function(SocketUser) { return; } - SocketUser.isAdminOrSelf(socket, data.uid, function(err) { + user.isAdminOrSelf(socket.uid, data.uid, function(err) { if (err) { return callback(err); } @@ -61,7 +61,7 @@ module.exports = function(SocketUser) { async.waterfall([ function (next) { - SocketUser.isAdminOrSelf(socket, data.uid, next); + user.isAdminOrSelf(socket.uid, data.uid, next); }, function (next) { user.getUserField(data.uid, 'uploadedpicture', next); diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index d966396a30..396fb0b265 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -8,6 +8,47 @@ var events = require('../../events'); module.exports = function(SocketUser) { + SocketUser.changeUsernameEmail = function(socket, data, callback) { + if (!data || !data.uid || !socket.uid) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.waterfall([ + function (next) { + isAdminOrSelfAndPasswordMatch(socket.uid, data, next); + }, + function (next) { + SocketUser.updateProfile(socket, data, next); + } + ], callback); + }; + + function isAdminOrSelfAndPasswordMatch(uid, data, callback) { + async.parallel({ + isAdmin: function(next) { + user.isAdministrator(uid, next); + }, + passwordMatch: function(next) { + user.isPasswordCorrect(data.uid, data.password, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + var self = parseInt(uid, 10) === parseInt(data.uid, 10); + + if (!results.isAdmin && !self) { + return callback(new Error('[[error:no-privileges]]')); + } + + if (self && !results.passwordMatch) { + return callback(new Error('[[error:invalid-password]]')); + } + + callback(); + }); + } + SocketUser.changePassword = function(socket, data, callback) { if (!data || !data.uid || data.newPassword.length < meta.config.minimumPasswordLength) { return callback(new Error('[[error:invalid-data]]')); @@ -31,7 +72,6 @@ module.exports = function(SocketUser) { }); }; - SocketUser.updateProfile = function(socket, data, callback) { if (!socket.uid) { return callback('[[error:invalid-uid]]'); @@ -55,9 +95,10 @@ module.exports = function(SocketUser) { if (parseInt(meta.config['username:disableEdit'], 10) === 1) { data.username = oldUserData.username; } - SocketUser.isAdminOrSelf(socket, data.uid, next); + user.isAdminOrSelf(socket.uid, data.uid, next); }, function (next) { + console.log('updating profile', data) user.updateProfile(data.uid, data, next); }, function (userData, next) { diff --git a/src/user.js b/src/user.js index 81b9bbafab..0ea081a66e 100644 --- a/src/user.js +++ b/src/user.js @@ -3,14 +3,11 @@ var async = require('async'), nconf = require('nconf'), gravatar = require('gravatar'), - validator = require('validator'), plugins = require('./plugins'), db = require('./database'), meta = require('./meta'), topics = require('./topics'), - groups = require('./groups'), - Password = require('./password'), privileges = require('./privileges'), utils = require('../public/src/utils'); @@ -36,6 +33,7 @@ var async = require('async'), require('./user/picture')(User); require('./user/approval')(User); require('./user/invite')(User); + require('./user/password')(User); User.updateLastOnlineTime = function(uid, callback) { callback = callback || function() {}; @@ -160,14 +158,6 @@ var async = require('async'), return gravatar.url(email, options, true); }; - User.hashPassword = function(password, callback) { - if (!password) { - return callback(null, password); - } - - Password.hash(nconf.get('bcrypt_rounds') || 12, password, callback); - }; - User.exists = function(uid, callback) { db.isSortedSetMember('users:joindate', uid, callback); }; @@ -176,7 +166,7 @@ var async = require('async'), User.getUidByUserslug(userslug, function(err, exists) { callback(err, !! exists); }); - } + }; User.getUidByUsername = function(username, callback) { if (!username) { @@ -242,6 +232,18 @@ var async = require('async'), privileges.users.isAdministrator(uid, callback); }; + User.isAdminOrSelf = function(callerUid, uid, callback) { + if (parseInt(callerUid, 10) === parseInt(uid, 10)) { + return callback(); + } + User.isAdministrator(callerUid, function(err, isAdmin) { + if (err || !isAdmin) { + return callback(err || new Error('[[error:no-privileges]]')); + } + callback(); + }); + }; + }(exports)); diff --git a/src/user/password.js b/src/user/password.js new file mode 100644 index 0000000000..0c15cd07e7 --- /dev/null +++ b/src/user/password.js @@ -0,0 +1,28 @@ +'use strict'; + +var nconf = require('nconf'); + +var db = require('../database'); +var Password = require('../password'); + +module.exports = function(User) { + + User.hashPassword = function(password, callback) { + if (!password) { + return callback(null, password); + } + + Password.hash(nconf.get('bcrypt_rounds') || 12, password, callback); + }; + + User.isPasswordCorrect = function(uid, password, callback) { + db.getObjectField('user:' + uid, 'password', function(err, hashedPassword) { + if (err || !hashedPassword) { + return callback(err); + } + + Password.compare(password || '', hashedPassword, callback); + }); + }; + +}; \ No newline at end of file diff --git a/src/user/profile.js b/src/user/profile.js index 95272d55fe..bf297847a5 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -273,30 +273,21 @@ module.exports = function(User) { return callback(new Error('[[user:change_password_error]]')); } - if(parseInt(uid, 10) !== parseInt(data.uid, 10)) { + if (parseInt(uid, 10) !== parseInt(data.uid, 10)) { User.isAdministrator(uid, function(err, isAdmin) { - if(err || !isAdmin) { + if (err || !isAdmin) { return callback(err || new Error('[[user:change_password_error_privileges')); } hashAndSetPassword(callback); }); } else { - db.getObjectField('user:' + uid, 'password', function(err, currentPassword) { - if(err) { - return callback(err); + User.isPasswordCorrect(uid, data.currentPassword, function(err, correct) { + if (err || !correct) { + return callback(err || new Error('[[user:change_password_error_wrong_current]]')); } - if (!currentPassword) { - return hashAndSetPassword(callback); - } - - Password.compare(data.currentPassword, currentPassword, function(err, res) { - if (err || !res) { - return callback(err || new Error('[[user:change_password_error_wrong_current]]')); - } - hashAndSetPassword(callback); - }); + hashAndSetPassword(callback); }); } }; From 974ccf13f0569cfb96bb95aa85806812ef71dae6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Oct 2015 18:00:57 -0400 Subject: [PATCH 14/91] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 71bc376bf7..12e2f70059 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.47", - "nodebb-theme-vanilla": "4.0.20", + "nodebb-theme-persona": "3.0.48", + "nodebb-theme-vanilla": "4.0.21", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", "passport": "^0.3.0", From 1ceb1c6d5fe830f525b10eb71b147850ea316723 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Oct 2015 23:55:04 -0400 Subject: [PATCH 15/91] fix dismissAllFlags --- src/posts/flags.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/posts/flags.js b/src/posts/flags.js index cbfa1f832c..47959a8d88 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -87,7 +87,12 @@ module.exports = function(Posts) { }; Posts.dismissAllFlags = function(callback) { - db.delete('posts:flagged', callback); + db.getSortedSetRange('posts:flagged', 0, -1, function(err, pids) { + if (err) { + return callback(err); + } + async.eachLimit(pids, 50, Posts.dismissFlag, callback); + }); }; Posts.getFlags = function(set, uid, start, stop, callback) { From 8a51c5a023a14d59d00f738827b5f696b93c2bd7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 10 Oct 2015 18:06:48 -0400 Subject: [PATCH 16/91] fix tooltip --- public/src/client/topic/postTools.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 002849f167..3221051d40 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -55,6 +55,13 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator } function createTooltip(el, data) { + function doCreateTooltip(title) { + el.attr('title', title).tooltip('fixTitle').tooltip('show'); + el.on('hidden.bs.tooltip', function() { + el.tooltip('destroy'); + el.off('hidden.bs.tooltip'); + }); + } var usernames = data.usernames; if (!usernames.length) { return; @@ -63,11 +70,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator 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'); + doCreateTooltip(translated); }); } else { usernames = usernames.join(', '); - el.attr('title', usernames).tooltip('destroy').tooltip('show'); + doCreateTooltip(usernames); } } From d5815194205acd69a3bf9dba8fc710ea02c0df4a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 10 Oct 2015 18:20:23 -0400 Subject: [PATCH 17/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12e2f70059..eb2eea0066 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.48", + "nodebb-theme-persona": "3.0.49", "nodebb-theme-vanilla": "4.0.21", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From d0150d0be23a9376d149ede8170a0334f00d8770 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 10 Oct 2015 19:02:29 -0400 Subject: [PATCH 18/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb2eea0066..387564f033 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.49", + "nodebb-theme-persona": "3.0.50", "nodebb-theme-vanilla": "4.0.21", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From fca2cf0a1002f6304e8fda8114f9731bec6a87a3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 14:27:00 -0400 Subject: [PATCH 19/91] closes #3745 --- public/src/client/topic/postTools.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 3221051d40..d6052798d7 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -143,7 +143,6 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator function onReplyClicked(button, tid, topicName) { showStaleWarning(function(proceed) { - console.log('proceed is', proceed); if (!proceed) { var selectionText = '', selection = window.getSelection ? window.getSelection() : document.selection.createRange(); From 79d5eea46d4b57d0fc89449960565fe8aef8b766 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 17:29:47 -0400 Subject: [PATCH 20/91] closes #3270 --- public/src/client/topic/posts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 222c07f98b..6ae9fbc8cb 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -221,7 +221,7 @@ define('forum/topic/posts', [ } }); postTools.updatePostCount(); - addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote')); + addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote')); hidePostToolsForDeletedPosts(posts); showBottomPostBar(); }; From c8fb68c2d509bde1d86dc2567a675bcea333d0bf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 21:56:28 -0400 Subject: [PATCH 21/91] shorter sort methods --- src/search.js | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/search.js b/src/search.js index ffd19b1cd6..33fc7ffca8 100644 --- a/src/search.js +++ b/src/search.js @@ -315,16 +315,12 @@ function sortPosts(posts, data) { } data.sortBy = data.sortBy || 'timestamp'; data.sortDirection = data.sortDirection || 'desc'; + var direction = data.sortDirection === 'desc' ? 1 : -1; + if (data.sortBy === 'timestamp') { - if (data.sortDirection === 'desc') { - posts.sort(function(p1, p2) { - return p2.timestamp - p1.timestamp; - }); - } else { - posts.sort(function(p1, p2) { - return p1.timestamp - p2.timestamp; - }); - } + posts.sort(function(p1, p2) { + return direction * (p2.timestamp - p1.timestamp); + }); return; } @@ -336,21 +332,13 @@ function sortPosts(posts, data) { return; } - var value = firstPost[fields[0]][fields[1]]; - var isNumeric = utils.isNumber(value); + var isNumeric = utils.isNumber(firstPost[fields[0]][fields[1]]); if (isNumeric) { - if (data.sortDirection === 'desc') { - posts.sort(function(p1, p2) { - return p2[fields[0]][fields[1]] - p1[fields[0]][fields[1]]; - }); - } else { - posts.sort(function(p1, p2) { - return p1[fields[0]][fields[1]] - p2[fields[0]][fields[1]]; - }); - } + posts.sort(function(p1, p2) { + return direction * (p2[fields[0]][fields[1]] - p1[fields[0]][fields[1]]); + }); } else { - var direction = data.sortDirection === 'desc' ? 1 : -1; posts.sort(function(p1, p2) { if (p1[fields[0]][fields[1]] > p2[fields[0]][fields[1]]) { return direction; From 9ac4704848ae36e4556a64a9dc1d34f7c5d1fd84 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 22:27:53 -0400 Subject: [PATCH 22/91] more search cleanup --- public/src/admin/manage/tags.js | 8 +++--- public/src/client/tags.js | 2 +- src/search.js | 43 ++++++--------------------------- src/topics/tags.js | 26 ++++++++++++-------- 4 files changed, 29 insertions(+), 50 deletions(-) diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index d85df5c266..27c5b2459b 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -1,5 +1,5 @@ "use strict"; -/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/ +/*global define, socket, app, utils, bootbox*/ define('admin/manage/tags', [ 'forum/infinitescroll', @@ -25,12 +25,12 @@ define('admin/manage/tags', [ } timeoutId = setTimeout(function() { - socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, tags) { + socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, result) { if (err) { return app.alertError(err.message); } - infinitescroll.parseAndTranslate('admin/manage/tags', 'tags', {tags: tags}, function(html) { + infinitescroll.parseAndTranslate('admin/manage/tags', 'tags', {tags: result.tags}, function(html) { $('.tag-list').html(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); timeoutId = 0; @@ -43,7 +43,7 @@ define('admin/manage/tags', [ } function handleModify() { - $('#modify').on('click', function(ev) { + $('#modify').on('click', function() { var tagsToModify = $('.tag-row.selected'); if (!tagsToModify.length) { return; diff --git a/public/src/client/tags.js b/public/src/client/tags.js index 4763d3aa44..dedeb4a746 100644 --- a/public/src/client/tags.js +++ b/public/src/client/tags.js @@ -24,7 +24,7 @@ define('forum/tags', ['forum/infinitescroll'], function(infinitescroll) { if (err) { return app.alertError(err.message); } - onTagsLoaded(results, true, function() { + onTagsLoaded(results.tags, true, function() { timeoutId = 0; }); }); diff --git a/src/search.js b/src/search.js index 33fc7ffca8..94bc4a708f 100644 --- a/src/search.js +++ b/src/search.js @@ -22,15 +22,13 @@ search.search = function(data, callback) { return callback(err); } - result.search_query = validator.escape(query); + data.search_query = validator.escape(query); if (searchIn === 'titles' || searchIn === 'titlesposts') { searchIn = 'posts'; } - result[searchIn] = data.matches; - result.matchCount = data.matchCount; - result.pageCount = data.pageCount; - result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2); - callback(null, result); + + data.time = (process.elapsedTimeSince(start) / 1000).toFixed(2); + callback(null, data); } var start = process.hrtime(); @@ -38,18 +36,12 @@ search.search = function(data, callback) { var query = data.query; var searchIn = data.searchIn || 'titlesposts'; - var result = { - posts: [], - users: [], - tags: [] - }; - if (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts') { searchInContent(data, done); } else if (searchIn === 'users') { - searchInUsers(data, done); + user.search(data, done); } else if (searchIn === 'tags') { - searchInTags(query, done); + topics.searchAndLoadTags(data, done); } else { callback(new Error('[[error:unknown-search-filter]]')); } @@ -91,7 +83,7 @@ function searchInContent(data, callback) { var matchCount = 0; if (!results || (!results.pids.length && !results.tids.length)) { - return callback(null, {matches: [], matchCount: matchCount, pageCount: 1}); + return callback(null, {posts: [], matchCount: matchCount, pageCount: 1}); } async.waterfall([ @@ -118,7 +110,7 @@ function searchInContent(data, callback) { posts.getPostSummaryByPids(pids, data.uid, {}, next); }, function(posts, next) { - next(null, {matches: posts, matchCount: matchCount, pageCount: Math.max(1, Math.ceil(parseInt(matchCount, 10) / 10))}); + next(null, {posts: posts, matchCount: matchCount, pageCount: Math.max(1, Math.ceil(parseInt(matchCount, 10) / 10))}); } ], callback); }); @@ -423,25 +415,6 @@ function getSearchUids(data, callback) { } } -function searchInUsers(data, callback) { - user.search(data, function(err, results) { - if (err) { - return callback(err); - } - callback(null, {matches: results.users, matchCount: results.matchCount, pageCount: results.pageCount}); - }); -} - -function searchInTags(query, callback) { - topics.searchAndLoadTags({query: query}, function(err, tags) { - if (err) { - return callback(err); - } - - callback(null, {matches: tags, matchCount: tags.length, pageCount: 1}); - }); -} - search.searchQuery = function(index, content, cids, uids, callback) { plugins.fireHook('filter:search.query', { index: index, diff --git a/src/topics/tags.js b/src/topics/tags.js index fe2e8d8ea2..54514ddc42 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -2,12 +2,12 @@ 'use strict'; var async = require('async'), - winston = require('winston'), + db = require('../database'), meta = require('../meta'), _ = require('underscore'), - plugins = require('../plugins'), - utils = require('../../public/src/utils'); + plugins = require('../plugins'); + module.exports = function(Topics) { @@ -248,7 +248,7 @@ module.exports = function(Topics) { }; Topics.searchTags = function(data, callback) { - if (!data) { + if (!data || !data.query) { return callback(null, []); } @@ -256,9 +256,7 @@ module.exports = function(Topics) { if (err) { return callback(null, []); } - if (data.query === '') { - return callback(null, tags); - } + data.query = data.query.toLowerCase(); var matches = []; @@ -279,8 +277,14 @@ module.exports = function(Topics) { }; Topics.searchAndLoadTags = function(data, callback) { + var searchResult = { + tags: [], + matchCount: 0, + pageCount: 1 + }; + if (!data.query || !data.query.length) { - return callback(null, []); + return callback(null, searchResult); } Topics.searchTags(data, function(err, tags) { if (err) { @@ -307,8 +311,10 @@ module.exports = function(Topics) { results.tagData.sort(function(a, b) { return b.score - a.score; }); - - callback(null, results.tagData); + searchResult.tags = results.tagData; + searchResult.matchCount = results.tagData.length; + searchResult.pageCount = 1; + callback(null, searchResult); }); }); }; From da4034a10aedb7fa4964b2fd0f3560ca2ccb7dfa Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 23:05:33 -0400 Subject: [PATCH 23/91] middleware refactor --- src/controllers/search.js | 7 +- src/middleware/header.js | 174 +++++++++++++++++++++ src/middleware/middleware.js | 292 +++-------------------------------- src/middleware/render.js | 83 ++++++++++ src/routes/accounts.js | 2 +- src/routes/index.js | 2 +- 6 files changed, 285 insertions(+), 275 deletions(-) create mode 100644 src/middleware/header.js create mode 100644 src/middleware/render.js diff --git a/src/controllers/search.js b/src/controllers/search.js index 82257f1a64..e32ab67c4b 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -2,7 +2,8 @@ 'use strict'; var async = require('async'), - validator = require('validator'), + + meta = require('../meta'), plugins = require('../plugins'), search = require('../search'), categories = require('../categories'), @@ -17,6 +18,10 @@ searchController.search = function(req, res, next) { return next(); } + if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) { + return helpers.notAllowed(req, res); + } + var page = Math.max(1, parseInt(req.query.page, 10)) || 1; if (req.query.categories && !Array.isArray(req.query.categories)) { req.query.categories = [req.query.categories]; diff --git a/src/middleware/header.js b/src/middleware/header.js new file mode 100644 index 0000000000..edd7306fa1 --- /dev/null +++ b/src/middleware/header.js @@ -0,0 +1,174 @@ +'use strict'; + +var async = require('async'); +var nconf = require('nconf'); + +var user = require('../user'); +var meta = require('../meta'); +var plugins = require('../plugins'); +var navigation = require('../navigation'); +var translator = require('../../public/src/modules/translator'); + +var controllers = { + api: require('../controllers/api'), + helpers: require('../controllers/helpers') +}; + +module.exports = function(app, middleware) { + + middleware.buildHeader = function(req, res, next) { + res.locals.renderHeader = true; + res.locals.isAPI = false; + + middleware.applyCSRF(req, res, function() { + async.parallel({ + config: function(next) { + controllers.api.getConfig(req, res, next); + }, + footer: function(next) { + app.render('footer', {loggedIn: (req.user ? parseInt(req.user.uid, 10) !== 0 : false)}, next); + }, + plugins: function(next) { + plugins.fireHook('filter:middleware.buildHeader', {req: req, locals: res.locals}, next); + } + }, function(err, results) { + if (err) { + return next(err); + } + + res.locals.config = results.config; + + translator.translate(results.footer, results.config.defaultLang, function(parsedTemplate) { + res.locals.footer = parsedTemplate; + next(); + }); + }); + }); + }; + + middleware.renderHeader = function(req, res, data, callback) { + var registrationType = meta.config.registrationType || 'normal'; + var templateValues = { + bootswatchCSS: meta.config['theme:src'], + title: meta.config.title || '', + description: meta.config.description || '', + 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', + 'brand:logo': meta.config['brand:logo'] || '', + 'brand:logo:url': meta.config['brand:logo:url'] || '', + 'brand:logo:alt': meta.config['brand:logo:alt'] || '', + 'brand:logo:display': meta.config['brand:logo']?'':'hide', + allowRegistration: registrationType === 'normal' || registrationType === 'admin-approval', + searchEnabled: plugins.hasListeners('filter:search.query'), + config: res.locals.config, + relative_path: nconf.get('relative_path') + }; + + templateValues.configJSON = JSON.stringify(res.locals.config); + + async.parallel({ + customCSS: function(next) { + templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1; + if (!templateValues.useCustomCSS || !meta.config.customCSS || !meta.config.renderedCustomCSS) { + return next(null, ''); + } + next(null, meta.config.renderedCustomCSS); + }, + customJS: function(next) { + templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1; + next(null, templateValues.useCustomJS ? meta.config.customJS : ''); + }, + settings: function(next) { + if (req.uid) { + user.getSettings(req.uid, next); + } else { + next(); + } + }, + title: function(next) { + next(null, controllers.helpers.buildTitle(data.title)); + }, + isAdmin: function(next) { + user.isAdministrator(req.uid, next); + }, + user: function(next) { + if (req.uid) { + user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'status', 'email:confirmed', 'banned'], next); + } else { + next(null, { + username: '[[global:guest]]', + userslug: '', + picture: user.createGravatarURLFromEmail(''), + status: 'offline', + banned: false, + uid: 0 + }); + } + }, + navigation: async.apply(navigation.get), + tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags) + }, function(err, results) { + if (err) { + return callback(err); + } + + if (results.user && parseInt(results.user.banned, 10) === 1) { + req.logout(); + return res.redirect('/'); + } + results.user.isAdmin = results.isAdmin || false; + results.user.uid = parseInt(results.user.uid, 10); + results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; + + if (results.settings && results.settings.bootswatchSkin && results.settings.bootswatchSkin !== 'default') { + templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + results.settings.bootswatchSkin + '/bootstrap.min.css'; + } + + templateValues.browserTitle = results.title; + templateValues.navigation = results.navigation; + templateValues.metaTags = results.tags.meta; + templateValues.linkTags = results.tags.link; + templateValues.isAdmin = results.user.isAdmin; + templateValues.user = results.user; + templateValues.userJSON = JSON.stringify(results.user); + templateValues.customCSS = results.customCSS; + templateValues.customJS = results.customJS; + templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin; + templateValues.defaultLang = meta.config.defaultLang || 'en_GB'; + + templateValues.template = {name: res.locals.template}; + templateValues.template[res.locals.template] = true; + + if (req.route && req.route.path === '/') { + modifyTitle(templateValues); + } + + plugins.fireHook('filter:middleware.renderHeader', {templateValues: templateValues, req: req, res: res}, function(err, data) { + if (err) { + return callback(err); + } + + app.render('header', data.templateValues, callback); + }); + }); + }; + + + function modifyTitle(obj) { + var title = controllers.helpers.buildTitle('[[pages:home]]'); + obj.browserTitle = title; + + if (obj.metaTags) { + obj.metaTags.forEach(function(tag, i) { + if (tag.property === 'og:title') { + obj.metaTags[i].content = title; + } + }); + } + + return title; + } + +}; + + + diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 6049970468..d7c06ae4aa 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -7,21 +7,16 @@ var app, async = require('async'), path = require('path'), csrf = require('csurf'), - winston = require('winston'), + validator = require('validator'), nconf = require('nconf'), ensureLoggedIn = require('connect-ensure-login'), plugins = require('../plugins'), - navigation = require('../navigation'), meta = require('../meta'), - translator = require('../../public/src/modules/translator'), user = require('../user'), groups = require('../groups'), - db = require('../database'), - categories = require('../categories'), - topics = require('../topics'), - messaging = require('../messaging'), + analytics = require('../analytics'), @@ -87,14 +82,6 @@ middleware.redirectToAccountIfLoggedIn = function(req, res, next) { }); }; -middleware.redirectToLoginIfGuest = function(req, res, next) { - if (!req.user || parseInt(req.user.uid, 10) === 0) { - return redirectToLogin(req, res); - } - - next(); -}; - middleware.validateFiles = function(req, res, next) { if (!Array.isArray(req.files.files) || !req.files.files.length) { return next(new Error(['[[error:invalid-files]]'])); @@ -108,14 +95,6 @@ middleware.prepareAPI = function(req, res, next) { next(); }; -middleware.guestSearchingAllowed = function(req, res, next) { - if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) { - return controllers.helpers.notAllowed(req, res); - } - - next(); -}; - middleware.checkGlobalPrivacySettings = function(req, res, next) { if (!req.user && !!parseInt(meta.config.privateUserInfo, 10)) { return controllers.helpers.notAllowed(req, res); @@ -149,11 +128,11 @@ middleware.checkAccountPermissions = function(req, res, next) { }; middleware.isAdmin = function(req, res, next) { - if (!req.user) { - return redirectToLogin(req, res); + if (!req.uid) { + return controllers.helpers.notAllowed(req, res); } - user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) { + user.isAdministrator(req.uid, function (err, isAdmin) { if (err || isAdmin) { return next(err); } @@ -168,218 +147,7 @@ middleware.isAdmin = function(req, res, next) { }); }; -middleware.buildHeader = function(req, res, next) { - res.locals.renderHeader = true; - res.locals.isAPI = false; - middleware.applyCSRF(req, res, function() { - async.parallel({ - config: function(next) { - controllers.api.getConfig(req, res, next); - }, - footer: function(next) { - app.render('footer', {loggedIn: (req.user ? parseInt(req.user.uid, 10) !== 0 : false)}, next); - }, - plugins: function(next) { - plugins.fireHook('filter:middleware.buildHeader', {req: req, locals: res.locals}, next); - } - }, function(err, results) { - if (err) { - return next(err); - } - - res.locals.config = results.config; - - translator.translate(results.footer, results.config.defaultLang, function(parsedTemplate) { - res.locals.footer = parsedTemplate; - next(); - }); - }); - }); -}; - -middleware.renderHeader = function(req, res, data, callback) { - var registrationType = meta.config.registrationType || 'normal'; - var templateValues = { - bootswatchCSS: meta.config['theme:src'], - title: meta.config.title || '', - description: meta.config.description || '', - 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', - 'brand:logo': meta.config['brand:logo'] || '', - 'brand:logo:url': meta.config['brand:logo:url'] || '', - 'brand:logo:alt': meta.config['brand:logo:alt'] || '', - 'brand:logo:display': meta.config['brand:logo']?'':'hide', - allowRegistration: registrationType === 'normal' || registrationType === 'admin-approval', - searchEnabled: plugins.hasListeners('filter:search.query'), - config: res.locals.config, - relative_path: nconf.get('relative_path') - }; - - templateValues.configJSON = JSON.stringify(res.locals.config); - - async.parallel({ - customCSS: function(next) { - templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1; - if (!templateValues.useCustomCSS || !meta.config.customCSS || !meta.config.renderedCustomCSS) { - return next(null, ''); - } - next(null, meta.config.renderedCustomCSS); - }, - customJS: function(next) { - templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1; - next(null, templateValues.useCustomJS ? meta.config.customJS : ''); - }, - settings: function(next) { - if (req.uid) { - user.getSettings(req.uid, next); - } else { - next(); - } - }, - title: function(next) { - next(null, controllers.helpers.buildTitle(data.title)); - }, - isAdmin: function(next) { - user.isAdministrator(req.uid, next); - }, - user: function(next) { - if (req.uid) { - user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'status', 'email:confirmed', 'banned'], next); - } else { - next(null, { - username: '[[global:guest]]', - userslug: '', - picture: user.createGravatarURLFromEmail(''), - status: 'offline', - banned: false, - uid: 0 - }); - } - }, - navigation: async.apply(navigation.get), - tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags) - }, function(err, results) { - if (err) { - return callback(err); - } - - if (results.user && parseInt(results.user.banned, 10) === 1) { - req.logout(); - return res.redirect('/'); - } - results.user.isAdmin = results.isAdmin || false; - results.user.uid = parseInt(results.user.uid, 10); - results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; - - if (results.settings && results.settings.bootswatchSkin && results.settings.bootswatchSkin !== 'default') { - templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + results.settings.bootswatchSkin + '/bootstrap.min.css'; - } - - templateValues.browserTitle = results.title; - templateValues.navigation = results.navigation; - templateValues.metaTags = results.tags.meta; - templateValues.linkTags = results.tags.link; - templateValues.isAdmin = results.user.isAdmin; - templateValues.user = results.user; - templateValues.userJSON = JSON.stringify(results.user); - templateValues.customCSS = results.customCSS; - templateValues.customJS = results.customJS; - templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin; - templateValues.defaultLang = meta.config.defaultLang || 'en_GB'; - - templateValues.template = {name: res.locals.template}; - templateValues.template[res.locals.template] = true; - - if (req.route && req.route.path === '/') { - modifyTitle(templateValues); - } - - plugins.fireHook('filter:middleware.renderHeader', {templateValues: templateValues, req: req, res: res}, function(err, data) { - if (err) { - return callback(err); - } - - app.render('header', data.templateValues, callback); - }); - }); -}; - -middleware.processRender = function(req, res, next) { - // res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687 - var render = res.render; - res.render = function(template, options, fn) { - var self = this, - req = this.req, - app = req.app, - defaultFn = function(err, str){ - if (err) { - return req.next(err); - } - - self.send(str); - }; - options = options || {}; - - if ('function' === typeof options) { - fn = options; - options = {}; - } - - options.loggedIn = req.user ? parseInt(req.user.uid, 10) !== 0 : false; - options.relative_path = nconf.get('relative_path'); - options.template = {name: template}; - options.template[template] = true; - res.locals.template = template; - - if ('function' !== typeof fn) { - fn = defaultFn; - } - - if (res.locals.isAPI) { - if (req.route && req.route.path === '/api/') { - options.title = '[[pages:home]]'; - } - - return res.json(options); - } - - var ajaxifyData = encodeURIComponent(JSON.stringify(options)); - render.call(self, template, options, function(err, str) { - if (err) { - winston.error(err); - return fn(err); - } - - str = str + ''; - str = (res.locals.postHeader ? res.locals.postHeader : '') + str + (res.locals.preFooter ? res.locals.preFooter : ''); - - if (res.locals.footer) { - str = str + res.locals.footer; - } else if (res.locals.adminFooter) { - str = str + res.locals.adminFooter; - } - - if (res.locals.renderHeader || res.locals.renderAdminHeader) { - var method = res.locals.renderHeader ? middleware.renderHeader : middleware.admin.renderHeader; - method(req, res, options, function(err, template) { - if (err) { - return fn(err); - } - str = template + str; - var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB'; - language = req.query.lang || language; - translator.translate(str, language, function(translated) { - fn(err, translated); - }); - }); - } else { - fn(err, str); - } - }); - }; - - next(); -}; middleware.routeTouchIcon = function(req, res) { if (meta.config['brand:logo'] && validator.isURL(meta.config['brand:logo'])) { @@ -403,8 +171,6 @@ middleware.addExpiresHeaders = function(req, res, next) { next(); }; - - middleware.privateTagListing = function(req, res, next) { if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) { controllers.helpers.notAllowed(req, res); @@ -414,7 +180,9 @@ middleware.privateTagListing = function(req, res, next) { }; middleware.exposeGroupName = function(req, res, next) { - if (!req.params.hasOwnProperty('slug')) { return next(); } + if (!req.params.hasOwnProperty('slug')) { + return next(); + } groups.getGroupNameByGroupSlug(req.params.slug, function(err, groupName) { if (err) { @@ -427,18 +195,17 @@ middleware.exposeGroupName = function(req, res, next) { }; middleware.exposeUid = function(req, res, next) { - if (req.params.hasOwnProperty('userslug')) { - user.getUidByUserslug(req.params.userslug, function(err, uid) { - if (err) { - return next(err); - } - - res.locals.uid = uid; - next(); - }); - } else { - next(); + if (!req.params.hasOwnProperty('userslug')) { + return nex(); } + user.getUidByUserslug(req.params.userslug, function(err, uid) { + if (err) { + return next(err); + } + + res.locals.uid = uid; + next(); + }); }; middleware.requireUser = function(req, res, next) { @@ -449,32 +216,13 @@ middleware.requireUser = function(req, res, next) { res.render('403', {title: '[[global:403.title]]'}); }; -function redirectToLogin(req, res) { - req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, ''); - return controllers.helpers.redirect(res, '/login'); -} - - - -function modifyTitle(obj) { - var title = controllers.helpers.buildTitle('[[pages:home]]'); - obj.browserTitle = title; - - if (obj.metaTags) { - obj.metaTags.forEach(function(tag, i) { - if (tag.property === 'og:title') { - obj.metaTags[i].content = title; - } - }); - } - - return title; -} module.exports = function(webserver) { app = webserver; middleware.admin = require('./admin')(webserver); + require('./header')(app, middleware); + require('./render')(middleware); require('./maintenance')(middleware); return middleware; diff --git a/src/middleware/render.js b/src/middleware/render.js new file mode 100644 index 0000000000..ba0c06552e --- /dev/null +++ b/src/middleware/render.js @@ -0,0 +1,83 @@ +'use strict'; + +var nconf = require('nconf'); +var translator = require('../../public/src/modules/translator'); + +module.exports = function(middleware) { + + middleware.processRender = function(req, res, next) { + // res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687 + var render = res.render; + res.render = function(template, options, fn) { + var self = this, + req = this.req, + defaultFn = function(err, str){ + if (err) { + return req.next(err); + } + + self.send(str); + }; + options = options || {}; + + if ('function' === typeof options) { + fn = options; + options = {}; + } + + options.loggedIn = req.user ? parseInt(req.user.uid, 10) !== 0 : false; + options.relative_path = nconf.get('relative_path'); + options.template = {name: template}; + options.template[template] = true; + res.locals.template = template; + + if (res.locals.isAPI) { + if (req.route && req.route.path === '/api/') { + options.title = '[[pages:home]]'; + } + + return res.json(options); + } + + if ('function' !== typeof fn) { + fn = defaultFn; + } + + var ajaxifyData = encodeURIComponent(JSON.stringify(options)); + render.call(self, template, options, function(err, str) { + if (err) { + return fn(err); + } + + str = str + ''; + str = (res.locals.postHeader ? res.locals.postHeader : '') + str + (res.locals.preFooter ? res.locals.preFooter : ''); + + if (res.locals.footer) { + str = str + res.locals.footer; + } else if (res.locals.adminFooter) { + str = str + res.locals.adminFooter; + } + + if (res.locals.renderHeader || res.locals.renderAdminHeader) { + var method = res.locals.renderHeader ? middleware.renderHeader : middleware.admin.renderHeader; + method(req, res, options, function(err, template) { + if (err) { + return fn(err); + } + str = template + str; + var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB'; + language = req.query.lang || language; + translator.translate(str, language, function(translated) { + fn(err, translated); + }); + }); + } else { + fn(err, str); + } + }); + }; + + next(); + }; + +}; \ No newline at end of file diff --git a/src/routes/accounts.js b/src/routes/accounts.js index 3b486ef5e5..2d492c6c78 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -23,5 +23,5 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get); - setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.redirectToLoginIfGuest], controllers.accounts.chats.get); + setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.authenticate], controllers.accounts.chats.get); }; diff --git a/src/routes/index.js b/src/routes/index.js index 16a6084cc2..93dc9aaedb 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -30,7 +30,7 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/compose', middleware, [middleware.authenticate], controllers.compose); setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); - setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search.search); + setupPageRoute(app, '/search/:term?', middleware, [], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); } From 17dd1ff485fb0a789d8bccce09f55348d600c08f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 23:07:02 -0400 Subject: [PATCH 24/91] fix next --- src/middleware/middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index d7c06ae4aa..bcb62d9b41 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -196,7 +196,7 @@ middleware.exposeGroupName = function(req, res, next) { middleware.exposeUid = function(req, res, next) { if (!req.params.hasOwnProperty('userslug')) { - return nex(); + return next(); } user.getUidByUserslug(req.params.userslug, function(err, uid) { if (err) { From 94129287d04fa2a4cf171176c4fa7b8aa6782669 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 23:20:57 -0400 Subject: [PATCH 25/91] moved non-async code --- src/middleware/header.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index edd7306fa1..52d96f0d79 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -66,17 +66,6 @@ module.exports = function(app, middleware) { templateValues.configJSON = JSON.stringify(res.locals.config); async.parallel({ - customCSS: function(next) { - templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1; - if (!templateValues.useCustomCSS || !meta.config.customCSS || !meta.config.renderedCustomCSS) { - return next(null, ''); - } - next(null, meta.config.renderedCustomCSS); - }, - customJS: function(next) { - templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1; - next(null, templateValues.useCustomJS ? meta.config.customJS : ''); - }, settings: function(next) { if (req.uid) { user.getSettings(req.uid, next); @@ -84,9 +73,6 @@ module.exports = function(app, middleware) { next(); } }, - title: function(next) { - next(null, controllers.helpers.buildTitle(data.title)); - }, isAdmin: function(next) { user.isAdministrator(req.uid, next); }, @@ -115,7 +101,8 @@ module.exports = function(app, middleware) { req.logout(); return res.redirect('/'); } - results.user.isAdmin = results.isAdmin || false; + + results.user.isAdmin = results.isAdmin; results.user.uid = parseInt(results.user.uid, 10); results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; @@ -123,15 +110,17 @@ module.exports = function(app, middleware) { templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + results.settings.bootswatchSkin + '/bootstrap.min.css'; } - templateValues.browserTitle = results.title; + templateValues.browserTitle = controllers.helpers.buildTitle(data.title); templateValues.navigation = results.navigation; templateValues.metaTags = results.tags.meta; templateValues.linkTags = results.tags.link; templateValues.isAdmin = results.user.isAdmin; templateValues.user = results.user; templateValues.userJSON = JSON.stringify(results.user); - templateValues.customCSS = results.customCSS; - templateValues.customJS = results.customJS; + templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS; + templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : ''; + templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1; + templateValues.customJS = templateValues.useCustomJS ? meta.config.customJS : ''; templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin; templateValues.defaultLang = meta.config.defaultLang || 'en_GB'; From 3a3c5486b7d8aa308b7b2cd7957e8000bd365635 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 11 Oct 2015 23:31:33 -0400 Subject: [PATCH 26/91] expose method --- src/middleware/middleware.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index bcb62d9b41..fa06d7f910 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -64,7 +64,7 @@ middleware.pageView = function(req, res, next) { middleware.pluginHooks = function(req, res, next) { async.each(plugins.loadedHooks['filter:router.page'] || [], function(hookObj, next) { hookObj.method(req, res, next); - }, function(req, res) { + }, function() { // If it got here, then none of the subscribed hooks did anything, or there were no hooks next(); }); @@ -180,33 +180,26 @@ middleware.privateTagListing = function(req, res, next) { }; middleware.exposeGroupName = function(req, res, next) { - if (!req.params.hasOwnProperty('slug')) { - return next(); - } - - groups.getGroupNameByGroupSlug(req.params.slug, function(err, groupName) { - if (err) { - return next(err); - } - - res.locals.groupName = groupName; - next(); - }); + expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next); }; middleware.exposeUid = function(req, res, next) { - if (!req.params.hasOwnProperty('userslug')) { + expose('uid', user.getUidByUserslug, 'userslug', req, res, next); +}; + +function expose(exposedField, method, field, req, res, next) { + if (!req.params.hasOwnProperty(field)) { return next(); } - user.getUidByUserslug(req.params.userslug, function(err, uid) { + method(req.params[field], function(err, id) { if (err) { return next(err); } - res.locals.uid = uid; + res.locals[exposedField] = id; next(); }); -}; +} middleware.requireUser = function(req, res, next) { if (req.user) { From f5e7d1ce01b12150767fb9a3f1992b9b261f172a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 12 Oct 2015 02:18:23 -0400 Subject: [PATCH 27/91] private uploads --- src/middleware/middleware.js | 10 ++++++++++ src/routes/index.js | 11 +---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index fa06d7f910..6215f6a5b7 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -209,6 +209,16 @@ middleware.requireUser = function(req, res, next) { res.render('403', {title: '[[global:403.title]]'}); }; +middleware.privateUploads = function(req, res, next) { + if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { + return next(); + } + if (req.path.startsWith('/uploads/files')) { + return res.status(403).json('not-allowed'); + } + next(); +}; + module.exports = function(webserver) { app = webserver; diff --git a/src/routes/index.js b/src/routes/index.js index 93dc9aaedb..ed872f9835 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,7 +4,6 @@ var nconf = require('nconf'), path = require('path'), winston = require('winston'), controllers = require('../controllers'), - meta = require('../meta'), plugins = require('../plugins'), express = require('express'), @@ -118,15 +117,7 @@ module.exports = function(app, middleware) { require('./debug')(app, middleware, controllers); } - app.use(function(req, res, next) { - if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { - return next(); - } - if (req.path.startsWith('/uploads/files')) { - return res.status(403).json('not-allowed'); - } - next(); - }); + app.use(middleware.privateUploads); app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { maxAge: app.enabled('cache') ? 5184000000 : 0 From e2cddefea461ce35be3e74ff7937a7bba036d16d Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 13 Oct 2015 13:06:37 -0400 Subject: [PATCH 28/91] closes #3750 --- .../locales/jquery.timeago.it-short.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js b/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js new file mode 100644 index 0000000000..f4d92ad209 --- /dev/null +++ b/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js @@ -0,0 +1,20 @@ +// Italian shortened +jQuery.timeago.settings.strings = { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "", + suffixFromNow: "", + seconds: "1m", + minute: "1m", + minutes: "%dm", + hour: "1h", + hours: "%dh", + day: "1g", + days: "%dg", + month: "1me", + months: "%dme", + year: "1a", + years: "%da", + wordSeparator: " ", + numbers: [] +}; \ No newline at end of file From de4d747e633c31e68f6d85a1e224d292585d9e3d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 13 Oct 2015 13:58:25 -0400 Subject: [PATCH 29/91] closes #3742 --- public/less/generics.less | 2 +- public/src/admin/manage/category.js | 2 +- src/socket.io/categories.js | 20 +++++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/public/less/generics.less b/public/less/generics.less index f9ef30469a..8104d2a35f 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -9,7 +9,7 @@ .border-radius(3px); &.disabled { - -webkit-filter: grayscale(30%); + background-color: #888!important; .opacity(0.5); } } diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 441afe29d6..0337064ad3 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -240,7 +240,7 @@ define('admin/manage/category', [ } categories = categories.filter(function(category) { - return category && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10); + return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10); }); templates.parse('partials/category_list', { diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 4f6e5c14c0..d43d0c8171 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -15,7 +15,25 @@ SocketCategories.getRecentReplies = function(socket, cid, callback) { }; SocketCategories.get = function(socket, data, callback) { - categories.getCategoriesByPrivilege('categories:cid', socket.uid, 'find', callback); + async.parallel({ + isAdmin: async.apply(user.isAdministrator, socket.uid), + categories: function(next) { + async.waterfall([ + async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), + async.apply(categories.getCategoriesData), + ], next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + results.categories = results.categories.filter(function(category) { + return category && (!category.disabled || results.isAdmin); + }); + + callback(null, results.categories); + }); }; SocketCategories.getWatchedCategories = function(socket, data, callback) { From bc2bf6ef3755dcdf87384d4e8d652187f7e17ffb Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 13 Oct 2015 16:18:54 -0400 Subject: [PATCH 30/91] fix list of allowed filetypes --- src/file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file.js b/src/file.js index 2f503f0309..80f0289a59 100644 --- a/src/file.js +++ b/src/file.js @@ -54,7 +54,7 @@ file.isFileTypeAllowed = function(path, allowedExtensions, callback) { var uploadedFileExtension = mime.extension(mimeType); if (allowedExtensions.indexOf(uploadedFileExtension) === -1) { - return callback(new Error('[[error:invalid-file-type, ' + allowedExtensions.join(', ') + ']]')); + return callback(new Error('[[error:invalid-file-type, ' + allowedExtensions.join(', ') + ']]')); } callback(); From 8ef46ebc7d00747a1aff575911674910c5041f12 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 13 Oct 2015 16:19:52 -0400 Subject: [PATCH 31/91] found some other places with the same problem --- src/controllers/admin/uploads.js | 2 +- src/socket.io/user/picture.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index c299eb5e5c..b57c5db290 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -114,7 +114,7 @@ function validateUpload(req, res, next, uploadedFile, allowedTypes) { } }); - res.json({error: '[[error:invalid-image-type, ' + allowedTypes.join(', ') + ']]'}); + res.json({error: '[[error:invalid-image-type, ' + allowedTypes.join(', ') + ']]'}); return false; } diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index 08d6263f3d..0dbd87b34f 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -23,7 +23,7 @@ module.exports = function(SocketUser) { } else if (type === 'uploaded') { type = 'uploadedpicture'; } else { - return callback(new Error('[[error:invalid-image-type, ' + ['gravatar', 'uploadedpicture'].join(', ') + ']]')); + return callback(new Error('[[error:invalid-image-type, ' + ['gravatar', 'uploadedpicture'].join(', ') + ']]')); } async.waterfall([ From 47e2dd947815f3e83288314c0ae7269e8e8246c3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 13 Oct 2015 16:36:43 -0400 Subject: [PATCH 32/91] closes #3107 --- public/language/en_GB/topic.json | 8 +++- public/src/client/topic/flag.js | 64 +++++++++++++++++++++++++ public/src/client/topic/postTools.js | 34 +++++-------- src/posts/flags.js | 71 ++++++++++++++++++++++++---- src/socket.io/posts/flag.js | 14 ++++-- src/views/admin/manage/flags.tpl | 5 ++ 6 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 public/src/client/topic/flag.js diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index ab2031be4d..c26d0c05f8 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -32,7 +32,6 @@ "bookmark_instructions" : "Click here to return to the last unread post in this thread.", "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 topic has been deleted. Only users with topic management privileges can see it.", @@ -117,5 +116,10 @@ "most_votes": "Most votes", "most_posts": "Most posts", - "stale_topic_warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?" + "stale_topic_warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?", + + "spam": "Spam", + "offensive": "Offensive", + "custom-flag-reason": "Enter a flagging reason" + } diff --git a/public/src/client/topic/flag.js b/public/src/client/topic/flag.js new file mode 100644 index 0000000000..3101c0cd1c --- /dev/null +++ b/public/src/client/topic/flag.js @@ -0,0 +1,64 @@ +'use strict'; + +/* globals define, app, socket, templates, translator */ + +define('forum/topic/flag', [], function() { + + var Flag = {}, + flagModal, + flagCommit; + + Flag.showFlagModal = function(pid) { + parseModal(function(html) { + flagModal = $(html); + + flagModal.on('hidden.bs.modal', function() { + flagModal.remove(); + }); + + flagCommit = flagModal.find('#flag-post-commit'); + + flagModal.on('click', '.flag-reason', function() { + flagPost(pid, $(this).text()); + }); + + flagCommit.on('click', function() { + flagPost(pid, flagModal.find('#flag-reason-custom').val()); + }); + + flagModal.modal('show'); + + flagModal.find('#flag-reason-custom').on('keyup blur change', checkFlagButtonEnable); + }); + }; + + function parseModal(callback) { + templates.parse('partials/modals/flag_post_modal', {}, function(html) { + translator.translate(html, callback); + }); + } + + function flagPost(pid, reason) { + if (!pid || !reason) { + return; + } + socket.emit('posts.flag', {pid: pid, reason: reason}, function(err) { + if (err) { + return app.alertError(err.message); + } + + flagModal.modal('hide'); + app.alertSuccess('[[topic:flag_success]]'); + }); + } + + function checkFlagButtonEnable() { + if (flagModal.find('#flag-reason-custom').val()) { + flagCommit.removeAttr('disabled'); + } else { + flagCommit.attr('disabled', true); + } + } + + return Flag; +}); diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index d6052798d7..4140d5e557 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals define, app, ajaxify, bootbox, socket, templates, utils */ +/* globals define, app, ajaxify, bootbox, socket, templates, utils, config */ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator'], function(share, navigator, components, translator) { @@ -110,33 +110,36 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator }); postContainer.on('click', '[component="post/flag"]', function() { - flagPost(getData($(this), 'data-pid')); + var pid = getData($(this), 'data-pid'); + require(['forum/topic/flag'], function(flag) { + flag.showFlagModal(pid); + }); }); - postContainer.on('click', '[component="post/edit"]', function(e) { + postContainer.on('click', '[component="post/edit"]', function() { var btn = $(this); $(window).trigger('action:composer.post.edit', { pid: getData(btn, 'data-pid') }); }); - postContainer.on('click', '[component="post/delete"]', function(e) { + postContainer.on('click', '[component="post/delete"]', function() { togglePostDelete($(this), tid); }); - postContainer.on('click', '[component="post/restore"]', function(e) { + postContainer.on('click', '[component="post/restore"]', function() { togglePostDelete($(this), tid); }); - postContainer.on('click', '[component="post/purge"]', function(e) { + postContainer.on('click', '[component="post/purge"]', function() { purgePost($(this), tid); }); - postContainer.on('click', '[component="post/move"]', function(e) { + postContainer.on('click', '[component="post/move"]', function() { openMovePostModal($(this)); }); - postContainer.on('click', '[component="post/chat"]', function(e) { + postContainer.on('click', '[component="post/chat"]', function() { openChat($(this)); }); } @@ -369,22 +372,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator }); } - function flagPost(pid) { - translator.translate('[[topic:flag_confirm]]', function(message) { - bootbox.confirm(message, function(confirm) { - if (!confirm) { - return; - } - socket.emit('posts.flag', pid, function(err) { - if (err) { - return app.alertError(err.message); - } - app.alertSuccess('[[topic:flag_success]]'); - }); - }); - }); - } function openChat(button) { var post = button.parents('[data-pid]'); diff --git a/src/posts/flags.js b/src/posts/flags.js index 47959a8d88..3adb6541f2 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -9,7 +9,10 @@ var async = require('async'), module.exports = function(Posts) { - Posts.flag = function(post, uid, callback) { + Posts.flag = function(post, uid, reason, callback) { + if (!parseInt(uid, 10) || !reason) { + return callback(); + } async.parallel({ hasFlagged: async.apply(hasFlagged, post.pid, uid), exists: async.apply(Posts.exists, post.pid) @@ -36,6 +39,9 @@ module.exports = function(Posts) { function(next) { db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); }, + function(next) { + db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); + }, function(next) { if (parseInt(post.uid, 10)) { db.sortedSetAdd('uid:' + post.uid + ':flag:pids', now, post.pid, next); @@ -50,7 +56,7 @@ module.exports = function(Posts) { next(); } } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -80,8 +86,11 @@ module.exports = function(Posts) { }, function(next) { db.delete('pid:' + pid + ':flag:uids', next); + }, + function(next) { + db.delete('pid:' + pid + ':flag:uid:reason', next); } - ], function(err, results) { + ], function(err) { callback(err); }); }; @@ -96,15 +105,56 @@ module.exports = function(Posts) { }; Posts.getFlags = function(set, uid, start, stop, callback) { - db.getSortedSetRevRange(set, start, stop, function(err, pids) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + db.getSortedSetRevRange(set, start, stop, next); + }, + function (pids, next) { + getFlaggedPostsWithReasons(pids, uid, next); } - - Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, callback); - }); + ], callback); }; + function getFlaggedPostsWithReasons(pids, uid, callback) { + async.waterfall([ + function (next) { + async.parallel({ + uidsReasons: function(next) { + async.map(pids, function(pid, next) { + db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next); + }, next); + }, + posts: function(next) { + Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, next); + } + }, next); + }, + function (results, next) { + async.map(results.uidsReasons, function(uidReasons, next) { + async.map(uidReasons, function(uidReason, next) { + var uid = uidReason.split(':')[0]; + var reason = uidReason.substr(uidReason.indexOf(':') + 1); + user.getUserFields(uid, ['username', 'userslug', 'picture'], function(err, userData) { + next(err, {user: userData, reason: reason}); + }); + }, next); + }, function(err, reasons) { + if (err) { + return callback(err); + } + + results.posts.forEach(function(post, index) { + if (post) { + post.flagReasons = reasons[index]; + } + }); + + next(null, results.posts); + }); + } + ], callback); + } + Posts.getUserFlags = function(byUsername, sortBy, callerUID, start, stop, callback) { async.waterfall([ function(next) { @@ -117,7 +167,7 @@ module.exports = function(Posts) { db.getSortedSetRevRange('uid:' + uid + ':flag:pids', 0, -1, next); }, function(pids, next) { - Posts.getPostSummaryByPids(pids, callerUID, {stripTags: false, extraFields: ['flags']}, next); + getFlaggedPostsWithReasons(pids, callerUID, next); }, function(posts, next) { if (sortBy === 'count') { @@ -125,6 +175,7 @@ module.exports = function(Posts) { return b.flags - a.flags; }); } + next(null, posts.slice(start, stop)); } ], callback); diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index 201ec350a5..3e5230db6b 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -13,17 +13,21 @@ var meta = require('../../meta'); module.exports = function(SocketPosts) { - SocketPosts.flag = function(socket, pid, callback) { + SocketPosts.flag = function(socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); } + if (!data || !data.pid || !data.reason) { + return callback(new Error('[[error:invalid-data]]')); + } + var flaggingUser = {}, post; async.waterfall([ function (next) { - posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next); + posts.getPostFields(data.pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next); }, function (postData, next) { if (parseInt(postData.deleted, 10) === 1) { @@ -55,7 +59,7 @@ module.exports = function(SocketPosts) { flaggingUser = user.userData; flaggingUser.uid = socket.uid; - posts.flag(post, socket.uid, next); + posts.flag(post, socket.uid, data.reason, next); }, function (next) { async.parallel({ @@ -74,8 +78,8 @@ module.exports = function(SocketPosts) { notifications.create({ bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + post.topic.title + ']]', bodyLong: post.content, - pid: pid, - nid: 'post_flag:' + pid + ':uid:' + socket.uid, + pid: data.pid, + nid: 'post_flag:' + data.pid + ':uid:' + socket.uid, from: socket.uid }, function(err, notification) { if (err || !notification) { diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index d3ea08f121..2b5ac35f2b 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -58,6 +58,11 @@
{posts.flags} +
+ + {../user.username}: "{../reason}"
+ +


From e2a5440ae8bc34633cb1c2d80d5f87f419a311c4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 13 Oct 2015 19:28:55 -0400 Subject: [PATCH 33/91] closes #3741 --- public/language/en_GB/error.json | 3 +- public/less/admin/manage/groups.less | 17 +++- public/src/admin/manage/group.js | 110 +++++++++++++++++-------- public/src/client/groups/details.js | 100 +++------------------- public/src/client/groups/memberlist.js | 97 ++++++++++++++++++++++ src/controllers/admin/groups.js | 3 +- src/socket.io/admin/groups.js | 25 +++++- src/socket.io/groups.js | 7 +- src/views/admin/header.tpl | 1 + src/views/admin/manage/group.tpl | 18 ++-- 10 files changed, 239 insertions(+), 142 deletions(-) create mode 100644 public/src/client/groups/memberlist.js diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 252e893d0d..167ab1aa66 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -77,7 +77,8 @@ "group-name-too-short": "Group name too short", "group-already-exists": "Group already exists", "group-name-change-not-allowed": "Group name change not allowed", - "group-already-member": "You are already part of this group", + "group-already-member": "Already part of this group", + "group-not-member": "Not a member of this group", "group-needs-owner": "This group requires at least one owner", "group-already-invited": "This user has already been invited", "group-already-requested": "Your membership request has already been submitted", diff --git a/public/less/admin/manage/groups.less b/public/less/admin/manage/groups.less index f6dac84921..ceeba06df7 100644 --- a/public/less/admin/manage/groups.less +++ b/public/less/admin/manage/groups.less @@ -1,5 +1,20 @@ .group { - .current_members { + [component="groups/members"] { padding: 0; + tbody { + max-height: 500px; + display: block; + overflow-y: auto; + padding-bottom: 100px; + .member-name { + width: 100%; + } + } + + + img { + width: 32px; + height: 32px; + } } } \ No newline at end of file diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 37d2f64295..d8d6f4ec23 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -1,16 +1,16 @@ "use strict"; -/*global define, templates, socket, ajaxify, app, admin, bootbox, utils, config */ +/*global define, templates, socket, ajaxify, app, bootbox, translator */ define('admin/manage/group', [ + 'forum/groups/memberlist', 'iconSelect', 'admin/modules/colorpicker' -], function(iconSelect, colorpicker) { +], function(memberList, iconSelect, colorpicker) { var Groups = {}; Groups.init = function() { var groupDetailsSearch = $('#group-details-search'), groupDetailsSearchResults = $('#group-details-search-results'), - groupMembersEl = $('ul.current_members'), groupIcon = $('#group-icon'), changeGroupUserTitle = $('#change-group-user-title'), changeGroupLabelColor = $('#change-group-label-color'), @@ -20,6 +20,8 @@ define('admin/manage/group', [ var groupName = ajaxify.data.group.name; + memberList.init(true); + changeGroupUserTitle.keyup(function() { groupLabelPreview.text(changeGroupUserTitle.val()); }); @@ -46,10 +48,17 @@ define('admin/manage/group', [ } groupDetailsSearchResults.empty(); + for (x = 0; x < numResults; x++) { foundUser = $('
  • '); foundUser - .attr({title: results.users[x].username, 'data-uid': results.users[x].uid}) + .attr({title: + results.users[x].username, + 'data-uid': results.users[x].uid, + 'data-username': results.users[x].username, + 'data-userslug': results.users[x].userslug, + 'data-picture': results.users[x].picture + }) .append($('').attr('src', results.users[x].picture)) .append($('').html(results.users[x].username)); @@ -64,45 +73,74 @@ define('admin/manage/group', [ groupDetailsSearchResults.on('click', 'li[data-uid]', function() { var userLabel = $(this), - uid = parseInt(userLabel.attr('data-uid'), 10), - members = []; + uid = parseInt(userLabel.attr('data-uid'), 10); - groupMembersEl.find('li[data-uid]').each(function() { - members.push(parseInt($(this).attr('data-uid'), 10)); - }); - - if (members.indexOf(uid) === -1) { - socket.emit('admin.groups.join', { - groupName: groupName, - uid: uid - }, function(err, data) { - if (!err) { - groupMembersEl.append(userLabel.clone(true)); - } - }); - } - }); - - groupMembersEl.on('click', 'li[data-uid]', function() { - var uid = $(this).attr('data-uid'); - - bootbox.confirm('Are you sure you want to remove this user?', function(confirm) { - if (!confirm) { - return; + socket.emit('admin.groups.join', { + groupName: groupName, + uid: uid + }, function(err) { + if (err) { + return app.alertError(err.message); } - socket.emit('admin.groups.leave', { - groupName: groupName, - uid: uid - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - groupMembersEl.find('li[data-uid="' + uid + '"]').remove(); + var member = { + uid: userLabel.attr('data-uid'), + username: userLabel.attr('data-username'), + userslug: userLabel.attr('data-userslug'), + picture: userLabel.attr('data-picture') + }; + + templates.parse('partials/groups/memberlist', 'members', {group: {isOwner: ajaxify.data.group.isOwner, members: [member]}}, function(html) { + translator.translate(html, function(html) { + $('[component="groups/members"] tr').first().before(html); + }); }); }); }); + $('[component="groups/members"]').on('click', '[data-action]', function() { + var btnEl = $(this), + userRow = btnEl.parents('[data-uid]'), + ownerFlagEl = userRow.find('.member-name i'), + isOwner = !ownerFlagEl.hasClass('invisible') ? true : false, + uid = userRow.attr('data-uid'), + action = btnEl.attr('data-action'); + + switch(action) { + case 'toggleOwnership': + socket.emit('groups.' + (isOwner ? 'rescind' : 'grant'), { + toUid: uid, + groupName: groupName + }, function(err) { + if (err) { + return app.alertError(err.message); + } + ownerFlagEl.toggleClass('invisible'); + }); + break; + + case 'kick': + bootbox.confirm('Are you sure you want to remove this user?', function(confirm) { + if (!confirm) { + return; + } + socket.emit('admin.groups.leave', { + uid: uid, + groupName: groupName + }, function(err) { + if (err) { + return app.alertError(err.message); + } + userRow.slideUp().remove(); + }); + + }); + break; + default: + break; + } + }); + $('#group-icon').on('click', function() { iconSelect.init(groupIcon); }); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index f5fb69e401..be624b2160 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -1,17 +1,22 @@ "use strict"; -/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */ +/* globals define, socket, ajaxify, app, bootbox, utils */ + +define('forum/groups/details', [ + 'forum/groups/memberlist', + 'iconSelect', + 'components', + 'vendor/colorpicker/colorpicker', + 'vendor/jquery/draggable-background/backgroundDraggable' +], function(memberList, iconSelect, components) { -define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescroll', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components, infinitescroll) { var Details = { cover: {} }; - var searchInterval; var groupName; Details.init = function() { - var detailsPage = components.get('groups/container'), - settingsFormEl = detailsPage.find('form'); + var detailsPage = components.get('groups/container'); groupName = ajaxify.data.group.name; @@ -20,8 +25,8 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol Details.initialiseCover(); } - handleMemberSearch(); - handleMemberInfiniteScroll(); + memberList.init(); + handleMemberInvitations(); components.get('groups/activity').find('.content img:not(.not-responsive)').addClass('img-responsive'); @@ -291,44 +296,6 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol }); }; - function handleMemberSearch() { - $('[component="groups/members/search"]').on('keyup', function() { - var query = $(this).val(); - if (searchInterval) { - clearInterval(searchInterval); - searchInterval = 0; - } - - searchInterval = setTimeout(function() { - socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) { - if (err) { - return app.alertError(err.message); - } - - infinitescroll.parseAndTranslate('groups/details', 'members', { - group: { - members: results.users, - isOwner: ajaxify.data.group.isOwner - } - }, function(html) { - $('[component="groups/members"] tbody').html(html); - $('[component="groups/members"]').attr('data-nextstart', 20); - }); - }); - }, 250); - }); - } - - function handleMemberInfiniteScroll() { - $('[component="groups/members"] tbody').on('scroll', function() { - var $this = $(this); - var bottom = ($this[0].scrollHeight - $this.height()) * 0.9; - if ($this.scrollTop() > bottom) { - loadMoreMembers(); - } - }); - } - function handleMemberInvitations() { if (ajaxify.data.group.isOwner) { var searchInput = $('[component="groups/members/invite"]'); @@ -349,48 +316,5 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol } } - function loadMoreMembers() { - - var members = $('[component="groups/members"]'); - if (members.attr('loading')) { - return; - } - - members.attr('loading', 1); - socket.emit('groups.loadMoreMembers', { - groupName: groupName, - after: members.attr('data-nextstart') - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - - if (data && data.users.length) { - onMembersLoaded(data.users, function() { - members.removeAttr('loading'); - members.attr('data-nextstart', data.nextStart); - }); - } else { - members.removeAttr('loading'); - } - }); - } - - function onMembersLoaded(users, callback) { - users = users.filter(function(user) { - return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length; - }); - - infinitescroll.parseAndTranslate('groups/details', 'members', { - group: { - members: users, - isOwner: ajaxify.data.group.isOwner - } - }, function(html) { - $('[component="groups/members"] tbody').append(html); - callback(); - }); - } - return Details; }); \ No newline at end of file diff --git a/public/src/client/groups/memberlist.js b/public/src/client/groups/memberlist.js new file mode 100644 index 0000000000..0cbc0e9116 --- /dev/null +++ b/public/src/client/groups/memberlist.js @@ -0,0 +1,97 @@ +"use strict"; +/* globals define, socket, ajaxify, app */ + +define('forum/groups/memberlist', ['components', 'forum/infinitescroll'], function(components, infinitescroll) { + + var MemberList = {}; + var searchInterval; + var groupName; + + MemberList.init = function() { + groupName = ajaxify.data.group.name; + + handleMemberSearch(); + handleMemberInfiniteScroll(); + }; + + function handleMemberSearch() { + $('[component="groups/members/search"]').on('keyup', function() { + var query = $(this).val(); + if (searchInterval) { + clearInterval(searchInterval); + searchInterval = 0; + } + + searchInterval = setTimeout(function() { + socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) { + if (err) { + return app.alertError(err.message); + } + parseAndTranslate(results.users, function(html) { + $('[component="groups/members"] tbody').html(html); + $('[component="groups/members"]').attr('data-nextstart', 20); + }); + }); + }, 250); + }); + } + + function handleMemberInfiniteScroll() { + $('[component="groups/members"] tbody').on('scroll', function() { + var $this = $(this); + var bottom = ($this[0].scrollHeight - $this.innerHeight()) * 0.9; + + if ($this.scrollTop() > bottom) { + loadMoreMembers(); + } + }); + } + + function loadMoreMembers() { + var members = $('[component="groups/members"]'); + if (members.attr('loading')) { + return; + } + + members.attr('loading', 1); + socket.emit('groups.loadMoreMembers', { + groupName: groupName, + after: members.attr('data-nextstart') + }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + + if (data && data.users.length) { + onMembersLoaded(data.users, function() { + members.removeAttr('loading'); + members.attr('data-nextstart', data.nextStart); + }); + } else { + members.removeAttr('loading'); + } + }); + } + + function onMembersLoaded(users, callback) { + users = users.filter(function(user) { + return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length; + }); + + parseAndTranslate(users, function(html) { + $('[component="groups/members"] tbody').append(html); + callback(); + }); + } + + function parseAndTranslate(users, callback) { + infinitescroll.parseAndTranslate('groups/details', 'members', { + group: { + members: users, + isOwner: ajaxify.data.group.isOwner + } + }, callback); + } + + return MemberList; +}); \ No newline at end of file diff --git a/src/controllers/admin/groups.js b/src/controllers/admin/groups.js index 259f93759d..dc8bf6ff82 100644 --- a/src/controllers/admin/groups.js +++ b/src/controllers/admin/groups.js @@ -59,12 +59,13 @@ groupsController.get = function(req, res, callback) { if (!exists) { return callback(); } - groups.get(groupName, {uid: req.uid}, next); + groups.get(groupName, {uid: req.uid, truncateUserList: true, userListCount: 20}, next); } ], function(err, group) { if (err) { return callback(err); } + group.isOwner = true; res.render('admin/manage/group', {group: group}); }); }; diff --git a/src/socket.io/admin/groups.js b/src/socket.io/admin/groups.js index 0cd47a778f..206604ec8f 100644 --- a/src/socket.io/admin/groups.js +++ b/src/socket.io/admin/groups.js @@ -1,5 +1,6 @@ "use strict"; +var async = require('async'); var groups = require('../../groups'), Groups = {}; @@ -20,7 +21,17 @@ Groups.join = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - groups.join(data.groupName, data.uid, callback); + async.waterfall([ + function (next) { + groups.isMember(data.uid, data.groupName, next); + }, + function (isMember, next) { + if (isMember) { + return next(new Error('[[error:group-already-member]]')); + } + groups.join(data.groupName, data.uid, next); + } + ], callback); }; Groups.leave = function(socket, data, callback) { @@ -32,7 +43,17 @@ Groups.leave = function(socket, data, callback) { return callback(new Error('[[error:cant-remove-self-as-admin]]')); } - groups.leave(data.groupName, data.uid, callback); + async.waterfall([ + function (next) { + groups.isMember(data.uid, data.groupName, next); + }, + function (isMember, next) { + if (!isMember) { + return next(new Error('[[error:group-not-member]]')); + } + groups.leave(data.groupName, data.uid, next); + } + ], callback); }; Groups.update = function(socket, data, callback) { diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index b75e6ba3df..1a07a90c26 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -62,8 +62,11 @@ SocketGroups.leave = function(socket, data, callback) { function isOwner(next) { return function (socket, data, callback) { - groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { - if (err || !isOwner) { + async.parallel({ + isAdmin: async.apply(user.isAdmin, socket.uid), + isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName) + }, function(err, results) { + if (err || (!isOwner && !results.isAdmin)) { return callback(err || new Error('[[error:no-privileges]]')); } next(socket, data, callback); diff --git a/src/views/admin/header.tpl b/src/views/admin/header.tpl index 3831f12a1c..c2828dfbd9 100644 --- a/src/views/admin/header.tpl +++ b/src/views/admin/header.tpl @@ -37,6 +37,7 @@ waitSeconds: 3, urlArgs: "{cache-buster}", paths: { + 'forum': '../client', 'admin': '../admin', 'vendor': '../../vendor', 'buzz': '../../vendor/buzz/buzz.min' diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl index e828d9da81..99acd57b13 100644 --- a/src/views/admin/manage/group.tpl +++ b/src/views/admin/manage/group.tpl @@ -54,24 +54,20 @@
      +
      - -

      Click on a user to remove them from the group

      -
      +
      +

      [[groups:details.members]]

      +
      -
        - -
      • - - {group.members.username} -
      • - -
      +
      + +
    • From 07a97717ab265d57727ed2b4314c2b6baf320053 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 13 Oct 2015 19:30:10 -0400 Subject: [PATCH 34/91] removed bool param --- public/src/admin/manage/group.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index d8d6f4ec23..cb75f5cf1b 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -20,7 +20,7 @@ define('admin/manage/group', [ var groupName = ajaxify.data.group.name; - memberList.init(true); + memberList.init(); changeGroupUserTitle.keyup(function() { groupLabelPreview.text(changeGroupUserTitle.val()); From 096ecce87b1ceeb8750a09629f857b37dc6d012b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 13 Oct 2015 19:30:39 -0400 Subject: [PATCH 35/91] fix indent --- public/src/admin/manage/group.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index cb75f5cf1b..c708cc3a14 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -52,8 +52,7 @@ define('admin/manage/group', [ for (x = 0; x < numResults; x++) { foundUser = $('
    • '); foundUser - .attr({title: - results.users[x].username, + .attr({title: results.users[x].username, 'data-uid': results.users[x].uid, 'data-username': results.users[x].username, 'data-userslug': results.users[x].userslug, From 16a65c8ffc66ca0b368c144f61f9b56f885dda87 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 14 Oct 2015 16:00:45 -0400 Subject: [PATCH 36/91] closes #3754 --- public/src/utils.js | 6 ++++++ src/topics/create.js | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/public/src/utils.js b/public/src/utils.js index 930e03501f..a2f8ea7a80 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -381,6 +381,12 @@ }; } + if (typeof String.prototype.rtrim != 'function') { + String.prototype.rtrim = function() { + return this.replace(/\s+$/g, ''); + }; + } + if ('undefined' !== typeof window) { window.utils = module.exports; } diff --git a/src/topics/create.js b/src/topics/create.js index ae60a8c191..6ccc06de12 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -101,6 +101,9 @@ module.exports = function(Topics) { check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags', next); }, function(next) { + if (data.content) { + data.content = data.content.rtrim(); + } check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next); }, function(next) { @@ -228,7 +231,7 @@ module.exports = function(Topics) { function(filteredData, next) { content = filteredData.content || data.content; if (content) { - content = content.trim(); + content = content.rtrim(); } check(content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next); From 41b50943475dd495552cf02e5af123997700c134 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 14 Oct 2015 18:48:26 -0400 Subject: [PATCH 37/91] up versions --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 387564f033..784d7e4a6a 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "mmmagic": "^0.4.0", "morgan": "^1.3.2", "nconf": "~0.7.1", - "nodebb-plugin-composer-default": "1.0.16", + "nodebb-plugin-composer-default": "1.0.17", "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", @@ -49,8 +49,8 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.50", - "nodebb-theme-vanilla": "4.0.21", + "nodebb-theme-persona": "3.0.51", + "nodebb-theme-vanilla": "4.0.22", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", "passport": "^0.3.0", From fc4e7c0fffac677e2b3f1fd10cc0eaded6f68d03 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 14 Oct 2015 19:22:49 -0400 Subject: [PATCH 38/91] navigation cleanup --- public/less/admin/general/navigation.less | 4 ++++ src/views/admin/general/navigation.tpl | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/public/less/admin/general/navigation.less b/public/less/admin/general/navigation.less index 1353d35013..bb3efac15b 100644 --- a/public/less/admin/general/navigation.less +++ b/public/less/admin/general/navigation.less @@ -2,6 +2,7 @@ #navigation { + #active-navigation { .active { background-color: #eee; @@ -23,6 +24,9 @@ .iconPicker i { cursor: pointer; } + .form-group { + min-height: 80px; + } } ul { diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index ac6ec94515..ba7f5942e5 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -31,32 +31,32 @@
      -
      - +
      +
      -
      - +
      +
      - +
      -
      +
      -
      +
      From c3b8d0b328c2759132a21dca7b28b1ac21efbedb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 00:43:02 -0400 Subject: [PATCH 39/91] check err first --- src/meta/settings.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/meta/settings.js b/src/meta/settings.js index 95661a6dbe..10c987f108 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -20,14 +20,16 @@ module.exports = function(Meta) { Meta.settings.set = function(hash, values, callback) { var key = 'settings:' + hash; db.setObject(key, values, function(err) { - if (!err) { - plugins.fireHook('action:settings.set', { - plugin: hash, - settings: values - }); + if (err) { + return callback(err); } - callback.apply(this, arguments); + plugins.fireHook('action:settings.set', { + plugin: hash, + settings: values + }); + + callback(); }); }; From 0da39d036b983375ee8356e704f029bf9fd5bc0b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 11:36:50 -0400 Subject: [PATCH 40/91] uploading string --- public/language/en_GB/modules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/language/en_GB/modules.json b/public/language/en_GB/modules.json index e4f509842f..673ad026a9 100644 --- a/public/language/en_GB/modules.json +++ b/public/language/en_GB/modules.json @@ -24,6 +24,7 @@ "composer.discard": "Are you sure you wish to discard this post?", "composer.submit_and_lock": "Submit and Lock", "composer.toggle_dropdown": "Toggle Dropdown", + "composer.uploading": "Uploading %1", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", From 3d7b8654bdf4a000d8b55c38412e625f3f97c169 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 11:40:56 -0400 Subject: [PATCH 41/91] up vers --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 784d7e4a6a..46999cdc83 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "mmmagic": "^0.4.0", "morgan": "^1.3.2", "nconf": "~0.7.1", - "nodebb-plugin-composer-default": "1.0.17", + "nodebb-plugin-composer-default": "1.0.18", "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.51", + "nodebb-theme-persona": "3.0.52", "nodebb-theme-vanilla": "4.0.22", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From 94a0c9aa9f8110fb204bef00e44e5530bae1ab87 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 11:41:31 -0400 Subject: [PATCH 42/91] closes #3757 --- src/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index 9fa6a1cba0..6c06328c70 100644 --- a/src/install.js +++ b/src/install.js @@ -13,7 +13,7 @@ var async = require('async'), "dependencies": ["redis@~0.10.1", "connect-redis@~2.0.0"] }, "mongo": { - "dependencies": ["mongodb@~2.0.0", "connect-mongo"] + "dependencies": ["mongodb@~2.0.0", "connect-mongo@~0.8.2"] } }; From f6fa0ace3c1b544ce4d98cdc181edb70f4c847e5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 12:18:21 -0400 Subject: [PATCH 43/91] closes #3732 --- public/src/client/topic/events.js | 1 + public/src/client/topic/postTools.js | 2 +- public/src/client/topic/posts.js | 10 ++++------ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index fe9196e7fb..47c7f2c328 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -162,6 +162,7 @@ define('forum/topic/events', [ function onPostPurged(pid) { components.get('post', 'pid', pid).fadeOut(500, function() { $(this).remove(); + posts.showBottomPostBar(); }); postTools.updatePostCount(); diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 4140d5e557..a4ef94d16c 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -40,7 +40,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator }; function addVoteHandler() { - components.get('topic').on('mouseenter', '[data-pid] .votes', function() { + components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', function() { loadDataAndCreateTooltip($(this)); }); } diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 6ae9fbc8cb..c9f25760b5 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -223,14 +223,12 @@ define('forum/topic/posts', [ postTools.updatePostCount(); addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote')); hidePostToolsForDeletedPosts(posts); - showBottomPostBar(); + Posts.showBottomPostBar(); }; - function showBottomPostBar() { - if (components.get('post').length > 1 || !components.get('post', 'index', 0).length) { - $('.bottom-post-bar').removeClass('hidden'); - } - } + Posts.showBottomPostBar = function() { + $('.bottom-post-bar').toggleClass('hidden', components.get('post').length <= 1 && !!components.get('post', 'index', 0).length); + }; function hidePostToolsForDeletedPosts(posts) { posts.each(function() { From b55665258272e0a5ea7088f793b877952a774597 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 13:00:32 -0400 Subject: [PATCH 44/91] fix chat redirect --- public/src/ajaxify.js | 4 +--- public/src/client/chats.js | 2 +- src/controllers/accounts/chats.js | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 2ab64c65e8..bf30ac0521 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -222,9 +222,7 @@ $(document).ready(function() { $(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data}); - if (callback) { - callback(null, data); - } + callback(null, data); }, error: function(data, textStatus) { if (data.status === 0 && textStatus === 'error') { diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 511a81ca15..3d7bd7bda0 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -140,7 +140,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll', Chats.switchChat = function(uid, username) { if (!$('[component="chat/messages"]').length) { - ajaxify.go('chats/' + username); + return ajaxify.go('chats/' + utils.slugify(username)); } var contactEl = $('.chats-list [data-uid="' + uid + '"]'); diff --git a/src/controllers/accounts/chats.js b/src/controllers/accounts/chats.js index 11f6535d8c..1f95db984b 100644 --- a/src/controllers/accounts/chats.js +++ b/src/controllers/accounts/chats.js @@ -19,7 +19,7 @@ chatsController.get = function(req, res, callback) { // In case a userNAME is passed in instead of a slug, the route should not 404 var slugified = utils.slugify(req.params.userslug); if (req.params.userslug && req.params.userslug !== slugified) { - return res.redirect(nconf.get('relative_path') + '/chats/' + slugified); + return helpers.redirect(res, '/chats/' + slugified); } async.parallel({ From d351d014909fe65082f4b92d9fddf2f0a639dd2c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 14:16:36 -0400 Subject: [PATCH 45/91] fix sort on category page --- public/src/modules/sort.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/sort.js b/public/src/modules/sort.js index a6fe38f90b..960e21facb 100644 --- a/public/src/modules/sort.js +++ b/public/src/modules/sort.js @@ -10,7 +10,7 @@ define('sort', ['components'], function(components) { var currentSetting = threadSort.find('a[data-sort="' + config[field] + '"]'); currentSetting.find('i').addClass('fa-check'); - components.get('topic').on('click', '[component="thread/sort"] a', function() { + $('.category, .topic').on('click', '[component="thread/sort"] a', function() { var newSetting = $(this).attr('data-sort'); socket.emit(method, newSetting, function(err) { if (err) { From cba871ec903f8082971de583bfc3d7827985e2c7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 16:08:26 -0400 Subject: [PATCH 46/91] closes #3759 --- public/src/client/topic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 116dc62a1b..8cb7f493ad 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -146,7 +146,7 @@ define('forum/topic', [ if (components.get('post/anchor', postIndex).length) { return navigator.scrollToPostIndex(postIndex, true); } - } else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > 10) { + } else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > 5) { app.alert({ alert_id: 'bookmark', message: '[[topic:bookmark_instructions]]', @@ -277,7 +277,7 @@ define('forum/topic', [ var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey); - if (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10)) { + if (ajaxify.data.postCount > 5 && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { if (app.user.uid) { socket.emit('topics.bookmark', { 'tid': ajaxify.data.tid, From 2f5eb248eb16e6acecdb70b9ecbcd29281e3f80a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 16:13:19 -0400 Subject: [PATCH 47/91] fix postcount --- public/src/client/topic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 8cb7f493ad..eb235b2488 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -277,7 +277,7 @@ define('forum/topic', [ var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey); - if (ajaxify.data.postCount > 5 && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { + if (ajaxify.data.postcount > 5 && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { if (app.user.uid) { socket.emit('topics.bookmark', { 'tid': ajaxify.data.tid, From c2b29ff4c469e754357a4c985bcf7c083317994d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 16:20:21 -0400 Subject: [PATCH 48/91] add page to canonical link #3758 --- 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 7f386c5af8..af62540ff6 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -236,7 +236,7 @@ topicsController.get = function(req, res, callback) { }, { rel: 'canonical', - href: nconf.get('url') + '/topic/' + topicData.slug + href: nconf.get('url') + '/topic/' + topicData.slug + (currentPage > 1 ? '?page=' + currentPage : '') } ]; From 75c2696f2e72ec91860df3825e345850e0ae3ade Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 16:43:06 -0400 Subject: [PATCH 49/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46999cdc83..6dd8ff930a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.52", + "nodebb-theme-persona": "3.0.53", "nodebb-theme-vanilla": "4.0.22", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From e60c1d87369525a39906794cd14f6f6a01710774 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 15 Oct 2015 20:29:54 -0400 Subject: [PATCH 50/91] added process info --- src/controllers/admin.js | 3 ++- src/controllers/admin/info.js | 24 ++++++++++++++++++++++++ src/routes/admin.js | 1 + src/views/admin/development/info.tpl | 13 +++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/controllers/admin/info.js create mode 100644 src/views/admin/development/info.tpl diff --git a/src/controllers/admin.js b/src/controllers/admin.js index b620dce3d2..0580bd42a4 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -24,7 +24,8 @@ var adminController = { navigation: require('./admin/navigation'), themes: require('./admin/themes'), users: require('./admin/users'), - uploads: require('./admin/uploads') + uploads: require('./admin/uploads'), + info: require('./admin/info') }; diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js new file mode 100644 index 0000000000..ad2a276294 --- /dev/null +++ b/src/controllers/admin/info.js @@ -0,0 +1,24 @@ +'use strict'; + +var infoController = {}; + +infoController.get = function(req, res, next) { + + var data = { + process: { + pid: process.pid, + title: process.title, + arch: process.arch, + platform: process.platform, + version: process.version, + versions: process.versions, + memoryUsage: process.memoryUsage(), + uptime: process.uptime() + } + }; + + res.render('admin/development/info', {info: JSON.stringify(data, null, 4)}); +}; + + +module.exports = infoController; \ No newline at end of file diff --git a/src/routes/admin.js b/src/routes/admin.js index eb261b58be..b5eec7871b 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -80,6 +80,7 @@ function addRoutes(router, middleware, controllers) { router.get('/advanced/post-cache', middlewares, controllers.admin.postCache.get); router.get('/development/logger', middlewares, controllers.admin.logger.get); + router.get('/development/info', middlewares, controllers.admin.info.get); } module.exports = function(app, middleware, controllers) { diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl new file mode 100644 index 0000000000..563d12c11f --- /dev/null +++ b/src/views/admin/development/info.tpl @@ -0,0 +1,13 @@ +
      +
      +
      +

      Info

      +
      + +
      +
      +
      {info}
      +
      +
      +
      +
      \ No newline at end of file From f2d6f931af4beca622b5efa2241ebbcf36958c6b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 16 Oct 2015 18:43:40 -0400 Subject: [PATCH 51/91] closes #3505 --- public/src/ajaxify.js | 2 ++ src/middleware/header.js | 3 ++- src/middleware/render.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index bf30ac0521..8716a4dc52 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -56,6 +56,7 @@ $(document).ready(function() { url = ajaxify.start(url, quiet); + $('body').removeClass(ajaxify.data.bodyClass); $('#footer, #content').removeClass('hide').addClass('ajaxifying'); ajaxify.loadData(url, function(err, data) { @@ -141,6 +142,7 @@ $(document).ready(function() { templates.parse(tpl_url, data, function(template) { translator.translate(template, function(translatedTemplate) { + $('body').addClass(data.bodyClass); $('#content').html(translatedTemplate); ajaxify.end(url, tpl_url); diff --git a/src/middleware/header.js b/src/middleware/header.js index 52d96f0d79..6cedf1241a 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -60,7 +60,8 @@ module.exports = function(app, middleware) { allowRegistration: registrationType === 'normal' || registrationType === 'admin-approval', searchEnabled: plugins.hasListeners('filter:search.query'), config: res.locals.config, - relative_path: nconf.get('relative_path') + relative_path: nconf.get('relative_path'), + bodyClass: data.bodyClass }; templateValues.configJSON = JSON.stringify(res.locals.config); diff --git a/src/middleware/render.js b/src/middleware/render.js index ba0c06552e..4a80d4b9c8 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -29,6 +29,8 @@ module.exports = function(middleware) { options.relative_path = nconf.get('relative_path'); options.template = {name: template}; options.template[template] = true; + options.bodyClass = buildBodyClass(req); + res.locals.template = template; if (res.locals.isAPI) { @@ -80,4 +82,14 @@ module.exports = function(middleware) { next(); }; + function buildBodyClass(req) { + var clean = req.path.replace(/^\/api/, '').replace(/^\//, ''); + var parts = clean.split('/').slice(0, 3); + parts.forEach(function(p, index) { + parts[index] = index ? parts[index - 1] + '-' + p : 'page-' + (p || 'home'); + }); + + return parts.join(' '); + } + }; \ No newline at end of file From 3b9fdcaa2b830e088e2df0be8fd5202f8cb166ed Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Oct 2015 18:26:03 -0400 Subject: [PATCH 52/91] more mongodb info --- src/database/mongo.js | 34 ++++++++++++++++++++------- src/views/admin/advanced/database.tpl | 4 ++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 49f2da5edd..619113794e 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -178,25 +178,43 @@ module.info = function(db, callback) { async.parallel({ - serverStats: function(next) { + serverStatus: function(next) { db.command({'serverStatus': 1}, next); }, stats: function(next) { - db.stats({scale:1024}, next); + db.stats({}, next); + }, + listCollections: function(next) { + db.listCollections().toArray(function(err, items) { + if (err) { + return next(err); + } + async.map(items, function(collection, next) { + db.collection(collection.name).stats(next); + }, next); + }); } }, function(err, results) { if (err) { return callback(err); } var stats = results.stats; + var scale = 1024 * 1024; + + stats.mem = results.serverStatus.mem; + stats.collectionData = results.listCollections; + stats.network = results.serverStatus.network; + stats.raw = JSON.stringify(stats, null, 4); 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.mem = results.serverStats.mem; - stats.raw = JSON.stringify(stats, null, 4); + stats.dataSize = (stats.dataSize / scale).toFixed(2); + stats.storageSize = (stats.storageSize / scale).toFixed(2); + stats.fileSize = (stats.fileSize / scale).toFixed(2); + stats.indexSize = (stats.indexSize / scale).toFixed(2); + stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1'; + stats.host = results.serverStatus.host; + stats.version = results.serverStatus.version; + stats.uptime = results.serverStatus.uptime; stats.mongo = true; callback(null, stats); diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl index b868b322a9..db161bd602 100644 --- a/src/views/admin/advanced/database.tpl +++ b/src/views/admin/advanced/database.tpl @@ -5,6 +5,10 @@
      Mongo
      + MongoDB Version {mongo.version}
      +
      + Uptime in Seconds {mongo.uptime}
      + Storage Engine {mongo.storageEngine}
      Collections {mongo.collections}
      Objects {mongo.objects}
      Avg. Object Size {mongo.avgObjSize} kb
      From 5fda800f2bb267c3c3eded9a2c2b73774750faec Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Oct 2015 18:34:04 -0400 Subject: [PATCH 53/91] filter info --- src/database/mongo.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/database/mongo.js b/src/database/mongo.js index 619113794e..6c57e60433 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -201,6 +201,18 @@ var stats = results.stats; var scale = 1024 * 1024; + results.listCollections = results.listCollections.map(function(collectionInfo) { + return { + name: collectionInfo.ns, + count: collectionInfo.count, + size: collectionInfo.size, + avgObjSize: collectionInfo.avgObjSize, + storageSize: collectionInfo.storageSize, + totalIndexSize: collectionInfo.totalIndexSize, + indexSizes: collectionInfo.indexSizes + }; + }); + stats.mem = results.serverStatus.mem; stats.collectionData = results.listCollections; stats.network = results.serverStatus.network; From a9fae94e5a155be39abe872e5e50f0ad2cb2bed0 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Oct 2015 18:37:07 -0400 Subject: [PATCH 54/91] switch to dbStats, only display fileSize if it exists --- src/database/mongo.js | 2 +- src/views/admin/advanced/database.tpl | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 6c57e60433..08bd3d3aa4 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -182,7 +182,7 @@ db.command({'serverStatus': 1}, next); }, stats: function(next) { - db.stats({}, next); + db.command({'dbStats': 1}, next); }, listCollections: function(next) { db.listCollections().toArray(function(err, items) { diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl index db161bd602..4f928b1e52 100644 --- a/src/views/admin/advanced/database.tpl +++ b/src/views/admin/advanced/database.tpl @@ -16,7 +16,9 @@ Data Size {mongo.dataSize} mb
      Storage Size {mongo.storageSize} mb
      Index Size {mongo.indexSize} mb
      + File Size {mongo.fileSize} mb
      +
      Resident Memory {mongo.mem.resident} mb
      Virtual Memory {mongo.mem.virtual} mb
      From a1dca63914b5997a7b721b423a1091ff45896fb7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Oct 2015 18:38:34 -0400 Subject: [PATCH 55/91] fileSize fix --- 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 08bd3d3aa4..0563e02633 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -221,7 +221,7 @@ stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2); stats.dataSize = (stats.dataSize / scale).toFixed(2); stats.storageSize = (stats.storageSize / scale).toFixed(2); - stats.fileSize = (stats.fileSize / scale).toFixed(2); + stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0; stats.indexSize = (stats.indexSize / scale).toFixed(2); stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1'; stats.host = results.serverStatus.host; From e2f590e0b4d085ae4e2f53f11da642b6b4ec0fcf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Oct 2015 20:12:51 -0400 Subject: [PATCH 56/91] only return id --- src/database/mongo/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 045bc2c5bc..f09f13f173 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -53,7 +53,7 @@ module.exports = function(db, module) { } } - db.collection('search' + key).find(searchQuery, {limit: limit}).toArray(function(err, results) { + db.collection('search' + key).find(searchQuery, {limit: limit, fields:{_id: 0, id: 1}}).toArray(function(err, results) { if (err) { return callback(err); } From 53d29e29af8f5347145fdd2c51c028e76f10334d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Oct 2015 18:30:17 -0400 Subject: [PATCH 57/91] performance improvements store parsed category description removed mongo _key from returns dont get category teaser for parent --- src/categories/data.js | 14 +-- src/categories/recentreplies.js | 172 +++++++++++++++----------------- src/categories/update.js | 11 ++ src/controllers/categories.js | 7 +- src/database/mongo/helpers.js | 1 + src/database/mongo/sorted.js | 2 +- 6 files changed, 105 insertions(+), 102 deletions(-) diff --git a/src/categories/data.js b/src/categories/data.js index c844e632ad..cccacc8a68 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -53,17 +53,11 @@ module.exports = function(Categories) { } if (category.description) { - plugins.fireHook('filter:parse.raw', category.description, function(err, parsedDescription) { - if (err) { - return callback(err); - } - category.descriptionParsed = parsedDescription; - category.description = validator.escape(category.description); - callback(null, category); - }); - } else { - callback(null, category); + category.description = validator.escape(category.description); + category.descriptionParsed = category.descriptionParsed || category.description; } + + callback(null, category); } Categories.getCategoryField = function(cid, field, callback) { diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index d7a2a165a0..84cb005de0 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -3,14 +3,13 @@ var async = require('async'), winston = require('winston'), + validator = require('validator'), _ = require('underscore'), - meta = require('../meta'), db = require('../database'), posts = require('../posts'), topics = require('../topics'), - privileges = require('../privileges'), - plugins = require('../plugins'); + privileges = require('../privileges'); module.exports = function(Categories) { Categories.getRecentReplies = function(cid, uid, count, callback) { @@ -38,25 +37,21 @@ module.exports = function(Categories) { async.waterfall([ function(next) { - async.map(categoryData, getRecentTopicPids, next); + async.map(categoryData, getRecentTopicTids, next); }, function(results, next) { - var pids = _.flatten(results); + var tids = _.flatten(results); - pids = pids.filter(function(pid, index, array) { - return !!pid && array.indexOf(pid) === index; + tids = tids.filter(function(tid, index, array) { + return !!tid && array.indexOf(tid) === index; }); - privileges.posts.filter('read', pids, uid, next); + privileges.topics.filterTids('read', tids, uid, next); }, - function(pids, next) { - if (meta.config.teaserPost === 'first') { - getMainPosts(pids, uid, next); - } else { - posts.getPostSummaryByPids(pids, uid, {stripTags: true}, next); - } + function(tids, next) { + getTopics(tids, next); }, - function(posts, next) { - assignPostsToCategories(categoryData, posts); + function(topics, next) { + assignTopicsToCategories(categoryData, topics); bubbleUpChildrenPosts(categoryData); @@ -65,29 +60,86 @@ module.exports = function(Categories) { ], callback); }; - function getMainPosts(pids, uid, callback) { + function getRecentTopicTids(category, callback) { + var count = parseInt(category.numRecentReplies, 10); + if (!count) { + return callback(null, []); + } + + if (count === 1) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange('cid:' + category.cid + ':pids', 0, 0, next); + }, + function (pid, next) { + posts.getPostField(pid, 'tid', next); + }, + function (tid, next) { + next(null, [tid]); + } + ], callback); + return; + } + + async.parallel({ + pinnedTids: function(next) { + db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, -1, '+inf', Date.now(), next); + }, + tids: function(next) { + db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(1, count), Date.now(), 0, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + results.tids = results.tids.concat(results.pinnedTids); + + callback(null, results.tids); + }); + } + + function getTopics(tids, callback) { + var topicData; async.waterfall([ - function(next) { - var keys = pids.map(function(pid) { - return 'post:' + pid; - }); - db.getObjectsFields(keys, ['tid'], next); + function (next) { + topics.getTopicsFields(tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'], next); }, - function(posts, next) { - var keys = posts.map(function(post) { - return 'topic:' + post.tid; + function (_topicData, next) { + topicData = _topicData; + topicData.forEach(function(topic) { + topic.teaserPid = topic.teaserPid || topic.mainPid; }); - db.getObjectsFields(keys, ['mainPid'], next); + + topics.getTeasers(topicData, next); }, - function(topics, next) { - var mainPids = topics.map(function(topic) { - return topic.mainPid; + function (teasers, next) { + teasers.forEach(function(teaser, index) { + if (teaser) { + teaser.cid = topicData[index].cid; + teaser.tid = teaser.uid = teaser.user.uid = undefined; + teaser.topic = { + slug: topicData[index].slug, + title: validator.escape(topicData[index].title) + }; + } }); - posts.getPostSummaryByPids(mainPids, uid, {stripTags: true}, next); + teasers = teasers.filter(Boolean); + next(null, teasers); } ], callback); } + function assignTopicsToCategories(categories, topics) { + categories.forEach(function(category) { + category.posts = topics.filter(function(topic) { + return topic.cid && parseInt(topic.cid, 10) === parseInt(category.cid, 10); + }).sort(function(a, b) { + return b.pid - a.pid; + }).slice(0, parseInt(category.numRecentReplies, 10)); + }); + } + function bubbleUpChildrenPosts(categoryData) { categoryData.forEach(function(category) { if (category.posts.length) { @@ -97,7 +149,7 @@ module.exports = function(Categories) { getPostsRecursive(category, posts); posts.sort(function(a, b) { - return b.timestamp - a.timestamp; + return b.pid - a.pid; }); if (posts.length) { category.posts = [posts[0]]; @@ -115,64 +167,6 @@ module.exports = function(Categories) { }); } - function assignPostsToCategories(categories, posts) { - categories.forEach(function(category) { - category.posts = posts.filter(function(post) { - return post.category && (parseInt(post.category.cid, 10) === parseInt(category.cid, 10) || - parseInt(post.category.parentCid, 10) === parseInt(category.cid, 10)); - }).sort(function(a, b) { - return b.timestamp - a.timestamp; - }).slice(0, parseInt(category.numRecentReplies, 10)); - }); - } - - function getRecentTopicPids(category, callback) { - var count = parseInt(category.numRecentReplies, 10); - if (!count) { - return callback(null, []); - } - - db.getSortedSetRevRange('cid:' + category.cid + ':pids', 0, 0, function(err, pids) { - if (err || !Array.isArray(pids) || !pids.length) { - return callback(err, []); - } - - if (count === 1) { - return callback(null, pids); - } - - async.parallel({ - pinnedTids: function(next) { - db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, -1, '+inf', Date.now(), next); - }, - tids: function(next) { - db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(0, count), Date.now(), 0, next); - } - }, function(err, results) { - if (err) { - return callback(err); - } - - results.tids = results.tids.concat(results.pinnedTids); - - async.map(results.tids, topics.getLatestUndeletedPid, function(err, topicPids) { - if (err) { - return callback(err); - } - - pids = pids.concat(topicPids).filter(function(pid, index, array) { - return !!pid && array.indexOf(pid) === index; - }).sort(function(a, b) { - return b - a; - }).slice(0, count); - - callback(null, pids); - }); - }); - }); - } - - Categories.moveRecentReplies = function(tid, oldCid, cid) { updatePostCount(tid, oldCid, cid); topics.getPids(tid, function(err, pids) { diff --git a/src/categories/update.js b/src/categories/update.js index 4ebfda2f11..ffd24e7d98 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -68,6 +68,8 @@ module.exports = function(Categories) { if (key === 'order') { updateOrder(cid, value, callback); + } else if (key === 'description') { + parseDescription(cid, value, callback); } else { callback(); } @@ -119,4 +121,13 @@ module.exports = function(Categories) { }); } + function parseDescription(cid, description, callback) { + plugins.fireHook('filter:parse.raw', description, function(err, parsedDescription) { + if (err) { + return callback(err); + } + Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, callback); + }); + } + }; diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 59068434ad..b2d36b91e8 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -69,7 +69,7 @@ categoriesController.list = function(req, res, next) { if (category && Array.isArray(category.posts) && category.posts.length) { category.teaser = { url: nconf.get('relative_path') + '/topic/' + category.posts[0].topic.slug + '/' + category.posts[0].index, - timestampISO: category.posts[0].relativeTime + timestampISO: category.posts[0].timestamp }; } }); @@ -203,8 +203,11 @@ categoriesController.get = function(req, res, callback) { }); }, function(categoryData, next) { + if (!categoryData.children.length) { + return next(null, categoryData); + } var allCategories = []; - categories.flattenCategories(allCategories, [categoryData]); + categories.flattenCategories(allCategories, categoryData.children); categories.getRecentTopicReplies(allCategories, req.uid, function(err) { next(err, categoryData); }); diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 0be79b5388..db84dbb369 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -6,6 +6,7 @@ helpers.toMap = function(data) { var map = {}; for (var i = 0; i Date: Mon, 19 Oct 2015 11:28:18 -0400 Subject: [PATCH 58/91] closes #3767 --- public/src/client/category.js | 8 ++++---- public/src/client/topic.js | 4 ++-- public/src/modules/navigator.js | 9 +++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/public/src/client/category.js b/public/src/client/category.js index cfb7de08d1..634317a21c 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -14,7 +14,7 @@ define('forum/category', [ $(window).on('action:ajaxify.start', function(ev, data) { if (ajaxify.currentPage !== data.url) { - navigator.hide(); + navigator.disable(); removeListeners(); } @@ -39,12 +39,12 @@ define('forum/category', [ sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug); - enableInfiniteLoadingOrPagination(); - if (!config.usePagination) { navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); } + enableInfiniteLoadingOrPagination(); + $('[component="category"]').on('click', '[component="topic/header"]', function() { var clickedIndex = $(this).parents('[data-index]').attr('data-index'); $('[component="category/topic"]').each(function(index, el) { @@ -173,7 +173,7 @@ define('forum/category', [ if (!config.usePagination) { infinitescroll.init($('[component="category"]'), Category.loadMoreTopics); } else { - navigator.hide(); + navigator.disable(); } } diff --git a/public/src/client/topic.js b/public/src/client/topic.js index eb235b2488..faaaddbf00 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -19,7 +19,7 @@ define('forum/topic', [ $(window).on('action:ajaxify.start', function(ev, data) { if (ajaxify.currentPage !== data.url) { - navigator.hide(); + navigator.disable(); components.get('navbar/title').find('span').text('').hide(); app.removeAlert('bookmark'); @@ -216,7 +216,7 @@ define('forum/topic', [ if (!config.usePagination) { infinitescroll.init($('[component="topic"]'), posts.loadMorePosts); } else { - navigator.hide(); + navigator.disable(); } } diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index c9275348f3..6a72ef89ea 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -18,7 +18,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com toTop = toTop || function() {}; toBottom = toBottom || function() {}; - $(window).on('scroll', navigator.update); + $(window).off('scroll', navigator.update).on('scroll', navigator.update); $('.pagination-block .dropdown-menu').off('click').on('click', function(e) { e.stopPropagation(); @@ -74,7 +74,12 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com toggle(true); }; - navigator.hide = function() { + navigator.disable = function() { + count = 0; + index = 1; + navigator.selector = navigator.callback = null; + $(window).off('scroll', navigator.update); + toggle(false); }; From 7f72d80292c4859361a686650def12cccbf06955 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Oct 2015 11:32:46 -0400 Subject: [PATCH 59/91] fix require --- src/meta/dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/dependencies.js b/src/meta/dependencies.js index 8b1af04806..e20f556b76 100644 --- a/src/meta/dependencies.js +++ b/src/meta/dependencies.js @@ -6,7 +6,7 @@ var path = require('path'), semver = require('semver'), winston = require('winston'), - pkg = require.main.require('./package.json'); + pkg = require('../../package.json'); module.exports = function(Meta) { Meta.dependencies = {}; From d61ac000a8ba1ed3e9dd6b5e4cf8cc354392e2e5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Oct 2015 11:45:07 -0400 Subject: [PATCH 60/91] fix a user test --- tests/user.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/user.js b/tests/user.js index 1a4a402185..f9423a62aa 100644 --- a/tests/user.js +++ b/tests/user.js @@ -55,12 +55,12 @@ describe('User', function() { }); }); - it('should have a valid email, if using an email', function() { - assert.throws( - User.create({username: userData.username, password: userData.password, email: 'fakeMail'},function(){}), - Error, - 'does not validate email' - ); + it('should have a valid email, if using an email', function(done) { + User.create({username: userData.username, password: userData.password, email: 'fakeMail'},function(err) { + assert(err); + assert.equal(err.message, '[[error:invalid-email]]'); + done(); + }); }); }); From f17ba88c0f43805b1a72e519b0308dab36260879 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Oct 2015 12:48:26 -0400 Subject: [PATCH 61/91] closes #3765 --- public/src/client/notifications.js | 29 +++++++++++++++++++++-- src/controllers/accounts/notifications.js | 3 ++- src/socket.io/notifications.js | 17 +++++++++++++ src/user/notifications.js | 12 +++++----- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js index 4f2dfbf197..7c0dcd48c3 100644 --- a/public/src/client/notifications.js +++ b/public/src/client/notifications.js @@ -2,12 +2,12 @@ /* globals define, socket, app */ -define('forum/notifications', ['components', 'notifications'], function(components, notifs) { +define('forum/notifications', ['components', 'notifications', 'forum/infinitescroll'], function(components, notifs, infinitescroll) { var Notifications = {}; Notifications.init = function() { var listEl = $('.notifications-list'); - listEl.on('click', '[component="notifications/item/link"]', function(e) { + listEl.on('click', '[component="notifications/item/link"]', function() { var nid = $(this).parents('[data-nid]').attr('data-nid'); socket.emit('notifications.markRead', nid, function(err) { if (err) { @@ -28,7 +28,32 @@ define('forum/notifications', ['components', 'notifications'], function(componen notifs.updateNotifCount(0); }); }); + + infinitescroll.init(loadMoreNotifications); }; + function loadMoreNotifications(direction) { + if (direction < 0) { + return; + } + var notifList = $('.notifications-list'); + infinitescroll.loadMore('notifications.loadMore', { + after: notifList.attr('data-nextstart') + }, function(data, done) { + if (!data) { + return done(); + } + notifList.attr('data-nextstart', data.nextStart); + if (!data.notifications || !data.notifications.length) { + return done(); + } + infinitescroll.parseAndTranslate('notifications', 'notifications', {notifications: data.notifications}, function(html) { + notifList.append(html); + html.find('.timeago').timeago(); + done(); + }); + }); + } + return Notifications; }); diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js index 2f540e9d94..aa60892f47 100644 --- a/src/controllers/accounts/notifications.js +++ b/src/controllers/accounts/notifications.js @@ -7,12 +7,13 @@ var user = require('../../user'), var notificationsController = {}; notificationsController.get = function(req, res, next) { - user.notifications.getAll(req.uid, 40, function(err, notifications) { + user.notifications.getAll(req.uid, 0, 39, function(err, notifications) { if (err) { return next(err); } res.render('notifications', { notifications: notifications, + nextStart: 40, title: '[[pages:notifications]]', breadcrumbs: helpers.buildBreadcrumbs([{text: '[[pages:notifications]]'}]) }); diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js index c957fff1a7..c540c338bb 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -12,6 +12,23 @@ SocketNotifs.get = function(socket, data, callback) { } }; +SocketNotifs.loadMore = function(socket, data, callback) { + if (!data || !parseInt(data.after, 10)) { + return callback(new Error('[[error:invalid-data]]')); + } + if (!socket.uid) { + return; + } + var start = parseInt(data.after, 10); + var stop = start + 20; + user.notifications.getAll(socket.uid, start, stop, function(err, notifications) { + if (err) { + return callback(err); + } + callback(null, {notifications: notifications, nextStart: stop}); + }); +}; + SocketNotifs.getCount = function(socket, data, callback) { user.notifications.getUnreadCount(socket.uid, callback); }; diff --git a/src/user/notifications.js b/src/user/notifications.js index 1a93a6ba4b..7cc309d1a8 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -21,7 +21,7 @@ var async = require('async'), if (!parseInt(uid, 10)) { return callback(null , {read: [], unread: []}); } - getNotifications(uid, 10, function(err, notifications) { + getNotifications(uid, 0, 9, function(err, notifications) { if (err) { return callback(err); } @@ -38,8 +38,8 @@ var async = require('async'), }); }; - UserNotifications.getAll = function(uid, count, callback) { - getNotifications(uid, count, function(err, notifs) { + UserNotifications.getAll = function(uid, start, stop, callback) { + getNotifications(uid, start, stop, function(err, notifs) { if (err) { return callback(err); } @@ -52,13 +52,13 @@ var async = require('async'), }); }; - function getNotifications(uid, count, callback) { + function getNotifications(uid, start, stop, callback) { async.parallel({ unread: function(next) { - getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, 0, count - 1, next); + getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, start, stop, next); }, read: function(next) { - getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, 0, count - 1, next); + getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, start, stop, next); } }, callback); } From 7f66494a57ee2c63b235dbda6abaafb76a168b5b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Oct 2015 12:59:40 -0400 Subject: [PATCH 62/91] closes #3764 --- public/language/en_GB/notifications.json | 4 ++-- src/socket.io/helpers.js | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json index 52d5a10194..7a96e0da53 100644 --- a/public/language/en_GB/notifications.json +++ b/public/language/en_GB/notifications.json @@ -14,8 +14,8 @@ "new_message_from": "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.", + "moved_your_post": "%1 has moved your post to %2", + "moved_your_topic": "%1 has moved %2", "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", diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index c538aca14d..caa4c9fa5b 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -3,6 +3,7 @@ var async = require('async'); var winston = require('winston'); var nconf = require('nconf'); +var validator = require('validator'); var websockets = require('./index'); var user = require('../user'); @@ -64,7 +65,7 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification) } notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicTitle + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + validator.escape(results.topicTitle) + ']]', bodyLong: results.postObj.content, pid: pid, nid: 'post:' + pid + ':uid:' + fromuid, @@ -86,14 +87,14 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification async.parallel({ username: async.apply(user.getUserField, fromuid, 'username'), - topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug']), + topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug', 'title']), }, function(err, results) { if (err || fromuid === parseInt(results.topicData.uid, 10)) { return; } notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + validator.escape(results.topicData.title) + ']]', path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, nid: 'tid:' + tid + ':uid:' + fromuid, from: fromuid From 0e7228eff7e888da65c859e6c3c25e3d2a3870d9 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Mon, 19 Oct 2015 15:48:05 -0400 Subject: [PATCH 63/91] fix admin menu + title if url has a query string ex. ?loggedin --- public/src/admin/admin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index 0309fb48fd..88998bc5f6 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -47,7 +47,8 @@ function selectMenuItem(url) { url = url .replace(/\/\d+$/, '') - .split('/').slice(0, 3).join('/'); + .split('/').slice(0, 3).join('/') + .split('?')[0]; // If index is requested, load the dashboard if (url === 'admin') { From 09747251d8506e7fab6a6825d55b1b3712655866 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Oct 2015 18:46:43 -0400 Subject: [PATCH 64/91] added hostname --- src/controllers/admin/info.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index ad2a276294..a9a489f6c4 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -1,5 +1,7 @@ 'use strict'; +var os = require('os'); + var infoController = {}; infoController.get = function(req, res, next) { @@ -14,6 +16,9 @@ infoController.get = function(req, res, next) { versions: process.versions, memoryUsage: process.memoryUsage(), uptime: process.uptime() + }, + os: { + hostname: os.hostname() } }; From 369e80a3c588dfa55bf59edcda6656b32099b6ee Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 12:44:31 -0400 Subject: [PATCH 65/91] up themes --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6dd8ff930a..de427f9cbb 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,9 @@ "nodebb-plugin-soundpack-default": "0.1.4", "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", - "nodebb-theme-lavender": "2.0.6", - "nodebb-theme-persona": "3.0.53", - "nodebb-theme-vanilla": "4.0.22", + "nodebb-theme-lavender": "2.0.7", + "nodebb-theme-persona": "3.0.54", + "nodebb-theme-vanilla": "4.0.23", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", "passport": "^0.3.0", From 36e89ae15a397221bb67da5f5e3884944ed29fd3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 17:39:30 -0400 Subject: [PATCH 66/91] closes #3771 --- src/routes/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/routes/index.js b/src/routes/index.js index ed872f9835..b2d1bac896 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -133,7 +133,11 @@ module.exports = function(app, middleware) { }; function handle404(app, middleware) { - app.use(function(req, res, next) { + var relativePath = nconf.get('relative_path'); + var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'), + isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); + + app.use(function(req, res) { if (plugins.hasListeners('action:meta.override404')) { return plugins.fireHook('action:meta.override404', { req: req, @@ -142,14 +146,12 @@ function handle404(app, middleware) { }); } - var relativePath = nconf.get('relative_path'); - var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'), - isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); - if (isClientScript.test(req.url)) { res.type('text/javascript').status(200).send(''); } else if (isLanguage.test(req.url)) { res.status(200).json({}); + } else if (req.path.startsWith(relativePath + '/uploads')) { + res.status(404).send(''); } else if (req.accepts('html')) { if (process.env.NODE_ENV === 'development') { winston.warn('Route requested but not found: ' + req.url); @@ -171,7 +173,7 @@ function handle404(app, middleware) { } function handleErrors(app, middleware) { - app.use(function(err, req, res, next) { + app.use(function(err, req, res) { if (err.code === 'EBADCSRFTOKEN') { winston.error(req.path + '\n', err.message); return res.sendStatus(403); From d146bff2a16474f6c0a6c231b5da4ee3c0c1365c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 17:53:44 -0400 Subject: [PATCH 67/91] send less data when leaving rooms --- public/src/ajaxify.js | 2 +- public/src/app.js | 15 +++++++++++++-- src/socket.io/meta.js | 34 ++++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 8716a4dc52..711d72d81f 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -46,7 +46,7 @@ $(document).ready(function() { return true; } - app.enterRoom(''); + app.leaveCurrentRoom(); $(window).off('scroll'); diff --git a/public/src/app.js b/public/src/app.js index f77c860b02..c3727f6c77 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -143,14 +143,25 @@ app.cacheBuster = null; status: app.user.status }, function(err) { if (err) { - app.alertError(err.message); - return; + return app.alertError(err.message); } app.currentRoom = room; }); } }; + app.leaveCurrentRoom = function() { + if (!socket) { + return; + } + socket.emit('meta.rooms.leaveCurrent', function(err) { + if (err) { + return app.alertError(err.message); + } + app.currentRoom = ''; + }); + } + function highlightNavigationLink() { var path = window.location.pathname; $('#main-nav li').removeClass('active'); diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 7134af36c7..c304833ef5 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -1,16 +1,10 @@ 'use strict'; -var nconf = require('nconf'), - gravatar = require('gravatar'), - winston = require('winston'), - validator = require('validator'), +var validator = require('validator'), - db = require('../database'), meta = require('../meta'), user = require('../user'), topics = require('../topics'), - logger = require('../logger'), - plugins = require('../plugins'), emitter = require('../emitter'), rooms = require('./rooms'), @@ -53,13 +47,7 @@ SocketMeta.rooms.enter = function(socket, data, callback) { return callback(new Error('[[error:not-allowed]]')); } - if (socket.currentRoom) { - rooms.leave(socket, socket.currentRoom); - if (socket.currentRoom.indexOf('topic') !== -1) { - websockets.in(socket.currentRoom).emit('event:user_leave', socket.uid); - } - socket.currentRoom = ''; - } + leaveCurrentRoom(socket); if (data.enter) { rooms.enter(socket, data.enter); @@ -76,6 +64,24 @@ SocketMeta.rooms.enter = function(socket, data, callback) { callback(); }; +SocketMeta.rooms.leaveCurrent = function(socket, data, callback) { + if (!socket.uid || !socket.currentRoom) { + return callback(); + } + leaveCurrentRoom(socket); + callback(); +}; + +function leaveCurrentRoom(socket) { + if (socket.currentRoom) { + rooms.leave(socket, socket.currentRoom); + if (socket.currentRoom.indexOf('topic') !== -1) { + websockets.in(socket.currentRoom).emit('event:user_leave', socket.uid); + } + socket.currentRoom = ''; + } +} + SocketMeta.rooms.getAll = function(socket, data, callback) { var roomClients = rooms.roomClients(); var socketData = { From 2ec0d3f376aff0e52accec608c3c792ff0588b8c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 18:08:59 -0400 Subject: [PATCH 68/91] dont make extra socket call on new post and IS --- public/src/client/topic/postTools.js | 16 +++++++--------- public/src/client/topic/posts.js | 4 +++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index a4ef94d16c..9ae1cb273e 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -15,6 +15,8 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator share.addShareHandlers(topicName); addVoteHandler(); + + PostTools.updatePostCount(ajaxify.data.postcount); }; PostTools.toggle = function(pid, isDeleted) { @@ -28,15 +30,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator postEl.find('[component="post/purge"]').toggleClass('hidden', !isDeleted); }; - PostTools.updatePostCount = function() { - socket.emit('topics.postcount', ajaxify.data.tid, function(err, postCount) { - if (!err) { - var postCountEl = components.get('topic/post-count'); - postCountEl.html(postCount).attr('title', postCount); - utils.makeNumbersHumanReadable(postCountEl); - navigator.setCount(postCount); - } - }); + PostTools.updatePostCount = function(postCount) { + var postCountEl = components.get('topic/post-count'); + postCountEl.html(postCount).attr('title', postCount); + utils.makeNumbersHumanReadable(postCountEl); + navigator.setCount(postCount); }; function addVoteHandler() { diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index c9f25760b5..784a11c4a4 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -28,6 +28,8 @@ define('forum/topic/posts', [ }); updatePostCounts(data.posts); + ajaxify.data.postcount ++; + postTools.updatePostCount(ajaxify.data.postcount); if (config.usePagination) { onNewPostPagination(data); @@ -220,7 +222,7 @@ define('forum/topic/posts', [ $this.wrap(''); } }); - postTools.updatePostCount(); + addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote')); hidePostToolsForDeletedPosts(posts); Posts.showBottomPostBar(); From 90f575cae7ef7dd8ff935b472560d2fb5105ac39 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 18:20:43 -0400 Subject: [PATCH 69/91] up mentions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de427f9cbb..8ca72ebcb3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", - "nodebb-plugin-mentions": "1.0.6", + "nodebb-plugin-mentions": "1.0.7", "nodebb-plugin-soundpack-default": "0.1.4", "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", From edf545cd2c272604872707e5916c7724e30a37e6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 18:47:34 -0400 Subject: [PATCH 70/91] dont load sound data on cold load --- public/src/modules/sounds.js | 56 ++++++++++++++++++++++-------------- src/meta/sounds.js | 2 +- src/socket.io/modules.js | 7 +++++ 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js index 3314500ce5..4a6bb80f51 100644 --- a/public/src/modules/sounds.js +++ b/public/src/modules/sounds.js @@ -5,31 +5,30 @@ define('sounds', ['buzz'], function(buzz) { var Sounds = {}; var loadedSounds = {}; - var eventSoundMapping = {}; - var files = {}; - - loadFiles(); - - loadMapping(); + var eventSoundMapping; + var files; socket.on('event:sounds.reloadMapping', loadMapping); - function loadFiles() { - socket.emit('modules.sounds.getSounds', function(err, sounds) { - if (err) { - return app.alertError('[sounds] Could not initialise!'); - } - - files = sounds; - }); - } - - function loadMapping() { + function loadMapping(callback) { + callback = callback || function() {}; socket.emit('modules.sounds.getMapping', function(err, mapping) { if (err) { return app.alertError('[sounds] Could not load sound mapping!'); } eventSoundMapping = mapping; + callback(); + }); + } + + function loadData(callback) { + socket.emit('modules.sounds.getData', function(err, data) { + if (err) { + return app.alertError('[sounds] Could not load sound mapping!'); + } + eventSoundMapping = data.mapping; + files = data.files; + callback(); }); } @@ -38,22 +37,37 @@ define('sounds', ['buzz'], function(buzz) { } function loadFile(fileName, callback) { + function createSound() { + if (files && files[fileName]) { + loadedSounds[fileName] = new buzz.sound(files[fileName]); + } + callback(); + } + if (isSoundLoaded(fileName)) { return callback(); } - if (files && files[fileName]) { - loadedSounds[fileName] = new buzz.sound(files[fileName]); + if (!files || !files[fileName]) { + return loadData(createSound); } - callback(); + createSound(); } Sounds.play = function(name) { + function play() { + Sounds.playFile(eventSoundMapping[name]); + } + if (!config.notificationSounds) { return; } - Sounds.playFile(eventSoundMapping[name]); + if (!eventSoundMapping) { + return loadData(play); + } + + play(); }; Sounds.playFile = function(fileName) { diff --git a/src/meta/sounds.js b/src/meta/sounds.js index 1449ae21f5..30f1f9e165 100644 --- a/src/meta/sounds.js +++ b/src/meta/sounds.js @@ -99,7 +99,7 @@ module.exports = function(Meta) { return callback(null, defaults); } - callback.apply(null, arguments); + callback(null, sounds); }); }; }; \ No newline at end of file diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index b0dde46c5c..d7b2c7c03b 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -147,4 +147,11 @@ SocketModules.sounds.getMapping = function(socket, data, callback) { meta.sounds.getMapping(callback); }; +SocketModules.sounds.getData = function(socket, data, callback) { + async.parallel({ + mapping: async.apply(meta.sounds.getMapping), + files: async.apply(meta.sounds.getFiles) + }, callback); +}; + module.exports = SocketModules; From 8630196a2d765439cb9667c1ba68492eafbf2ccf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 19:07:24 -0400 Subject: [PATCH 71/91] up composer --- package.json | 2 +- src/privileges/categories.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8ca72ebcb3..88ef98e1dd 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "mmmagic": "^0.4.0", "morgan": "^1.3.2", "nconf": "~0.7.1", - "nodebb-plugin-composer-default": "1.0.18", + "nodebb-plugin-composer-default": "1.0.19", "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 43518861d7..fa03bca51b 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -179,7 +179,8 @@ module.exports = function(privileges) { 'topics:create': results['topics:create'][0] || isAdminOrMod, editable: isAdminOrMod, view_deleted: isAdminOrMod, - read: results.read[0] || isAdminOrMod + read: results.read[0] || isAdminOrMod, + isAdminOrMod: isAdminOrMod }, callback); }); }; From 5e5cafafd0fffd4c07ac7ae711a977de17f34dde Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 19:19:50 -0400 Subject: [PATCH 72/91] make one socket call to load unread counts --- public/src/client/footer.js | 27 ++++++++++++++------------- public/src/modules/notifications.js | 8 -------- src/messaging.js | 2 +- src/socket.io/user.js | 11 +++++++++++ src/topics/unread.js | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/public/src/client/footer.js b/public/src/client/footer.js index 5e39ac142c..288123c8e1 100644 --- a/public/src/client/footer.js +++ b/public/src/client/footer.js @@ -7,21 +7,13 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu Chat.prepareDOM(); translator.prepareDOM(); - function updateUnreadTopicCount(err, count) { - if (err) { - return console.warn('Error updating unread count', err); - } - + function updateUnreadTopicCount(count) { $('#unread-count i') .toggleClass('unread-count', count > 0) .attr('data-content', count > 20 ? '20+' : count); } - function updateUnreadChatCount(err, count) { - if (err) { - return console.warn('Error updating unread count', err); - } - + function updateUnreadChatCount(count) { components.get('chat/icon') .toggleClass('unread-count', count > 0) .attr('data-content', count > 20 ? '20+' : count); @@ -62,11 +54,20 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu socket.on('event:new_post', onNewPost); } - socket.on('event:unread.updateCount', updateUnreadTopicCount); - socket.emit('user.getUnreadCount', updateUnreadTopicCount); + if (app.user.uid) { + socket.emit('user.getUnreadCounts', function(err, data) { + if (err) { + return app.alert(err.message); + } + updateUnreadTopicCount(data.unreadTopicCount); + updateUnreadChatCount(data.unreadChatCount); + Notifications.updateNotifCount(data.unreadNotificationCount); + }); + } + + socket.on('event:unread.updateCount', updateUnreadTopicCount); socket.on('event:unread.updateChatCount', updateUnreadChatCount); - socket.emit('user.getUnreadChatCount', updateUnreadChatCount); initUnreadTopics(); }); diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 03f5e7b2d5..cb6a4ef04e 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -64,14 +64,6 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound, Notifications.updateNotifCount(count); } - socket.emit('notifications.getCount', function(err, count) { - if (!err) { - Notifications.updateNotifCount(count); - } else { - Notifications.updateNotifCount(0); - } - }); - socket.on('event:new_notification', function(notifData) { app.alert({ alert_id: 'new_notif', diff --git a/src/messaging.js b/src/messaging.js index 99b01fec2f..7609ad24f9 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -314,7 +314,7 @@ var db = require('./database'), if (err) { return; } - sockets.in('uid_' + uid).emit('event:unread.updateChatCount', null, unreadCount); + sockets.in('uid_' + uid).emit('event:unread.updateChatCount', unreadCount); }); }; diff --git a/src/socket.io/user.js b/src/socket.io/user.js index ad7e4d3b52..08b5f11d0a 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -207,6 +207,17 @@ SocketUser.getUnreadChatCount = function(socket, data, callback) { messaging.getUnreadCount(socket.uid, callback); }; +SocketUser.getUnreadCounts = function(socket, data, callback) { + if (!socket.uid) { + return callback(null, {}); + } + async.parallel({ + unreadTopicCount: async.apply(topics.getTotalUnread, socket.uid), + unreadChatCount: async.apply(messaging.getUnreadCount, socket.uid), + unreadNotificationCount: async.apply(user.notifications.getUnreadCount, socket.uid) + }, callback); +}; + SocketUser.loadMore = function(socket, data, callback) { if (!data || !data.set || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/topics/unread.js b/src/topics/unread.js index d70915a7b7..294ac3cf17 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -142,7 +142,7 @@ module.exports = function(Topics) { if (err) { return callback(err); } - require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', null, count); + require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', count); callback(); }); }; From 85d09ce2e41ecbbcaa811c07a410dc48ca19a07c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 19:34:40 -0400 Subject: [PATCH 73/91] removed unsused require --- src/socket.io/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index ba1acc5408..fd3ab9e995 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -3,7 +3,6 @@ var SocketIO = require('socket.io'), socketioWildcard = require('socketio-wildcard')(), async = require('async'), - fs = require('fs'), nconf = require('nconf'), cookieParser = require('cookie-parser')(nconf.get('secret')), winston = require('winston'), From 9040a1a063095d5465a751bad5a85424f3be66b1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Oct 2015 22:54:32 -0400 Subject: [PATCH 74/91] concat minimize most common routes --- src/meta/js.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/meta/js.js b/src/meta/js.js index dd8c364924..ec00f5fb18 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -5,7 +5,6 @@ var winston = require('winston'), path = require('path'), async = require('async'), _ = require('underscore'), - os = require('os'), nconf = require('nconf'), fs = require('fs'), file = require('../file'), @@ -50,15 +49,32 @@ module.exports = function(Meta) { 'public/src/client/chats.js', 'public/src/client/infinitescroll.js', 'public/src/client/pagination.js', + 'public/src/client/recent.js', + 'public/src/client/unread.js', + 'public/src/client/topic.js', + 'public/src/client/topic/browsing.js', + 'public/src/client/topic/events.js', + 'public/src/client/topic/flag.js', + 'public/src/client/topic/fork.js', + 'public/src/client/topic/move.js', + 'public/src/client/topic/posts.js', + 'public/src/client/topic/postTools.js', + 'public/src/client/topic/threadTools.js', + 'public/src/client/categories.js', + 'public/src/client/category.js', + 'public/src/client/categoryTools.js', + 'public/src/modules/csrf.js', 'public/src/modules/translator.js', 'public/src/modules/notifications.js', 'public/src/modules/chat.js', 'public/src/modules/components.js', - 'public/src/modules/composer/formatting.js', - 'public/src/modules/composer/controls.js', - 'public/src/modules/composer/preview.js', - 'public/src/modules/categories.js', + 'public/src/modules/sort.js', + 'public/src/modules/navigator.js', + 'public/src/modules/topicSelect.js', + 'public/src/modules/share.js', + 'public/src/modules/search.js', + 'public/src/modules/alerts.js', 'public/src/modules/taskbar.js', 'public/src/modules/helpers.js', 'public/src/modules/sounds.js', @@ -72,8 +88,7 @@ module.exports = function(Meta) { async.apply(getPluginScripts), // plugin scripts via filter:scripts.get function(next) { // client scripts via "scripts" config in plugin.json var pluginsScripts = [], - pluginDirectories = [], - clientScripts = []; + pluginDirectories = []; pluginsScripts = plugins.clientScripts.filter(function(path) { if (path.endsWith('.js')) { @@ -268,4 +283,4 @@ module.exports = function(Meta) { callback(); }); } -}; \ No newline at end of file +}; From 60dc2fb9c460fb4875a8de97c9a7342ab46eaffb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 21 Oct 2015 15:27:55 -0400 Subject: [PATCH 75/91] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88ef98e1dd..33ed2c5bcb 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", "nodebb-theme-lavender": "2.0.7", - "nodebb-theme-persona": "3.0.54", + "nodebb-theme-persona": "3.0.55", "nodebb-theme-vanilla": "4.0.23", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", From 8e80eca449db260f22e4e980d61d2d83cd5ca807 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 21 Oct 2015 16:59:19 -0400 Subject: [PATCH 76/91] removed double escape --- src/socket.io/helpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index caa4c9fa5b..5b6a2a2e67 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -65,7 +65,7 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification) } notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ', ' + validator.escape(results.topicTitle) + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicTitle + ']]', bodyLong: results.postObj.content, pid: pid, nid: 'post:' + pid + ':uid:' + fromuid, @@ -94,7 +94,7 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification } notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ', ' + validator.escape(results.topicData.title) + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicData.title + ']]', path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, nid: 'tid:' + tid + ':uid:' + fromuid, from: fromuid From c426c7288f0e3c96ed2d19ddbc49f5a340226ab8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 14:56:23 -0400 Subject: [PATCH 77/91] find closest to middle in navigator --- public/src/modules/navigator.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 6a72ef89ea..d635eae181 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -97,13 +97,19 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com var middleOfViewport = $(window).scrollTop() + $(window).height() / 2; - index = parseInt($(navigator.selector).first().attr('data-index'), 10); - + index = parseInt($(navigator.selector).first().attr('data-index'), 10) + 1; + var previousDistance = Number.MAX_VALUE; $(navigator.selector).each(function() { - index++; - if ($(this).offset().top > middleOfViewport) { + var distanceToMiddle = Math.abs(middleOfViewport - $(this).offset().top); + + if (distanceToMiddle > previousDistance) { return false; } + + if (distanceToMiddle < previousDistance) { + index = parseInt($(this).attr('data-index'), 10) + 1; + previousDistance = distanceToMiddle; + } }); if (typeof navigator.callback === 'function') { From 2064f20f94bc931f8b9e7c71504c08685c2eae94 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 15:08:39 -0400 Subject: [PATCH 78/91] fix tooltip positioning --- public/src/client/topic/postTools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 9ae1cb273e..764ec7cd0a 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -39,7 +39,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator function addVoteHandler() { components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', function() { - loadDataAndCreateTooltip($(this)); + loadDataAndCreateTooltip($(this).parent()); }); } From 07e7498f458183fcb8fef7f83282a52ed6569d46 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 15:16:31 -0400 Subject: [PATCH 79/91] use sortedSetsRemove --- src/user/delete.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/user/delete.js b/src/user/delete.js index 1eba9939a2..bb7898b40a 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -125,20 +125,20 @@ module.exports = function(User) { }; function deleteUserIps(uid, callback) { - db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, function(err, ips) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, next); + }, + function (ips, next) { + var keys = ips.map(function(ip) { + return 'ip:' + ip + ':uid'; + }); + db.sortedSetsRemove(keys, uid, next); + }, + function (next) { + db.delete('uid:' + uid + ':ip', next); } - - async.each(ips, function(ip, next) { - db.sortedSetRemove('ip:' + ip + ':uid', uid, next); - }, function(err) { - if (err) { - return callback(err); - } - db.delete('uid:' + uid + ':ip', callback); - }); - }); + ], callback); } function deleteUserFromFollowers(uid, callback) { From 0210c033ee5504b474526aac0d5165091cbb7bdf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 16:59:25 -0400 Subject: [PATCH 80/91] closes #3779 --- src/favourites.js | 6 +++--- src/user/delete.js | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/favourites.js b/src/favourites.js index 137c47464e..c55702b433 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -26,13 +26,13 @@ var async = require('async'), var now = Date.now(); - if(type === 'upvote' && !unvote) { + if (type === 'upvote' && !unvote) { db.sortedSetAdd('uid:' + uid + ':upvote', now, pid); } else { db.sortedSetRemove('uid:' + uid + ':upvote', pid); } - if(type === 'upvote' || unvote) { + if (type === 'upvote' || unvote) { db.sortedSetRemove('uid:' + uid + ':downvote', pid); } else { db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); @@ -213,7 +213,7 @@ var async = require('async'), if (voteStatus.upvoted && command === 'downvote' || voteStatus.downvoted && command === 'upvote') { // e.g. User *has* upvoted, and clicks downvote hook = command; - } else if (voteStatus.upvoted || voteStatus.downvoted) { // e.g. User *has* upvotes, clicks upvote (so we "unvote") + } else if (voteStatus.upvoted || voteStatus.downvoted) { // e.g. User *has* upvoted, clicks upvote (so we "unvote") hook = 'unvote'; } else { // e.g. User *has not* voted, clicks upvote hook = command; diff --git a/src/user/delete.js b/src/user/delete.js index bb7898b40a..778d270ecc 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -4,6 +4,7 @@ var async = require('async'), db = require('../database'), posts = require('../posts'), topics = require('../topics'), + favourites = require('../favourites'), groups = require('../groups'), plugins = require('../plugins'), batch = require('../batch'); @@ -21,12 +22,35 @@ module.exports = function(User) { function(next) { deleteTopics(uid, next); }, + function(next) { + deleteVotes(uid, next); + }, function(next) { User.deleteAccount(uid, next); } ], callback); }; + function deleteVotes(uid, callback) { + async.waterfall([ + function (next) { + async.parallel({ + upvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':upvote', 0, -1), + downvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':downvote', 0, -1) + }, next); + }, + function (pids, next) { + pids = pids.upvotedPids.concat(pids.downvotedPids).filter(function(pid, index, array) { + return pid && array.indexOf(pid) === index; + }); + + async.eachLimit(pids, 50, function(pid, next) { + favourites.unvote(pid, uid, next); + }, next); + } + ], callback); + } + function deletePosts(uid, callback) { deleteSortedSetElements('uid:' + uid + ':posts', posts.purge, callback); } From 28ae101d90d77d0d272d4ca9951bd904c5ab3828 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 17:37:24 -0400 Subject: [PATCH 81/91] allow changing username if user has no password set, ie sso login --- src/socket.io/user/profile.js | 11 ++++------- src/user/password.js | 6 ++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 396fb0b265..1661eac92c 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -25,12 +25,9 @@ module.exports = function(SocketUser) { function isAdminOrSelfAndPasswordMatch(uid, data, callback) { async.parallel({ - isAdmin: function(next) { - user.isAdministrator(uid, next); - }, - passwordMatch: function(next) { - user.isPasswordCorrect(data.uid, data.password, next); - } + isAdmin: async.apply(user.isAdministrator, uid), + hasPassword: async.apply(user.hasPassword, data.uid), + passwordMatch: async.apply(user.isPasswordCorrect, data.uid, data.password) }, function(err, results) { if (err) { return callback(err); @@ -41,7 +38,7 @@ module.exports = function(SocketUser) { return callback(new Error('[[error:no-privileges]]')); } - if (self && !results.passwordMatch) { + if (self && results.hasPassword && !results.passwordMatch) { return callback(new Error('[[error:invalid-password]]')); } diff --git a/src/user/password.js b/src/user/password.js index 0c15cd07e7..b326313be4 100644 --- a/src/user/password.js +++ b/src/user/password.js @@ -25,4 +25,10 @@ module.exports = function(User) { }); }; + User.hasPassword = function(uid, callback) { + db.getObjectField('user:' + uid, 'password', function(err, hashedPassword) { + callback(err, !!hashedPassword); + }); + }; + }; \ No newline at end of file From 98e659188b6bcf4effca5d9176c8d836171cb663 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Oct 2015 17:41:13 -0400 Subject: [PATCH 82/91] removed console. log --- src/socket.io/user/profile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 1661eac92c..4e0be0acc5 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -95,7 +95,6 @@ module.exports = function(SocketUser) { user.isAdminOrSelf(socket.uid, data.uid, next); }, function (next) { - console.log('updating profile', data) user.updateProfile(data.uid, data, next); }, function (userData, next) { From 531eaa6411bd780f32d2d1695c0ef99418463560 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 13:50:30 -0400 Subject: [PATCH 83/91] cache navigation data --- src/navigation/admin.js | 15 +++++++++++++-- src/navigation/index.js | 2 -- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/navigation/admin.js b/src/navigation/admin.js index d608e1b620..b66982a310 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -7,6 +7,7 @@ var admin = {}, db = require('../database'), translator = require('../../public/src/modules/translator'); +var navigationCache = null; admin.save = function(data, callback) { var order = Object.keys(data), @@ -23,6 +24,7 @@ admin.save = function(data, callback) { return JSON.stringify(data); }); + navigationCache = null; async.waterfall([ function(next) { db.delete('navigation:enabled', next); @@ -41,10 +43,19 @@ admin.getAdmin = function(callback) { }; admin.get = function(callback) { + if (navigationCache) { + return callback(null, navigationCache); + } + db.getSortedSetRange('navigation:enabled', 0, -1, function(err, data) { - callback(err, data.map(function(item, idx) { + if (err) { + return callback(err); + } + navigationCache = data.map(function(item, idx) { return JSON.parse(item)[idx]; - })); + }); + + callback(null, navigationCache); }); }; diff --git a/src/navigation/index.js b/src/navigation/index.js index 6c8be11d2f..7d536dcca9 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -2,8 +2,6 @@ var navigation = {}, - plugins = require('../plugins'), - db = require('../database'), admin = require('./admin'), translator = require('../../public/src/modules/translator'); From 0e057a3d92bc3a094e13b15148ca18d55650b413 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 13:52:42 -0400 Subject: [PATCH 84/91] dont render 404 page for favicon --- src/routes/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/index.js b/src/routes/index.js index b2d1bac896..57a8c8134a 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -152,6 +152,8 @@ function handle404(app, middleware) { res.status(200).json({}); } else if (req.path.startsWith(relativePath + '/uploads')) { res.status(404).send(''); + } else if (req.path === '/favicon.ico') { + res.status(404).send(''); } else if (req.accepts('html')) { if (process.env.NODE_ENV === 'development') { winston.warn('Route requested but not found: ' + req.url); From 751a6c68396d420015da0086f32932d9a05a78a1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 14:07:09 -0400 Subject: [PATCH 85/91] got rid of extra call to user.getSettings --- src/controllers/api.js | 2 ++ src/middleware/header.js | 11 ++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/controllers/api.js b/src/controllers/api.js index 56308ef535..421673e7c4 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -84,6 +84,7 @@ apiController.getConfig = function(req, res, next) { config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.csrf_token = req.csrfToken(); config.searchEnabled = plugins.hasListeners('filter:search.query'); + config.bootswatchSkin = 'default'; if (!req.user) { return filterConfig(); @@ -103,6 +104,7 @@ apiController.getConfig = function(req, res, next) { config.topicPostSort = settings.topicPostSort || config.topicPostSort; config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort; config.topicSearchEnabled = settings.topicSearchEnabled || false; + config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin; filterConfig(); }); diff --git a/src/middleware/header.js b/src/middleware/header.js index 6cedf1241a..8dcde8a93a 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -67,13 +67,6 @@ module.exports = function(app, middleware) { templateValues.configJSON = JSON.stringify(res.locals.config); async.parallel({ - settings: function(next) { - if (req.uid) { - user.getSettings(req.uid, next); - } else { - next(); - } - }, isAdmin: function(next) { user.isAdministrator(req.uid, next); }, @@ -107,8 +100,8 @@ module.exports = function(app, middleware) { results.user.uid = parseInt(results.user.uid, 10); results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; - if (results.settings && results.settings.bootswatchSkin && results.settings.bootswatchSkin !== 'default') { - templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + results.settings.bootswatchSkin + '/bootstrap.min.css'; + if (res.locals.config.bootswatchSkin !== 'default') { + templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css'; } templateValues.browserTitle = controllers.helpers.buildTitle(data.title); From 88740c2e410abc70bc09b6fef4c4a3e6ecab281b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 14:11:12 -0400 Subject: [PATCH 86/91] up themes --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 33ed2c5bcb..7cb50d8f5b 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,9 @@ "nodebb-plugin-soundpack-default": "0.1.4", "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5", - "nodebb-theme-lavender": "2.0.7", - "nodebb-theme-persona": "3.0.55", - "nodebb-theme-vanilla": "4.0.23", + "nodebb-theme-lavender": "2.0.8", + "nodebb-theme-persona": "3.0.56", + "nodebb-theme-vanilla": "4.0.24", "nodebb-widget-essentials": "2.0.2", "npm": "^2.1.4", "passport": "^0.3.0", From c93b2f7fbbf04389551f27a975e39ea07b69a34b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 16:23:54 -0400 Subject: [PATCH 87/91] up packages --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7cb50d8f5b..7fefe37728 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "async": "~1.4.2", - "bcryptjs": "~2.2.1", + "bcryptjs": "~2.3.0", "body-parser": "^1.9.0", "colors": "^1.1.0", "compression": "^1.1.0", @@ -39,7 +39,7 @@ "mkdirp": "~0.5.0", "mmmagic": "^0.4.0", "morgan": "^1.3.2", - "nconf": "~0.7.1", + "nconf": "~0.8.2", "nodebb-plugin-composer-default": "1.0.19", "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", @@ -73,7 +73,7 @@ "underscore.deep": "^0.5.1", "validator": "^4.0.5", "winston": "^1.0.1", - "xregexp": "~2.0.0" + "xregexp": "~3.0.0" }, "devDependencies": { "mocha": "~1.13.0", From 74b02ee0966ca9ab72ef5e56ef400efdc55670de Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 16:24:02 -0400 Subject: [PATCH 88/91] dont crash if item is undefined --- public/src/modules/helpers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 1210b10753..a40008dfdc 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -10,8 +10,11 @@ var helpers = {}; helpers.displayMenuItem = function(data, index) { - var item = data.navigation[index], - properties = item.properties; + var item = data.navigation[index]; + if (!item) { + return false; + } + var properties = item.properties; if (properties) { if ((properties.loggedIn && !data.config.loggedIn) || From 02abcf0da76f519ce93075893fec02d934de64c1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 23 Oct 2015 17:21:23 -0400 Subject: [PATCH 89/91] add newlines tabs to meta and link tabs --- public/src/modules/helpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index a40008dfdc..ce47d78c11 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -40,7 +40,7 @@ property = tag.property ? 'property="' + tag.property + '" ' : '', content = tag.content ? 'content="' + tag.content.replace(/\n/g, ' ') + '" ' : ''; - return ''; + return '\n\t'; }; helpers.buildLinkTag = function(tag) { @@ -50,7 +50,7 @@ href = tag.href ? 'href="' + tag.href + '" ' : '', sizes = tag.sizes ? 'sizes="' + tag.sizes + '" ' : ''; - return ''; + return '\n\t'; }; helpers.stringify = function(obj) { From c10076491500c83f5dca73e32cdb014d059d9667 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 24 Oct 2015 12:31:22 -0400 Subject: [PATCH 90/91] fix t.js syntax --- src/views/admin/manage/category.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index b1d9eeb8af..b68e7892ac 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -90,7 +90,7 @@

      -
      +
      From a739ad81ab65cffac9200e93411d2d94f81fed4a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 24 Oct 2015 14:17:00 -0400 Subject: [PATCH 91/91] up mentions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7fefe37728..f237242210 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "nodebb-plugin-dbsearch": "0.2.17", "nodebb-plugin-emoji-extended": "0.4.15", "nodebb-plugin-markdown": "4.0.6", - "nodebb-plugin-mentions": "1.0.7", + "nodebb-plugin-mentions": "1.0.8", "nodebb-plugin-soundpack-default": "0.1.4", "nodebb-plugin-spam-be-gone": "0.4.2", "nodebb-rewards-essentials": "0.0.5",