mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	closes #6216
This commit is contained in:
		| @@ -6,6 +6,7 @@ | ||||
| 	"description": "Select tags via clicking and/or dragging, use shift to select multiple.", | ||||
| 	"create": "Create Tag", | ||||
| 	"modify": "Modify Tags", | ||||
| 	"rename": "Rename Tags", | ||||
| 	"delete": "Delete Selected Tags", | ||||
| 	"search": "Search for tags...", | ||||
| 	"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.", | ||||
|   | ||||
| @@ -15,6 +15,7 @@ define('admin/manage/tags', [ | ||||
| 		handleCreate(); | ||||
| 		handleSearch(); | ||||
| 		handleModify(); | ||||
| 		handleRename(); | ||||
| 		handleDeleteSelected(); | ||||
| 	}; | ||||
|  | ||||
| @@ -103,15 +104,25 @@ define('admin/manage/tags', [ | ||||
| 							var modal = $('.bootbox'); | ||||
| 							var bgColor = modal.find('[data-name="bgColor"]').val(); | ||||
| 							var color = modal.find('[data-name="color"]').val(); | ||||
|  | ||||
| 							var data = []; | ||||
| 							tagsToModify.each(function (idx, tag) { | ||||
| 								tag = $(tag); | ||||
| 								data.push({ | ||||
| 									value: tag.attr('data-tag'), | ||||
| 									color: modal.find('[data-name="color"]').val(), | ||||
| 									bgColor: modal.find('[data-name="bgColor"]').val(), | ||||
| 								}); | ||||
|  | ||||
| 								tag.find('[data-name="bgColor"]').val(bgColor); | ||||
| 								tag.find('[data-name="color"]').val(color); | ||||
| 								tag.find('.tag-item').css('background-color', bgColor).css('color', color); | ||||
| 							}); | ||||
|  | ||||
| 								save(tag); | ||||
| 							socket.emit('admin.tags.update', data, function (err) { | ||||
| 								if (err) { | ||||
| 									return app.alertError(err.message); | ||||
| 								} | ||||
| 								app.alertSuccess('[[admin/manage/tags:alerts.update-success]]'); | ||||
| 							}); | ||||
| 						}, | ||||
| 					}, | ||||
| @@ -122,6 +133,46 @@ define('admin/manage/tags', [ | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function handleRename() { | ||||
| 		$('#rename').on('click', function () { | ||||
| 			var tagsToModify = $('.tag-row.ui-selected'); | ||||
| 			if (!tagsToModify.length) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			var firstTag = $(tagsToModify[0]); | ||||
| 			var title = tagsToModify.length > 1 ? '[[admin/manage/tags:alerts.editing-multiple]]' : '[[admin/manage/tags:alerts.editing-x, ' + firstTag.find('.tag-item').attr('data-tag') + ']]'; | ||||
|  | ||||
| 			var modal = bootbox.dialog({ | ||||
| 				title: title, | ||||
| 				message: $('.rename-modal').html(), | ||||
| 				buttons: { | ||||
| 					success: { | ||||
| 						label: 'Save', | ||||
| 						className: 'btn-primary save', | ||||
| 						callback: function () { | ||||
| 							var data = []; | ||||
| 							tagsToModify.each(function (idx, tag) { | ||||
| 								tag = $(tag); | ||||
| 								data.push({ | ||||
| 									value: tag.attr('data-tag'), | ||||
| 									newName: modal.find('[data-name="value"]').val(), | ||||
| 								}); | ||||
| 							}); | ||||
|  | ||||
| 							socket.emit('admin.tags.rename', data, function (err) { | ||||
| 								if (err) { | ||||
| 									return app.alertError(err.message); | ||||
| 								} | ||||
| 								app.alertSuccess('[[admin/manage/tags:alerts.update-success]]'); | ||||
| 							}); | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function handleDeleteSelected() { | ||||
| 		$('#deleteSelected').on('click', function () { | ||||
| 			var tagsToDelete = $('.tag-row.ui-selected'); | ||||
| @@ -158,21 +209,5 @@ define('admin/manage/tags', [ | ||||
| 		modal.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker); | ||||
| 	} | ||||
|  | ||||
| 	function save(tag) { | ||||
| 		var data = { | ||||
| 			tag: tag.attr('data-tag'), | ||||
| 			bgColor: tag.find('[data-name="bgColor"]').val(), | ||||
| 			color: tag.find('[data-name="color"]').val(), | ||||
| 		}; | ||||
|  | ||||
| 		socket.emit('admin.tags.update', data, function (err) { | ||||
| 			if (err) { | ||||
| 				return app.alertError(err.message); | ||||
| 			} | ||||
|  | ||||
| 			app.alertSuccess('[[admin/manage/tags:alerts.update-success]]'); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	return Tags; | ||||
| }); | ||||
|   | ||||
| @@ -140,7 +140,7 @@ module.exports = function (Posts) { | ||||
| 				db.setObject('topic:' + tid, results.topic, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				topics.updateTags(tid, data.tags, next); | ||||
| 				topics.updateTopicTags(tid, data.tags, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				topics.getTopicTagsObjects(tid, next); | ||||
|   | ||||
| @@ -13,11 +13,19 @@ Tags.create = function (socket, data, callback) { | ||||
| }; | ||||
|  | ||||
| Tags.update = function (socket, data, callback) { | ||||
| 	if (!data) { | ||||
| 	if (!Array.isArray(data)) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
|  | ||||
| 	topics.updateTag(data.tag, data, callback); | ||||
| 	topics.updateTags(data, callback); | ||||
| }; | ||||
|  | ||||
| Tags.rename = function (socket, data, callback) { | ||||
| 	if (!Array.isArray(data)) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
|  | ||||
| 	topics.renameTags(data, callback); | ||||
| }; | ||||
|  | ||||
| Tags.deleteTags = function (socket, data, callback) { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ var meta = require('../meta'); | ||||
| var _ = require('lodash'); | ||||
| var plugins = require('../plugins'); | ||||
| var utils = require('../utils'); | ||||
|  | ||||
| var batch = require('../batch'); | ||||
|  | ||||
| module.exports = function (Topics) { | ||||
| 	Topics.createTags = function (tags, tid, timestamp, callback) { | ||||
| @@ -96,13 +96,61 @@ module.exports = function (Topics) { | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Topics.updateTag = function (tag, data, callback) { | ||||
| 		if (!tag) { | ||||
| 			return setImmediate(callback, new Error('[[error:invalid-tag]]')); | ||||
| 		} | ||||
| 		db.setObject('tag:' + tag, data, callback); | ||||
| 	Topics.updateTags = function (data, callback) { | ||||
| 		async.eachSeries(data, function (tagData, next) { | ||||
| 			db.setObject('tag:' + tagData.value, { | ||||
| 				color: tagData.color, | ||||
| 				bgColor: tagData.bgColor, | ||||
| 			}, next); | ||||
| 		}, callback); | ||||
| 	}; | ||||
|  | ||||
| 	Topics.renameTags = function (data, callback) { | ||||
| 		async.eachSeries(data, function (tagData, next) { | ||||
| 			renameTag(tagData.value, tagData.newName, next); | ||||
| 		}, callback); | ||||
| 	}; | ||||
|  | ||||
| 	function renameTag(tag, newTagName, callback) { | ||||
| 		if (!newTagName || tag === newTagName) { | ||||
| 			return setImmediate(callback); | ||||
| 		} | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				Topics.createEmptyTag(newTagName, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				batch.processSortedSet('tag:' + tag + ':topics', function (tids, next) { | ||||
| 					async.waterfall([ | ||||
| 						function (next) { | ||||
| 							db.sortedSetScores('tag:' + tag + ':topics', tids, next); | ||||
| 						}, | ||||
| 						function (scores, next) { | ||||
| 							db.sortedSetAdd('tag:' + newTagName + ':topics', scores, tids, next); | ||||
| 						}, | ||||
| 						function (next) { | ||||
| 							var keys = tids.map(function (tid) { | ||||
| 								return 'topic:' + tid + ':tags'; | ||||
| 							}); | ||||
|  | ||||
| 							async.series([ | ||||
| 								async.apply(db.sortedSetRemove, 'tag:' + tag + ':topics', tids), | ||||
| 								async.apply(db.setsRemove, keys, tag), | ||||
| 								async.apply(db.setsAdd, keys, newTagName), | ||||
| 							], next); | ||||
| 						}, | ||||
| 					], next); | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				Topics.deleteTag(tag, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				updateTagCount(newTagName, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	} | ||||
|  | ||||
| 	function updateTagCount(tag, callback) { | ||||
| 		callback = callback || function () {}; | ||||
| 		async.waterfall([ | ||||
| @@ -148,7 +196,9 @@ module.exports = function (Topics) { | ||||
| 					return 'tag:' + tag; | ||||
| 				}), next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		], function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	function removeTagsFromTopics(tags, callback) { | ||||
| @@ -266,7 +316,7 @@ module.exports = function (Topics) { | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	Topics.updateTags = function (tid, tags, callback) { | ||||
| 	Topics.updateTopicTags = function (tid, tags, callback) { | ||||
| 		callback = callback || function () {}; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
|   | ||||
| @@ -41,6 +41,7 @@ | ||||
| 				<p>[[admin/manage/tags:description]]</p> | ||||
| 				<button class="btn btn-primary btn-block" id="create">[[admin/manage/tags:create]]</button> | ||||
| 				<button class="btn btn-primary btn-block" id="modify">[[admin/manage/tags:modify]]</button> | ||||
| 				<button class="btn btn-primary btn-block" id="rename">[[admin/manage/tags:rename]]</button> | ||||
| 				<button class="btn btn-warning btn-block" id="deleteSelected">[[admin/manage/tags:delete]]</button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| @@ -74,4 +75,11 @@ | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| 	<div class="rename-modal hidden"> | ||||
| 		<div class="form-group"> | ||||
| 			<label for="value">[[admin/manage/tags:name]]</label> | ||||
| 			<input id="value" data-name="value" value="{tags.value}" class="form-control" /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|   | ||||
| @@ -1350,22 +1350,22 @@ describe('Topic\'s', function () { | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		it('should error if data.tag is invalid', function (done) { | ||||
| 		it('should error if data is not an array', function (done) { | ||||
| 			socketAdmin.tags.update({ uid: adminUid }, { | ||||
| 				bgColor: '#ff0000', | ||||
| 				color: '#00ff00', | ||||
| 			}, function (err) { | ||||
| 				assert.equal(err.message, '[[error:invalid-tag]]'); | ||||
| 				assert.equal(err.message, '[[error:invalid-data]]'); | ||||
| 				done(); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		it('should update tag', function (done) { | ||||
| 			socketAdmin.tags.update({ uid: adminUid }, { | ||||
| 				tag: 'emptytag', | ||||
| 			socketAdmin.tags.update({ uid: adminUid }, [{ | ||||
| 				value: 'emptytag', | ||||
| 				bgColor: '#ff0000', | ||||
| 				color: '#00ff00', | ||||
| 			}, function (err) { | ||||
| 			}], function (err) { | ||||
| 				assert.ifError(err); | ||||
| 				db.getObject('tag:emptytag', function (err, data) { | ||||
| 					assert.ifError(err); | ||||
| @@ -1376,6 +1376,35 @@ describe('Topic\'s', function () { | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		it('should rename tags', function (done) { | ||||
| 			async.parallel({ | ||||
| 				topic1: function (next) { | ||||
| 					topics.post({ uid: adminUid, tags: ['plugins'], title: 'topic tagged with plugins', content: 'topic 1 content', cid: topic.categoryId }, next); | ||||
| 				}, | ||||
| 				topic2: function (next) { | ||||
| 					topics.post({ uid: adminUid, tags: ['plugin'], title: 'topic tagged with plugin', content: 'topic 2 content', cid: topic.categoryId }, next); | ||||
| 				}, | ||||
| 			}, function (err, result) { | ||||
| 				assert.ifError(err); | ||||
| 				socketAdmin.tags.rename({ uid: adminUid }, [{ | ||||
| 					value: 'plugin', | ||||
| 					newName: 'plugins', | ||||
| 				}], function (err) { | ||||
| 					assert.ifError(err); | ||||
| 					topics.getTagTids('plugins', 0, -1, function (err, tids) { | ||||
| 						assert.ifError(err); | ||||
| 						assert.equal(tids.length, 2); | ||||
| 						topics.getTopicTags(result.topic2.topicData.tid, function (err, tags) { | ||||
| 							assert.ifError(err); | ||||
| 							assert.equal(tags.length, 1); | ||||
| 							assert.equal(tags[0], 'plugins'); | ||||
| 							done(); | ||||
| 						}); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		it('should return related topics', function (done) { | ||||
| 			var meta = require('../src/meta'); | ||||
| 			meta.config.maximumRelatedTopics = 2; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user