Merge commit '33d50637a33814f10bb5e8d78b8e314a39bea417' into v4.x

This commit is contained in:
Misty Release Bot
2025-04-10 14:03:45 +00:00
13 changed files with 157 additions and 151 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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', () => {