From f3fda152bfc2582d4e5dab46df1ae2cfec96e44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Jan 2018 18:20:57 -0500 Subject: [PATCH] closes #6216 --- public/language/en-GB/admin/manage/tags.json | 1 + public/src/admin/manage/tags.js | 71 +++++++++++++++----- src/posts/edit.js | 2 +- src/socket.io/admin/tags.js | 12 +++- src/topics/tags.js | 66 +++++++++++++++--- src/views/admin/manage/tags.tpl | 8 +++ test/topics.js | 39 +++++++++-- 7 files changed, 165 insertions(+), 34 deletions(-) diff --git a/public/language/en-GB/admin/manage/tags.json b/public/language/en-GB/admin/manage/tags.json index db40e9f098..df597a6166 100644 --- a/public/language/en-GB/admin/manage/tags.json +++ b/public/language/en-GB/admin/manage/tags.json @@ -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 here to visit the tag settings page.", diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index 717d4bba1d..de67b86743 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -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; }); diff --git a/src/posts/edit.js b/src/posts/edit.js index 0a9867feeb..991639af40 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -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); diff --git a/src/socket.io/admin/tags.js b/src/socket.io/admin/tags.js index 8fe50790eb..f3c403e704 100644 --- a/src/socket.io/admin/tags.js +++ b/src/socket.io/admin/tags.js @@ -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.updateTags(data, callback); +}; + +Tags.rename = function (socket, data, callback) { + if (!Array.isArray(data)) { return callback(new Error('[[error:invalid-data]]')); } - topics.updateTag(data.tag, data, callback); + topics.renameTags(data, callback); }; Tags.deleteTags = function (socket, data, callback) { diff --git a/src/topics/tags.js b/src/topics/tags.js index 48cf353005..6d725c2269 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -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) { diff --git a/src/views/admin/manage/tags.tpl b/src/views/admin/manage/tags.tpl index 8fa01a1200..56c1c5b8c7 100644 --- a/src/views/admin/manage/tags.tpl +++ b/src/views/admin/manage/tags.tpl @@ -41,6 +41,7 @@

[[admin/manage/tags:description]]

+ @@ -74,4 +75,11 @@ + + diff --git a/test/topics.js b/test/topics.js index 903b6b8db4..559eaab973 100644 --- a/test/topics.js +++ b/test/topics.js @@ -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;