CTRL
to select multiple tags.",
"create": "Create Tag",
"modify": "Modify Tags",
diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js
index 512e0732d7..6cb4ad96f6 100644
--- a/public/src/admin/manage/tags.js
+++ b/public/src/admin/manage/tags.js
@@ -13,7 +13,6 @@ define('admin/manage/tags', [
handleCreate();
handleSearch();
- handleModify();
handleRename();
handleDeleteSelected();
};
@@ -82,54 +81,6 @@ define('admin/manage/tags', [
});
}
- function handleModify() {
- $('#modify').on('click', function () {
- var tagsToModify = $('.tag-row.ui-selected');
- if (!tagsToModify.length) {
- return;
- }
-
- var firstTag = $(tagsToModify[0]);
- bootbox.dialog({
- title: '[[admin/manage/tags:alerts.editing]]',
- message: firstTag.find('.tag-modal').html(),
- buttons: {
- success: {
- label: 'Save',
- className: 'btn-primary save',
- callback: function () {
- var modal = $('.bootbox');
- var resetColors = modal.find('#reset-colors').is(':checked');
- var bgColor = resetColors ? '' : modal.find('[data-name="bgColor"]').val();
- var color = resetColors ? '' : modal.find('[data-name="color"]').val();
-
- var data = [];
- tagsToModify.each(function (idx, tag) {
- tag = $(tag);
- data.push({
- value: tag.attr('data-tag'),
- color: color,
- bgColor: bgColor,
- });
-
- 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);
- });
-
- socket.emit('admin.tags.update', data, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
- app.alertSuccess('[[admin/manage/tags:alerts.update-success]]');
- });
- },
- },
- },
- });
- });
- }
-
function handleRename() {
$('#rename').on('click', function () {
var tagsToModify = $('.tag-row.ui-selected');
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index 4132b79b9e..19b3eb3172 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -89,8 +89,11 @@ Topics.addTags = async (req, res) => {
if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) {
return helpers.formatApiResponse(403, res);
}
+ const cid = await topics.getTopicField(req.params.tid, 'cid');
+ await topics.validateTags(req.body.tags, cid, req.user.uid, req.params.tid);
+ const tags = await topics.filterTags(req.body.tags);
- await topics.createTags(req.body.tags, req.params.tid, Date.now());
+ await topics.addTags(tags, [req.params.tid]);
helpers.formatApiResponse(200, res);
};
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
index 4a908e37bd..ead7f6fc5e 100644
--- a/src/privileges/admin.js
+++ b/src/privileges/admin.js
@@ -93,7 +93,6 @@ privsAdmin.socketMap = {
'admin.user.invite': 'admin:users',
'admin.tags.create': 'admin:tags',
- 'admin.tags.update': 'admin:tags',
'admin.tags.rename': 'admin:tags',
'admin.tags.deleteTags': 'admin:tags',
diff --git a/src/search.js b/src/search.js
index 7ef8eb8062..99fae633e8 100644
--- a/src/search.js
+++ b/src/search.js
@@ -155,18 +155,15 @@ async function getUsers(uids, data) {
async function getTopics(tids, data) {
const topicsData = await topics.getTopicsData(tids);
const cids = _.uniq(topicsData.map(topic => topic && topic.cid));
- const [categories, tags] = await Promise.all([
- getCategories(cids, data),
- getTags(tids, data),
- ]);
+ const categories = await getCategories(cids, data);
const cidToCategory = _.zipObject(cids, categories);
- topicsData.forEach((topic, index) => {
+ topicsData.forEach((topic) => {
if (topic && categories && cidToCategory[topic.cid]) {
topic.category = cidToCategory[topic.cid];
}
- if (topic && tags && tags[index]) {
- topic.tags = tags[index];
+ if (topic && topic.tags) {
+ topic.tags = topic.tags.map(tag => tag.value);
}
});
@@ -186,13 +183,6 @@ async function getCategories(cids, data) {
return await db.getObjectsFields(cids.map(cid => `category:${cid}`), categoryFields);
}
-async function getTags(tids, data) {
- if (Array.isArray(data.hasTags) && data.hasTags.length) {
- return await topics.getTopicsTags(tids);
- }
- return null;
-}
-
function filterByPostcount(posts, postCount, repliesFilter) {
postCount = parseInt(postCount, 10);
if (postCount) {
diff --git a/src/socket.io/admin/tags.js b/src/socket.io/admin/tags.js
index 698c303224..cc67c017d1 100644
--- a/src/socket.io/admin/tags.js
+++ b/src/socket.io/admin/tags.js
@@ -12,14 +12,6 @@ Tags.create = async function (socket, data) {
await topics.createEmptyTag(data.tag);
};
-Tags.update = async function (socket, data) {
- if (!Array.isArray(data)) {
- throw new Error('[[error:invalid-data]]');
- }
-
- await topics.updateTags(data);
-};
-
Tags.rename = async function (socket, data) {
if (!Array.isArray(data)) {
throw new Error('[[error:invalid-data]]');
diff --git a/src/topics/create.js b/src/topics/create.js
index a4b2c9ecac..666d203648 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -34,6 +34,11 @@ module.exports = function (Topics) {
postcount: 0,
viewcount: 0,
};
+
+ if (Array.isArray(data.tags) && data.tags.length) {
+ topicData.tags = data.tags.join(',');
+ }
+
const result = await plugins.hooks.fire('filter:topic.create', { topic: topicData, data: data });
topicData = result.topic;
await db.setObject(`topic:${topicData.tid}`, topicData);
@@ -79,6 +84,7 @@ module.exports = function (Topics) {
}
Topics.checkTitle(data.title);
await Topics.validateTags(data.tags, data.cid, uid);
+ data.tags = await Topics.filterTags(data.tags, data.cid);
Topics.checkContent(data.content);
const [categoryExists, canCreate, canTag] = await Promise.all([
diff --git a/src/topics/data.js b/src/topics/data.js
index db13036176..50acd0615b 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -126,4 +126,12 @@ function modifyTopic(topic, fields) {
if (fields.includes('teaserPid') || !fields.length) {
topic.teaserPid = topic.teaserPid || null;
}
+
+ if (fields.includes('tags') || !fields.length) {
+ const tags = String(topic.tags || '');
+ topic.tags = tags.split(',').filter(Boolean).map(tag => ({
+ value: tag,
+ valueEscaped: validator.escape(String(tag)),
+ }));
+ }
}
diff --git a/src/topics/index.js b/src/topics/index.js
index 92a74fa1bc..85f9aefb49 100644
--- a/src/topics/index.js
+++ b/src/topics/index.js
@@ -116,9 +116,8 @@ Topics.getTopicsByTids = async function (tids, options) {
};
}
- const [result, tags, hasRead, isIgnored, bookmarks, callerSettings] = await Promise.all([
+ const [result, hasRead, isIgnored, bookmarks, callerSettings] = await Promise.all([
loadTopics(),
- Topics.getTopicsTagsObjects(tids),
Topics.hasReadTopics(tids, uid),
Topics.isIgnoring(tids, uid),
Topics.getUserBookmarks(tids, uid),
@@ -136,8 +135,6 @@ Topics.getTopicsByTids = async function (tids, options) {
topic.user.displayname = topic.user.username;
}
topic.teaser = result.teasers[i] || null;
- topic.tags = tags[i];
-
topic.isOwner = topic.uid === parseInt(uid, 10);
topic.ignored = isIgnored[i];
topic.unread = parseInt(uid, 10) > 0 && !hasRead[i] && !isIgnored[i];
@@ -180,7 +177,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
social.getActivePostSharing(),
getDeleter(topicData),
getMerger(topicData),
- getRelated(topicData, uid),
+ Topics.getRelatedTopics(topicData, uid),
Topics.thumbs.load([topicData]),
Topics.events.get(topicData.tid, uid),
]);
@@ -268,12 +265,6 @@ async function getMerger(topicData) {
return merger;
}
-async function getRelated(topicData, uid) {
- const tags = await Topics.getTopicTagsObjects(topicData.tid);
- topicData.tags = tags;
- return await Topics.getRelatedTopics(topicData, uid);
-}
-
Topics.getMainPost = async function (tid, uid) {
const mainPosts = await Topics.getMainPosts([tid], uid);
return Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null;
diff --git a/src/topics/tags.js b/src/topics/tags.js
index 5407ebdeb4..f47dc2aad5 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -19,23 +19,23 @@ module.exports = function (Topics) {
if (!Array.isArray(tags) || !tags.length) {
return;
}
- const result = await plugins.hooks.fire('filter:tags.filter', { tags: tags, tid: tid });
- tags = _.uniq(result.tags)
- .map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
- .filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3));
- tags = await filterCategoryTags(tags, tid);
const cid = await Topics.getTopicField(tid, 'cid');
const topicSets = tags.map(tag => `tag:${tag}:topics`).concat(
tags.map(tag => `cid:${cid}:tag:${tag}:topics`)
);
- await Promise.all([
- db.setAdd(`topic:${tid}:tags`, tags),
- db.sortedSetsAdd(topicSets, timestamp, tid),
- ]);
- cache.del(`topic:${tid}:tags`);
+ await db.sortedSetsAdd(topicSets, timestamp, tid);
await Topics.updateCategoryTagsCount([cid], tags);
- await Promise.all(tags.map(tag => updateTagCount(tag)));
+ await Promise.all(tags.map(updateTagCount));
+ };
+
+ Topics.filterTags = async function (tags, cid) {
+ const result = await plugins.hooks.fire('filter:tags.filter', { tags: tags, cid: cid });
+ tags = _.uniq(result.tags)
+ .map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
+ .filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3));
+
+ return await filterCategoryTags(tags, cid);
};
Topics.updateCategoryTagsCount = async function (cids, tags) {
@@ -92,8 +92,7 @@ module.exports = function (Topics) {
}
};
- async function filterCategoryTags(tags, tid) {
- const cid = await Topics.getTopicField(tid, 'cid');
+ async function filterCategoryTags(tags, cid) {
const tagWhitelist = await categories.getTagWhitelist([cid]);
if (!Array.isArray(tagWhitelist[0]) || !tagWhitelist[0].length) {
return tags;
@@ -116,15 +115,6 @@ module.exports = function (Topics) {
}
};
- Topics.updateTags = async function (data) {
- await async.eachSeries(data, async (tagData) => {
- await db.setObject(`tag:${tagData.value}`, {
- color: tagData.color,
- bgColor: tagData.bgColor,
- });
- });
- };
-
Topics.renameTags = async function (data) {
await async.eachSeries(data, async (tagData) => {
await renameTag(tagData.value, tagData.newName);
@@ -136,19 +126,12 @@ module.exports = function (Topics) {
return;
}
newTagName = utils.cleanUpTag(newTagName, meta.config.maximumTagLength);
- const targetExists = await db.isSortedSetMember('tags:topic:count', newTagName);
+
await Topics.createEmptyTag(newTagName);
const allCids = {};
- const tagData = await db.getObject(`tag:${tag}`);
- if (tagData && !targetExists) {
- await db.setObject(`tag:${newTagName}`, {
- color: tagData.color,
- bgColor: tagData.bgColor,
- });
- }
await batch.processSortedSet(`tag:${tag}:topics`, async (tids) => {
- const topicData = await Topics.getTopicsFields(tids, ['tid', 'cid']);
+ const topicData = await Topics.getTopicsFields(tids, ['tid', 'cid', 'tags']);
const cids = topicData.map(t => t.cid);
topicData.forEach((t) => { allCids[t.cid] = true; });
const scores = await db.sortedSetScores(`tag:${tag}:topics`, tids);
@@ -162,11 +145,18 @@ module.exports = function (Topics) {
));
await db.sortedSetRemove(cids.map(cid => `cid:${cid}:tag:${tag}:topics`), tids);
- // update topic:[[admin/manage/tags:description]]