mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-18 16:31:45 +02:00
Merge commit 'e30024119334479c57e442a72ed72a07084fe1e9' into v4.x
This commit is contained in:
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
||||
#### v4.6.1 (2025-10-17)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up persona (b309a672)
|
||||
* up harmony (79327e6c)
|
||||
* incrementing version number - v4.6.0 (ee395bc5)
|
||||
* update changelog for v4.6.0 (c0d9bb07)
|
||||
* 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)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* do not include image or icon props if they are falsy values (ecf95d18)
|
||||
* #13705, don't cover link if preview is opening up (499c50a4)
|
||||
* logic error in image mime type checking (623cec9d)
|
||||
* omg what. (ec399897)
|
||||
|
||||
#### v4.6.0 (2025-10-01)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"nodebb-plugin-2factor": "7.6.0",
|
||||
"nodebb-plugin-composer-default": "10.3.1",
|
||||
"nodebb-plugin-dbsearch": "6.3.2",
|
||||
"nodebb-plugin-emoji": "6.0.3",
|
||||
"nodebb-plugin-emoji": "6.0.5",
|
||||
"nodebb-plugin-emoji-android": "4.1.1",
|
||||
"nodebb-plugin-markdown": "13.2.1",
|
||||
"nodebb-plugin-mentions": "4.7.6",
|
||||
@@ -108,7 +108,7 @@
|
||||
"nodebb-rewards-essentials": "1.0.2",
|
||||
"nodebb-theme-harmony": "2.1.21",
|
||||
"nodebb-theme-lavender": "7.1.19",
|
||||
"nodebb-theme-peace": "2.2.48",
|
||||
"nodebb-theme-peace": "2.2.49",
|
||||
"nodebb-theme-persona": "14.1.15",
|
||||
"nodebb-widget-essentials": "7.0.40",
|
||||
"nodemailer": "7.0.6",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
define('forum/category', [
|
||||
'forum/infinitescroll',
|
||||
'share',
|
||||
'navigator',
|
||||
'topicList',
|
||||
'sort',
|
||||
@@ -11,7 +10,7 @@ define('forum/category', [
|
||||
'alerts',
|
||||
'api',
|
||||
'clipboard',
|
||||
], function (infinitescroll, share, navigator, topicList, sort, categorySelector, hooks, alerts, api, clipboard) {
|
||||
], function (infinitescroll, navigator, topicList, sort, categorySelector, hooks, alerts, api, clipboard) {
|
||||
const Category = {};
|
||||
|
||||
$(window).on('action:ajaxify.start', function (ev, data) {
|
||||
|
||||
@@ -216,9 +216,12 @@ define('topicList', [
|
||||
'reputation:disabled': data['reputation:disabled'],
|
||||
template: {
|
||||
name: templateName,
|
||||
[templateName]: true,
|
||||
},
|
||||
};
|
||||
tplData.template[templateName] = true;
|
||||
if (ajaxify.data.cid) {
|
||||
tplData.cid = ajaxify.data.cid;
|
||||
}
|
||||
|
||||
hooks.fire('action:topics.loading', { topics: topics, after: after, before: before });
|
||||
|
||||
|
||||
@@ -119,8 +119,13 @@ ActivityPub.resolveInboxes = async (ids) => {
|
||||
|
||||
if (!meta.config.activitypubAllowLoopback) {
|
||||
ids = ids.filter((id) => {
|
||||
const { hostname } = new URL(id);
|
||||
return hostname !== nconf.get('url_parsed').hostname;
|
||||
try {
|
||||
const { hostname } = new URL(id);
|
||||
return hostname !== nconf.get('url_parsed').hostname;
|
||||
} catch (err) {
|
||||
winston.error(`[activitypub/resolveInboxes] Invalid id: ${id}`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,11 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
const { hostname } = new URL(mainPid);
|
||||
remoteCid = Array.from(set).filter((id, idx) => {
|
||||
const { hostname: cidHostname } = new URL(id);
|
||||
return assertedGroups[idx] && cidHostname === hostname;
|
||||
const explicitAudience = Array.isArray(_activitypub.audience) ?
|
||||
_activitypub.audience.includes(id) :
|
||||
_activitypub.audience === id;
|
||||
|
||||
return assertedGroups[idx] && (explicitAudience || cidHostname === hostname);
|
||||
}).shift();
|
||||
} catch (e) {
|
||||
// noop
|
||||
|
||||
@@ -165,9 +165,9 @@ module.exports = function (Categories) {
|
||||
throw new Error('[[error:category.handle-taken]]');
|
||||
}
|
||||
|
||||
await db.sortedSetRemove('categoryhandle:cid', existing);
|
||||
await Promise.all([
|
||||
db.setObjectField(`category:${cid}`, 'handle', handle),
|
||||
db.sortedSetRemove('categoryhandle:cid', existing),
|
||||
db.sortedSetAdd('categoryhandle:cid', cid, handle),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ authenticationController.logout = async function (req, res) {
|
||||
};
|
||||
await plugins.hooks.fire('filter:user.logout', payload);
|
||||
|
||||
if (req.body?.noscript === 'true') {
|
||||
if (req.body?.noscript === 'true' || res.locals.logoutRedirect === true) {
|
||||
return res.redirect(payload.next);
|
||||
}
|
||||
res.status(200).send(payload);
|
||||
|
||||
@@ -61,7 +61,7 @@ async function uploadAsImage(req, uploadedFile) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
await image.checkDimensions(uploadedFile.path);
|
||||
await image.stripEXIF(uploadedFile.path);
|
||||
await image.stripEXIF({ path: uploadedFile.path, type: uploadedFile.type });
|
||||
|
||||
if (plugins.hooks.hasListeners('filter:uploadImage')) {
|
||||
return await plugins.hooks.fire('filter:uploadImage', {
|
||||
|
||||
@@ -102,14 +102,15 @@ image.size = async function (path) {
|
||||
return imageData ? { width: imageData.width, height: imageData.height } : undefined;
|
||||
};
|
||||
|
||||
image.stripEXIF = async function (path) {
|
||||
if (!meta.config.stripEXIFData || path.endsWith('.gif') || path.endsWith('.svg')) {
|
||||
image.stripEXIF = async function ({ path, type }) {
|
||||
if (!meta.config.stripEXIFData || type === 'image/gif' || type === 'image/svg+xml') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (plugins.hooks.hasListeners('filter:image.stripEXIF')) {
|
||||
await plugins.hooks.fire('filter:image.stripEXIF', {
|
||||
path: path,
|
||||
type: type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
17
src/middleware/multer.js
Normal file
17
src/middleware/multer.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const multer = require('multer');
|
||||
const storage = multer.diskStorage({});
|
||||
const upload = multer({
|
||||
storage,
|
||||
// from https://github.com/TriliumNext/Trilium/pull/3058/files
|
||||
fileFilter: (req, file, cb) => {
|
||||
// UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side.
|
||||
// See https://github.com/expressjs/multer/pull/1102.
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf-8');
|
||||
cb(null, true);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = upload;
|
||||
|
||||
@@ -53,6 +53,12 @@ module.exports = function (middleware) {
|
||||
}
|
||||
|
||||
if (req.loggedIn) {
|
||||
const exists = await user.exists(req.uid);
|
||||
if (!exists) {
|
||||
res.locals.logoutRedirect = true;
|
||||
return controllers.authentication.logout(req, res);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (req.headers.hasOwnProperty('authorization')) {
|
||||
const user = await passportAuthenticateAsync(req, res);
|
||||
|
||||
@@ -194,7 +194,7 @@ Plugins.listTrending = async () => {
|
||||
|
||||
Plugins.normalise = async function (apiReturn) {
|
||||
const pluginMap = {};
|
||||
const { dependencies } = require(paths.currentPackage);
|
||||
const { dependencies } = require(paths.installPackage);
|
||||
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
|
||||
apiReturn.forEach((packageData) => {
|
||||
packageData.id = packageData.name;
|
||||
@@ -229,7 +229,7 @@ Plugins.normalise = async function (apiReturn) {
|
||||
pluginMap[plugin.id].settingsRoute = plugin.settingsRoute;
|
||||
pluginMap[plugin.id].license = plugin.license;
|
||||
|
||||
// If package.json defines a version to use, stick to that
|
||||
// If install/package.json defines a version to use, stick to that
|
||||
if (dependencies.hasOwnProperty(plugin.id) && semver.valid(dependencies[plugin.id])) {
|
||||
pluginMap[plugin.id].latest = dependencies[plugin.id];
|
||||
} else {
|
||||
|
||||
@@ -85,9 +85,7 @@ function apiRoutes(router, name, middleware, controllers) {
|
||||
router.post(`/api/${name}/manage/categories/:cid/name`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.renameRemote));
|
||||
router.delete(`/api/${name}/manage/categories/:cid`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.removeRemote));
|
||||
|
||||
const multer = require('multer');
|
||||
const storage = multer.diskStorage({});
|
||||
const upload = multer({ storage });
|
||||
const upload = require('../middleware/multer');
|
||||
|
||||
const middlewares = [
|
||||
upload.array('files[]', 20),
|
||||
|
||||
@@ -23,9 +23,7 @@ module.exports = function (app, middleware, controllers) {
|
||||
router.get('/topic/teaser/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.teaser));
|
||||
router.get('/topic/pagination/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.pagination));
|
||||
|
||||
const multer = require('multer');
|
||||
const storage = multer.diskStorage({});
|
||||
const upload = multer({ storage });
|
||||
const upload = require('../middleware/multer');
|
||||
|
||||
const postMiddlewares = [
|
||||
middleware.maintenanceMode,
|
||||
|
||||
@@ -154,10 +154,7 @@ Auth.reloadRoutes = async function (params) {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const multer = require('multer');
|
||||
const storage = multer.diskStorage({});
|
||||
const upload = multer({ storage });
|
||||
const upload = require('../middleware/multer');
|
||||
const middlewares = [
|
||||
upload.any(),
|
||||
Auth.middleware.applyCSRF,
|
||||
|
||||
@@ -54,9 +54,7 @@ helpers.setupApiRoute = function (...args) {
|
||||
const [router, verb, name] = args;
|
||||
let middlewares = args.length > 4 ? args[args.length - 2] : [];
|
||||
const controller = args[args.length - 1];
|
||||
const multer = require('multer');
|
||||
const storage = multer.diskStorage({});
|
||||
const upload = multer({ storage });
|
||||
const upload = require('../middleware/multer');
|
||||
middlewares = [
|
||||
middleware.autoLocale,
|
||||
middleware.applyBlacklist,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
<!-- users toggle -->
|
||||
{{{ if users.length }}}
|
||||
<div component="chat/user/list/btn" class="btn btn-ghost btn-sm d-none d-lg-flex flex-nowrap gap-3" title="[[modules:chat.view-users-list]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<div component="chat/user/list/btn" class="btn btn-ghost btn-sm d-none d-lg-flex flex-nowrap align-items-center gap-2" title="[[modules:chat.view-users-list]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<div class="d-flex text-nowrap">
|
||||
{{{ if ./users.0 }}}
|
||||
<span style="width: 18px; z-index: 3;" class="text-decoration-none" href="{config.relative_path}/user/{./users.0.userslug}">{buildAvatar(./users.0, "24px", true)}</span>
|
||||
@@ -91,10 +91,10 @@
|
||||
<span style="width: 18px; z-index: 2;" class="text-decoration-none" href="{config.relative_path}/user/{./users.1.userslug}">{buildAvatar(./users.1, "24px", true)}</span>
|
||||
{{{ end }}}
|
||||
{{{ if ./users.2 }}}
|
||||
<span style="width: 18px; z-index: 1;" class="text-decoration-none" href="{config.relative_path}/user/{./users.2.userslug}">{buildAvatar(./users.2, "24px", true)}</span>
|
||||
<span style="width: 24px; z-index: 1;" class="text-decoration-none" href="{config.relative_path}/user/{./users.2.userslug}">{buildAvatar(./users.2, "24px", true)}</span>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
{./userCount}
|
||||
{formattedNumber(./userCount)}
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
|
||||
BIN
test/files/测试.jpg
Normal file
BIN
test/files/测试.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1010 B |
@@ -163,7 +163,7 @@ describe('i18n', () => {
|
||||
assert.strictEqual(
|
||||
sourceKeys.length,
|
||||
translationKeys.length,
|
||||
`Extra keys found in namespace ${namespace.slice(1, -5)} for language "${language}"`
|
||||
`Extra keys found in namespace "${namespace.slice(1, -5)}" for language "${language}"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,6 +194,12 @@ describe('Upload Controllers', () => {
|
||||
assert(body.response.images[0].url);
|
||||
});
|
||||
|
||||
it('should upload a file with utf8 characters in the name to a post', async () => {
|
||||
const { body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/测试.jpg'), {}, jar, csrf_token);
|
||||
|
||||
assert(body.response.images[0].url.endsWith('测试.jpg'));
|
||||
});
|
||||
|
||||
it('should fail to upload image to post if image dimensions are too big', async () => {
|
||||
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token);
|
||||
assert.strictEqual(response.statusCode, 500);
|
||||
|
||||
Reference in New Issue
Block a user