mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-06 14:17:38 +02:00
Merge commit 'd99e46ab2d8cafffd9c2178432d0c56e6cd4362f' into v4.x
This commit is contained in:
72
CHANGELOG.md
72
CHANGELOG.md
@@ -1,3 +1,75 @@
|
||||
#### v4.10.2 (2026-04-08)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v4.10.1 (0c9bbcea)
|
||||
* update changelog for v4.10.1 (afe8683e)
|
||||
* incrementing version number - v4.10.0 (5b703104)
|
||||
* incrementing version number - v4.9.2 (e6846052)
|
||||
* incrementing version number - v4.9.1 (72e44c86)
|
||||
* incrementing version number - v4.9.0 (3fdd1bef)
|
||||
* incrementing version number - v4.8.1 (713ae0c0)
|
||||
* incrementing version number - v4.8.0 (3fac737a)
|
||||
* incrementing version number - v4.7.2 (cd419d8a)
|
||||
* incrementing version number - v4.7.1 (afb88805)
|
||||
* incrementing version number - v4.7.0 (e82d40f8)
|
||||
* incrementing version number - v4.6.3 (9fc5b0f3)
|
||||
* incrementing version number - v4.6.2 (f98747db)
|
||||
* incrementing version number - v4.6.1 (f47aa678)
|
||||
* incrementing version number - v4.6.0 (ee395bc5)
|
||||
* incrementing version number - v4.5.2 (ad2da639)
|
||||
* incrementing version number - v4.5.1 (69f4b61f)
|
||||
* incrementing version number - v4.5.0 (f05c5d06)
|
||||
* incrementing version number - v4.4.6 (074043ad)
|
||||
* incrementing version number - v4.4.5 (6f106923)
|
||||
* incrementing version number - v4.4.4 (d323af44)
|
||||
* incrementing version number - v4.4.3 (d354c2eb)
|
||||
* incrementing version number - v4.4.2 (55c510ae)
|
||||
* incrementing version number - v4.4.1 (5ae79b4e)
|
||||
* incrementing version number - v4.4.0 (0a75eee3)
|
||||
* incrementing version number - v4.3.2 (b92b5d80)
|
||||
* incrementing version number - v4.3.1 (308e6b9f)
|
||||
* incrementing version number - v4.3.0 (bff291db)
|
||||
* incrementing version number - v4.2.2 (17fecc24)
|
||||
* incrementing version number - v4.2.1 (852a270c)
|
||||
* incrementing version number - v4.2.0 (87581958)
|
||||
* incrementing version number - v4.1.1 (b2afbb16)
|
||||
* incrementing version number - v4.1.0 (36c80850)
|
||||
* incrementing version number - v4.0.6 (4a52fb2e)
|
||||
* incrementing version number - v4.0.5 (1792a62b)
|
||||
* incrementing version number - v4.0.4 (b1125cce)
|
||||
* incrementing version number - v4.0.3 (2b65c735)
|
||||
* incrementing version number - v4.0.2 (73fe5fcf)
|
||||
* incrementing version number - v4.0.1 (a461b758)
|
||||
* incrementing version number - v4.0.0 (c1eaee45)
|
||||
|
||||
##### New Features
|
||||
|
||||
* add unreadNids to /api/notifications (83572348)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* use file.exists instead of try/catch to detect missing email logo (#14154) (4366bdd0)
|
||||
* closes #14151, handle null req.body (62b65e69)
|
||||
* remove optional (20e751f0)
|
||||
* #14147, dont create wrong backlinks (0568ef43)
|
||||
* user image og:image (fb48ab34)
|
||||
* closes #14133, don't modify displayName for system groups (af0e3d96)
|
||||
* try a save point in retry (203f4cc7)
|
||||
* try upsert type if it fails (991e9778)
|
||||
* on exit, dont write analytics data on all nodes (6c4e9284)
|
||||
* align-center user and name on post queue (1a0c2a21)
|
||||
* ./nodebb upgrade on windows (82d380a3)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* use renderHeaderType instead of two variables (55290da0)
|
||||
* break long line (4b503db4)
|
||||
|
||||
##### Tests
|
||||
|
||||
* dont create users parallel (b04976ed)
|
||||
|
||||
#### v4.10.1 (2026-03-25)
|
||||
|
||||
##### Chores
|
||||
|
||||
37
README.md
37
README.md
@@ -43,12 +43,49 @@ NodeBB requires the following software to be installed:
|
||||
* MongoDB, version 5 or greater **or** Redis, version 7.2 or greater
|
||||
* If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured.
|
||||
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
|
||||
* (Optional) [Docker](https://docs.docker.com/get-docker/) for container-based setup
|
||||
|
||||
> Installation steps vary by operating system. Please follow the official documentation links above.
|
||||
|
||||
## Installation
|
||||
|
||||
[Please refer to platform-specific installation documentation](https://docs.nodebb.org/installing/os).
|
||||
If installing via the cloud (or using Docker), [please see cloud-based installation documentation](https://docs.nodebb.org/installing/cloud/).
|
||||
|
||||
## Development Setup Overview
|
||||
|
||||
> NodeBB uses a CLI-based setup and does not run via standard `npm start`.
|
||||
|
||||
You can run NodeBB locally in two ways:
|
||||
|
||||
### Option 1: Native Setup (Recommended for Beginners & Contributors)
|
||||
|
||||
This approach helps you understand how NodeBB works internally.
|
||||
|
||||
**Basic flow:**
|
||||
1. Clone the repository ```` https://github.com/NodeBB/NodeBB.git ````
|
||||
2. Run the setup script ```` cd NodeBB ```` ```` ./nodebb setup ````
|
||||
3. Start the application ```` ./nodebb start ````
|
||||
|
||||
**During setup, you will configure:**
|
||||
- Database (MongoDB / Redis)
|
||||
- Admin account
|
||||
- Port (default: 4567)
|
||||
|
||||
### Option 2: Docker Setup (Quick & Isolated)
|
||||
|
||||
> Requires Docker to be installed: https://docs.docker.com/get-docker/
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
````
|
||||
|
||||
This will start NodeBB along with required services at: ```` http://localhost:4567 ````
|
||||
|
||||
**For more details, see: https://docs.nodebb.org**
|
||||
|
||||
## Securing NodeBB
|
||||
|
||||
It is important to ensure that your NodeBB and database servers are secured. Bear these points in mind:
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
"nodebb-theme-harmony": "2.2.63",
|
||||
"nodebb-theme-lavender": "7.1.21",
|
||||
"nodebb-theme-peace": "2.2.58",
|
||||
"nodebb-theme-persona": "14.2.34",
|
||||
"nodebb-theme-persona": "14.2.35",
|
||||
"nodebb-widget-essentials": "7.0.43",
|
||||
"nodemailer": "8.0.3",
|
||||
"nprogress": "0.2.0",
|
||||
|
||||
@@ -495,6 +495,7 @@ define('admin/manage/users', [
|
||||
});
|
||||
|
||||
const tableEl = document.querySelector('.users-table');
|
||||
const $tableEl = $(tableEl);
|
||||
const actionBtn = document.getElementById('action-dropdown');
|
||||
tableEl.addEventListener('change', (e) => {
|
||||
const subselector = e.target.closest('[component="user/select/single"]') || e.target.closest('[component="user/select/all"]');
|
||||
@@ -509,7 +510,7 @@ define('admin/manage/users', [
|
||||
});
|
||||
|
||||
let lastSelectedUser;
|
||||
$(tableEl).on('click', '[component="user/select/single"]', function (ev) {
|
||||
$tableEl.on('click', '[component="user/select/single"]', function (ev) {
|
||||
function selectRange(clickedUserRow) {
|
||||
function selectIndexRange(start, end, isChecked) {
|
||||
if (start > end) {
|
||||
@@ -545,7 +546,7 @@ define('admin/manage/users', [
|
||||
lastSelectedUser = userRow;
|
||||
});
|
||||
|
||||
$('[data-copy]').on('click', function () {
|
||||
$tableEl.on('click', '[data-copy]', function () {
|
||||
const btn = $(this);
|
||||
navigator.clipboard.writeText(this.getAttribute('data-copy'));
|
||||
btn.find('i')
|
||||
|
||||
@@ -77,7 +77,7 @@ module.exports = function (Groups) {
|
||||
|
||||
if (values.hasOwnProperty('memberPostCids')) {
|
||||
const validCids = await categories.getCidsByPrivilege('categories:cid', groupName, 'topics:read');
|
||||
const cidsArray = values.memberPostCids.split(',').map(cid => parseInt(cid.trim(), 10)).filter(Boolean);
|
||||
const cidsArray = values.memberPostCids.split(',').map(cid => (cid || '').trim()).filter(Boolean);
|
||||
payload.memberPostCids = cidsArray.filter(cid => validCids.includes(cid)).join(',') || '';
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ async function isAllowedToCids(privilege, uidOrGroupName, cids) {
|
||||
const groupKeys = cids.map(cid => `cid:${cid}:privileges:groups:${privilege}`);
|
||||
|
||||
// Group handling
|
||||
if (isNaN(parseInt(uidOrGroupName, 10)) && (uidOrGroupName || '').length) {
|
||||
if (!utils.isNumber(uidOrGroupName) && (uidOrGroupName || '').length) {
|
||||
return await checkIfAllowedGroup(uidOrGroupName, groupKeys);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = function (SocketUser) {
|
||||
userPictures.forEach((picture) => {
|
||||
list.pictures.push({
|
||||
type: 'uploaded',
|
||||
url: `${nconf.get('relative_path')}${picture}`,
|
||||
url: picture.startsWith('http') ? picture : `${nconf.get('relative_path')}${picture}`,
|
||||
text: '[[user:uploaded-picture]]',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,14 +167,15 @@ module.exports = function (Topics) {
|
||||
topicData.index = 0;
|
||||
postData.index = 0;
|
||||
|
||||
if (topicData.scheduled) {
|
||||
await Topics.delete(tid);
|
||||
if (data.deleted || topicData.scheduled) {
|
||||
await Topics.delete(tid, uid);
|
||||
topicData.deleted = true;
|
||||
}
|
||||
|
||||
analytics.increment(['topics', `topics:byCid:${topicData.cid}`]);
|
||||
plugins.hooks.fire('action:topic.post', { topic: topicData, post: postData, data: data });
|
||||
|
||||
if (!topicData.scheduled) {
|
||||
if (!topicData.scheduled && !topicData.deleted) {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
if (utils.isNumber(uid)) {
|
||||
|
||||
@@ -197,13 +197,13 @@ describe('Categories', () => {
|
||||
content: 'The content of test topic',
|
||||
tags: ['nodebb'],
|
||||
});
|
||||
const data = await Topics.post({
|
||||
await Topics.post({
|
||||
uid: posterUid,
|
||||
cid: categoryObj.cid,
|
||||
title: 'will delete',
|
||||
content: 'The content of deleted topic',
|
||||
deleted: 1,
|
||||
});
|
||||
await Topics.delete(data.topicData.tid, adminUid);
|
||||
});
|
||||
|
||||
it('should get recent replies in category', (done) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ const nconf = require('nconf');
|
||||
|
||||
const db = require('./mocks/databasemock');
|
||||
const helpers = require('./helpers');
|
||||
const Categories = require('../src/categories');
|
||||
const Groups = require('../src/groups');
|
||||
const User = require('../src/user');
|
||||
const plugins = require('../src/plugins');
|
||||
@@ -566,6 +567,20 @@ describe('Groups', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly set memberPostCids', async () => {
|
||||
const c1 = await Categories.create({ name: 'Test Category' });
|
||||
const c2 = await Categories.create({ name: 'Test Category' });
|
||||
const c3 = await Categories.create({ name: 'Test Category' });
|
||||
await Groups.create({
|
||||
name: '3rd party devs',
|
||||
});
|
||||
await Groups.update('3rd party devs', {
|
||||
memberPostCids: `${c1.cid},${c2.cid},${c3.cid}`,
|
||||
});
|
||||
const groupData = await Groups.get('3rd party devs', {});
|
||||
assert.strictEqual(groupData.memberPostCids, '1,2,3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.destroy()', () => {
|
||||
|
||||
@@ -1420,8 +1420,14 @@ describe('Topic\'s', () => {
|
||||
|
||||
it('should not return topic as unread if topic is deleted', async () => {
|
||||
const uid = await User.create({ username: 'regularJoe' });
|
||||
const result = await topics.post({ uid: adminUid, title: 'deleted unread', content: 'not unread', cid: categoryObj.cid });
|
||||
await topics.delete(result.topicData.tid, adminUid);
|
||||
const result = await topics.post({
|
||||
uid: adminUid,
|
||||
title: 'deleted unread',
|
||||
content: 'not unread',
|
||||
cid: categoryObj.cid,
|
||||
deleted: 1,
|
||||
});
|
||||
|
||||
const unreadTids = await topics.getUnreadTids({ cid: 0, uid: uid });
|
||||
|
||||
await sleep(2000);
|
||||
|
||||
@@ -64,15 +64,11 @@ describe('Topic thumbs', () => {
|
||||
cid: categoryObj.cid,
|
||||
title: 'Test Topic Title',
|
||||
content: 'The content of test topic',
|
||||
thumbs: [relativeThumbPaths[0]],
|
||||
});
|
||||
|
||||
// Touch a couple files and associate it to a topic
|
||||
createFiles();
|
||||
|
||||
await topics.setTopicFields(topicObj.topicData.tid, {
|
||||
numThumbs: 1,
|
||||
thumbs: JSON.stringify([relativeThumbPaths[0]]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return bool for whether a thumb exists', async () => {
|
||||
|
||||
Reference in New Issue
Block a user