mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-07 01:27:01 +02:00
Merge commit '72f48fd9c47eaddfacd85d5ad963236190270278' into v4.x
This commit is contained in:
231
CHANGELOG.md
231
CHANGELOG.md
@@ -1,3 +1,234 @@
|
||||
#### v4.10.0 (2026-03-19)
|
||||
|
||||
##### Chores
|
||||
|
||||
* **deps:**
|
||||
* update dependency lint-staged to v16.4.0 (#14099) (bea46026)
|
||||
* update commitlint monorepo to v20.5.0 (#14098) (06c3b88b)
|
||||
* update dependency jsdom to v29 (#14100) (3825c755)
|
||||
* update commitlint monorepo to v20.4.4 (#14088) (02f8ea2c)
|
||||
* update dependency lint-staged to v16.3.3 (#14075) (ac45b719)
|
||||
* update dependency @stylistic/eslint-plugin to v5.10.0 (#14065) (9e2c6b67)
|
||||
* update docker/build-push-action action to v7 (#14066) (6d8c4493)
|
||||
* update docker/metadata-action action to v6 (#14067) (73b5bce5)
|
||||
* update docker/setup-buildx-action action to v4 (#14060) (d7de8cf6)
|
||||
* update docker/login-action action to v4 (#14054) (8c15096f)
|
||||
* update dependency lint-staged to v16.3.2 (#14048) (ddd6db0f)
|
||||
* update commitlint monorepo to v20.4.3 (#14047) (07881cbf)
|
||||
* update dependency lint-staged to v16.3.1 (#14029) (2e4ee9f1)
|
||||
* update dependency globals to v17.4.0 (#14035) (32864460)
|
||||
* update github artifact actions (#14027) (aec68c6b)
|
||||
* update postgres docker tag to v18.3 (#14023) (b69dbc38)
|
||||
* update dependency nyc to v18 (#14011) (dc1ce5e1)
|
||||
* up develop (4a01d55f)
|
||||
* up mentions (168b17e8)
|
||||
* up composer (73b023b4)
|
||||
* up harmony (ec4e87ff)
|
||||
* incrementing version number - v4.9.2 (e6846052)
|
||||
* update changelog for v4.9.2 (2c00b137)
|
||||
* incrementing version number - v4.9.1 (72e44c86)
|
||||
* incrementing version number - v4.9.0 (3fdd1bef)
|
||||
* incrementing version number - v4.8.1 (713ae0c0)
|
||||
* incrementing version number - v4.8.0 (3fac737a)
|
||||
* incrementing version number - v4.7.2 (cd419d8a)
|
||||
* incrementing version number - v4.7.1 (afb88805)
|
||||
* incrementing version number - v4.7.0 (e82d40f8)
|
||||
* incrementing version number - v4.6.3 (9fc5b0f3)
|
||||
* incrementing version number - v4.6.2 (f98747db)
|
||||
* incrementing version number - v4.6.1 (f47aa678)
|
||||
* incrementing version number - v4.6.0 (ee395bc5)
|
||||
* 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)
|
||||
* **i18n:**
|
||||
* fallback strings for new resources: nodebb.admin-settings-activitypub (aeb53043)
|
||||
* fallback strings for new resources: nodebb.world (e9063a63)
|
||||
* fallback strings for new resources: nodebb.world (61414fa4)
|
||||
* fallback strings for new resources: nodebb.world (702f7c62)
|
||||
* fallback strings for new resources: nodebb.admin-settings-general (e3ba38f2)
|
||||
* fallback strings for new resources: nodebb.admin-manage-users (a46f0136)
|
||||
* fallback strings for new resources: nodebb.admin-advanced-jobs (8e050353)
|
||||
* fallback strings for new resources: nodebb.admin-menu (5a8a1661)
|
||||
* fallback strings for new resources: nodebb.error (a17cd6c7)
|
||||
* fallback strings for new resources: nodebb.world (4d44e913)
|
||||
* fallback strings for new resources: nodebb.admin-dashboard (49a21a1f)
|
||||
* fallback strings for new resources: nodebb.admin-dashboard (858d84ff)
|
||||
|
||||
##### Documentation Changes
|
||||
|
||||
* wrong type for worldDefaultCid (895997b2)
|
||||
|
||||
##### New Features
|
||||
|
||||
* add /world as a potential home page route (58d3aa77)
|
||||
* add category selector to /world quick composer (2f5021e5)
|
||||
* ability to show only local posts in /world (44e65b8d)
|
||||
* #14094, notification drawer UX improvements (6c01a5d8)
|
||||
* allow 3 profile pics (#14092) (533ae69c)
|
||||
* screenshot upload in ACP, send fallback brand icons in manifest, serve assets for richer PWA install UI (75a6dfff)
|
||||
* category group actor outbox, #14083 (b317cdd3)
|
||||
* new ap mocks, now publishing user outboxes (f848393e)
|
||||
* show cronjobs in acp (#14068) (3c0a6540)
|
||||
* redirect cold requests to remote resources to their canonical source, #14043 (2b12f8b5)
|
||||
* include alt text in image/attachment property federating out (ca5aee10)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* improve idempotency of ap test (8ca34e74)
|
||||
* call syncfollowcounts on unfollow as well (ebe709da)
|
||||
* sync follow counts on local and remote follows, #14105 (44e78e47)
|
||||
* cold load redirect should only affect guests (cc606677)
|
||||
* schema fix for new api config value (7e2c7db3)
|
||||
* close notif drawer on item click, fix crash in module (7c65471b)
|
||||
* schema fix for new api config value (efaf8eb9)
|
||||
* issue where initial quickcreate post wouldn't go to the right cid (35c03e5c)
|
||||
* only show category selector on quickreply on /world (27b0fbe6)
|
||||
* delete cid:<cid>:privilegeMask on category.purge (902533db)
|
||||
* bump themes (3aa8d5ba)
|
||||
* removing topic tools/checkbox from /world for guests, reword guest CTA in /world (53286625)
|
||||
* add back 'after' query param handling in /world that was removed accidentally (67a93da5)
|
||||
* restrict contextmenu preventDefault to the checkbox only (2eb0964d)
|
||||
* long-press support for topicSelect, #14045 (d1e1a008)
|
||||
* debug log (58da9036)
|
||||
* restore guest access to /world, default to latest(all) (1aa5ca88)
|
||||
* bump harmony (10859455)
|
||||
* restored popular calculation behaviour that was broken by e2131d1d2e1c6f14cb8867ac7e22840da3f4c63f, removed followingOnly arg passing for popular (1af83564)
|
||||
* imagesLoaded integration for handleBack in world.js (38a1da46)
|
||||
* merged chat notifications if all the messages are from the same user (26bb60ef)
|
||||
* type (59dd22ca)
|
||||
* type (40fecd01)
|
||||
* merged chat notifications if all the messages are from the same user (6147a4d0)
|
||||
* screenshot fallback (09c54127)
|
||||
* buildRecipients to handle if local uids are passed in followers (a8f081c0)
|
||||
* update Like/Dislike to have addressees in activity (c8e349ca)
|
||||
* accidental hardcoded cid (464bc275)
|
||||
* skip AP cache on context processing methods (a3ee7447)
|
||||
* skip AP cache on context processing methods (10e4d579)
|
||||
* cache key (9eea12ec)
|
||||
* cache key (74fa77dd)
|
||||
* missing orderedItems on category outbox index retrieval (8496e1ef)
|
||||
* #14084, fix tags not getting properly removed from topics (0a94cecb)
|
||||
* group badge on group details page (52e8ede8)
|
||||
* return digest header only if it is set to something (aka not null) (1ad9ce5f)
|
||||
* missing page parseInt (9978af59)
|
||||
* dont add self username when clicking reply (9fcaad38)
|
||||
* notifs (ed3a3672)
|
||||
* also use tx.compile in chat notif merge (6d22e33a)
|
||||
* publish id with user outbox, fixes #13478 (c08a45a5)
|
||||
* merge with txArgs (e1d0e2a0)
|
||||
* merged notification translations (34b68109)
|
||||
* derped handleBack in world.js (ac483152)
|
||||
* syntax error on undefined value (6b3b3e7e)
|
||||
* filter out image attachments from remote data if they are already embedded in content (40b8544f)
|
||||
* update thumbs loading logic to always include post attachments as part of thumbs (prior: was controlled by thumbsOnly flag orshowPostUploadsAsThumbnail setting) (c2d190e1)
|
||||
* #14072, world to call thumbs with thumbsOnly filter (f1976168)
|
||||
* promises in groups.leave (f826e629)
|
||||
* #14071, duplicate items loaded via IS on /world (d29f1fbd)
|
||||
* #14043, cold-load redirect should only affect guests (5a7316b1)
|
||||
* hacking handleBack module to work with world page (971c8603)
|
||||
* bump web-push (27e12a28)
|
||||
* patch translateKey to wrap arguments in first string isolate (FSI) and Pop Directional Isolate (PDI) characters (59f19ba4)
|
||||
* add missing db call (4d1d1c86)
|
||||
* #14061, world.js show more buttons on infinite scroll (0aead782)
|
||||
* update clamp-fade to use mask-image, add background to btn-link on Brite skin (9bc1b400)
|
||||
* #14046, sneak in a mention to the community in mocked replies (b8ef027c)
|
||||
* world page 'see more' bugs (cc733631)
|
||||
* #13239, unescape custom user field values (9e69b9ad)
|
||||
* restore `preview` as it is now supported by BridyFed (8a371d23)
|
||||
* missing done (9604a0cd)
|
||||
* bump harmony, #14042 (b02cdaa9)
|
||||
* #14042, adopt plugin-feed's show-more/less logic/scss (43f2951a)
|
||||
* update minimum title length default to zero to allow title-less topics via composer (6bfe3cd0)
|
||||
* parent cid (f567d970)
|
||||
* skip parsing of duplicate emoji tags (363cad29)
|
||||
* **deps:**
|
||||
* update dependency cronstrue to v3.14.0 (#14107) (f51e1b2a)
|
||||
* update dependency nodemailer to v8.0.3 (#14104) (fa7c1a52)
|
||||
* update dependency esbuild to v0.27.4 (#14090) (c26bfddf)
|
||||
* update dependency lru-cache to v11.2.7 (#14096) (3765fb37)
|
||||
* update dependency tough-cookie to v6.0.1 (#14097) (d5f4a370)
|
||||
* update dependency nodebb-theme-peace to v2.2.57 (#14076) (cd08a5e4)
|
||||
* update dependency nodemailer to v8.0.2 (#14077) (add3c651)
|
||||
* update dependency satori to v0.25.0 (#14037) (817c38b9)
|
||||
* update dependency postcss to v8.5.8 (#14051) (942619db)
|
||||
* update dependency pg to v8.20.0 (#14058) (9cc2c2f9)
|
||||
* update dependency pg-cursor to v2.19.0 (#14059) (045a7073)
|
||||
* update dependency terser-webpack-plugin to v5.3.17 (#14052) (4410d884)
|
||||
* update dependency multer to v2.1.1 (#14050) (de22b7a9)
|
||||
* update dependency webpack to v5.105.4 (#14053) (3dc3b2e2)
|
||||
* update dependency fs-extra to v11.3.4 (#14049) (cfb6145e)
|
||||
* update dependency satori to v0.19.3 (#14036) (250911b7)
|
||||
* update dependency nodebb-plugin-emoji to v6.0.6 (#14034) (7434103c)
|
||||
* update dependency sitemap to v9.0.1 (#14028) (f97484c2)
|
||||
* update dependency webpack to v5.105.3 (#14022) (38787a2d)
|
||||
* update dependency multer to v2.1.0 (#14024) (9b65e316)
|
||||
* update dependency pg-cursor to v2.18.0 (#14026) (54810cfd)
|
||||
* update dependency pg to v8.19.0 (#14025) (badb57f2)
|
||||
* update dependency autoprefixer to v10.4.27 (#14021) (054b4aa6)
|
||||
|
||||
##### Other Changes
|
||||
|
||||
* remove unused (19bb37ca)
|
||||
* jobs.json (e1b6e617)
|
||||
* remove unused (d6d3116e)
|
||||
* remove unused (b50a10df)
|
||||
|
||||
##### Performance Improvements
|
||||
|
||||
* switch to set, remove parseFloat in redis (09de6fb9)
|
||||
* move out nconf.get and isClientScript regex (4d55ee0a)
|
||||
* move out nconf.get and isClientScript regex (f2bca332)
|
||||
* make a single round trip for set(s)Remove (bcbb7bc4)
|
||||
* cache groups:createtime (380d9895)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* /world sorting logic to always use topics/sorted logic (e2131d1d)
|
||||
* move to data (36bf3f16)
|
||||
* get rid of cleanupUids use missing set (6569ea51)
|
||||
* remove async.series, use batch.processSortedSet (894248e6)
|
||||
* get rid of helper function (1cc77343)
|
||||
* switch to cursor (1c7daf0d)
|
||||
* use set (d9344140)
|
||||
* pass in cid to rename/remove (fe4a22fb)
|
||||
* remove admin.themes.getInstalled (92d72f67)
|
||||
|
||||
##### Tests
|
||||
|
||||
* add missing selectedCategory to world.yaml (779a372f)
|
||||
* exclude uploadScreenshot from routeMap parsing test (ff1e1b92)
|
||||
* make tests happy (08bed89b)
|
||||
* fix test maybe (25f6088f)
|
||||
* add one more topic to tag test (e01cb104)
|
||||
* cleaner user.delete test (215d6440)
|
||||
* set minimumtitlelength for test (7429b5d4)
|
||||
* added test to ensure that Likes do not get processed when privilege is rescinded (8cba65cd)
|
||||
* break apart inbox handling tests to its own file in test/activitypub (06e0bd6a)
|
||||
* add debug test to see if failing test is due to race condition (e3119c76)
|
||||
* fix spec (6dd9f734)
|
||||
|
||||
#### v4.9.2 (2026-03-11)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
"watch.title": "Be notified of new replies in this topic",
|
||||
"unwatch.title": "Stop watching this topic",
|
||||
"share-this-post": "Share this Post",
|
||||
"share-mail-subject": "Check out this post on \"%1\"",
|
||||
"share-mail-body": "I thought you might be interested in this post: %1",
|
||||
"watching": "Watching",
|
||||
"not-watching": "Not Watching",
|
||||
"ignoring": "Ignoring",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ define('forum/header/notifications', function () {
|
||||
notifTrigger.on('show.bs.dropdown', async (ev) => {
|
||||
const notifications = await app.require('notifications');
|
||||
const triggerEl = $(ev.target);
|
||||
const dropdownEl = triggerEl.parent().find('.dropdown-menu');
|
||||
dropdownEl.find('[data-filter="all"]').addClass('active');
|
||||
dropdownEl.find('[data-filter="unread"]').removeClass('active');
|
||||
notifications.loadNotifications(triggerEl, triggerEl.parent().find('[component="notifications/list"]'));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
define('share', ['hooks'], function (hooks) {
|
||||
define('share', ['hooks', 'translator'], function (hooks, translator) {
|
||||
const share = {};
|
||||
const baseUrl = window.location.protocol + '//' + window.location.host;
|
||||
|
||||
@@ -17,7 +17,7 @@ define('share', ['hooks'], function (hooks) {
|
||||
|
||||
$('#content').off('shown.bs.dropdown', '.share-dropdown').on('shown.bs.dropdown', '.share-dropdown', function () {
|
||||
const postLink = $(this).find('.post-link');
|
||||
postLink.val(baseUrl + getPostUrl($(this)));
|
||||
postLink.val(getPostUrl($(this)));
|
||||
|
||||
// without the setTimeout can't select the text in the input
|
||||
setTimeout(function () {
|
||||
@@ -69,6 +69,16 @@ define('share', ['hooks'], function (hooks) {
|
||||
return openShare(mastodon_url, postUrl, 626, 760);
|
||||
});
|
||||
|
||||
addHandler('[component="share/email"]', async function () {
|
||||
const postUrl = getPostUrl($(this));
|
||||
const [subject, body] = await translator.translateKeys([
|
||||
translator.compile('topic:share-mail-subject', config.siteTitle),
|
||||
translator.compile('topic:share-mail-body', postUrl),
|
||||
]);
|
||||
const mailtoUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||
window.location.href = mailtoUrl;
|
||||
});
|
||||
|
||||
hooks.fire('action:share.addHandlers', { openShare: openShare });
|
||||
};
|
||||
|
||||
@@ -77,9 +87,10 @@ define('share', ['hooks'], function (hooks) {
|
||||
}
|
||||
|
||||
function getPostUrl(clickedElement) {
|
||||
const pid = parseInt(clickedElement.parents('[data-pid]').attr('data-pid'), 10);
|
||||
const path = '/post' + (pid ? '/' + (pid) : '');
|
||||
return baseUrl + config.relative_path + path;
|
||||
const pid = clickedElement.parents('[data-pid]').attr('data-pid');
|
||||
return pid ?
|
||||
`${baseUrl + config.relative_path}/post/${pid}` :
|
||||
window.location.href;
|
||||
}
|
||||
|
||||
return share;
|
||||
|
||||
@@ -409,21 +409,20 @@ authenticationController.localLogin = async function (req, username, password, n
|
||||
|
||||
userData.isAdminOrGlobalMod = isAdminOrGlobalMod;
|
||||
|
||||
if (!canLoginIfBanned) {
|
||||
return next(await getBanError(uid));
|
||||
}
|
||||
|
||||
// Doing this after the ban check, because user's privileges might change after a ban expires
|
||||
const hasLoginPrivilege = await privileges.global.can('local:login', uid);
|
||||
if (parseInt(uid, 10) && !hasLoginPrivilege) {
|
||||
return next(new Error('[[error:local-login-disabled]]'));
|
||||
}
|
||||
|
||||
try {
|
||||
const passwordMatch = await user.isPasswordCorrect(uid, password, req.ip);
|
||||
if (!passwordMatch) {
|
||||
return next(new Error('[[error:invalid-login-credentials]]'));
|
||||
}
|
||||
if (!canLoginIfBanned) {
|
||||
return next(await getBanError(uid));
|
||||
}
|
||||
|
||||
// Doing this after the ban check, because user's privileges might change after a ban expires
|
||||
const hasLoginPrivilege = await privileges.global.can('local:login', uid);
|
||||
if (parseInt(uid, 10) && !hasLoginPrivilege) {
|
||||
return next(new Error('[[error:local-login-disabled]]'));
|
||||
}
|
||||
} catch (e) {
|
||||
if (req.loggedIn) {
|
||||
await logoutAsync(req);
|
||||
|
||||
@@ -45,6 +45,11 @@ social.getPostSharing = async function () {
|
||||
name: 'Mastodon',
|
||||
class: 'fa-brands fa-mastodon',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
name: 'Email',
|
||||
class: 'fa-regular fa-envelope',
|
||||
},
|
||||
];
|
||||
networks = await plugins.hooks.fire('filter:social.posts', networks);
|
||||
networks.forEach((network) => {
|
||||
|
||||
@@ -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}`,
|
||||
];
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
35
test/user.js
35
test/user.js
@@ -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,
|
||||
@@ -1394,6 +1418,17 @@ describe('User', () => {
|
||||
assert.strictEqual(await db.isSortedSetMember('users:banned', testUid), false);
|
||||
});
|
||||
|
||||
it('should not return ban reason if login is incorrect', async () => {
|
||||
const testUid = await User.create({ username: 'bannedUser4', password: '654321' });
|
||||
await User.bans.ban(testUid, 0, 'testing bans');
|
||||
let { response, body } = await helpers.loginUser('bannedUser4', '5555555');
|
||||
assert.strictEqual(response.status, 403);
|
||||
assert.strictEqual(body, '[[error:invalid-login-credentials]]');
|
||||
|
||||
({ response, body } = await helpers.loginUser('bannedUser4', '654321'));
|
||||
assert.strictEqual(response.status, 403);
|
||||
assert.strictEqual(body.reason, 'testing bans');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Digest.getSubscribers', () => {
|
||||
|
||||
Reference in New Issue
Block a user