Merge branch 'master' into develop

This commit is contained in:
Barış Soner Uşaklı
2026-03-25 10:21:36 -04:00
15 changed files with 61 additions and 31 deletions

View File

@@ -108,7 +108,7 @@
"nodebb-plugin-spam-be-gone": "2.3.2",
"nodebb-plugin-web-push": "0.7.7",
"nodebb-rewards-essentials": "1.0.2",
"nodebb-theme-harmony": "2.2.61",
"nodebb-theme-harmony": "2.2.62",
"nodebb-theme-lavender": "7.1.21",
"nodebb-theme-peace": "2.2.57",
"nodebb-theme-persona": "14.2.33",

View File

@@ -115,14 +115,20 @@ define('forum/category', [
}
function handleDescription() {
const fadeEl = document.querySelector(`.description.clamp-fade-sm-4`);
if (!fadeEl) {
return;
}
fadeEl.addEventListener('click', () => {
const state = fadeEl.classList.contains('line-clamp-4');
fadeEl.classList.toggle('line-clamp-4', !state);
const fadeEl = $(`.description[class*="clamp-fade-"]`);
fadeEl.on('click', function () {
const $this = $(this);
let clampClass = $this.data('clampClass');
if (!clampClass) {
const match = $this.attr('class').match(/line-clamp-(\S+)/);
if (match && match[1]) {
clampClass = `line-clamp-${match[1]}`;
fadeEl.data('clampClass', clampClass);
}
}
if (clampClass) {
fadeEl.toggleClass(clampClass);
}
});
}

View File

@@ -134,7 +134,7 @@ module.exports = function (User) {
`uid:${uid}:flag:pids`,
`uid:${uid}:sessions`,
`uid:${uid}:shares`,
`uid:${uid}:profile:images`,
`uid:${uid}:profile:pictures`,
`invitation:uid:${uid}`,
];

View File

@@ -143,7 +143,7 @@ module.exports = function (User) {
const filename = generateProfileImageFilename(updateUid, extension);
const uploadedImage = await image.uploadImage(filename, `profile/uid-${updateUid}`, {
uid: updateUid,
path: picture.path,
path: normalizedPath,
name: 'profileAvatar',
});

View File

@@ -125,7 +125,7 @@
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">[[admin/extend/plugins:order-active]]</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<p>

View File

@@ -6,7 +6,7 @@
<div class="">
<div component="toaster/tray" class="alert-window fixed-bottom mb-5 mb-md-2 me-2 me-md-5 ms-auto" style="width:300px; z-index: 1090;">
<div id="reconnect-alert" class="alert alert-dismissible alert-warning fade hide" component="toaster/toast">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="[[global:buttons.close]]"></button>
<p class="mb-0">[[global:reconnecting-message, {config.siteTitle}]]</p>
</div>
</div>

View File

@@ -45,12 +45,12 @@
</div>
</div>
<div class="modal fade" id="create-modal">
<div class="modal fade" id="create-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">[[admin/manage/tags:create]]</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<form>

View File

@@ -1,9 +1,9 @@
<div class="modal fade" id="create-modal">
<div class="modal fade" id="create-modal" tabindex="-1" aria-label="[[admin/manage/groups:create]]">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">[[admin/manage/groups:create]]</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<form>

View File

@@ -1,8 +1,8 @@
<div id="chat-modal" class="chat-modal d-flex flex-nowrap modal hide overflow-visible" tabindex="-1" role="dialog" aria-labelledby="Chat" aria-hidden="true" data-center="false">
<div id="chat-modal" class="chat-modal d-flex flex-nowrap modal overflow-visible" tabindex="-1" role="dialog" aria-labelledby="chat-room-title-{roomId}" data-center="false">
<div class="modal-dialog">
<div class="modal-content" component="chat/message/window">
<div class="modal-header d-flex gap-4 justify-content-between">
<div class="fs-6 flex-grow-1 fw-semibold tracking-tight text-truncate text-nowrap" component="chat/room/name" data-icon="{icon}">{{{ if ./roomName }}}<i class="fa {icon} text-muted"></i> {roomName}{{{ else }}}{./chatWithMessage}{{{ end}}}</div>
<div id="chat-room-title-{roomId}" class="fs-6 flex-grow-1 fw-semibold tracking-tight text-truncate text-nowrap" component="chat/room/name" data-icon="{icon}">{{{ if ./roomName }}}<i class="fa {icon} text-muted"></i> {roomName}{{{ else }}}{./chatWithMessage}{{{ end}}}</div>
<div class="d-flex gap-1 align-items-center">
<button type="button" class="btn btn-ghost btn-sm d-none d-md-flex align-self-stretch align-items-center" data-action="maximize" title="[[modules:chat.maximize]]" data-bs-toggle="tooltip" data-bs-placement="bottom">
<i class="fa fa-fw fa-expand text-muted"></i>

View File

@@ -1,9 +1,9 @@
<div id="crop-picture-modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="crop-picture" aria-hidden="true">
<div id="crop-picture-modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="crop-picture" aria-labelledby="crop-picture">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 id="crop-picture">[[user:crop-picture]]</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<div id="upload-progress-box" class="progress hide">
@@ -30,7 +30,7 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" aria-hidden="true">Close</button>
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
<button class="btn btn-primary upload-btn {{{ if !allowSkippingCrop }}}hidden{{{ end }}}">[[user:upload-picture]]</button>
<button class="btn btn-primary crop-btn">[[user:upload-cropped-picture]]</button>
</div>

View File

@@ -1,9 +1,9 @@
<div class="modal" tabindex="-1" role="dialog" aria-labelledby="[[flags:modal-title]]" aria-hidden="true">
<div class="modal" tabindex="-1" role="dialog" aria-label="[[flags:modal-title]]">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">[[flags:modal-title]]</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<p class="lead">

View File

@@ -1,9 +1,9 @@
<div class="modal" tabindex="-1" role="dialog" aria-labelledby="upload-file" aria-hidden="true">
<div class="modal" tabindex="-1" role="dialog" aria-label="{title}">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<form class="mb-3" id="uploadForm" action="" method="post" enctype="multipart/form-data">
@@ -36,7 +36,7 @@
<div id="alert-error" class="alert alert-danger hide"></div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" aria-hidden="true">[[global:close]]</button>
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">[[global:close]]</button>
<button id="fileUploadSubmitBtn" class="btn btn-primary">{button}</button>
</div>
</div>

View File

@@ -1,15 +1,15 @@
<div id="upload-picture-from-url-modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="upload-picture-url" aria-hidden="true">
<div id="upload-picture-from-url-modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="upload-picture-url" aria-label="[[user:upload-picture]]">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="upload-picture-url">[[user:upload-picture]]</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="[[global:buttons.close]]"></button>
</div>
<div class="modal-body">
<input id="uploadFromUrl" class="form-control" type="text"/>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" aria-hidden="true">[[global:close]]</button>
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">[[global:close]]</button>
<button class="btn btn-primary upload-btn">[[user:upload-picture]]</button>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div id="reconnect-alert" class="alert alert-dismissible alert-warning fade hide" component="toaster/toast" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-hidden="true"></button>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="[[global:buttons.close]]"></button>
<p class="mb-0">[[global:reconnecting-message, {config.siteTitle}]]</p>
</div>

View File

@@ -1144,6 +1144,30 @@ describe('User', () => {
});
});
it('should normalize uploaded image to png', async () => {
const oldValue = meta.config['profile:convertProfileImageToPNG'];
meta.config['profile:convertProfileImageToPNG'] = 1;
const uid = await User.create({ username: 'pngnormalize', password: '123456' });
const { jar, csrf_token } = await helpers.loginUser('pngnormalize', '123456');
const pathToJpeg = path.join(__dirname, '../test/files/normalise.jpg');
const { response } = await helpers.uploadFile(
`${nconf.get('url')}/api/user/pngnormalize/uploadpicture`,
pathToJpeg, { }, jar, csrf_token
);
assert.strictEqual(response.statusCode, 200);
const picture = await db.getObjectField(`user:${uid}`, 'picture');
const uploadedPath = path.join(
nconf.get('upload_path'), `${picture.replace(nconf.get('upload_url'), '')}`
);
const sharp = require('sharp');
const metadata = await sharp(uploadedPath).metadata();
assert.strictEqual(metadata.format, 'png');
meta.config['profile:convertProfileImageToPNG'] = oldValue;
});
it('should not allow image data with bad MIME type to be passed in', (done) => {
User.uploadCroppedPicture({
callerUid: uid,