mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-06 07:07:13 +02:00
Merge commit '33d50637a33814f10bb5e8d78b8e314a39bea417' into v4.x
This commit is contained in:
100
CHANGELOG.md
100
CHANGELOG.md
@@ -1,3 +1,103 @@
|
||||
#### v4.2.0 (2025-03-19)
|
||||
|
||||
##### Chores
|
||||
|
||||
* **deps:**
|
||||
* update commitlint monorepo to v19.8.0 (#13244) (ee3c0bf4)
|
||||
* update dependency lint-staged to v15.5.0 (#13245) (f4fe3f5f)
|
||||
* update dependency sass-embedded to v1.86.0 (#13251) (3bb861ae)
|
||||
* up widgets (ed57f896)
|
||||
* incrementing version number - v4.1.1 (b2afbb16)
|
||||
* update changelog for v4.1.1 (2c3f8561)
|
||||
* add missing file to az lang (d7116adc)
|
||||
* language labels for az/pl (61d17c95)
|
||||
* alphabetize transifex config file languages (b7bb35f8)
|
||||
* 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)
|
||||
* **i18n:**
|
||||
* fallback strings for new resources: nodebb.admin-settings-email (866cd539)
|
||||
* fallback strings for new resources: nodebb.error (b4dfd7fe)
|
||||
* fallback strings for new resources: nodebb.admin-settings-chat (2f957655)
|
||||
* fallback strings for new resources: nodebb.user (78a2c087)
|
||||
* fallback strings for new resources: nodebb.admin-manage-categories (c3993018)
|
||||
* fallback strings for new resources: nodebb.admin-manage-categories (efdb416c)
|
||||
* fallback strings for new resources: nodebb.category (8314d8ba)
|
||||
|
||||
##### Documentation Changes
|
||||
|
||||
* openapi schema for admin/extend/plugins (f2a16422)
|
||||
* update openapi schema from 6c26d9f4a3c398b4d7add0a2d9c91685a0336a74 (128dd2d3)
|
||||
|
||||
##### New Features
|
||||
|
||||
* 1b12 category announce on post move to a new tid, #13236 (254f0738)
|
||||
* add Azerbaijani localisations, təşəkkür edirəm! (825e4c70)
|
||||
* if an incoming remote message is too long, don't create the room, but notify the local recipients instead (885b83e5)
|
||||
* new ACP config for max length of remote chat messages, #13174 (81509b13)
|
||||
* add `federatedDescription` property to a category. (dfabadbe)
|
||||
* add link to category edit page in acp category sidebar (fa8216f2)
|
||||
* add additional logic that checks whether a cid follows the activity actor, and asserts note into that category if so (3589c570)
|
||||
* move category sidebar to ACP partial, add sidebar to category-federation (dc2dcaf1)
|
||||
* add line to description exposing a category's handle if accessible by fediverse pseudo-user, closes #13126 (6c26d9f4)
|
||||
* call announceObject on topic fork, #13215 (e3edfef8)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* wrong property name used (08796a0a)
|
||||
* #13254, configurable ap content prune (80e03c85)
|
||||
* only 1b12 announce topic fork if OP is local, fix race condition in tests (945617cb)
|
||||
* bug where remote post was attempted to be announced on post move (291af926)
|
||||
* handling of `href` in remote object attachments, #13169 (44354dac)
|
||||
* #13100, direct access to a tag page no longer excludes cid -1 (14fd33ce)
|
||||
* expose remote url in user object (78c9239b)
|
||||
* hide disable and purge buttons from category-federation.tpl (43248578)
|
||||
* show 'copy settings from' button in acp category sidebar only on category.tpl (1f6871e5)
|
||||
* improper cc and object fields in announceObject (deb5ee5e)
|
||||
* move AP send logging earlier (dca3c35d)
|
||||
* #13224, handle note attributedTo when it is of type object (d9483347)
|
||||
* allow actor assertion of loopback actors depending on ACP setting (73aaa990)
|
||||
* **deps:**
|
||||
* update dependency mongodb to v6.15.0 (#13253) (1c23d0cf)
|
||||
* update dependency pg to v8.14.1 (#13247) (4d6d71d8)
|
||||
* update dependency nodebb-widget-essentials to v7.0.36 (#13250) (dbd0fd22)
|
||||
* update dependency sass to v1.86.0 (#13252) (a1465268)
|
||||
* update dependency esbuild to v0.25.1 (#13243) (ce3bb8b5)
|
||||
* update dependency ioredis to v5.6.0 (#13246) (b96f532b)
|
||||
* update dependency connect-redis to v8.0.2 (#13242) (e90a8b26)
|
||||
* update dependency autoprefixer to v10.4.21 (#13241) (4e69ed56)
|
||||
* update dependency pg-cursor to v2.13.1 (#13248) (92727549)
|
||||
* update fontsource monorepo to v5.2.5 (#13226) (8ca1d6e6)
|
||||
* update dependency mongodb to v6.14.2 (#13229) (b39e4d19)
|
||||
* update dependency terser-webpack-plugin to v5.3.14 (#13230) (7b40e210)
|
||||
* update dependency mongodb to v6.14.1 (#13225) (a58af228)
|
||||
* update dependency tough-cookie to v5.1.2 (#13217) (e19109ad)
|
||||
* update dependency mongodb to v6.14.0 (#13214) (ad680d6a)
|
||||
* update dependency terser-webpack-plugin to v5.3.12 (#13213) (4c22af8c)
|
||||
* update dependency cron to v4.1.0 (#13200) (f56838a3)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* remove announceObject in favour of feps.announce, added create activity mock to support (74443c3b)
|
||||
* move all input note normalization into helper method, have assertPrivate mock a message object (with said normalization) before sending message (4ec7552c)
|
||||
|
||||
##### Tests
|
||||
|
||||
* add url/statusCode to failing test (8982923e)
|
||||
* fix remoteUrl property generation, tests for topic moving (0e1006fb)
|
||||
* fix actor tests (537880d2)
|
||||
* openapi schema for remoteUrl (fc64e89f)
|
||||
* add failing test for #13215 (feb94215)
|
||||
* adjust test runner detection in AP code (7ceb6d69)
|
||||
* new test file for feps (e510e826)
|
||||
* log outgoing AP messages for local test runner (6e872b5f)
|
||||
* allow ap/notes tests to be run in isolation (98aafaaf)
|
||||
|
||||
#### v4.1.1 (2025-03-12)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -108,10 +108,10 @@
|
||||
"nodebb-plugin-spam-be-gone": "2.3.1",
|
||||
"nodebb-plugin-web-push": "0.7.3",
|
||||
"nodebb-rewards-essentials": "1.0.1",
|
||||
"nodebb-theme-harmony": "2.0.40",
|
||||
"nodebb-theme-harmony": "2.0.43",
|
||||
"nodebb-theme-lavender": "7.1.18",
|
||||
"nodebb-theme-peace": "2.2.39",
|
||||
"nodebb-theme-persona": "14.0.16",
|
||||
"nodebb-theme-persona": "14.0.18",
|
||||
"nodebb-widget-essentials": "7.0.36",
|
||||
"nodemailer": "6.10.0",
|
||||
"nprogress": "0.2.0",
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
"email-confirmed": "Email Confirmed",
|
||||
"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
|
||||
"email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
|
||||
"email-confirm-error-message-already-validated": "Your email address was already validated.",
|
||||
"email-confirm-sent": "Confirmation email sent.",
|
||||
|
||||
"none": "None",
|
||||
|
||||
@@ -24,9 +24,6 @@ get:
|
||||
error:
|
||||
type: string
|
||||
description: Translation key for client-side localisation
|
||||
alreadyValidated:
|
||||
type: boolean
|
||||
description: set to true if the email was already validated
|
||||
required:
|
||||
- title
|
||||
- $ref: ../../components/schemas/CommonProps.yaml#/CommonProps
|
||||
@@ -1,59 +1,17 @@
|
||||
.picture-switcher {
|
||||
h4 {
|
||||
line-height: 46px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-body .btn {
|
||||
padding: 10px 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
label {
|
||||
vertical-align: top;
|
||||
line-height: 26px;
|
||||
|
||||
> input[type="radio"] {
|
||||
display: none;
|
||||
|
||||
&:checked {
|
||||
+ span:before {
|
||||
border-radius: 50%;
|
||||
border: 2px solid $primary;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
&[value="transparent"] {
|
||||
&:checked + span:before {
|
||||
padding-top: 2px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
+ span:before {
|
||||
content: '\f05e';
|
||||
font-family: FontAwesome;
|
||||
color: $gray-200;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
[data-bg-color] {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
&.selected::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px; left: -2px;
|
||||
right: -2px; bottom: -2px;
|
||||
border-radius: 50%;
|
||||
margin-right: .5em;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
border: 2px solid $primary;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,10 @@ define('accounts/picture', [
|
||||
modal.find('.list-group-item').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
modal.on('change', 'input[type="radio"][name="icon:bgColor"]', (e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
modal.on('click', '[data-bg-color]', function () {
|
||||
const value = $(this).attr('data-bg-color');
|
||||
$(this).addClass('selected').siblings().removeClass('selected');
|
||||
modal.find('[component="avatar/icon"]').css('background-color', value);
|
||||
});
|
||||
|
||||
@@ -80,18 +82,17 @@ define('accounts/picture', [
|
||||
}
|
||||
|
||||
// Update avatar background colour
|
||||
const radioEl = document.querySelector(`.modal input[type="radio"][value="${ajaxify.data['icon:bgColor']}"]`);
|
||||
if (radioEl) {
|
||||
radioEl.checked = true;
|
||||
const iconbgEl = modal.find(`[data-bg-color="${ajaxify.data['icon:bgColor']}"]`);
|
||||
if (iconbgEl.length) {
|
||||
iconbgEl.addClass('selected');
|
||||
} else {
|
||||
// Check the first one
|
||||
document.querySelector('.modal input[type="radio"]').checked = true;
|
||||
modal.find('[data-bg-color="transparent"]').addClass('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function saveSelection() {
|
||||
const type = modal.find('.list-group-item.active').attr('data-type');
|
||||
const iconBgColor = document.querySelector('.modal.picture-switcher input[type="radio"]:checked').value || 'transparent';
|
||||
const iconBgColor = modal.find('[data-bg-color].selected').attr('data-bg-color') || 'transparent';
|
||||
|
||||
changeUserPicture(type, iconBgColor).then(() => {
|
||||
Picture.updateHeader(
|
||||
|
||||
@@ -156,7 +156,7 @@ Categories.setUnread = async function (tree, cids, uid) {
|
||||
if (category) {
|
||||
category.unread = false;
|
||||
if (unreadCids.includes(category.cid)) {
|
||||
category.unread = category.topic_count > 0 && true;
|
||||
category.unread = category.topic_count > 0;
|
||||
} else if (category.children.length) {
|
||||
category.children.forEach(setCategoryUnread);
|
||||
category.unread = category.children.some(c => c && c.unread);
|
||||
|
||||
@@ -28,12 +28,13 @@ dashboardController.get = async function (req, res) {
|
||||
getPopularSearches(),
|
||||
]);
|
||||
const version = nconf.get('version');
|
||||
const latestValidVersion = semver.valid(latestVersion);
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
version: version,
|
||||
lookupFailed: latestVersion === null,
|
||||
latestVersion: latestVersion,
|
||||
upgradeAvailable: latestVersion && semver.gt(latestVersion, version),
|
||||
lookupFailed: latestValidVersion === null,
|
||||
latestVersion: latestValidVersion,
|
||||
upgradeAvailable: latestValidVersion && semver.gt(latestValidVersion, version),
|
||||
currentPrerelease: versions.isPrerelease.test(version),
|
||||
notices: notices,
|
||||
stats: stats,
|
||||
|
||||
@@ -235,12 +235,6 @@ Controllers.confirmEmail = async (req, res) => {
|
||||
return renderPage();
|
||||
}
|
||||
try {
|
||||
if (req.loggedIn) {
|
||||
const emailValidated = await user.getUserField(req.uid, 'email:confirmed');
|
||||
if (emailValidated) {
|
||||
return renderPage({ alreadyValidated: true });
|
||||
}
|
||||
}
|
||||
await user.email.confirmByCode(req.params.code, req.session.id);
|
||||
if (req.session.registration) {
|
||||
// After confirmation, no need to send user back to email change form
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
{{{ if alreadyValidated }}}
|
||||
<div class="alert alert-info">
|
||||
<p>[[notifications:email-confirm-error-message-already-validated]]</p>
|
||||
{{{ end }}}
|
||||
|
||||
{{{ if error }}}
|
||||
<div class="alert alert-warning">
|
||||
<p>[[notifications:email-confirm-error-message]]</p>
|
||||
{{{ end }}}
|
||||
|
||||
{{{ if (!error && !alreadyValidated )}}}
|
||||
{{{ if !error }}}
|
||||
<div class="alert alert-success">
|
||||
<strong>[[notifications:email-confirmed]]</strong>
|
||||
<p>[[notifications:email-confirmed-message]]</p>
|
||||
|
||||
@@ -35,8 +35,9 @@
|
||||
<hr />
|
||||
|
||||
<h4>[[user:avatar-background-colour]]</h4>
|
||||
|
||||
<label><input type="radio" name="icon:bgColor" value="transparent" /><span></span></label>
|
||||
{{{ each iconBackgrounds }}}
|
||||
<label><input type="radio" name="icon:bgColor" value="{@value}" /><span style="background-color: {@value};"></span></label>
|
||||
{{{ end }}}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="#" class="lh-1 p-1" data-bg-color="transparent"><i class="fa-solid fa-2x fa-ban text-secondary"></i></a>
|
||||
{{{ each iconBackgrounds }}}
|
||||
<a href="#" class="lh-1 p-1" data-bg-color="{@value}" style="color: {@value};"><i class="fa-solid fa-2x fa-circle"></i></a>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-8 offset-sm-2">
|
||||
<p class="lead text-center">
|
||||
<h1 class="text-center fs-5">
|
||||
{{{ if register }}}[[register:interstitial.intro-new]]{{{ else }}}[[register:interstitial.intro]]{{{ end }}}
|
||||
</p>
|
||||
</h1>
|
||||
|
||||
{{{ if errors.length }}}
|
||||
<div class="alert alert-warning">
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const async = require('async');
|
||||
const assert = require('assert');
|
||||
const db = require('../mocks/databasemock');
|
||||
|
||||
@@ -115,67 +113,29 @@ describe('Key methods', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete all keys passed in', (done) => {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.set('key1', 'value1', next);
|
||||
},
|
||||
function (next) {
|
||||
db.set('key2', 'value2', next);
|
||||
},
|
||||
], (err) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
db.deleteAll(['key1', 'key2'], function (err) {
|
||||
assert.ifError(err);
|
||||
assert.equal(arguments.length, 1);
|
||||
async.parallel({
|
||||
key1exists: function (next) {
|
||||
db.exists('key1', next);
|
||||
},
|
||||
key2exists: function (next) {
|
||||
db.exists('key2', next);
|
||||
},
|
||||
}, (err, results) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(results.key1exists, false);
|
||||
assert.equal(results.key2exists, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should delete all keys passed in', async () => {
|
||||
await Promise.all([
|
||||
db.set('key1', 'value1'),
|
||||
db.set('key2', 'value2'),
|
||||
]);
|
||||
|
||||
await db.deleteAll(['key1', 'key2']);
|
||||
const [key1Exists, key2Exists] = await db.exists(['key1', 'key2']);
|
||||
assert.strictEqual(key1Exists, false);
|
||||
assert.strictEqual(key2Exists, false);
|
||||
});
|
||||
|
||||
it('should delete all sorted set elements', (done) => {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.sortedSetAdd('deletezset', 1, 'value1', next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('deletezset', 2, 'value2', next);
|
||||
},
|
||||
], (err) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
db.delete('deletezset', (err) => {
|
||||
assert.ifError(err);
|
||||
async.parallel({
|
||||
key1exists: function (next) {
|
||||
db.isSortedSetMember('deletezset', 'value1', next);
|
||||
},
|
||||
key2exists: function (next) {
|
||||
db.isSortedSetMember('deletezset', 'value2', next);
|
||||
},
|
||||
}, (err, results) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(results.key1exists, false);
|
||||
assert.equal(results.key2exists, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should delete all sorted set elements', async () => {
|
||||
await db.sortedSetAddBulk([
|
||||
['deletezset', 1, 'value1'],
|
||||
['deletezset', 2, 'value2'],
|
||||
]);
|
||||
|
||||
await db.delete('deletezset');
|
||||
const [key1Exists, key2Exists] = await db.isSortedSetMembers('deletezset', ['value1', 'value2']);
|
||||
|
||||
assert.strictEqual(key1Exists, false);
|
||||
assert.strictEqual(key2Exists, false);
|
||||
});
|
||||
|
||||
describe('increment', () => {
|
||||
|
||||
Reference in New Issue
Block a user