Merge commit 'e30024119334479c57e442a72ed72a07084fe1e9' into v4.x

This commit is contained in:
Misty Release Bot
2025-11-19 15:31:55 +00:00
21 changed files with 105 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),
]);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

View File

@@ -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}"`
);
});
});

View File

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