mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	| @@ -5,7 +5,7 @@ define('forum/category', [ | |||||||
| 	'forum/infinitescroll', | 	'forum/infinitescroll', | ||||||
| 	'share', | 	'share', | ||||||
| 	'navigator', | 	'navigator', | ||||||
| 	'forum/categoryTools', | 	'forum/category/tools', | ||||||
| 	'sort', | 	'sort', | ||||||
| 	'components', | 	'components', | ||||||
| 	'translator', | 	'translator', | ||||||
|   | |||||||
| @@ -4,7 +4,12 @@ | |||||||
| /* globals define, app, socket, bootbox, ajaxify */ | /* 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 = {}; | 	var CategoryTools = {}; | ||||||
| 
 | 
 | ||||||
| @@ -13,6 +18,8 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', | |||||||
| 
 | 
 | ||||||
| 		topicSelect.init(updateDropdownOptions); | 		topicSelect.init(updateDropdownOptions); | ||||||
| 
 | 
 | ||||||
|  | 		handlePinnedTopicSort(); | ||||||
|  | 
 | ||||||
| 		components.get('topic/delete').on('click', function () { | 		components.get('topic/delete').on('click', function () { | ||||||
| 			categoryCommand('delete', topicSelect.getSelectedTids()); | 			categoryCommand('delete', topicSelect.getSelectedTids()); | ||||||
| 			return false; | 			return false; | ||||||
| @@ -235,5 +242,30 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', | |||||||
| 		getTopicEl(data.tid).remove(); | 		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; | 	return CategoryTools; | ||||||
| }); | }); | ||||||
| @@ -58,7 +58,7 @@ module.exports = function (Meta) { | |||||||
| 				'public/src/client/topic/threadTools.js', | 				'public/src/client/topic/threadTools.js', | ||||||
| 				'public/src/client/categories.js', | 				'public/src/client/categories.js', | ||||||
| 				'public/src/client/category.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/translator.js', | ||||||
| 				'public/src/modules/notifications.js', | 				'public/src/modules/notifications.js', | ||||||
|   | |||||||
| @@ -121,4 +121,12 @@ module.exports = function (SocketTopics) { | |||||||
| 		], callback); | 		], 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); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| }; | }; | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); | var async = require('async'); | ||||||
|  | var _ = require('underscore'); | ||||||
|  |  | ||||||
| var db = require('../database'); | var db = require('../database'); | ||||||
| var categories = require('../categories'); | var categories = require('../categories'); | ||||||
| @@ -211,6 +212,49 @@ module.exports = function (Topics) { | |||||||
| 		], callback); | 		], 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) { | 	topicTools.move = function (tid, cid, uid, callback) { | ||||||
| 		var topic; | 		var topic; | ||||||
| 		async.waterfall([ | 		async.waterfall([ | ||||||
|   | |||||||
| @@ -1,2 +1,2 @@ | |||||||
| --reporter dot | --reporter dot | ||||||
| --timeout 10000 | --timeout 15000 | ||||||
|   | |||||||
| @@ -101,6 +101,7 @@ describe('Plugins', function () { | |||||||
| 		var latest; | 		var latest; | ||||||
| 		var pluginName = 'nodebb-plugin-imgur'; | 		var pluginName = 'nodebb-plugin-imgur'; | ||||||
| 		it('should install a plugin', function (done) { | 		it('should install a plugin', function (done) { | ||||||
|  | 			this.timeout(20000); | ||||||
| 			plugins.toggleInstall(pluginName, '1.0.16', function (err, pluginData) { | 			plugins.toggleInstall(pluginName, '1.0.16', function (err, pluginData) { | ||||||
| 				assert.ifError(err); | 				assert.ifError(err); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,8 +45,6 @@ describe('Topic\'s', function () { | |||||||
| 				done(); | 				done(); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  |  | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	describe('.post', function () { | 	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 () { | 	describe('.ignore', function () { | ||||||
| 		var newTid; | 		var newTid; | ||||||
| 		var uid; | 		var uid; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user