mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-31 19:15:58 +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