mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-05 05:25:49 +01:00
Merge branch 'master' of https://github.com/NodeBB/NodeBB
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
"reorder-plugins": "重新排序插件",
|
"reorder-plugins": "重新排序插件",
|
||||||
"order-active": "排序生效插件",
|
"order-active": "排序生效插件",
|
||||||
"dev-interested": "有兴趣为NodeBB开发插件?",
|
"dev-interested": "有兴趣为NodeBB开发插件?",
|
||||||
"docs-info": "有关插件创作的完整文档可以在 <a target=\"_blank\" href=\"https://docs.nodebb.org/development/plugins/\">NodeBB 文档</a>中找到。",
|
"docs-info": "有关插件创作的完整文档可以在 <a target=\"_blank\" href=\"https://docs.nodebb-cn.org/development\">NodeBB 文档</a>中找到。",
|
||||||
|
|
||||||
"order.description": "部分插件需要在其它插件启用之后才能完美运作。",
|
"order.description": "部分插件需要在其它插件启用之后才能完美运作。",
|
||||||
"order.explanation": "插件将按照以下顺序载入,从上至下。",
|
"order.explanation": "插件将按照以下顺序载入,从上至下。",
|
||||||
|
|||||||
43
src/flags.js
43
src/flags.js
@@ -19,6 +19,16 @@ const utils = require('../public/src/utils');
|
|||||||
|
|
||||||
const Flags = module.exports;
|
const Flags = module.exports;
|
||||||
|
|
||||||
|
Flags._constants = {
|
||||||
|
states: ['open', 'wip', 'resolved', 'rejected'],
|
||||||
|
state_class: {
|
||||||
|
open: 'info',
|
||||||
|
wip: 'warning',
|
||||||
|
resolved: 'success',
|
||||||
|
rejected: 'danger',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
Flags.init = async function () {
|
Flags.init = async function () {
|
||||||
// Query plugins for custom filter strategies and merge into core filter strategies
|
// Query plugins for custom filter strategies and merge into core filter strategies
|
||||||
function prepareSets(sets, orSets, prefix, value) {
|
function prepareSets(sets, orSets, prefix, value) {
|
||||||
@@ -162,13 +172,7 @@ Flags.list = async function (filters, uid) {
|
|||||||
'icon:text': userObj['icon:text'],
|
'icon:text': userObj['icon:text'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const stateToLabel = {
|
flagObj.labelClass = Flags._constants.state_class[flagObj.state];
|
||||||
open: 'info',
|
|
||||||
wip: 'warning',
|
|
||||||
resolved: 'success',
|
|
||||||
rejected: 'danger',
|
|
||||||
};
|
|
||||||
flagObj.labelClass = stateToLabel[flagObj.state];
|
|
||||||
|
|
||||||
return Object.assign(flagObj, {
|
return Object.assign(flagObj, {
|
||||||
description: validator.escape(String(flagObj.description)),
|
description: validator.escape(String(flagObj.description)),
|
||||||
@@ -344,6 +348,7 @@ Flags.getTargetCid = async function (type, id) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Flags.update = async function (flagId, uid, changeset) {
|
Flags.update = async function (flagId, uid, changeset) {
|
||||||
|
const current = await db.getObjectFields('flag:' + flagId, ['state', 'assignee', 'type', 'targetId']);
|
||||||
const now = changeset.datetime || Date.now();
|
const now = changeset.datetime || Date.now();
|
||||||
const notifyAssignee = async function (assigneeId) {
|
const notifyAssignee = async function (assigneeId) {
|
||||||
if (assigneeId === '' || parseInt(uid, 10) === parseInt(assigneeId, 10)) {
|
if (assigneeId === '' || parseInt(uid, 10) === parseInt(assigneeId, 10)) {
|
||||||
@@ -359,23 +364,43 @@ Flags.update = async function (flagId, uid, changeset) {
|
|||||||
});
|
});
|
||||||
await notifications.push(notifObj, [assigneeId]);
|
await notifications.push(notifObj, [assigneeId]);
|
||||||
};
|
};
|
||||||
|
const isAssignable = async function (assigneeId) {
|
||||||
|
let allowed = false;
|
||||||
|
allowed = await user.isAdminOrGlobalMod(assigneeId);
|
||||||
|
|
||||||
// Retrieve existing flag data to compare for history-saving purposes
|
// Mods are also allowed to be assigned, if flag target is post in uid's moderated cid
|
||||||
const current = await db.getObjectFields('flag:' + flagId, ['state', 'assignee']);
|
if (!allowed && current.type === 'post') {
|
||||||
|
const cid = await posts.getCidByPid(current.targetId);
|
||||||
|
allowed = await user.isModerator(assigneeId, cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retrieve existing flag data to compare for history-saving/reference purposes
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
for (var prop in changeset) {
|
for (var prop in changeset) {
|
||||||
if (changeset.hasOwnProperty(prop)) {
|
if (changeset.hasOwnProperty(prop)) {
|
||||||
if (current[prop] === changeset[prop]) {
|
if (current[prop] === changeset[prop]) {
|
||||||
delete changeset[prop];
|
delete changeset[prop];
|
||||||
} else if (prop === 'state') {
|
} else if (prop === 'state') {
|
||||||
|
if (!Flags._constants.states.includes(changeset[prop])) {
|
||||||
|
delete changeset[prop];
|
||||||
|
} else {
|
||||||
tasks.push(db.sortedSetAdd('flags:byState:' + changeset[prop], now, flagId));
|
tasks.push(db.sortedSetAdd('flags:byState:' + changeset[prop], now, flagId));
|
||||||
tasks.push(db.sortedSetRemove('flags:byState:' + current[prop], flagId));
|
tasks.push(db.sortedSetRemove('flags:byState:' + current[prop], flagId));
|
||||||
|
}
|
||||||
} else if (prop === 'assignee') {
|
} else if (prop === 'assignee') {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
if (!await isAssignable(parseInt(changeset[prop], 10))) {
|
||||||
|
delete changeset[prop];
|
||||||
|
} else {
|
||||||
tasks.push(db.sortedSetAdd('flags:byAssignee:' + changeset[prop], now, flagId));
|
tasks.push(db.sortedSetAdd('flags:byAssignee:' + changeset[prop], now, flagId));
|
||||||
tasks.push(notifyAssignee(changeset[prop]));
|
tasks.push(notifyAssignee(changeset[prop]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!Object.keys(changeset).length) {
|
if (!Object.keys(changeset).length) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
@@ -150,7 +151,14 @@ exports.build = function (targets, options, callback) {
|
|||||||
targets = targets.split(',');
|
targets = targets.split(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
var parallel = !nconf.get('series') && !options.series;
|
let series = nconf.get('series') || options.series;
|
||||||
|
if (series === undefined) {
|
||||||
|
// Detect # of CPUs and select strategy as appropriate
|
||||||
|
winston.verbose('[build] Querying CPU core count for build strategy');
|
||||||
|
const cpus = os.cpus();
|
||||||
|
series = cpus.length < 4;
|
||||||
|
winston.verbose('[build] System returned ' + cpus.length + ' cores, opting for ' + (series ? 'series' : 'parallel') + ' build strategy');
|
||||||
|
}
|
||||||
|
|
||||||
targets = targets
|
targets = targets
|
||||||
// get full target name
|
// get full target name
|
||||||
@@ -195,14 +203,14 @@ exports.build = function (targets, options, callback) {
|
|||||||
require('./minifier').maxThreads = threads - 1;
|
require('./minifier').maxThreads = threads - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parallel) {
|
if (!series) {
|
||||||
winston.info('[build] Building in parallel mode');
|
winston.info('[build] Building in parallel mode');
|
||||||
} else {
|
} else {
|
||||||
winston.info('[build] Building in series mode');
|
winston.info('[build] Building in series mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime = Date.now();
|
startTime = Date.now();
|
||||||
buildTargets(targets, parallel, next);
|
buildTargets(targets, !series, next);
|
||||||
},
|
},
|
||||||
function (next) {
|
function (next) {
|
||||||
totalTime = (Date.now() - startTime) / 1000;
|
totalTime = (Date.now() - startTime) / 1000;
|
||||||
|
|||||||
109
test/flags.js
109
test/flags.js
@@ -13,40 +13,30 @@ var Groups = require('../src/groups');
|
|||||||
var Meta = require('../src/meta');
|
var Meta = require('../src/meta');
|
||||||
|
|
||||||
describe('Flags', function () {
|
describe('Flags', function () {
|
||||||
before(function (done) {
|
let uid1;
|
||||||
|
let uid2;
|
||||||
|
let uid3;
|
||||||
|
let category;
|
||||||
|
before(async () => {
|
||||||
// Create some stuff to flag
|
// Create some stuff to flag
|
||||||
async.waterfall([
|
uid1 = await User.create({ username: 'testUser', password: 'abcdef', email: 'b@c.com' });
|
||||||
async.apply(User.create, { username: 'testUser', password: 'abcdef', email: 'b@c.com' }),
|
|
||||||
function (uid, next) {
|
|
||||||
Categories.create({
|
|
||||||
name: 'test category',
|
|
||||||
}, function (err, category) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
Topics.post({
|
uid2 = await User.create({ username: 'testUser2', password: 'abcdef', email: 'c@d.com' });
|
||||||
|
await Groups.join('administrators', uid2);
|
||||||
|
|
||||||
|
category = await Categories.create({
|
||||||
|
name: 'test category',
|
||||||
|
});
|
||||||
|
await Topics.post({
|
||||||
cid: category.cid,
|
cid: category.cid,
|
||||||
uid: uid,
|
uid: uid1,
|
||||||
title: 'Topic to flag',
|
title: 'Topic to flag',
|
||||||
content: 'This is flaggable content',
|
content: 'This is flaggable content',
|
||||||
}, next);
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
function (topicData, next) {
|
uid3 = await User.create({
|
||||||
User.create({
|
|
||||||
username: 'testUser2', password: 'abcdef', email: 'c@d.com',
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (uid, next) {
|
|
||||||
Groups.join('administrators', uid, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
User.create({
|
|
||||||
username: 'unprivileged', password: 'abcdef', email: 'd@e.com',
|
username: 'unprivileged', password: 'abcdef', email: 'd@e.com',
|
||||||
}, next);
|
});
|
||||||
},
|
|
||||||
], done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.create()', function () {
|
describe('.create()', function () {
|
||||||
@@ -274,9 +264,9 @@ describe('Flags', function () {
|
|||||||
|
|
||||||
describe('.update()', function () {
|
describe('.update()', function () {
|
||||||
it('should alter a flag\'s various attributes and persist them to the database', function (done) {
|
it('should alter a flag\'s various attributes and persist them to the database', function (done) {
|
||||||
Flags.update(1, 1, {
|
Flags.update(1, uid2, {
|
||||||
state: 'wip',
|
state: 'wip',
|
||||||
assignee: 1,
|
assignee: uid2,
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
db.getObjectFields('flag:1', ['state', 'assignee'], function (err, data) {
|
db.getObjectFields('flag:1', ['state', 'assignee'], function (err, data) {
|
||||||
@@ -286,7 +276,7 @@ describe('Flags', function () {
|
|||||||
|
|
||||||
assert.strictEqual('wip', data.state);
|
assert.strictEqual('wip', data.state);
|
||||||
assert.ok(!isNaN(parseInt(data.assignee, 10)));
|
assert.ok(!isNaN(parseInt(data.assignee, 10)));
|
||||||
assert.strictEqual(1, parseInt(data.assignee, 10));
|
assert.strictEqual(uid2, parseInt(data.assignee, 10));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -313,6 +303,65 @@ describe('Flags', function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow assignment if user is an admin and do nothing otherwise', async () => {
|
||||||
|
await Flags.update(1, uid2, {
|
||||||
|
assignee: uid2,
|
||||||
|
});
|
||||||
|
let assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid2, parseInt(assignee, 10));
|
||||||
|
|
||||||
|
await Flags.update(1, uid2, {
|
||||||
|
assignee: uid3,
|
||||||
|
});
|
||||||
|
assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid2, parseInt(assignee, 10));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow assignment if user is a global mod and do nothing otherwise', async () => {
|
||||||
|
await Groups.join('Global Moderators', uid3);
|
||||||
|
|
||||||
|
await Flags.update(1, uid3, {
|
||||||
|
assignee: uid3,
|
||||||
|
});
|
||||||
|
let assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid3, parseInt(assignee, 10));
|
||||||
|
|
||||||
|
await Flags.update(1, uid3, {
|
||||||
|
assignee: uid1,
|
||||||
|
});
|
||||||
|
assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid3, parseInt(assignee, 10));
|
||||||
|
|
||||||
|
await Groups.leave('Global Moderators', uid3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow assignment if user is a mod of the category, do nothing otherwise', async () => {
|
||||||
|
await Groups.join('cid:' + category.cid + ':privileges:moderate', uid3);
|
||||||
|
|
||||||
|
await Flags.update(1, uid3, {
|
||||||
|
assignee: uid3,
|
||||||
|
});
|
||||||
|
let assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid3, parseInt(assignee, 10));
|
||||||
|
|
||||||
|
await Flags.update(1, uid3, {
|
||||||
|
assignee: uid1,
|
||||||
|
});
|
||||||
|
assignee = await db.getObjectField('flag:1', 'assignee');
|
||||||
|
assert.strictEqual(uid3, parseInt(assignee, 10));
|
||||||
|
|
||||||
|
await Groups.leave('cid:' + category.cid + ':privileges:moderate', uid3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when you attempt to set a bogus state', async () => {
|
||||||
|
await Flags.update(1, uid2, {
|
||||||
|
state: 'hocus pocus',
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = await db.getObjectField('flag:1', 'state');
|
||||||
|
assert.strictEqual('wip', state);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.getTarget()', function () {
|
describe('.getTarget()', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user