From 8d6a4ed8757b942b7f0d53f0c7394e6f29259a2d Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 14 Jan 2026 17:54:33 +0000 Subject: [PATCH 01/14] chore: incrementing version number - v4.8.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 30e9433b4e..040fac1dee 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.7.2", + "version": "4.8.0", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -203,4 +203,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From a9fbcf2aeb66d15242b67e733553b5ced749943c Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 14 Jan 2026 17:54:34 +0000 Subject: [PATCH 02/14] chore: update changelog for v4.8.0 --- CHANGELOG.md | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac75051f0..8f43cfc6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,213 @@ +#### v4.8.0 (2026-01-14) + +##### Chores + +* **deps:** + * update dependency @stylistic/eslint-plugin to v5.7.0 (#13879) (be0d43cf) + * update commitlint monorepo to v20.3.1 (#13876) (c88ce519) + * update dependency sass-embedded to v1.97.2 (#13870) (27d511ff) + * update commitlint monorepo to v20.3.0 (#13865) (447cfd03) + * update dependency smtp-server to v3.18.0 (#13858) (f35c77dd) + * update dependency jsdom to v27.4.0 (#13860) (37c052f4) + * update dependency sass-embedded to v1.97.1 (#13850) (d28866ab) + * update dependency sass-embedded to v1.97.0 (#13837) (168b6e63) + * update dependency smtp-server to v3.17.1 (#13829) (ad895efb) + * update dependency @eslint/js to v9.39.2 (#13830) (22fe83f0) + * update github artifact actions (#13831) (b1696218) + * update actions/cache action to v5 (#13828) (0fcc8543) + * update dependency smtp-server to v3.17.0 (#13824) (3adcbe0f) + * update dependency sass-embedded to v1.96.0 (#13821) (b992511b) + * update dependency sass-embedded to v1.95.1 (#13817) (a2f2c8c7) + * update dependency jsdom to v27.3.0 (#13814) (a35c326a) + * update commitlint monorepo to v20.2.0 (#13810) (e50edd52) + * update dependency lint-staged to v16.2.7 (#13785) (76b6b3b2) + * update actions/checkout action to v6 (#13802) (7f21a171) +* bump profile max upload size default (bed6ed3c) +* up themes (b323b5d8) +* up markdown (eb77c9bf) +* up mentions (648d9c78) +* incrementing version number - v4.7.2 (cd419d8a) +* update changelog for v4.7.2 (2f0526b8) +* incrementing version number - v4.7.1 (afb88805) +* allow direct testing in test/categories.js (29687722) +* 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) + +##### Documentation Changes + +* update openapi schema for missing routes related to crossposting (d81b644d) + +##### New Features + +* user crossposts federate as:Announce (273bc68c) +* add missing files, minor changes to crossposts list modal (38fd1798) +* introduce new front-end UI button for cross-posting, hide move on topics in remote cids (0041cfe2) +* disallow moving topics to and from remote categories, + basic tests for topic moving (ea1e4c7d) +* API v3 calls to crosspost and uncrosspost a topic to and from a category (74172ecc) +* refactor out.announce.topic to allow user announces, refactor tests to accommodate (874ffd7b) +* stop extraneous vote and tids_read data from being saved for remote users (097d0802) +* support remote Dislike activity, federate out a Dislike on downvote, bwahahah (528cd258) +* expand postingRestrictedToMods mask testing, handle actor update for that prop (6a561050) +* setAddBulk (#13805) (7d5402fe) +* save privilege masking set when asserting group (f0a7a442) +* patch low-level privilege query calls to accept privilege masks at the cid level (4020e1be) +* federate out topic removal activities when topic is deleted and purged from a local category (3ab61615) + +##### Bug Fixes + +* i18n fallbacks (a73ab8ee) +* #13889, custom emoji from Piefed (0c75934a) +* #13888, decode html entities for AP category name and description (6eea4df5) +* derp (bcc204fa) +* bump themes (a4c470ff) +* guard against negative uids crossposting (2f96eed4) +* bump themes (943b53b0) +* calling sortedSetRemove to remove multiple values, instead of baking it into sortedSetRemoveBulk (82507c0f) +* unused values (b9b33f9f) +* typo, client-side handling of crossposts as pertains to uncategorized topics (7465762d) +* client-side handling of category selector when cross-posting so only local cids are sent to backend (ea417b06) +* update category sync logic to utilise crossposts instead (e5ee52e5) +* remove old remote user to remote category migration logic + tests (28249efb) +* update auto-categorization rules to also handle already-categorized topics via crosspost (148663c5) +* topic crosspost delete and purge handling (f6cc556d) +* bug where privileges users could not uncrosspost others' crossposts. Tests (0a0a7da9) +* allow non-mods to crosspost, move crosspost button out of topic tools, in-modal state updates (6daaad81) +* removed ajaxify refresh on crosspost commit, dynamically update post stats in template, logic fix (b981082d) +* nodeinfo route to publish federation.enabled in metadata section (14aa2bee) +* bump link-preview again (74e47820) +* bump link-preview (486e77c7) +* remove commented out require (ffc3d279) +* bump link-preview (cc1649e0) +* auto-enable post queue as default, adjust tests to compensate (9390ccb6) +* remove bidiControls from notification.bodyShort (b0679cad) +* author of boosted content was not targeted in the activity (b05199d8) +* closes #13872, use translator.compile for notification text (5a031d01) +* #13715, dont reduce hardcap if usersPerPage is < 50 (cb31e70e) +* dont use sass-embedded on freebsd, #13867 (b7de0cc7) +* wrong increment value (20918b52) +* increment progress on upgrade script (8abe0dfa) +* add join-lemmy context for outgoing category group actors context prop (f1d50c35) +* use setsAdd (d8e55d58) +* missing await (4a6dcf1a) +* admin privilege overrides only apply to local categories (7b194c69) +* have notes.assert call out.announce.topic only if uid is set (so, if note assertion is called via search; manual pull) (3b7bcba6) +* deep clone activity prop before execution; feps.announce (977a67f4) +* minor comment fix (411baa21) +* publish `postingRestrictedToMods` property in group actor (c365c1dc) +* **deps:** + * update dependency spdx-license-list to v6.11.0 (#13890) (9b1c32b1) + * update dependency diff to v8.0.3 (#13882) (974ab1f8) + * update dependency nodebb-theme-persona to v14.1.23 (#13878) (47074b3c) + * update dependency nodebb-theme-harmony to v2.1.31 (#13877) (125c8e58) + * update dependency body-parser to v2.2.2 (#13873) (e717f00e) + * update dependency sass to v1.97.2 (#13871) (5100cc4f) + * update dependency nodebb-plugin-markdown to v13.2.3 (#13869) (a8c18f8a) + * update dependency nodebb-theme-harmony to v2.1.30 (#13863) (49379e2e) + * update dependency nodebb-theme-persona to v14.1.22 (#13864) (e4435e52) + * update dependency @isaacs/ttlcache to v2.1.4 (#13861) (89abdca1) + * update socket.io packages to v4.8.3 (#13857) (6807f860) + * update dependency sass to v1.97.1 (#13856) (7325b995) + * update dependency nodebb-theme-persona to v14.1.20 (#13855) (b8f68fb4) + * update dependency nodebb-theme-harmony to v2.1.28 (#13854) (f98fd6dc) + * update dependency fs-extra to v11.3.3 (#13851) (160ce17f) + * update dependency nodemailer to v7.0.12 (#13853) (f6ef041c) + * update dependency nodebb-plugin-2factor to v7.6.1 (#13852) (abcb2382) + * update dependency validator to v13.15.26 (#13846) (2a10f904) + * update dependency nodebb-theme-persona to v14.1.19 (#13849) (b933d1a2) + * update dependency nodebb-theme-harmony to v2.1.27 (#13848) (61d8cba9) + * update dependency webpack to v5.104.1 (#13847) (bb5a90a3) + * update dependency esbuild to v0.27.2 (#13842) (5844e393) + * update dependency nodebb-plugin-mentions to v4.8.4 (#13845) (2ffa4383) + * update dependency webpack to v5.104.0 (#13839) (f16eec30) + * update dependency sass to v1.97.0 (#13838) (ab8dbb41) + * update dependency fetch-cookie to v3.2.0 (#13836) (0ef5cbbb) + * update dependency autoprefixer to v10.4.23 (#13835) (7c2e8330) + * update dependency terser-webpack-plugin to v5.3.16 (#13827) (da7c9b32) + * update dependency sass to v1.96.0 (#13822) (d4f53a62) + * update dependency winston to v3.19.0 (#13812) (81c232f1) + * update dependency cron to v4.4.0 (#13818) (f077c4ca) + * update dependency sass to v1.95.1 (#13816) (adedb7b6) + * update dependency sass to v1.95.0 (#13815) (eaa6e71a) + * update dependency terser-webpack-plugin to v5.3.15 (#13811) (10d2e929) + * update dependency esbuild to v0.27.1 (#13806) (6b1dcb4b) + * update dependency jsonwebtoken to v9.0.3 (#13807) (7b734cfd) + * update dependency ace-builds to v1.43.5 (#13797) (93057306) + * update dependency lru-cache to v11.2.4 (#13798) (731933a6) + * update dependency express to v4.22.1 (#13800) (38321220) + * update dependency ipaddr.js to v2.3.0 (#13801) (ad5cd27b) + * update dependency nodemailer to v7.0.11 (#13799) (ecec1f45) + * update dependency cron to v4.3.5 (#13796) (5ba6bea0) + * update dependency body-parser to v2.2.1 (#13795) (624ef616) + * update dependency @isaacs/ttlcache to v2.1.3 (#13791) (5f55ca85) + * update dependency sass to v1.94.2 (#13786) (1cb8b381) + * update dependency redis to v5.10.0 (#13787) (1bcfe3f0) + +##### Other Changes + +* fix... tests (d20906b5) +* still broken... more debug logs (a82e1f44) +* log mock results (8236b594) + +##### Refactors + +* check if tid is truthy (0e1ccfc9) +* crossposts.get to return limited category data (name, icon, etc.), fixed up crosspost modal to hide uncategorized and all categories options (349b0875) +* move crosspost methods into their own file in src/topics (1be88ca0) +* silence if-function deprecation on prod (403230cc) +* clear quick reply as soon as submitting (a331f8da) + +##### Tests + +* intify uid/cid if they are numbers (when getting crossposts) (47e37ed5) +* stop using partialDeepStrictEqual for now (0677689a) +* ensure auto-cat and cat sync logic properly integrates with crossposts (add163a4) +* crossposting behaviour and logic tests (947676ef) +* new test file for crossposts (3560b6a3) +* additional logic to allow multi-typing in schema type (4f1fa2d1) +* lowercase tags (81cac015) +* fix test to check for Secure in cookie string if test runner domain is https (5954015e) +* more out.announce tests (cfdbbb04) +* basic tests for activitypub.out (67912dc9) +* update activitypub._sent to save targets as well, updated tests to accommodate format change (41368ef8) +* test runs should not actually federate activities out (483ab083) +* check if tests pass without await (5414cf47) +* add back logs for failing test (301b5386) +* add a test for set db.exists (#13809) (69562704) +* fix failing test by adjusting the tests (c5292442) +* privilege masking tests (934e6be9) +* log label (22d3c523) +* log activities (e39c9149) +* on test fail show activities (841bd825) +* new mongodb deps (#13793) (287b2569) + #### v4.7.2 (2025-12-24) ##### Chores From 98c0a3fedcfbc05d35d5687e4408f357f424c64b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 14 Jan 2026 14:18:02 -0500 Subject: [PATCH 03/14] fix: #13892, logical flaw --- src/activitypub/out.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activitypub/out.js b/src/activitypub/out.js index 4e11c243ef..701464ba8c 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -310,7 +310,7 @@ Out.announce.topic = enabledCheck(async (tid, uid) => { if (uid) { const exists = await user.exists(uid); - if (!exists || !utils.isNumber(cid)) { + if (!exists) { return; } } else { From c494d002ba24757061d75ef133edad0b32a4388b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 14 Jan 2026 15:01:33 -0500 Subject: [PATCH 04/14] fix: consider crossposts when building teasers, fixes #13891 --- src/categories/recentreplies.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index c162d438b8..d5ac911d89 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -96,10 +96,14 @@ module.exports = function (Categories) { }; async function getTopics(tids, uid) { - const topicData = await topics.getTopicsFields( - tids, - ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'] - ); + const [topicData, crossposts] = await Promise.all([ + topics.getTopicsFields( + tids, + ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'] + ), + Promise.all(tids.map(async tid => topics.crossposts.get(tid))), + ]); + topicData.forEach((topic) => { if (topic) { topic.teaserPid = topic.teaserPid || topic.mainPid; @@ -124,6 +128,7 @@ module.exports = function (Categories) { slug: topicData[index].slug, title: topicData[index].title, }; + teaser.crossposts = crossposts[index]; } }); return teasers.filter(Boolean); @@ -132,10 +137,12 @@ module.exports = function (Categories) { function assignTopicsToCategories(categories, topics) { categories.forEach((category) => { if (category) { - category.posts = topics.filter( - t => t.cid && - (t.cid === category.cid || (t.parentCids && t.parentCids.includes(category.cid))) - ) + category.posts = topics.filter(t => + t.cid && + (t.cid === category.cid || + (t.parentCids && t.parentCids.includes(category.cid)) || + (t.crossposts.some(({ cid }) => parseInt(cid, 10) === category.cid)) + )) .sort((a, b) => b.timestamp - a.timestamp) .slice(0, parseInt(category.numRecentReplies, 10)); } From be5b36bcd19b59099a2d372b9708345a5afdf4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 14 Jan 2026 18:14:49 -0500 Subject: [PATCH 05/14] test: dont return cross posts --- src/categories/recentreplies.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index d5ac911d89..e1be6a9a6d 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -147,7 +147,10 @@ module.exports = function (Categories) { .slice(0, parseInt(category.numRecentReplies, 10)); } }); - topics.forEach((t) => { t.parentCids = undefined; }); + topics.forEach((t) => { + t.parentCids = undefined; + t.crossposts = undefined; + }); } function bubbleUpChildrenPosts(categoryData) { From 57a73c4854106913a258fceed8508f1da9e5aa7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 14 Jan 2026 18:47:52 -0500 Subject: [PATCH 06/14] refactor: crossposts.get to support multiple tids --- src/categories/recentreplies.js | 2 +- src/topics/crossposts.js | 46 ++++++++++++++++++++------------- test/topics/crossposts.js | 12 ++++----- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index e1be6a9a6d..1021181e22 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -101,7 +101,7 @@ module.exports = function (Categories) { tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'] ), - Promise.all(tids.map(async tid => topics.crossposts.get(tid))), + topics.crossposts.get(tids), ]); topicData.forEach((topic) => { diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index b4ae973ad2..e677e11481 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const db = require('../database'); const topics = require('.'); const user = require('../user'); @@ -10,30 +11,39 @@ const utils = require('../utils'); const Crossposts = module.exports; -Crossposts.get = async function (tid) { - const crosspostIds = await db.getSortedSetMembers(`tid:${tid}:crossposts`); - let crossposts = await db.getObjects(crosspostIds.map(id => `crosspost:${id}`)); - const cids = crossposts.reduce((cids, crossposts) => { - cids.add(crossposts.cid); - return cids; - }, new Set()); - let categoriesData = await categories.getCategoriesFields( - Array.from(cids), ['cid', 'name', 'icon', 'bgColor', 'color', 'slug'] +Crossposts.get = async function (tids) { + const isArray = Array.isArray(tids); + if (!isArray) { + tids = [tids]; + } + + const crosspostIds = await db.getSortedSetsMembers(tids.map(tid => `tid:${tid}:crossposts`)); + const allCrosspostIds = crosspostIds.flat(); + const allCrossposts = await db.getObjects(allCrosspostIds.map(id => `crosspost:${id}`)); + + const categoriesData = await categories.getCategoriesFields( + _.uniq(allCrossposts.map(c => c.cid)), ['cid', 'name', 'icon', 'bgColor', 'color', 'slug'] ); - categoriesData = categoriesData.reduce((map, category) => { + + const categoriesMap = categoriesData.reduce((map, category) => { map.set(parseInt(category.cid, 10), category); return map; }, new Map()); - crossposts = crossposts.map((crosspost, idx) => { - crosspost.id = crosspostIds[idx]; - crosspost.category = categoriesData.get(parseInt(crosspost.cid, 10)); - crosspost.uid = utils.isNumber(crosspost.uid) ? parseInt(crosspost.uid) : crosspost.uid; - crosspost.cid = utils.isNumber(crosspost.cid) ? parseInt(crosspost.cid) : crosspost.cid; - return crosspost; - }); + const crosspostMap = allCrossposts.reduce((map, crosspost, index) => { + const id = allCrosspostIds[index]; + if (id && crosspost) { + map.set(id, crosspost); + crosspost.id = id; + crosspost.category = categoriesMap.get(parseInt(crosspost.cid, 10)); + crosspost.uid = utils.isNumber(crosspost.uid) ? parseInt(crosspost.uid, 10) : crosspost.uid; + crosspost.cid = utils.isNumber(crosspost.cid) ? parseInt(crosspost.cid, 10) : crosspost.cid; + } + return map; + }, new Map()); - return crossposts; + const crossposts = crosspostIds.map(ids => ids.map(id => crosspostMap.get(id))); + return isArray ? crossposts : crossposts[0]; }; Crossposts.add = async function (tid, cid, uid) { diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index ec6fe66aa5..cac82f9d0b 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -159,9 +159,9 @@ describe('Crossposting (& related logic)', () => { it('should not let another user uncrosspost', async () => { const uid2 = await user.create({ username: utils.generateUUID().slice(0, 8) }); - assert.rejects( + await assert.rejects( topics.crossposts.remove(tid, cid2, uid2), - '[[error:invalid-data]]', + { message: '[[error:invalid-data]]' }, ); }); @@ -184,9 +184,9 @@ describe('Crossposting (& related logic)', () => { }); it('should throw on uncrossposting if already uncrossposted', async () => { - assert.rejects( + await assert.rejects( topics.crossposts.remove(tid, cid2, uid), - '[[error:invalid-data]]', + { message: '[[error:invalid-data]]' }, ); }); }); @@ -286,9 +286,9 @@ describe('Crossposting (& related logic)', () => { it('should fail to uncrosspost if not mod of passed-in category', async () => { await privileges.categories.give(['moderate'], cid1, [privUid]); - assert.rejects( + await assert.rejects( topics.crossposts.remove(tid, cid2, privUid), - '[[error:invalid-data]]', + { message: '[[error:invalid-data]]' }, ); }); From 317be96fb1c9fd98b3077523dec93f64249204df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 15 Jan 2026 15:18:20 -0500 Subject: [PATCH 07/14] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 040fac1dee..e39de0dd1b 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.6", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.33", + "nodebb-theme-harmony": "2.1.34", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.25", From c1a92c472317bf87814ab59b1d8d5a0ecb972e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 15 Jan 2026 15:33:07 -0500 Subject: [PATCH 08/14] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index e39de0dd1b..d9cb07647f 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.6", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.34", + "nodebb-theme-harmony": "2.1.35", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.25", From 469a8ef9b4c99d9136c621b96c2c7a1835e551a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 15 Jan 2026 15:46:12 -0500 Subject: [PATCH 09/14] chore: up dbsearch --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d9cb07647f..3248025468 100644 --- a/install/package.json +++ b/install/package.json @@ -98,7 +98,7 @@ "nconf": "0.13.0", "nodebb-plugin-2factor": "7.6.1", "nodebb-plugin-composer-default": "10.3.1", - "nodebb-plugin-dbsearch": "6.3.4", + "nodebb-plugin-dbsearch": "6.3.5", "nodebb-plugin-emoji": "6.0.5", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-link-preview": "2.2.1", From 639ea42d5a35913688582b411310aabdc60fe801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 15 Jan 2026 16:47:28 -0500 Subject: [PATCH 10/14] refactor: put alltime in query string for term --- src/controllers/helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index a6ade8c73b..97ff1a3129 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -40,6 +40,7 @@ helpers.noScriptErrors = async function (req, res, error, httpStatus) { }; helpers.terms = { + alltime: 'alltime', daily: 'day', weekly: 'week', monthly: 'month', @@ -101,7 +102,7 @@ helpers.buildFilters = function (url, filter, query) { helpers.buildTerms = function (url, term, query) { return [{ name: '[[recent:alltime]]', - url: url + helpers.buildQueryString(query, 'term', ''), + url: url + helpers.buildQueryString(query, 'term', 'alltime'), selected: term === 'alltime', term: 'alltime', }, { From 7d36c75790d4eb08540d5fef0ec2a35ece00ca8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 16 Jan 2026 10:58:03 -0500 Subject: [PATCH 11/14] fix: closes #13897, display group create errors properly fix typo in redirect after group deletion --- public/src/admin/manage/group.js | 2 +- public/src/admin/manage/groups.js | 9 +-------- public/src/client/groups/list.js | 13 ++++++++----- src/views/admin/partials/create_group_modal.tpl | 1 - 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 1e91b17c0b..c856f0b487 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -88,7 +88,7 @@ define('admin/manage/group', [ bootbox.confirm('[[admin/manage/groups:alerts.confirm-delete]]', function (confirm) { if (confirm) { api.del(`/groups/${slugify(ajaxify.data.group.name)}`, {}).then(() => { - ajaxify.go('/admin/managegroups'); + ajaxify.go('/admin/manage/groups'); }).catch(alerts.error); } }); diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index ba3d11f258..a12881af26 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -40,7 +40,6 @@ define('admin/manage/groups', [ const createModal = $('#create-modal'); const createGroupName = $('#create-group-name'); const createModalGo = $('#create-modal-go'); - const createModalError = $('#create-modal-error'); createGroupName.trigger('focus'); createModal.on('keypress', function (e) { @@ -61,18 +60,12 @@ define('admin/manage/groups', [ }; api.post('/groups', submitObj).then((response) => { - createModalError.addClass('hide'); createGroupName.val(''); createModal.on('hidden.bs.modal', function () { ajaxify.go('admin/manage/groups/' + response.name); }); createModal.modal('hide'); - }).catch((err) => { - if (!utils.hasLanguageKey(err.status.message)) { - err.status.message = '[[admin/manage/groups:alerts.create-failure]]'; - } - createModalError.translateHtml(err.status.message).removeClass('hide'); - }); + }).catch(alerts.error); }); }); }); diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index bd13996044..946de7faac 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -8,13 +8,16 @@ define('forum/groups/list', [ Groups.init = function () { // Group creation $('button[data-action="new"]').on('click', function () { - bootbox.prompt('[[groups:new-group.group-name]]', function (name) { - if (name && name.length) { - api.post('/groups', { - name: name, - }).then((res) => { + const modal = bootbox.prompt('[[groups:new-group.group-name]]', function (name) { + if (name === '') { + return false; + } + if (name && name.trim().length) { + api.post('/groups', { name }).then((res) => { + modal.modal('hide'); ajaxify.go('groups/' + res.slug); }).catch(alerts.error); + return false; } }); }); diff --git a/src/views/admin/partials/create_group_modal.tpl b/src/views/admin/partials/create_group_modal.tpl index 49ee2678fd..51554b6064 100644 --- a/src/views/admin/partials/create_group_modal.tpl +++ b/src/views/admin/partials/create_group_modal.tpl @@ -6,7 +6,6 @@