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.", | 	"description": "Select tags via clicking and/or dragging, use shift to select multiple.", | ||||||
| 	"create": "Create Tag", | 	"create": "Create Tag", | ||||||
| 	"modify": "Modify Tags", | 	"modify": "Modify Tags", | ||||||
|  | 	"rename": "Rename Tags", | ||||||
| 	"delete": "Delete Selected Tags", | 	"delete": "Delete Selected Tags", | ||||||
| 	"search": "Search for tags...", | 	"search": "Search for tags...", | ||||||
| 	"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.", | 	"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.", | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ define('admin/manage/tags', [ | |||||||
| 		handleCreate(); | 		handleCreate(); | ||||||
| 		handleSearch(); | 		handleSearch(); | ||||||
| 		handleModify(); | 		handleModify(); | ||||||
|  | 		handleRename(); | ||||||
| 		handleDeleteSelected(); | 		handleDeleteSelected(); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| @@ -103,15 +104,25 @@ define('admin/manage/tags', [ | |||||||
| 							var modal = $('.bootbox'); | 							var modal = $('.bootbox'); | ||||||
| 							var bgColor = modal.find('[data-name="bgColor"]').val(); | 							var bgColor = modal.find('[data-name="bgColor"]').val(); | ||||||
| 							var color = modal.find('[data-name="color"]').val(); | 							var color = modal.find('[data-name="color"]').val(); | ||||||
|  | 							var data = []; | ||||||
| 							tagsToModify.each(function (idx, tag) { | 							tagsToModify.each(function (idx, tag) { | ||||||
| 								tag = $(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="bgColor"]').val(bgColor); | ||||||
| 								tag.find('[data-name="color"]').val(color); | 								tag.find('[data-name="color"]').val(color); | ||||||
| 								tag.find('.tag-item').css('background-color', bgColor).css('color', 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() { | 	function handleDeleteSelected() { | ||||||
| 		$('#deleteSelected').on('click', function () { | 		$('#deleteSelected').on('click', function () { | ||||||
| 			var tagsToDelete = $('.tag-row.ui-selected'); | 			var tagsToDelete = $('.tag-row.ui-selected'); | ||||||
| @@ -158,21 +209,5 @@ define('admin/manage/tags', [ | |||||||
| 		modal.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker); | 		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; | 	return Tags; | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -140,7 +140,7 @@ module.exports = function (Posts) { | |||||||
| 				db.setObject('topic:' + tid, results.topic, next); | 				db.setObject('topic:' + tid, results.topic, next); | ||||||
| 			}, | 			}, | ||||||
| 			function (next) { | 			function (next) { | ||||||
| 				topics.updateTags(tid, data.tags, next); | 				topics.updateTopicTags(tid, data.tags, next); | ||||||
| 			}, | 			}, | ||||||
| 			function (next) { | 			function (next) { | ||||||
| 				topics.getTopicTagsObjects(tid, next); | 				topics.getTopicTagsObjects(tid, next); | ||||||
|   | |||||||
| @@ -13,11 +13,19 @@ Tags.create = function (socket, data, callback) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| Tags.update = function (socket, data, callback) { | Tags.update = function (socket, data, callback) { | ||||||
| 	if (!data) { | 	if (!Array.isArray(data)) { | ||||||
| 		return callback(new Error('[[error:invalid-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) { | Tags.deleteTags = function (socket, data, callback) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ var meta = require('../meta'); | |||||||
| var _ = require('lodash'); | var _ = require('lodash'); | ||||||
| var plugins = require('../plugins'); | var plugins = require('../plugins'); | ||||||
| var utils = require('../utils'); | var utils = require('../utils'); | ||||||
|  | var batch = require('../batch'); | ||||||
|  |  | ||||||
| module.exports = function (Topics) { | module.exports = function (Topics) { | ||||||
| 	Topics.createTags = function (tags, tid, timestamp, callback) { | 	Topics.createTags = function (tags, tid, timestamp, callback) { | ||||||
| @@ -96,13 +96,61 @@ module.exports = function (Topics) { | |||||||
| 		], callback); | 		], callback); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Topics.updateTag = function (tag, data, callback) { | 	Topics.updateTags = function (data, callback) { | ||||||
| 		if (!tag) { | 		async.eachSeries(data, function (tagData, next) { | ||||||
| 			return setImmediate(callback, new Error('[[error:invalid-tag]]')); | 			db.setObject('tag:' + tagData.value, { | ||||||
| 		} | 				color: tagData.color, | ||||||
| 		db.setObject('tag:' + tag, data, callback); | 				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) { | 	function updateTagCount(tag, callback) { | ||||||
| 		callback = callback || function () {}; | 		callback = callback || function () {}; | ||||||
| 		async.waterfall([ | 		async.waterfall([ | ||||||
| @@ -148,7 +196,9 @@ module.exports = function (Topics) { | |||||||
| 					return 'tag:' + tag; | 					return 'tag:' + tag; | ||||||
| 				}), next); | 				}), next); | ||||||
| 			}, | 			}, | ||||||
| 		], callback); | 		], function (err) { | ||||||
|  | 			callback(err); | ||||||
|  | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function removeTagsFromTopics(tags, callback) { | 	function removeTagsFromTopics(tags, callback) { | ||||||
| @@ -266,7 +316,7 @@ module.exports = function (Topics) { | |||||||
| 		], callback); | 		], callback); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Topics.updateTags = function (tid, tags, callback) { | 	Topics.updateTopicTags = function (tid, tags, callback) { | ||||||
| 		callback = callback || function () {}; | 		callback = callback || function () {}; | ||||||
| 		async.waterfall([ | 		async.waterfall([ | ||||||
| 			function (next) { | 			function (next) { | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ | |||||||
| 				<p>[[admin/manage/tags:description]]</p> | 				<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="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="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> | 				<button class="btn btn-warning btn-block" id="deleteSelected">[[admin/manage/tags:delete]]</button> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| @@ -74,4 +75,11 @@ | |||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</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> | </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 }, { | 			socketAdmin.tags.update({ uid: adminUid }, { | ||||||
| 				bgColor: '#ff0000', | 				bgColor: '#ff0000', | ||||||
| 				color: '#00ff00', | 				color: '#00ff00', | ||||||
| 			}, function (err) { | 			}, function (err) { | ||||||
| 				assert.equal(err.message, '[[error:invalid-tag]]'); | 				assert.equal(err.message, '[[error:invalid-data]]'); | ||||||
| 				done(); | 				done(); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		it('should update tag', function (done) { | 		it('should update tag', function (done) { | ||||||
| 			socketAdmin.tags.update({ uid: adminUid }, { | 			socketAdmin.tags.update({ uid: adminUid }, [{ | ||||||
| 				tag: 'emptytag', | 				value: 'emptytag', | ||||||
| 				bgColor: '#ff0000', | 				bgColor: '#ff0000', | ||||||
| 				color: '#00ff00', | 				color: '#00ff00', | ||||||
| 			}, function (err) { | 			}], function (err) { | ||||||
| 				assert.ifError(err); | 				assert.ifError(err); | ||||||
| 				db.getObject('tag:emptytag', function (err, data) { | 				db.getObject('tag:emptytag', function (err, data) { | ||||||
| 					assert.ifError(err); | 					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) { | 		it('should return related topics', function (done) { | ||||||
| 			var meta = require('../src/meta'); | 			var meta = require('../src/meta'); | ||||||
| 			meta.config.maximumRelatedTopics = 2; | 			meta.config.maximumRelatedTopics = 2; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user