mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-10 07:55:46 +01:00
feat: #3783, min/max tags per category
This commit is contained in:
@@ -63,3 +63,9 @@ CategoryObject:
|
|||||||
totalTopicCount:
|
totalTopicCount:
|
||||||
type: number
|
type: number
|
||||||
description: The number of topics in the category
|
description: The number of topics in the category
|
||||||
|
minTags:
|
||||||
|
type: number
|
||||||
|
description: Minimum tags per topic in this category
|
||||||
|
maxTags:
|
||||||
|
type: number
|
||||||
|
description: Maximum tags per topic in this category
|
||||||
@@ -3130,6 +3130,10 @@ paths:
|
|||||||
type: number
|
type: number
|
||||||
totalTopicCount:
|
totalTopicCount:
|
||||||
type: number
|
type: number
|
||||||
|
minTags:
|
||||||
|
type: number
|
||||||
|
maxTags:
|
||||||
|
type: number
|
||||||
/api/categories:
|
/api/categories:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -3655,6 +3659,10 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
minTags:
|
||||||
|
type: number
|
||||||
|
maxTags:
|
||||||
|
type: number
|
||||||
thread_tools:
|
thread_tools:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ module.exports = function (Categories) {
|
|||||||
destination.class = source.class;
|
destination.class = source.class;
|
||||||
destination.image = source.image;
|
destination.image = source.image;
|
||||||
destination.imageClass = source.imageClass;
|
destination.imageClass = source.imageClass;
|
||||||
|
destination.minTags = source.minTags;
|
||||||
|
destination.maxTags = source.maxTags;
|
||||||
|
|
||||||
if (copyParent) {
|
if (copyParent) {
|
||||||
destination.parentCid = source.parentCid || 0;
|
destination.parentCid = source.parentCid || 0;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var validator = require('validator');
|
const validator = require('validator');
|
||||||
|
|
||||||
var db = require('../database');
|
const db = require('../database');
|
||||||
|
const meta = require('../meta');
|
||||||
|
|
||||||
const intFields = [
|
const intFields = [
|
||||||
'cid', 'parentCid', 'disabled', 'isSection', 'order',
|
'cid', 'parentCid', 'disabled', 'isSection', 'order',
|
||||||
'topic_count', 'post_count', 'numRecentReplies',
|
'topic_count', 'post_count', 'numRecentReplies',
|
||||||
|
'minTags', 'maxTags',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = function (Categories) {
|
module.exports = function (Categories) {
|
||||||
@@ -59,6 +61,21 @@ function modifyCategory(category, fields) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fields.length || fields.includes('minTags')) {
|
||||||
|
const useDefault = !category.hasOwnProperty('minTags') ||
|
||||||
|
category.minTags === null ||
|
||||||
|
category.minTags === '' ||
|
||||||
|
!parseInt(category.minTags, 10);
|
||||||
|
category.minTags = useDefault ? meta.config.minimumTagsPerTopic : category.minTags;
|
||||||
|
}
|
||||||
|
if (!fields.length || fields.includes('maxTags')) {
|
||||||
|
const useDefault = !category.hasOwnProperty('maxTags') ||
|
||||||
|
category.maxTags === null ||
|
||||||
|
category.maxTags === '' ||
|
||||||
|
!parseInt(category.maxTags, 10);
|
||||||
|
category.maxTags = useDefault ? meta.config.maximumTagsPerTopic : category.maxTags;
|
||||||
|
}
|
||||||
|
|
||||||
db.parseIntFields(category, intFields, fields);
|
db.parseIntFields(category, intFields, fields);
|
||||||
|
|
||||||
if (category.hasOwnProperty('name')) {
|
if (category.hasOwnProperty('name')) {
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ module.exports = function (Posts) {
|
|||||||
throw new Error('[[error:no-privileges]]');
|
throw new Error('[[error:no-privileges]]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await topics.validateTags(data.tags, topicData.cid);
|
||||||
|
|
||||||
const results = await plugins.fireHook('filter:topic.edit', { req: data.req, topic: newTopicData, data: data });
|
const results = await plugins.fireHook('filter:topic.edit', { req: data.req, topic: newTopicData, data: data });
|
||||||
await db.setObject('topic:' + tid, results.topic);
|
await db.setObject('topic:' + tid, results.topic);
|
||||||
await topics.updateTopicTags(tid, data.tags);
|
await topics.updateTopicTags(tid, data.tags);
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ module.exports = function (SocketPosts) {
|
|||||||
throw new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]');
|
throw new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]');
|
||||||
} else if (data.title && data.title.length > meta.config.maximumTitleLength) {
|
} else if (data.title && data.title.length > meta.config.maximumTitleLength) {
|
||||||
throw new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]');
|
throw new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]');
|
||||||
} else if (data.tags && data.tags.length < meta.config.minimumTagsPerTopic) {
|
|
||||||
throw new Error('[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]');
|
|
||||||
} else if (data.tags && data.tags.length > meta.config.maximumTagsPerTopic) {
|
|
||||||
throw new Error('[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]');
|
|
||||||
} else if (meta.config.minimumPostLength !== 0 && contentLen < meta.config.minimumPostLength) {
|
} else if (meta.config.minimumPostLength !== 0 && contentLen < meta.config.minimumPostLength) {
|
||||||
throw new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]');
|
throw new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]');
|
||||||
} else if (contentLen > meta.config.maximumPostLength) {
|
} else if (contentLen > meta.config.maximumPostLength) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ module.exports = function (Topics) {
|
|||||||
data.content = utils.rtrim(data.content);
|
data.content = utils.rtrim(data.content);
|
||||||
}
|
}
|
||||||
check(data.title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long');
|
check(data.title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long');
|
||||||
check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags');
|
await Topics.validateTags(data.tags, data.cid);
|
||||||
check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long');
|
check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long');
|
||||||
|
|
||||||
const [categoryExists, canCreate, canTag] = await Promise.all([
|
const [categoryExists, canCreate, canTag] = await Promise.all([
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
|
|||||||
topicData.posts = posts;
|
topicData.posts = posts;
|
||||||
topicData.category = category;
|
topicData.category = category;
|
||||||
topicData.tagWhitelist = tagWhitelist[0];
|
topicData.tagWhitelist = tagWhitelist[0];
|
||||||
|
topicData.minTags = category.minTags;
|
||||||
|
topicData.maxTags = category.maxTags;
|
||||||
topicData.thread_tools = threadTools.tools;
|
topicData.thread_tools = threadTools.tools;
|
||||||
topicData.isFollowing = followData[0].following;
|
topicData.isFollowing = followData[0].following;
|
||||||
topicData.isNotFollowing = !followData[0].following && !followData[0].ignoring;
|
topicData.isNotFollowing = !followData[0].following && !followData[0].ignoring;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module.exports = function (Topics) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await plugins.fireHook('filter:tags.filter', { tags: tags, tid: tid });
|
const result = await plugins.fireHook('filter:tags.filter', { tags: tags, tid: tid });
|
||||||
tags = _.uniq(result.tags).slice(0, meta.config.maximumTagsPerTopic || 5)
|
tags = _.uniq(result.tags)
|
||||||
.map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
|
.map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
|
||||||
.filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3));
|
.filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3));
|
||||||
|
|
||||||
@@ -32,6 +32,19 @@ module.exports = function (Topics) {
|
|||||||
await Promise.all(tags.map(tag => updateTagCount(tag)));
|
await Promise.all(tags.map(tag => updateTagCount(tag)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Topics.validateTags = async function (tags, cid) {
|
||||||
|
if (!Array.isArray(tags)) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
|
tags = _.uniq(tags);
|
||||||
|
const categoryData = await categories.getCategoryFields(cid, ['minTags', 'maxTags']);
|
||||||
|
if (tags.length < parseInt(categoryData.minTags, 10)) {
|
||||||
|
throw new Error('[[error:not-enough-tags, ' + categoryData.minTags + ']]');
|
||||||
|
} else if (tags.length > parseInt(categoryData.maxTags, 10)) {
|
||||||
|
throw new Error('[[error:too-many-tags, ' + categoryData.maxTags + ']]');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async function filterCategoryTags(tags, tid) {
|
async function filterCategoryTags(tags, tid) {
|
||||||
const cid = await Topics.getTopicField(tid, 'cid');
|
const cid = await Topics.getTopicField(tid, 'cid');
|
||||||
const tagWhitelist = await categories.getTagWhitelist([cid]);
|
const tagWhitelist = await categories.getTagWhitelist([cid]);
|
||||||
|
|||||||
@@ -95,9 +95,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset class="row">
|
||||||
<label for="tag-whitelist">Tag Whitelist</label><br />
|
<div class="col-sm-6 col-xs-12">
|
||||||
<input id="tag-whitelist" type="text" class="form-control" placeholder="Enter category tags here" data-name="tagWhitelist" value="" />
|
<div class="form-group">
|
||||||
|
<label for="cid-min-tags">
|
||||||
|
[[admin/settings/tags:min-per-topic]]
|
||||||
|
</label>
|
||||||
|
<input id="cid-min-tags" type="text" class="form-control" data-name="minTags" value="{category.minTags}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-xs-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cid-max-tags">
|
||||||
|
[[admin/settings/tags:max-per-topic]]
|
||||||
|
</label>
|
||||||
|
<input id="cid-max-tags" type="text" class="form-control" data-name="maxTags" value="{category.maxTags}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<label for="tag-whitelist">Tag Whitelist</label><br />
|
||||||
|
<input id="tag-whitelist" type="text" class="form-control" placeholder="Enter category tags here" data-name="tagWhitelist" value="" />
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1782,6 +1782,58 @@ describe('Topic\'s', function () {
|
|||||||
tags = await topics.getTopicTags(tid);
|
tags = await topics.getTopicTags(tid);
|
||||||
assert.deepStrictEqual(tags, ['tag2', 'tag4', 'tag6']);
|
assert.deepStrictEqual(tags, ['tag2', 'tag4', 'tag6']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect minTags', async () => {
|
||||||
|
const oldValue = meta.config.minimumTagsPerTopic;
|
||||||
|
meta.config.minimumTagsPerTopic = 2;
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await topics.post({ uid: adminUid, tags: ['tag4'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId });
|
||||||
|
} catch (_err) {
|
||||||
|
err = _err;
|
||||||
|
}
|
||||||
|
assert.equal(err.message, '[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]');
|
||||||
|
meta.config.minimumTagsPerTopic = oldValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect maxTags', async () => {
|
||||||
|
const oldValue = meta.config.maximumTagsPerTopic;
|
||||||
|
meta.config.maximumTagsPerTopic = 2;
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await topics.post({ uid: adminUid, tags: ['tag1', 'tag2', 'tag3'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId });
|
||||||
|
} catch (_err) {
|
||||||
|
err = _err;
|
||||||
|
}
|
||||||
|
assert.equal(err.message, '[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]');
|
||||||
|
meta.config.maximumTagsPerTopic = oldValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect minTags per category', async () => {
|
||||||
|
const minTags = 2;
|
||||||
|
await categories.setCategoryField(topic.categoryId, 'minTags', minTags);
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await topics.post({ uid: adminUid, tags: ['tag4'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId });
|
||||||
|
} catch (_err) {
|
||||||
|
err = _err;
|
||||||
|
}
|
||||||
|
assert.equal(err.message, '[[error:not-enough-tags, ' + minTags + ']]');
|
||||||
|
await db.deleteObjectField('category:' + topic.categoryId, 'minTags');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect maxTags per category', async () => {
|
||||||
|
const maxTags = 2;
|
||||||
|
await categories.setCategoryField(topic.categoryId, 'maxTags', maxTags);
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await topics.post({ uid: adminUid, tags: ['tag1', 'tag2', 'tag3'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId });
|
||||||
|
} catch (_err) {
|
||||||
|
err = _err;
|
||||||
|
}
|
||||||
|
assert.equal(err.message, '[[error:too-many-tags, ' + maxTags + ']]');
|
||||||
|
await db.deleteObjectField('category:' + topic.categoryId, 'maxTags');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('follow/unfollow', function () {
|
describe('follow/unfollow', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user