mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-25 01:40:27 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78b8fab3e7 | ||
|
|
5f597dc97f | ||
|
|
107f5613bf | ||
|
|
b2a988190c | ||
|
|
52796bc54e | ||
|
|
00e29403f0 | ||
|
|
c61b3bbd25 | ||
|
|
3960d370e0 | ||
|
|
4e51bf81bb | ||
|
|
4a405ce032 | ||
|
|
debaa2b9cd | ||
|
|
62e3a59c27 | ||
|
|
e01bceff97 | ||
|
|
5fec8b2381 | ||
|
|
88e9fa379f | ||
|
|
04039f7620 |
97
CHANGELOG.md
97
CHANGELOG.md
@@ -1,3 +1,100 @@
|
||||
#### v3.6.6 (2024-02-14)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v3.6.5 (6c653625)
|
||||
* update changelog for v3.6.5 (04039f76)
|
||||
* incrementing version number - v3.6.4 (83d131b4)
|
||||
* incrementing version number - v3.6.3 (fc7d2bfd)
|
||||
* incrementing version number - v3.6.2 (0f577a57)
|
||||
* incrementing version number - v3.6.1 (f1a69468)
|
||||
* incrementing version number - v3.6.0 (4cdf85f8)
|
||||
* incrementing version number - v3.5.3 (ed0e8783)
|
||||
* incrementing version number - v3.5.2 (52fbb2da)
|
||||
* incrementing version number - v3.5.1 (4c543488)
|
||||
* incrementing version number - v3.5.0 (d06fb4f0)
|
||||
* incrementing version number - v3.4.3 (5c984250)
|
||||
* incrementing version number - v3.4.2 (3f0dac38)
|
||||
* incrementing version number - v3.4.1 (01e69574)
|
||||
* incrementing version number - v3.4.0 (fd9247c5)
|
||||
* incrementing version number - v3.3.9 (5805e770)
|
||||
* incrementing version number - v3.3.8 (a5603565)
|
||||
* incrementing version number - v3.3.7 (b26f1744)
|
||||
* incrementing version number - v3.3.6 (7fb38792)
|
||||
* incrementing version number - v3.3.4 (a67f84ea)
|
||||
* incrementing version number - v3.3.3 (f94d239b)
|
||||
* incrementing version number - v3.3.2 (ec9dac97)
|
||||
* incrementing version number - v3.3.1 (151cc68f)
|
||||
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||
* incrementing version number - v3.2.3 (b06d3e63)
|
||||
* incrementing version number - v3.2.2 (758ecfcd)
|
||||
* incrementing version number - v3.2.1 (20145074)
|
||||
* incrementing version number - v3.2.0 (9ecac38e)
|
||||
* incrementing version number - v3.1.7 (0b4e81ab)
|
||||
* incrementing version number - v3.1.6 (b3a3b130)
|
||||
* incrementing version number - v3.1.5 (ec19343a)
|
||||
* incrementing version number - v3.1.4 (2452783c)
|
||||
* incrementing version number - v3.1.3 (3b4e9d3f)
|
||||
* incrementing version number - v3.1.2 (40fa3489)
|
||||
* incrementing version number - v3.1.1 (40250733)
|
||||
* incrementing version number - v3.1.0 (0cb386bd)
|
||||
* incrementing version number - v3.0.1 (26f6ea49)
|
||||
* incrementing version number - v3.0.0 (224e08cd)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* closes #12329, fix default value of categoryWatchState (88e9fa37)
|
||||
|
||||
##### Tests
|
||||
|
||||
* fix spec (5fec8b23)
|
||||
|
||||
#### v3.6.5 (2024-01-31)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v3.6.4 (83d131b4)
|
||||
* update changelog for v3.6.4 (6e6c3974)
|
||||
* incrementing version number - v3.6.3 (fc7d2bfd)
|
||||
* incrementing version number - v3.6.2 (0f577a57)
|
||||
* incrementing version number - v3.6.1 (f1a69468)
|
||||
* incrementing version number - v3.6.0 (4cdf85f8)
|
||||
* incrementing version number - v3.5.3 (ed0e8783)
|
||||
* incrementing version number - v3.5.2 (52fbb2da)
|
||||
* incrementing version number - v3.5.1 (4c543488)
|
||||
* incrementing version number - v3.5.0 (d06fb4f0)
|
||||
* incrementing version number - v3.4.3 (5c984250)
|
||||
* incrementing version number - v3.4.2 (3f0dac38)
|
||||
* incrementing version number - v3.4.1 (01e69574)
|
||||
* incrementing version number - v3.4.0 (fd9247c5)
|
||||
* incrementing version number - v3.3.9 (5805e770)
|
||||
* incrementing version number - v3.3.8 (a5603565)
|
||||
* incrementing version number - v3.3.7 (b26f1744)
|
||||
* incrementing version number - v3.3.6 (7fb38792)
|
||||
* incrementing version number - v3.3.4 (a67f84ea)
|
||||
* incrementing version number - v3.3.3 (f94d239b)
|
||||
* incrementing version number - v3.3.2 (ec9dac97)
|
||||
* incrementing version number - v3.3.1 (151cc68f)
|
||||
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||
* incrementing version number - v3.2.3 (b06d3e63)
|
||||
* incrementing version number - v3.2.2 (758ecfcd)
|
||||
* incrementing version number - v3.2.1 (20145074)
|
||||
* incrementing version number - v3.2.0 (9ecac38e)
|
||||
* incrementing version number - v3.1.7 (0b4e81ab)
|
||||
* incrementing version number - v3.1.6 (b3a3b130)
|
||||
* incrementing version number - v3.1.5 (ec19343a)
|
||||
* incrementing version number - v3.1.4 (2452783c)
|
||||
* incrementing version number - v3.1.3 (3b4e9d3f)
|
||||
* incrementing version number - v3.1.2 (40fa3489)
|
||||
* incrementing version number - v3.1.1 (40250733)
|
||||
* incrementing version number - v3.1.0 (0cb386bd)
|
||||
* incrementing version number - v3.0.1 (26f6ea49)
|
||||
* incrementing version number - v3.0.0 (224e08cd)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* #12320, .text() gets \n\t characters (67c8bd99)
|
||||
|
||||
#### v3.6.4 (2024-01-24)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
"onlineCutoff": 30,
|
||||
"timeagoCutoff": 30,
|
||||
"necroThreshold": 7,
|
||||
"categoryWatchState": "watching",
|
||||
"categoryWatchState": "tracking",
|
||||
"submitPluginUsage": 1,
|
||||
"showAverageApprovalTime": 1,
|
||||
"autoApproveTime": 0,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "3.6.5",
|
||||
"version": "3.6.7",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -117,7 +117,7 @@ get:
|
||||
categoryWatchState:
|
||||
type: object
|
||||
properties:
|
||||
watching:
|
||||
tracking:
|
||||
type: boolean
|
||||
disableCustomUserSkins:
|
||||
type: number
|
||||
|
||||
@@ -5,7 +5,7 @@ define('admin/manage/digest', ['bootbox', 'alerts'], function (bootbox, alerts)
|
||||
const Digest = {};
|
||||
|
||||
Digest.init = function () {
|
||||
$('table').on('click', '[data-action]', function () {
|
||||
$('.digest').on('click', '[data-action]', function () {
|
||||
const action = this.getAttribute('data-action');
|
||||
const uid = this.getAttribute('data-uid');
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, b
|
||||
const searchEl = $('[component="groups/members/search"]');
|
||||
searchEl.on('keyup', utils.debounce(function () {
|
||||
const query = searchEl.val();
|
||||
api.get(`/groups/${groupName}/members`, { query }, function (err, results) {
|
||||
api.get(`/groups/${ajaxify.data.group.slug}/members`, { query }, function (err, results) {
|
||||
if (err) {
|
||||
return alerts.error(err);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,6 @@ module.exports = function (Categories) {
|
||||
const notification = await notifications.create({
|
||||
type: 'new-topic-in-category',
|
||||
nid: `new_topic:tid:${postData.topic.tid}:uid:${exceptUid}`,
|
||||
subject: bodyShort,
|
||||
bodyShort: bodyShort,
|
||||
bodyLong: postData.content,
|
||||
pid: postData.pid,
|
||||
|
||||
@@ -42,7 +42,6 @@ groupsController.get = async function (req, res, next) {
|
||||
if (!group || groupName === groups.BANNED_USERS) {
|
||||
return next();
|
||||
}
|
||||
group.isOwner = true;
|
||||
|
||||
const groupNameData = groupNames.map(name => ({
|
||||
encodedName: encodeURIComponent(name),
|
||||
|
||||
@@ -72,7 +72,6 @@ groupsController.details = async function (req, res, next) {
|
||||
if (!groupData) {
|
||||
return next();
|
||||
}
|
||||
groupData.isOwner = groupData.isOwner || isAdmin || (isGlobalMod && !groupData.system);
|
||||
|
||||
res.render('groups/details', {
|
||||
title: `[[pages:group, ${groupData.displayName}]]`,
|
||||
|
||||
@@ -13,17 +13,25 @@ module.exports = function (module) {
|
||||
}
|
||||
value = value.map(v => helpers.valueToString(v));
|
||||
|
||||
await module.client.collection('objects').updateOne({
|
||||
_key: key,
|
||||
}, {
|
||||
$addToSet: {
|
||||
members: {
|
||||
$each: value,
|
||||
try {
|
||||
await module.client.collection('objects').updateOne({
|
||||
_key: key,
|
||||
}, {
|
||||
$addToSet: {
|
||||
members: {
|
||||
$each: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
upsert: true,
|
||||
});
|
||||
}, {
|
||||
upsert: true,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err && err.message.includes('E11000 duplicate key error')) {
|
||||
console.log(new Error('e11000').stack, key, value);
|
||||
return await module.setAdd(key, value);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
module.setsAdd = async function (keys, value) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const user = require('../user');
|
||||
const db = require('../database');
|
||||
const plugins = require('../plugins');
|
||||
const privileges = require('../privileges');
|
||||
const slugify = require('../slugify');
|
||||
|
||||
const Groups = module.exports;
|
||||
@@ -130,30 +131,37 @@ Groups.get = async function (groupName, options) {
|
||||
stop = (parseInt(options.userListCount, 10) || 4) - 1;
|
||||
}
|
||||
|
||||
const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([
|
||||
const [groupData, members, isMember, isPending, isInvited, isOwner, isAdmin, isGlobalMod] = await Promise.all([
|
||||
Groups.getGroupData(groupName),
|
||||
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop),
|
||||
Groups.getPending(groupName),
|
||||
Groups.getInvites(groupName),
|
||||
Groups.isMember(options.uid, groupName),
|
||||
Groups.isPending(options.uid, groupName),
|
||||
Groups.isInvited(options.uid, groupName),
|
||||
Groups.ownership.isOwner(options.uid, groupName),
|
||||
privileges.admin.can('admin:groups', options.uid),
|
||||
user.isGlobalModerator(options.uid),
|
||||
]);
|
||||
|
||||
if (!groupData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
groupData.isOwner = isOwner || isAdmin || (isGlobalMod && !groupData.system);
|
||||
if (groupData.isOwner) {
|
||||
([groupData.pending, groupData.invited] = await Promise.all([
|
||||
Groups.getPending(groupName),
|
||||
Groups.getInvites(groupName),
|
||||
]));
|
||||
}
|
||||
|
||||
|
||||
const descriptionParsed = await plugins.hooks.fire('filter:parse.raw', String(groupData.description || ''));
|
||||
groupData.descriptionParsed = descriptionParsed;
|
||||
groupData.members = members;
|
||||
groupData.membersNextStart = stop + 1;
|
||||
groupData.pending = pending.filter(Boolean);
|
||||
groupData.invited = invited.filter(Boolean);
|
||||
groupData.isMember = isMember;
|
||||
groupData.isPending = isPending;
|
||||
groupData.isInvited = isInvited;
|
||||
groupData.isOwner = isOwner;
|
||||
const results = await plugins.hooks.fire('filter:group.get', { group: groupData });
|
||||
return results.group;
|
||||
};
|
||||
|
||||
@@ -4,13 +4,10 @@ const db = require('../database');
|
||||
const user = require('../user');
|
||||
|
||||
module.exports = function (Groups) {
|
||||
Groups.getUsersFromSet = async function (set, fields) {
|
||||
Groups.getUsersFromSet = async function (set, fields = []) {
|
||||
const uids = await db.getSetMembers(set);
|
||||
|
||||
if (fields) {
|
||||
return await user.getUsersFields(uids, fields);
|
||||
}
|
||||
return await user.getUsersData(uids);
|
||||
const userData = await user.getUsersFields(uids, fields);
|
||||
return userData.filter(u => u && u.uid);
|
||||
};
|
||||
|
||||
Groups.getUserGroups = async function (uids) {
|
||||
|
||||
@@ -207,6 +207,38 @@ Hooks.hasListeners = function (hook) {
|
||||
return !!(plugins.loadedHooks[hook] && plugins.loadedHooks[hook].length > 0);
|
||||
};
|
||||
|
||||
function hookHandlerPromise(hook, hookObj, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false;
|
||||
function _resolve(result) {
|
||||
if (resolved) {
|
||||
winston.warn(`[plugins] ${hook} already resolved in plugin ${hookObj.id}`);
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
resolve(result);
|
||||
}
|
||||
const returned = hookObj.method(params, (err, result) => {
|
||||
if (err) reject(err); else _resolve(result);
|
||||
});
|
||||
|
||||
if (utils.isPromise(returned)) {
|
||||
returned.then(
|
||||
payload => _resolve(payload),
|
||||
err => reject(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hook.startsWith('filter:') && returned !== undefined) {
|
||||
_resolve(returned);
|
||||
} else if (hook.startsWith('static:') && hookObj.method.length <= 1) {
|
||||
// make sure it is resolved if static hook doesn't return anything and doesn't use callback
|
||||
_resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function fireFilterHook(hook, hookList, params) {
|
||||
if (!Array.isArray(hookList) || !hookList.length) {
|
||||
return params;
|
||||
@@ -223,31 +255,7 @@ async function fireFilterHook(hook, hookList, params) {
|
||||
if (hookObj.method.constructor && hookObj.method.constructor.name === 'AsyncFunction') {
|
||||
return await hookObj.method(params);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false;
|
||||
function _resolve(result) {
|
||||
if (resolved) {
|
||||
winston.warn(`[plugins] ${hook} already resolved in plugin ${hookObj.id}`);
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
resolve(result);
|
||||
}
|
||||
const returned = hookObj.method(params, (err, result) => {
|
||||
if (err) reject(err); else _resolve(result);
|
||||
});
|
||||
|
||||
if (utils.isPromise(returned)) {
|
||||
returned.then(
|
||||
payload => _resolve(payload),
|
||||
err => reject(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (returned) {
|
||||
_resolve(returned);
|
||||
}
|
||||
});
|
||||
return hookHandlerPromise(hook, hookObj, params);
|
||||
}
|
||||
|
||||
for (const hookObj of hookList) {
|
||||
@@ -303,28 +311,7 @@ async function fireStaticHook(hook, hookList, params) {
|
||||
return timeout(hookObj.method(params), 10000, 'timeout');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false;
|
||||
function _resolve(result) {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
resolve(result);
|
||||
}
|
||||
const returned = hookObj.method(params, (err, result) => {
|
||||
if (err) reject(err); else _resolve(result);
|
||||
});
|
||||
|
||||
if (utils.isPromise(returned)) {
|
||||
returned.then(
|
||||
payload => _resolve(payload),
|
||||
err => reject(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
_resolve();
|
||||
});
|
||||
return hookHandlerPromise(hook, hookObj, params);
|
||||
}
|
||||
|
||||
for (const hookObj of hookList) {
|
||||
|
||||
@@ -620,7 +620,6 @@ module.exports = function (Topics) {
|
||||
const notification = await notifications.create({
|
||||
type: 'new-topic-with-tag',
|
||||
nid: `new_topic:tid:${postData.topic.tid}:uid:${exceptUid}`,
|
||||
subject: bodyShort,
|
||||
bodyShort: bodyShort,
|
||||
bodyLong: postData.content,
|
||||
pid: postData.pid,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="px-lg-4">
|
||||
<div class="px-lg-4 digest">
|
||||
<p class="lead">[[admin/manage/digest:lead]]</p>
|
||||
<p>[[admin/manage/digest:disclaimer]]</p>
|
||||
<p>[[admin/manage/digest:disclaimer-continued]]</p>
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-2"><em>[[admin/manage/digest:default-help, {default}]]</em></div>
|
||||
<div class="d-flex gap-1">
|
||||
<div class="d-flex gap-1 align-items-center">
|
||||
<div>[[admin/manage/digest:manual-run]]</div>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="resend-day">[[admin/settings/user:digest-freq.daily]]</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="resend-week">[[admin/settings/user:digest-freq.weekly]]</button>
|
||||
|
||||
@@ -30,6 +30,19 @@ describe('Set methods', () => {
|
||||
assert.deepStrictEqual(members, []);
|
||||
assert(!exists);
|
||||
});
|
||||
|
||||
it('should not error with parallel adds', async () => {
|
||||
await Promise.all([
|
||||
db.setAdd('parallelset', 1),
|
||||
db.setAdd('parallelset', 2),
|
||||
db.setAdd('parallelset', 3),
|
||||
]);
|
||||
const members = await db.getSetMembers('parallelset');
|
||||
assert.strictEqual(members.length, 3);
|
||||
assert(members.includes('1'));
|
||||
assert(members.includes('2'));
|
||||
assert(members.includes('3'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSetMembers()', () => {
|
||||
|
||||
Reference in New Issue
Block a user