From 29111ba7ca944a7034aa0cf6347545cc551ccd1a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 26 Feb 2026 15:43:02 -0500 Subject: [PATCH] fix: allow break string and summary limits to be defined and applied --- install/data/defaults.json | 4 +- public/language/en-GB/admin/menu.json | 3 +- .../en-GB/admin/settings/activitypub.json | 8 +++- src/activitypub/mocks.js | 18 ++++----- src/controllers/admin/federation.js | 6 +++ src/posts/parse.js | 2 +- src/routes/admin.js | 1 + src/views/admin/federation/content.tpl | 29 ++++++++++++++ src/views/admin/partials/navigation.tpl | 1 + test/activitypub/mocks.js | 40 +++++++++++++++++++ 10 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 src/views/admin/federation/content.tpl diff --git a/install/data/defaults.json b/install/data/defaults.json index 449234192c..fcbac89921 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -206,5 +206,7 @@ "activitypubProbeTimeout": 2000, "activitypubContentPruneDays": 30, "activitypubUserPruneDays": 7, - "activitypubFilter": 0 + "activitypubFilter": 0, + "activitypubSummaryLimit": 500, + "activitypubBreakString": "[...]" } diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json index 319e4d3af2..9a2566141e 100644 --- a/public/language/en-GB/admin/menu.json +++ b/public/language/en-GB/admin/menu.json @@ -50,9 +50,10 @@ "section-federation": "Federation", "federation/general": "General", + "federation/content": "Content", "federation/rules": "Categorization", "federation/relays": "Relays", - "federation/pruning": "Content Pruning", + "federation/pruning": "Storage", "federation/safety": "Trust & Safety", "section-appearance": "Appearance", diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 5ab4fa43e8..0486870db1 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -44,5 +44,11 @@ "count": "This NodeBB is currently aware of %1 server(s)", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.", - "server.filter-allow-list": "Use this as an Allow List instead" + "server.filter-allow-list": "Use this as an Allow List instead", + + "content.outgoing": "Outgoing", + "content.summary-limit": "Character count after which a summary is generated", + "content.summary-limit-help": "When content is federated out that exceeds this character count, a summary is generated, comprising of all complete sentences prior to this limit. (Default: 500)", + "content.break-string": "Note/Article Delimiter", + "content.break-string-help": "This delimiter can be manually inserted by power users when composing new topics. It instructs NodeBB to use content up until that point as part of the summary. If this string is not used, then the character count fallback applies. (Default: [...])" } \ No newline at end of file diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 793bf9d8fd..6eef6d2665 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -8,6 +8,7 @@ const sanitize = require('sanitize-html'); const tokenizer = require('sbd'); const db = require('../database'); +const meta = require('../meta'); const user = require('../user'); const categories = require('../categories'); const posts = require('../posts'); @@ -710,7 +711,7 @@ Mocks.notes.public = async (post) => { // Special handling for main posts (as:Article w/ as:Note preview) const plaintext = posts.sanitizePlaintext(content); - const isArticle = post.pid === post.topic.mainPid && plaintext.length > 500; + const isArticle = post.pid === post.topic.mainPid && plaintext.length > meta.config.activitypubSummaryLimit; if (post.isMainPost) { const thumbs = await topics.thumbs.get(post.tid); @@ -755,17 +756,16 @@ Mocks.notes.public = async (post) => { // attachment, // }; - const breakString = '[...]'; - if (post.content.includes(breakString)) { - const index = post.content.indexOf(breakString); - summary = post.content.slice(0, index + breakString.length); + if (post.content.includes(meta.config.activitypubBreakString)) { + const index = post.content.indexOf(meta.config.activitypubBreakString); + summary = post.content.slice(0, index + meta.config.activitypubBreakString.length); } else { const sentences = tokenizer.sentences(post.content, { newline_boundaries: true }); - // Append sentences to summary until it contains just under 500 characters of content - const limit = 500; + // Append sentences to summary until until just under configured character limit + const limit = meta.config.activitypubSummaryLimit; let remaining = limit; let finished = false; - summary = sentences.reduce((memo, sentence) => { + summary = sentences.reduce((memo, sentence, index) => { if (finished) { return memo; } @@ -776,7 +776,7 @@ Mocks.notes.public = async (post) => { }); remaining = remaining - clean.length; if (remaining > 0) { - memo += ` ${sentence}`; + memo += `${index > 0 ? ' ' : ''}${sentence}`; } else { // There was more but summary generation is complete finished = true; memo += ' [...]'; diff --git a/src/controllers/admin/federation.js b/src/controllers/admin/federation.js index 4d6cd104a4..ec6b93c35e 100644 --- a/src/controllers/admin/federation.js +++ b/src/controllers/admin/federation.js @@ -10,6 +10,12 @@ federationController.general = function (req, res) { }); }; +federationController.content = function (req, res) { + res.render(`admin/federation/content`, { + title: '[[admin/menu:federation/content]]', + }); +}; + federationController.rules = async function (req, res) { const rules = await activitypub.rules.list(); diff --git a/src/posts/parse.js b/src/posts/parse.js index 37cdd52c9c..a4d8f57443 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -58,7 +58,7 @@ module.exports = function (Posts) { } if (!type.startsWith('activitypub.')) { - postData.content = postData.content.replace('[...]', ''); + postData.content = postData.content.replace(meta.config.activitypubBreakString, ''); } ({ postData } = await plugins.hooks.fire('filter:parse.post', { postData, type })); postData.content = translator.escape(postData.content); diff --git a/src/routes/admin.js b/src/routes/admin.js index bca37af2ce..d00abd0056 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -53,6 +53,7 @@ module.exports = function (app, name, middleware, controllers) { helpers.setupAdminPageRoute(app, `/${name}/settings/advanced`, middlewares, controllers.admin.settings.advanced); helpers.setupAdminPageRoute(app, `/${name}/federation/general`, middlewares, controllers.admin.federation.general); + helpers.setupAdminPageRoute(app, `/${name}/federation/content`, middlewares, controllers.admin.federation.content); helpers.setupAdminPageRoute(app, `/${name}/federation/rules`, middlewares, controllers.admin.federation.rules); helpers.setupAdminPageRoute(app, `/${name}/federation/relays`, middlewares, controllers.admin.federation.relays); helpers.setupAdminPageRoute(app, `/${name}/federation/pruning`, middlewares, controllers.admin.federation.pruning); diff --git a/src/views/admin/federation/content.tpl b/src/views/admin/federation/content.tpl new file mode 100644 index 0000000000..3ee43fcf21 --- /dev/null +++ b/src/views/admin/federation/content.tpl @@ -0,0 +1,29 @@ +
+ + +
+
+
+
[[admin/settings/activitypub:content.outgoing]]
+
+
+ + +
+ [[admin/settings/activitypub:content.summary-limit-help]] +
+
+
+ + +
+ [[admin/settings/activitypub:content.break-string-help]] +
+
+
+
+
+ + +
+
diff --git a/src/views/admin/partials/navigation.tpl b/src/views/admin/partials/navigation.tpl index a2a2a08fa4..23b6a2c9df 100644 --- a/src/views/admin/partials/navigation.tpl +++ b/src/views/admin/partials/navigation.tpl @@ -104,6 +104,7 @@
[[admin/menu:federation/general]] + [[admin/menu:federation/content]] [[admin/menu:federation/rules]] [[admin/menu:federation/relays]] [[admin/menu:federation/pruning]] diff --git a/test/activitypub/mocks.js b/test/activitypub/mocks.js index 2fce246e78..5ba674ba47 100644 --- a/test/activitypub/mocks.js +++ b/test/activitypub/mocks.js @@ -4,6 +4,7 @@ const assert = require('assert'); const nconf = require('nconf'); const db = require('../mocks/databasemock'); +const meta = require('../../src/meta'); const user = require('../../src/user'); const categories = require('../../src/categories'); const topics = require('../../src/topics'); @@ -82,6 +83,45 @@ describe('Mocking', () => { const { content } = await posts.parsePost(clone); assert(!content.includes('[...]')); }); + + describe('Altered magic break string', () => { + let string; + before(() => { + string = meta.config.activitypubBreakString; + meta.config.activitypubBreakString = 'Mauris'; + }); + + after(() => { + meta.config.activitypubBreakString = string; + }); + + it('should work with a customized break string', async function () { + const mocked = await activitypub.mocks.notes.public(this.withBreakPost); + assert.strictEqual(mocked.summary, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\ + Aliquam vel augue, id luctus nulla. Mauris'); + }); + }); + + describe('Altered summary limit', () => { + let string; + let limit; + before(() => { + string = meta.config.activitypubBreakString; + meta.config.activitypubBreakString = 'lkjsdnfkjsdfkjsdhfkd'; + limit = meta.config.activitypubSummaryLimit; + meta.config.activitypubSummaryLimit = 60; + }); + + after(() => { + meta.config.activitypubBreakString = string; + meta.config.activitypubSummaryLimit = limit; + }); + + it('should work with a customized summary limit', async function () { + const mocked = await activitypub.mocks.notes.public(this.withBreakPost); + assert.strictEqual(mocked.summary, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. [...]'); + }); + }); }); }); });