From c4d4d2385b01796413576815721416d65fe8163c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 22 Dec 2016 01:48:41 +0300 Subject: [PATCH] closes #3973 closes #5303 --- public/src/client/category.js | 2 +- .../{categoryTools.js => category/tools.js} | 34 ++++++- src/meta/js.js | 2 +- src/socket.io/topics/tools.js | 8 ++ src/topics/tools.js | 44 +++++++++ test/mocha.opts | 2 +- test/plugins.js | 1 + test/topics.js | 94 ++++++++++++++++++- 8 files changed, 181 insertions(+), 6 deletions(-) rename public/src/client/{categoryTools.js => category/tools.js} (88%) diff --git a/public/src/client/category.js b/public/src/client/category.js index 4592c551a1..4afabb5093 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -5,7 +5,7 @@ define('forum/category', [ 'forum/infinitescroll', 'share', 'navigator', - 'forum/categoryTools', + 'forum/category/tools', 'sort', 'components', 'translator', diff --git a/public/src/client/categoryTools.js b/public/src/client/category/tools.js similarity index 88% rename from public/src/client/categoryTools.js rename to public/src/client/category/tools.js index 548386ffc6..947ab50d28 100644 --- a/public/src/client/categoryTools.js +++ b/public/src/client/category/tools.js @@ -4,7 +4,12 @@ /* globals define, app, socket, bootbox, ajaxify */ -define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', 'translator'], function (move, topicSelect, components, translator) { +define('forum/category/tools', [ + 'forum/topic/move', + 'topicSelect', + 'components', + 'translator' +], function (move, topicSelect, components, translator) { var CategoryTools = {}; @@ -13,6 +18,8 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', topicSelect.init(updateDropdownOptions); + handlePinnedTopicSort(); + components.get('topic/delete').on('click', function () { categoryCommand('delete', topicSelect.getSelectedTids()); return false; @@ -235,5 +242,30 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', getTopicEl(data.tid).remove(); } + function handlePinnedTopicSort() { + if (!ajaxify.data.privileges.isAdminOrMod) { + return; + } + app.loadJQueryUI(function () { + $('[component="category"]').sortable({ + items: '[component="category/topic"].pinned', + update: function () { + var data = []; + + var pinnedTopics = $('[component="category/topic"].pinned'); + pinnedTopics.each(function (index, element) { + data.push({tid: $(element).attr('data-tid'), order: pinnedTopics.length - index - 1}); + }); + + socket.emit('topics.orderPinnedTopics', data, function (err) { + if (err) { + return app.alertError(err.message); + } + }); + } + }); + }); + } + return CategoryTools; }); diff --git a/src/meta/js.js b/src/meta/js.js index 05f045f283..7f39235993 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -58,7 +58,7 @@ module.exports = function (Meta) { 'public/src/client/topic/threadTools.js', 'public/src/client/categories.js', 'public/src/client/category.js', - 'public/src/client/categoryTools.js', + 'public/src/client/category/tools.js', 'public/src/modules/translator.js', 'public/src/modules/notifications.js', diff --git a/src/socket.io/topics/tools.js b/src/socket.io/topics/tools.js index b124c7e3fd..ede87d2599 100644 --- a/src/socket.io/topics/tools.js +++ b/src/socket.io/topics/tools.js @@ -121,4 +121,12 @@ module.exports = function (SocketTopics) { ], callback); } + SocketTopics.orderPinnedTopics = function (socket, data, callback) { + if (!Array.isArray(data)) { + return callback(new Error('[[error:invalid-data]]')); + } + + topics.tools.orderPinnedTopics(socket.uid, data, callback); + }; + }; \ No newline at end of file diff --git a/src/topics/tools.js b/src/topics/tools.js index 28d5cd42d4..c69b0692ab 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -1,6 +1,7 @@ 'use strict'; var async = require('async'); +var _ = require('underscore'); var db = require('../database'); var categories = require('../categories'); @@ -211,6 +212,49 @@ module.exports = function (Topics) { ], callback); } + topicTools.orderPinnedTopics = function (uid, data, callback) { + var cid; + async.waterfall([ + function (next) { + var tids = data.map(function (topic) { + return topic && topic.tid; + }); + Topics.getTopicsFields(tids, ['cid'], next); + }, + function (topicData, next) { + var uniqueCids = _.unique(topicData.map(function (topicData) { + return topicData && parseInt(topicData.cid, 10); + })); + + if (uniqueCids.length > 1 || !uniqueCids.length || !uniqueCids[0]) { + return next(new Error('[[error:invalid-data]]')); + } + cid = uniqueCids[0]; + + privileges.categories.isAdminOrMod(cid, uid, next); + }, + function (isAdminOrMod, next) { + if (!isAdminOrMod) { + return next(new Error('[[error:no-privileges]]')); + } + async.eachSeries(data, function (topicData, next) { + async.waterfall([ + function (next) { + db.isSortedSetMember('cid:' + cid + ':tids:pinned', topicData.tid, next); + }, + function (isPinned, next) { + if (isPinned) { + db.sortedSetAdd('cid:' + cid + ':tids:pinned', topicData.order, topicData.tid, next); + } else { + setImmediate(next); + } + } + ], next); + }, next); + } + ], callback); + }; + topicTools.move = function (tid, cid, uid, callback) { var topic; async.waterfall([ diff --git a/test/mocha.opts b/test/mocha.opts index b0a5a2aa02..49399dd418 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1,2 @@ --reporter dot ---timeout 10000 +--timeout 15000 diff --git a/test/plugins.js b/test/plugins.js index 67709bcf49..09537d8990 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -101,6 +101,7 @@ describe('Plugins', function () { var latest; var pluginName = 'nodebb-plugin-imgur'; it('should install a plugin', function (done) { + this.timeout(20000); plugins.toggleInstall(pluginName, '1.0.16', function (err, pluginData) { assert.ifError(err); diff --git a/test/topics.js b/test/topics.js index 9577f89a1e..3a192b9e94 100644 --- a/test/topics.js +++ b/test/topics.js @@ -45,8 +45,6 @@ describe('Topic\'s', function () { done(); }); }); - - }); describe('.post', function () { @@ -362,6 +360,98 @@ describe('Topic\'s', function () { }); }); + describe('order pinned topics', function () { + var tid1; + var tid2; + var tid3; + before(function (done) { + function createTopic(callback) { + topics.post({ + uid: topic.userId, + title: 'topic for test', + content: 'topic content', + cid: topic.categoryId + }, callback); + } + async.series({ + topic1: function (next) { + createTopic(next); + }, + topic2: function (next) { + createTopic(next); + }, + topic3: function (next) { + createTopic(next); + } + }, function (err, results) { + assert.ifError(err); + tid1 = results.topic1.topicData.tid; + tid2 = results.topic2.topicData.tid; + tid3 = results.topic3.topicData.tid; + async.series([ + function (next) { + topics.tools.pin(tid1, adminUid, next); + }, + function (next) { + topics.tools.pin(tid2, adminUid, next); + } + ], done); + }); + }); + + var socketTopics = require('../src/socket.io/topics'); + it('should error with invalid data', function (done) { + socketTopics.orderPinnedTopics({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error with invalid data', function (done) { + socketTopics.orderPinnedTopics({uid: adminUid}, [null, null], function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error with unprivileged user', function (done) { + socketTopics.orderPinnedTopics({uid: 0}, [{tid: tid1}, {tid: tid2}], function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should not do anything if topics are not pinned', function (done) { + socketTopics.orderPinnedTopics({uid: adminUid}, [{tid: tid3}], function (err) { + assert.ifError(err); + db.isSortedSetMember('cid:' + topic.categoryId + ':tids:pinned', tid3, function (err, isMember) { + assert.ifError(err); + assert(!isMember); + done(); + }); + }); + }); + + it('should order pinned topics', function (done) { + db.getSortedSetRevRange('cid:' + topic.categoryId + ':tids:pinned', 0, -1, function (err, pinnedTids) { + assert.ifError(err); + assert.equal(pinnedTids[0], tid2); + assert.equal(pinnedTids[1], tid1); + socketTopics.orderPinnedTopics({uid: adminUid}, [{tid: tid1, order: 1}, {tid: tid2, order: 0}], function (err) { + assert.ifError(err); + db.getSortedSetRevRange('cid:' + topic.categoryId + ':tids:pinned', 0, -1, function (err, pinnedTids) { + assert.ifError(err); + assert.equal(pinnedTids[0], tid1); + assert.equal(pinnedTids[1], tid2); + done(); + }); + }); + }); + }); + + }); + + describe('.ignore', function () { var newTid; var uid;