From 3d8638ed5cbb13642535a76f7956edbe58c9ba4e Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 26 Nov 2025 16:59:42 +0000 Subject: [PATCH 01/21] chore: incrementing version number - v4.7.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 36c08a9e2e..50f27f95a2 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.6.3", + "version": "4.7.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 1c0a43dc556464fb692db2c317f7ff8b0e01cc2f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 26 Nov 2025 16:59:43 +0000 Subject: [PATCH 02/21] chore: update changelog for v4.7.0 --- CHANGELOG.md | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47192ee9f1..e7364985fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,188 @@ +#### v4.7.0 (2025-11-26) + +##### Chores + +* incrementing version number - v4.6.3 (9fc5b0f3) +* update changelog for v4.6.3 (3fd193e3) +* incrementing version number - v4.6.2 (f98747db) +* up dbsearch (dfe53d29) +* up harmony, closes #13753 (4e33c1df) +* up express-useragent (b5ea2089) +* up ttlcache to 2.x (a0a10c8b) +* up themes (52c56bc5) +* 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) +* **deps:** + * update dependency @stylistic/eslint-plugin to v5.6.1 (#13778) (894f1988) + * update redis docker tag to v8.4.0 (#13782) (e24d8c17) + * update postgres docker tag to v18.1 (#13771) (3ea029bd) + * update dependency jsdom to v27.2.0 (#13770) (899414f4) + * update dependency smtp-server to v3.16.1 (#13755) (bc64d27f) + * update dependency mocha to v11.7.5 (#13754) (e1bf80dc) + * update redis docker tag to v8.2.3 (#13750) (4c5f7f60) + * update github artifact actions (#13730) (13c23fdd) + * update dependency @eslint/js to v9.39.1 (#13747) (4e7867a9) + * update dependency sass-embedded to v1.93.3 (#13745) (cb96701b) + * update dependency jsdom to v27.1.0 (#13743) (4ce4e773) + * update mongo docker tag to v8.2 (#13738) (97e5aa1d) + * update dependency smtp-server to v3.16.0 (#13737) (07d169d2) + * update dependency lint-staged to v16.2.6 (#13725) (e3c55f76) + * update dependency lint-staged to v16.2.5 (#13721) (83a172c9) + * update dependency @stylistic/eslint-plugin to v5.5.0 (#13717) (93d46c84) + * update dependency jsdom to v27.0.1 (#13718) (9d2b83f5) + * update dependency @eslint/js to v9.38.0 (#13716) (7fd9e894) + * update actions/setup-node action to v6 (#13708) (febe0ae0) + * update dependency smtp-server to v3.15.0 (#13702) (238600a0) + * update dependency lint-staged to v16.2.4 (#13699) (f608c7c7) + * update postgres docker tag to v18 (#13679) (923ddbc1) + * update dependency @eslint/js to v9.37.0 (#13693) (d73892ae) + * update redis docker tag to v8.2.2 (#13692) (4640a63e) + * update dependency mocha to v11.7.4 (#13685) (c7696667) + * update dependency @commitlint/cli to v20.1.0 (#13686) (eb06bda8) +* **i18n:** + * fallback strings for new resources: nodebb.admin-manage-categories (49567c72) + * fallback strings for new resources: nodebb.admin-settings-uploads (e7498e8f) + +##### New Features + +* federate out undo(announce) when moving topics (832477f8) +* native image appending for remote private notes (822f4edc) +* add isNumber to client-side helpers (172aabcb) +* handle Move(Context) activity (8ca52c7e) +* update Remove(Context) to use target instead of origin, federate out Move(Context) on topic move between local cids (d02e188a) +* context removal logic (aka moving topics to uncategorized, and federating this to other NodeBBs) (34e95e6d) +* add new setting to control posts uploads being shown as thumbs (97e59fbe) +* handle Delete(Context) as a move to cid -1 if the remote context still exists (f98a7216) +* handle incoming Announce(Delete), closes #13712 (4d5005b9) +* execute 1b12 rebroadcast logic on all tids even if not posted to a local cid (9583f0d4) +* auto-enable link-preview plugin on new installations (b153941c) +* bundle link-preview plugin (e7bdf6bc) +* federate topic deletion on topic deletion as well as purge (4d24309a) +* federate Delete on post delete as well as purge, topic deletion federates Announce(Delete(Object)) (93b6cb59) + +##### Bug Fixes + +* **deps:** + * bump mentions to fix #13637 (e3ac9ccf) + * update dependency rimraf to v6.1.2 (#13784) (5ab8f877) + * update dependency @isaacs/ttlcache to v2.1.2 (#13780) (cecc0fee) + * update dependency workerpool to v10.0.1 (#13781) (bfffb4b9) + * update dependency webpack to v5.103.0 (#13783) (5acfd184) + * update dependency sass to v1.94.1 (#13777) (b0c9bb1e) + * update dependency mongodb to v6.21.0 (#13772) (111ae163) + * update dependency sass to v1.94.0 (#13773) (c95bfcbf) + * update dependency validator to v13.15.23 (#13769) (93c69f9d) + * update dependency express-useragent to v2.0.2 (#13767) (e14d3ac1) + * update dependency autoprefixer to v10.4.22 (#13768) (9271e267) + * update dependency @isaacs/ttlcache to v2.1.1 (#13763) (f24bb090) + * update dependency esbuild to v0.27.0 (#13766) (63789ebb) + * update dependency cron to v4.3.4 (#13762) (6ad93cd3) + * update dependency sharp to v0.34.5 (#13758) (5be0a630) + * update dependency bcryptjs to v3.0.3 (#13751) (a34284df) + * update dependency sitemap to v9 (#13752) (1921ccaa) + * update dependency esbuild to v0.25.12 (#13748) (090eb088) + * update dependency rimraf to v6.1.0 (#13744) (a36d89fc) + * update dependency sass to v1.93.3 (#13746) (ba123073) + * update dependency sitemap to v8.0.2 (#13736) (b5c1e8e7) + * update mentions (5c3b1261) + * update dependency validator to v13.15.20 (#13733) (6f448ce2) + * bump mentions to 4.8.0 (964a5388) + * update dependency commander to v14.0.2 (#13731) (a49efe49) + * update dependency redis to v5.9.0 (#13727) (418717fd) + * update dependency nodemailer to v7.0.10 (#13726) (c1f6e52b) + * update dependency workerpool to v10 (#13723) (5a6c2097) + * update dependency sitemap to v8.0.1 (#13720) (1d9d7fc5) + * update dependency ace-builds to v1.43.4 (#13714) (27a0dc73) + * bump dbsearch (c25c6290) + * update dependency esbuild to v0.25.11 (#13710) (41b7a91d) + * update dependency chart.js to v4.5.1 (#13704) (bf37c7bd) + * update dependency nodebb-theme-persona to v14.1.15 (#13701) (fa18287d) + * update dependency nodebb-theme-harmony to v2.1.21 (#13700) (49a29325) + * update dependency nodemailer to v7.0.9 (#13695) (5d3709f0) + * update dependency semver to v7.7.3 (#13697) (a2892f60) + * update dependency webpack to v5.102.1 (#13698) (bb7b65ea) + * update dependency nodemailer to v7.0.7 (#13694) (5dc9f2c5) + * update dependency redis to v5.8.3 (#13691) (9b6e9b2a) + * update dependency winston to v3.18.3 (#13687) (19dc1025) +* null check on attachments property in assertPrivate (9d83a3d0) +* update announce and undo(announce) so that their IDs don't use timestamps (24e17683) +* incorrect topic event added when topic moved out of cid -1 (used to be a share by the user; since removed.) (2b733e4a) +* #13654, improper OrderedCollectionPage ID (aa7e078f) +* IS logic when body.height < window.height (bdb45248) +* update markdown and web-push to latest versions (c51b7b65) +* bump mentions to 4.8.2 (2ce691cb) +* rename activitypub.out.announce.category, federate out Delete on topic move to cid -1 (9bb8a955) +* bump harmony and persona for #13756 (c616e657) +* renderOverride to not clobber url if already set in template data (2066727f) +* bump themes for cross-post support, #13396 (9d3e8179) +* add replies in parallel during note assertion (4858abe1) +* logic error in context generation (748cc5ee) +* relax toPid assertion checks so that it only checks that it is a number or uri (30b1212a) +* update logic so that purging a post does not remove toPid fields from children, updated addParentPosts so that post existence is checked (f6219d00) +* update category mock to save full handle (524df6e5) +* logic error in out.remove.context (ab9154aa) +* cross-check remove(context) target prop against cid (194cedb4) +* update logic re: federating out topic moves (4f2f872b) +* bad var (22868d3f) +* call api.topics method on topic move during note assertion, have category announce new topic on note assertion (3df4970c) +* do not include image or icon props if they are falsy values (603068ae) +* rebroadcasting logic should only execute for local tids if the remote cid is not addressed already (1d529473) +* move Announce(Delete) out of topics.move and into topics API method (fadac616) +* do not include actor from reflected activity when rebroadcasting remote cid (3fa74d4c) +* broken category urls in to, cc (d4695f10) +* update getPrivateKey to send application actor key when cid 0 (a45f6f9c) +* update targets in 1b12 rebroadcast when cid is remote (58a9e1c4) +* update 1b12 rebroadcast logic to send as application actor if post is in remote cid (79d08853) +* regression caused by d3b3720915f5846e8f5a8e0bee9c17b3ff233902 (af5efbd7) +* crash in tests (6c210068) +* add attachments to retrieved post data onNewPost (07bed55e) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13713 (2425f3b6) + +##### Refactors + +* deleteOrRestore internal method to federate out a Delete on delete, not just purge; better adheres to FEP 4f05 (e6911be3) +* get rid of post.exists check, if post doesnt exist content is falsy (17944037) +* move all methods in src/api/activitypub.js to src/activitypub.out.js (3ede64d8) +* user announces no longer occur on topic move. Instead, the new category announces. Only occurs when topic moved to local categories. (e09bb8b6) +* inbox announce(delete) handling to also handle context deletion, #13712 (2b2028e4) +* move post attachment handling directly into posts.create (d3b37209) + +##### Reverts + +* remove `federatedDescription` category field, closes #13757 (ed83bc5b) + +##### Tests + +* update test for toPid logic to reflect that toPid stays even if parent is purged (98a1101d) + #### v4.6.3 (2025-11-20) ##### Chores From 59f649b8851f4a224b5b4133df170f8996da2588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 1 Dec 2025 11:15:14 -0500 Subject: [PATCH 03/21] 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 50f27f95a2..03e5d0057d 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.25", + "nodebb-theme-harmony": "2.1.26", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.18", From 5bd1f7b7ac95a3498f8e8d7543289ec467206a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 1 Dec 2025 17:46:01 -0500 Subject: [PATCH 04/21] feat: #13790, allow ssl setup in psql --- src/database/postgres/connection.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/database/postgres/connection.js b/src/database/postgres/connection.js index 19d796d7ed..34a7bca5e8 100644 --- a/src/database/postgres/connection.js +++ b/src/database/postgres/connection.js @@ -1,5 +1,6 @@ 'use strict'; +const fs = require('fs'); const nconf = require('nconf'); const winston = require('winston'); const _ = require('lodash'); @@ -32,6 +33,19 @@ connection.getConnectionOptions = function (postgres) { connectionTimeoutMillis: 90000, }; + if (typeof postgres.ssl === 'object' && !Array.isArray(postgres.ssl) && postgres.ssl !== null) { + const { ssl } = postgres; + connOptions.ssl = { + rejectUnauthorized: ssl.rejectUnauthorized, + }; + ['ca', 'key', 'cert'].forEach((prop) => { + if (ssl.hasOwnProperty(prop)) { + connOptions.ssl[prop] = fs.readFileSync(ssl[prop]).toString(); + } + }); + console.log(connOptions); + } + return _.merge(connOptions, postgres.options || {}); }; From 2142b680d995b433b2cc1291d80b9995fe4f4d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 1 Dec 2025 17:47:46 -0500 Subject: [PATCH 05/21] chore: remove log --- src/database/postgres/connection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database/postgres/connection.js b/src/database/postgres/connection.js index 34a7bca5e8..c49e7caac4 100644 --- a/src/database/postgres/connection.js +++ b/src/database/postgres/connection.js @@ -43,7 +43,6 @@ connection.getConnectionOptions = function (postgres) { connOptions.ssl[prop] = fs.readFileSync(ssl[prop]).toString(); } }); - console.log(connOptions); } return _.merge(connOptions, postgres.options || {}); From 53e22acffbfe7f3eb4edbbefeb94a73725520573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 2 Dec 2025 11:12:05 -0500 Subject: [PATCH 06/21] fix: remove hardcoded name for sentinel, #13794 --- src/database/redis/connection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index 7f0b1912f8..41a9243355 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -22,7 +22,6 @@ connection.connect = async function (options) { const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port })); cxn = createSentinel({ ...options.options, - name: 'sentinel-db', sentinelRootNodes, }); } else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) { From ba85474dfba92740ac877a531ee7af9ed7a5aef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 3 Dec 2025 09:49:16 -0500 Subject: [PATCH 07/21] feat: add hreflang to buildLinkTag --- public/src/modules/helpers.common.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index aa1825adda..b9c46fa3cc 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -77,10 +77,12 @@ module.exports = function (utils, Benchpress, relative_path) { } function buildLinkTag(tag) { - const attributes = ['link', 'rel', 'as', 'type', 'href', 'sizes', 'title', 'crossorigin']; - const [link, rel, as, type, href, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); + const attributes = [ + 'link', 'rel', 'as', 'type', 'href', 'hreflang', 'sizes', 'title', 'crossorigin' + ]; + const [link, rel, as, type, href, hreflang, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); - return '\n\t'; + return '\n\t'; } function stringify(obj) { From 9fb41c693351c566127248c2e424e620b97e3a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 3 Dec 2025 10:14:08 -0500 Subject: [PATCH 08/21] lint: fix missing comma --- public/src/modules/helpers.common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index b9c46fa3cc..1350ff4ce7 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -78,7 +78,7 @@ module.exports = function (utils, Benchpress, relative_path) { function buildLinkTag(tag) { const attributes = [ - 'link', 'rel', 'as', 'type', 'href', 'hreflang', 'sizes', 'title', 'crossorigin' + 'link', 'rel', 'as', 'type', 'href', 'hreflang', 'sizes', 'title', 'crossorigin', ]; const [link, rel, as, type, href, hreflang, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); From 254370c5beca5062fc0307e4d1d4af09aea32bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bli=C5=BAniuk?= Date: Fri, 5 Dec 2025 00:11:04 +0100 Subject: [PATCH 09/21] ci: drop ARM v7 from docker builds (#13808) Removed optional ARM v7 (32 bit) platform from the workflow due to lack of support from Node and very limited usefulness. As the platform had been flaky in the past, this part of the workflow was already optional and didn't cause it to fail. So this is just the next step here. --- .github/workflows/docker.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e74e58c07b..d72d49e5c2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,9 +26,6 @@ jobs: - os: ubuntu-24.04-arm platforms: linux/arm64 required: true - - os: ubuntu-24.04-arm - platforms: linux/arm/v7 - required: false continue-on-error: ${{ !matrix.required }} runs-on: ${{ matrix.os }} steps: @@ -131,4 +128,4 @@ jobs: $(printf '${{ env.IMAGE }}@sha256:%s ' *) - name: Inspect image run: | - docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} \ No newline at end of file + docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} From ebf2a2c5afa31e69eb94901f8801620fe5f6fcd4 Mon Sep 17 00:00:00 2001 From: Nephilim Date: Fri, 5 Dec 2025 11:26:53 -0600 Subject: [PATCH 10/21] fix: respect user pagination settings in infinite scroll (#13765) (#13788) - Changed hardcoded topicsPerPage value of 20 to use settings.topicsPerPage - Allows infinite scroll to respect user's configured page size preference - Consistent with pagination handling in other controllers (category.js, recent.js, etc) - Validates against admin's maxTopicsPerPage setting - Fixes issue where all users were limited to 20 topics per request regardless of settings --- src/api/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/categories.js b/src/api/categories.js index 476a0d4d9d..4806868f93 100644 --- a/src/api/categories.js +++ b/src/api/categories.js @@ -126,7 +126,7 @@ categoriesAPI.getTopics = async (caller, data) => { throw new Error('[[error:no-privileges]]'); } - const infScrollTopicsPerPage = 20; + const infScrollTopicsPerPage = settings.topicsPerPage; const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'recently_replied'; let start = Math.max(0, parseInt(data.after || 0, 10)); From 193aaf55d5e2df41b6e5116f14098fbe55b5a166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 6 Dec 2025 20:08:05 -0500 Subject: [PATCH 11/21] fix: closes #13666, update category label on topic move if we are not on category page --- public/src/client/category/tools.js | 16 ++++++++++++++-- public/src/modules/helpers.common.js | 2 +- src/api/topics.js | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index b372d7a2f6..42160e6e6a 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -284,8 +284,20 @@ define('forum/category/tools', [ topic.find('[component="topic/locked"]').toggleClass('hidden', !data.isLocked); } - function onTopicMoved(data) { - getTopicEl(data.tid).remove(); + async function onTopicMoved(data) { + if (ajaxify.data.template.category) { + getTopicEl(data.tid).remove(); + } else { + const category = await api.get(`/categories/${data.toCid}`); + const html = await app.parseAndTranslate('partials/topics_list', { + topics: [{ + ...data, + category, + }], + }); + const categoryLabelSelector = `[component="category/topic"][data-tid="${data.tid}"] [component="topic/category"]`; + $(categoryLabelSelector).replaceWith(html.find(categoryLabelSelector)); + } } function onTopicPurged(data) { diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 1350ff4ce7..a4b77c0802 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -113,7 +113,7 @@ module.exports = function (utils, Benchpress, relative_path) { } const href = tag === 'a' ? `href="${relative_path}/category/${category.slug}"` : ''; - return `<${tag} ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> + return `<${tag} ${href} component="topic/category" class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> ${category.icon && category.icon !== 'fa-nbb-none' ? `` : ''} ${category.name} `; diff --git a/src/api/topics.js b/src/api/topics.js index 91e0882db4..80f2b28cb7 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -309,6 +309,7 @@ topicsAPI.move = async (caller, { tid, cid }) => { throw new Error('[[error:no-privileges]]'); } const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'mainPid', 'slug', 'deleted']); + topicData.toCid = cid; if (!cids.includes(topicData.cid)) { cids.push(topicData.cid); } From 2e00c0ff428f0b078619889679fbddf598d4afe9 Mon Sep 17 00:00:00 2001 From: Konrad Moskal Date: Sun, 7 Dec 2025 02:12:27 +0100 Subject: [PATCH 12/21] Modify delete post diff response format (#13761) * Modify delete post diff response format Updated the delete operation response to return JSON content. * fix: timestamp open api schema --- public/openapi/write/posts/pid/diffs/timestamp.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/openapi/write/posts/pid/diffs/timestamp.yaml b/public/openapi/write/posts/pid/diffs/timestamp.yaml index 15d942fc26..02d4219d9c 100644 --- a/public/openapi/write/posts/pid/diffs/timestamp.yaml +++ b/public/openapi/write/posts/pid/diffs/timestamp.yaml @@ -19,7 +19,9 @@ delete: description: a valid UNIX timestamp example: 1611850000000 responses: - '200': + "200": description: Post diff successfully deleted content: - $ref: ../diffs.yaml#/get/responses/200/content \ No newline at end of file + application/json: + schema: + $ref: ../diffs.yaml#/get/responses/200/content From 11b01dfccbf166a0cffa33098392e64f6b303533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 6 Dec 2025 20:44:51 -0500 Subject: [PATCH 13/21] test: fix tests --- public/src/modules/helpers.common.js | 2 +- test/template-helpers.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index a4b77c0802..a33e7af8c7 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -113,7 +113,7 @@ module.exports = function (utils, Benchpress, relative_path) { } const href = tag === 'a' ? `href="${relative_path}/category/${category.slug}"` : ''; - return `<${tag} ${href} component="topic/category" class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> + return `<${tag} component="topic/category" ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> ${category.icon && category.icon !== 'fa-nbb-none' ? `` : ''} ${category.name} `; diff --git a/test/template-helpers.js b/test/template-helpers.js index b0bbb334c5..ea4d142354 100644 --- a/test/template-helpers.js +++ b/test/template-helpers.js @@ -107,7 +107,7 @@ describe('helpers', () => { imageClass: 'auto', name: 'Category 1', }, 'a', ''), - `\n\t\t\t\n\t\t\tCategory 1\n\t\t` + `\n\t\t\t\n\t\t\tCategory 1\n\t\t` ); assert.strictEqual( helpers.buildCategoryLabel({ @@ -118,7 +118,7 @@ describe('helpers', () => { name: 'Category 1', icon: 'fa-book', }, 'span', 'rounded-1'), - `\n\t\t\t\n\t\t\tCategory 1\n\t\t`, + `\n\t\t\t\n\t\t\tCategory 1\n\t\t`, ); done(); }); From 9d6665505e9d7930afadb752c691ed6661e25c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 8 Dec 2025 09:43:50 -0500 Subject: [PATCH 14/21] chore: up widget-essentials --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 03e5d0057d..19d9192df2 100644 --- a/install/package.json +++ b/install/package.json @@ -111,7 +111,7 @@ "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.18", - "nodebb-widget-essentials": "7.0.40", + "nodebb-widget-essentials": "7.0.41", "nodemailer": "7.0.10", "nprogress": "0.2.0", "passport": "0.7.0", From b19281b0611328e4213259e9c3c0d686cfdd6cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 8 Dec 2025 10:18:38 -0500 Subject: [PATCH 15/21] revert: spec change --- public/openapi/write/posts/pid/diffs/timestamp.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/openapi/write/posts/pid/diffs/timestamp.yaml b/public/openapi/write/posts/pid/diffs/timestamp.yaml index 02d4219d9c..15d942fc26 100644 --- a/public/openapi/write/posts/pid/diffs/timestamp.yaml +++ b/public/openapi/write/posts/pid/diffs/timestamp.yaml @@ -19,9 +19,7 @@ delete: description: a valid UNIX timestamp example: 1611850000000 responses: - "200": + '200': description: Post diff successfully deleted content: - application/json: - schema: - $ref: ../diffs.yaml#/get/responses/200/content + $ref: ../diffs.yaml#/get/responses/200/content \ No newline at end of file From f49f540bfab071ba54a273300e52e1725c05a8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 11 Dec 2025 21:25:42 -0500 Subject: [PATCH 16/21] fix: show errors when saving settings --- public/src/client/account/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 9445fcb340..2e220fb080 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -102,7 +102,7 @@ define('forum/account/settings', [ if (languageChanged && parseInt(app.user.uid, 10) === parseInt(ajaxify.data.theirid, 10)) { window.location.reload(); } - }); + }).catch(alerts.error); } function toggleCustomRoute() { From 90a151348e344b7002b8126aa8d235bda88879ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Dec 2025 17:19:16 -0500 Subject: [PATCH 17/21] fix: moving topic to cid=-1 will remove it from list --- public/src/client/category/tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index 42160e6e6a..f298cbe85d 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -285,7 +285,7 @@ define('forum/category/tools', [ } async function onTopicMoved(data) { - if (ajaxify.data.template.category) { + if (ajaxify.data.template.category || String(data.toCid) === '-1') { getTopicEl(data.tid).remove(); } else { const category = await api.get(`/categories/${data.toCid}`); From 5ae8d553ed1ba276b92afeb2b32b26623209d225 Mon Sep 17 00:00:00 2001 From: Shlomo <78599753+ShlomoCode@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:16:38 +0200 Subject: [PATCH 18/21] fix: disallow inline viewing of unsafe files (#13833) --- src/middleware/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 14cb3138e1..02024b8a19 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -273,10 +273,19 @@ middleware.buildSkinAsset = helpers.try(async (req, res, next) => { middleware.addUploadHeaders = function addUploadHeaders(req, res, next) { // Trim uploaded files' timestamps when downloading + force download if html let basename = path.basename(req.path); - const extname = path.extname(req.path); - if (req.path.startsWith('/uploads/files/') && middleware.regexes.timestampedUpload.test(basename)) { - basename = basename.slice(14); - res.header('Content-Disposition', `${extname.startsWith('.htm') ? 'attachment' : 'inline'}; filename="${basename}"`); + const extname = path.extname(req.path).toLowerCase(); + const unsafeExtensions = [ + '.html', '.htm', '.xhtml', '.mht', '.mhtml', '.stm', '.shtm', '.shtml', + '.svg', '.svgz', + '.xml', '.xsl', '.xslt', + ]; + const isInlineSafe = !unsafeExtensions.includes(extname); + const dispositionType = isInlineSafe ? 'inline' : 'attachment'; + if (req.path.startsWith('/uploads/files/')) { + if (middleware.regexes.timestampedUpload.test(basename)) { + basename = basename.slice(14); + } + res.header('Content-Disposition', `${dispositionType}; filename="${basename}"`); } next(); From 9f729964161bb12b1b860b18c46379e24132d2f6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 10:56:57 -0500 Subject: [PATCH 19/21] feat: stop extraneous vote and tids_read data from being saved for remote users --- src/posts/votes.js | 20 ++++--- src/topics/unread.js | 2 +- .../4.7.1/remove_extraneous_ap_data.js | 24 ++++++++ test/activitypub/notes.js | 55 +++++++++++++++---- 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/upgrades/4.7.1/remove_extraneous_ap_data.js diff --git a/src/posts/votes.js b/src/posts/votes.js index 599c4f92d0..d9487b1dc2 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -177,16 +177,18 @@ module.exports = function (Posts) { } const now = Date.now(); - if (type === 'upvote' && !unvote) { - await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); - } else { - await db.sortedSetRemove(`uid:${uid}:upvote`, pid); - } + if (utils.isNumber(uid)) { + if (type === 'upvote' && !unvote) { + await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); + } else { + await db.sortedSetRemove(`uid:${uid}:upvote`, pid); + } - if (type === 'upvote' || unvote) { - await db.sortedSetRemove(`uid:${uid}:downvote`, pid); - } else { - await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + if (type === 'upvote' || unvote) { + await db.sortedSetRemove(`uid:${uid}:downvote`, pid); + } else { + await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + } } const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); diff --git a/src/topics/unread.js b/src/topics/unread.js index 4dd5a94435..ed93f19abf 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -290,7 +290,7 @@ module.exports = function (Topics) { }; Topics.markAsRead = async function (tids, uid) { - if (!Array.isArray(tids) || !tids.length) { + if (!Array.isArray(tids) || !tids.length || !utils.isNumber(uid)) { return false; } diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js new file mode 100644 index 0000000000..99b5d9de4d --- /dev/null +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -0,0 +1,24 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Remove extraneous upvote and tids_read data for remote users', + timestamp: Date.UTC(2025, 11, 11), + method: async function () { + const { progress } = this; + await batch.processSortedSet('usersRemote:lastCrawled', async (uids) => { + const readKeys = uids.map(uid => `uid:${uid}:tids_read`); + const voteKeys = uids.map(uid => `uid:${uid}:upvote`); + + const combined = readKeys.concat(voteKeys); + + await db.deleteAll(combined); + }, { + batch: 500, + progress, + }); + + }, +}; diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index b43f2da49f..1eba189b92 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -432,6 +432,7 @@ describe('Notes', () => { describe('Create', () => { let uid; + let cid; before(async () => { uid = await user.create({ username: utils.generateUUID() }); @@ -451,6 +452,17 @@ describe('Notes', () => { assert.strictEqual(cid, -1); }); + it('should not append to the tids_read sorted set', async () => { + const { note, id } = helpers.mocks.note(); + const { activity } = helpers.mocks.create(note); + + await db.sortedSetAdd(`followersRemote:${note.attributedTo}`, Date.now(), uid); + await activitypub.inbox.create({ body: activity }); + + const exists = await db.exists(`uid:${note.attributedTo}:tids_read`); + assert(!exists); + }); + it('should create a new topic in a remote category if addressed (category same-origin)', async () => { const { id: remoteCid } = helpers.mocks.group(); const { note, id } = helpers.mocks.note({ @@ -467,40 +479,63 @@ describe('Notes', () => { }); it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - this.timeout(30000); - const start = Date.now(); const { id: remoteCid } = helpers.mocks.group({ id: `https://example.com/${utils.generateUUID()}`, }); - console.log('1', Date.now() - start); const { note, id } = helpers.mocks.note({ audience: [remoteCid], }); - console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); - console.log('3', Date.now() - start); try { await activitypub.inbox.create({ body: activity }); } catch (err) { - console.log('error in test', err.stack); assert(false); } - console.log('4', Date.now() - start); assert(await posts.exists(id)); - console.log('5', Date.now() - start); const cid = await posts.getCidByPid(id); - console.log('6', Date.now() - start); assert.strictEqual(cid, -1); }); }); + + describe('(Like)', () => { + let pid; + let voterUid; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID() })); + const { postData } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + pid = postData.pid; + const object = await activitypub.mocks.notes.public(postData); + const { activity } = helpers.mocks.like({ object }); + voterUid = activity.actor; + await activitypub.inbox.like({ body: activity }); + }); + + it('should increment a like for the post', async () => { + const voted = await posts.hasVoted(pid, voterUid); + const count = await posts.getPostField(pid, 'upvotes'); + assert(voted); + assert.strictEqual(count, 1); + }); + + it('should not append to the uid upvotes zset', async () => { + const exists = await db.exists(`uid:${voterUid}:upvote`); + assert(!exists); + }); + }); }); describe('Announce', () => { let cid; before(async () => { - ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ cid } = await categories.create({ name: utils.generateUUID() })); }); describe('(Create)', () => { From 9f94a72117d6dcd661fd52e017c40c34efa15f54 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 11:09:13 -0500 Subject: [PATCH 20/21] fix: increment progress on upgrade script --- src/upgrades/4.7.1/remove_extraneous_ap_data.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js index 99b5d9de4d..7f2d0f7e52 100644 --- a/src/upgrades/4.7.1/remove_extraneous_ap_data.js +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -15,6 +15,7 @@ module.exports = { const combined = readKeys.concat(voteKeys); await db.deleteAll(combined); + progress.incr(combined.length); }, { batch: 500, progress, From b1fc5bfdaab55d0b428767b9a2640c77fac5ca07 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 11:10:29 -0500 Subject: [PATCH 21/21] fix: wrong increment value --- src/upgrades/4.7.1/remove_extraneous_ap_data.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js index 7f2d0f7e52..e32590cb01 100644 --- a/src/upgrades/4.7.1/remove_extraneous_ap_data.js +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -15,11 +15,10 @@ module.exports = { const combined = readKeys.concat(voteKeys); await db.deleteAll(combined); - progress.incr(combined.length); + progress.incr(uids.length); }, { batch: 500, progress, }); - }, };