Compare commits

..

13 Commits

Author SHA1 Message Date
Misty Release Bot
672dcc5d14 chore: incrementing version number - v4.4.1 2025-05-16 16:37:49 +00:00
Julian Lam
948bfe46f1 test: fix tests to account for a460a55064 2025-05-16 11:43:26 -04:00
Julian Lam
ce5ef1ab6e fix: openapi schema to handle additional attachments field in postsobject 2025-05-16 10:04:43 -04:00
Barış Soner Uşaklı
61a63851d4 chore: up themes 2025-05-15 18:25:10 -04:00
Barış Soner Uşaklı
0a574d7240 fix: group edit url 2025-05-15 18:23:38 -04:00
Julian Lam
8f9f377121 fix: add attachments to getpostsummaries call in search, #13324 2025-05-15 16:57:05 -04:00
Julian Lam
a460a55064 fix: bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in attributedTo 2025-05-15 15:40:01 -04:00
Julian Lam
3674fa5783 feat: save width and height values into post attachment 2025-05-15 13:56:31 -04:00
Julian Lam
45a11d45fc fix: #13419, handle remote content with mediaType text/markdown 2025-05-15 12:01:45 -04:00
Barış Soner Uşaklı
6c3e2a8e22 refactor: create date once per digest.send 2025-05-15 09:42:55 -04:00
Barış Soner Uşaklı
3faae559a8 Merge branch 'master' of https://github.com/NodeBB/NodeBB 2025-05-15 09:38:49 -04:00
Barış Soner Uşaklı
3d96afb2d1 feat: use local date string for digest subject
closes #13420
2025-05-15 09:38:43 -04:00
Misty Release Bot
09cc91d5a0 chore: update changelog for v4.4.0 2025-05-14 20:36:36 +00:00
9 changed files with 188 additions and 19 deletions

View File

@@ -1,3 +1,113 @@
#### v4.4.0 (2025-05-14)
##### Breaking Changes
* removal of deprecated privilege hooks (8ea377a4)
* removal of `filter:flags.getFilters` (547fb482)
* removal of `filter:user.verify.code` (7e25946c)
* removal of `filter:post.purge` (df5c1a93)
* removal of `filter:post.purge` (c84b72fb)
* removal of `filter:router.page` (9d8061ea)
* removal of `filter:email.send` (b73a8d3e)
##### Chores
* **deps:**
* update redis docker tag to v8.0.1 (#13415) (fbe97b4e)
* update redis docker tag to v8 (#13387) (1df7313c)
* update postgres docker tag to v17.5 (#13398) (d319b0aa)
* update dependency sass-embedded to v1.88.0 (#13402) (694c79bc)
* update dependency lint-staged to v16 (#13404) (9d877481)
* update commitlint monorepo to v19.8.1 (#13394) (7a7a4f0a)
* update dependency lint-staged to v15.5.2 (#13383) (96dc5c89)
* update dependency @eslint/js to v9.26.0 (#13371) (450ce3b8)
* update dependency mocha to v11.2.2 (#13366) (e958010f)
* incrementing version number - v4.3.2 (b92b5d80)
* update changelog for v4.3.2 (0aa9c187)
* incrementing version number - v4.3.1 (308e6b9f)
* remove unused require (15b6a2c1)
* 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
* remove since-removed `labels` property from api (860ac895)
##### Bug Fixes
* adjust Peertube-specific handling to shove mp4 into post attachments, #13324 (799b08db)
* #13081, don't add mention when you are replying to yourself (d5865613)
* add `announces` to postdataobject schema (0f576a42)
* #13375, plus additional tests (fe13c755)
* missing awaits, more comprehensive 1b12 tests (5802c7dd)
* another case (6bfe4e62)
* handle missing orderedItems property in followers route (e042201f)
* missing await (651ebaaf)
* handle missing orderedItems (53bb0bbc)
* extra `orderedItems` property in generated paginated OrderedCollection, #13153 (f83b1fbf)
* #13153, follower and following collections to use generateCollection helper (a2de7aae)
* #13374, updates to posts.edit to handle remote content updates better (b4338489)
* leftover `handle` var (625ce96f)
* AP inbox update handling for non-note objects (f8d012c8)
* 1b12 creates being dropped (9f80d10d)
* update AP api (un)follow ids to be url encoded id instead of handle (7cf61ab0)
* **deps:**
* update dependency diff to v8 (#13409) (919d62ab)
* update dependency sanitize-html to v2.17.0 (#13418) (3e18af1e)
* update dependency satori to v0.13.1 (#13408) (f176d6b2)
* update dependency pg-cursor to v2.15.0 (#13414) (7320a858)
* update dependency nodebb-plugin-markdown to v13.2.1 (#13416) (84b8ecc7)
* update dependency semver to v7.7.2 (#13410) (366651d6)
* update dependency pg to v8.16.0 (#13411) (0825c569)
* update dependency nodebb-plugin-mentions to v4.7.6 (#13417) (383a7ce5)
* update dependency lru-cache to v11 (#12685) (23374fd7)
* update dependency rimraf to v6 (#12686) (6a4ffe02)
* update dependency bootswatch to v5.3.6 (#13400) (7a7cf830)
* update dependency csrf-sync to v4.2.1 (#13401) (ecce9998)
* update dependency sass to v1.88.0 (#13403) (7ffba218)
* update dependency nodemailer to v7.0.3 (#13395) (af3afba0)
* update dependency nodemailer to v7 (#13381) (0b4d403c)
* update dependency csrf-sync to v4.2.0 (#13364) (4f0f67a4)
* update dependency webpack to v5.99.8 (#13390) (c7a164ae)
* update dependency bootstrap to v5.3.6 (#13384) (e6a19612)
* update dependency esbuild to v0.25.4 (#13385) (b6f4de5b)
* update dependency @fontsource/poppins to v5.2.6 (#13376) (e2a8cf98)
* update dependency nodebb-plugin-mentions to v4.7.5 (#13386) (2c0aba02)
* update dependency nodebb-widget-essentials to v7.0.38 (#13380) (7f757615)
* update dependency nodebb-theme-persona to v14.1.11 (#13379) (954aa541)
* update dependency nodebb-theme-peace to v2.2.42 (#13378) (2aa0bfc5)
* update dependency nodebb-theme-harmony to v2.1.12 (#13377) (72b3a215)
* update dependency ace-builds to v1.41.0 (#13372) (4b78710b)
* bump markdown (f3bd8590)
##### Other Changes
* //github.com/NodeBB/NodeBB/issues/13367 (39953ee1)
##### Refactors
* use a single until (1b0b1da6)
* Helpers.generateCollection so that total count and a bound function can be passed in, #13153 (7f59238d)
##### Tests
* a few additional tests for announce handling (61f6806b)
* fix regression from 5802c7ddd9506a4e296f6dbdf2d9a32621c7f4ef (5b118904)
* fix broken test due to adjusted note assertion relation logic (9dc91f11)
* update filter:router.page tests to response:router.page (a819d39c)
* adjustment for now-removed labels property (52df41b9)
#### v4.3.2 (2025-05-12)
##### Chores

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "4.4.0",
"version": "4.4.1",
"homepage": "https://www.nodebb.org",
"repository": {
"type": "git",
@@ -107,10 +107,10 @@
"nodebb-plugin-spam-be-gone": "2.3.2",
"nodebb-plugin-web-push": "0.7.4",
"nodebb-rewards-essentials": "1.0.2",
"nodebb-theme-harmony": "2.1.12",
"nodebb-theme-harmony": "2.1.13",
"nodebb-theme-lavender": "7.1.19",
"nodebb-theme-peace": "2.2.42",
"nodebb-theme-persona": "14.1.11",
"nodebb-theme-peace": "2.2.43",
"nodebb-theme-persona": "14.1.12",
"nodebb-widget-essentials": "7.0.38",
"nodemailer": "7.0.3",
"nprogress": "0.2.0",

View File

@@ -2,4 +2,15 @@ PostsObject:
description: One of the objects in the array returned from `Posts.getPostSummaryByPids`
type: array
items:
$ref: ./PostObject.yaml#/PostObject
allOf:
- $ref: ./PostObject.yaml#/PostObject
- type: object
description: Optional properties that may or may not be present (except for `pid`, which is always present, and is only here as a hack to pass validation)
properties:
pid:
type: number
description: A post identifier
attachments:
type: array
required:
- pid

View File

@@ -42,7 +42,7 @@ const sanitizeConfig = {
Mocks._normalize = async (object) => {
// Normalized incoming AP objects into expected types for easier mocking
let { type, attributedTo, url, image, content, source, attachment } = object;
let { type, attributedTo, url, image, mediaType, content, source, attachment, cc } = object;
switch (true) { // non-string attributedTo handling
case Array.isArray(attributedTo): {
@@ -52,6 +52,10 @@ Mocks._normalize = async (object) => {
} else if (typeof cur === 'object') {
if (cur.type === 'Person' && cur.id) {
valid.push(cur.id);
} else if (cur.type === 'Group' && cur.id) {
// Add any groups found to cc where it is expected
cc = Array.isArray(cc) ? cc : [cc];
cc.push(cur.id);
}
}
@@ -70,6 +74,9 @@ Mocks._normalize = async (object) => {
if (sourceContent) {
content = null;
sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(sourceContent, true);
} else if (mediaType === 'text/markdown') {
sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(content, true);
content = null;
} else if (content && content.length) {
content = sanitize(content, sanitizeConfig);
content = await activitypub.helpers.remoteAnchorToLocalProfile(content);
@@ -107,9 +114,9 @@ Mocks._normalize = async (object) => {
const stream = url.reduce((memo, { type, mediaType, tag }) => {
if (!memo) {
if (type === 'Link' && mediaType === 'application/x-mpegURL') {
memo = tag.reduce((memo, { type, mediaType, href }) => {
memo = tag.reduce((memo, { type, mediaType, href, width, height }) => {
if (!memo && (type === 'Link' && mediaType === 'video/mp4')) {
memo = { type, mediaType, href };
memo = { mediaType, href, width, height };
}
return memo;
@@ -145,6 +152,7 @@ Mocks._normalize = async (object) => {
return {
...object,
cc,
attributedTo,
content,
sourceContent,

View File

@@ -115,18 +115,33 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
if (hasTid) {
mainPid = await topics.getTopicField(tid, 'mainPid');
} else {
// Check recipients/audience for local category
// Check recipients/audience for category (local or remote)
const set = activitypub.helpers.makeSet(_activitypub, ['to', 'cc', 'audience']);
await activitypub.actors.assert(Array.from(set));
// Local
const resolved = await Promise.all(Array.from(set).map(async id => await activitypub.helpers.resolveLocalId(id)));
const recipientCids = resolved
.filter(Boolean)
.filter(({ type }) => type === 'category')
.map(obj => obj.id);
if (recipientCids.length) {
// Remote
let remoteCid;
const assertedGroups = await categories.exists(Array.from(set));
try {
const { hostname } = new URL(mainPid);
remoteCid = Array.from(set).filter((id, idx) => {
const { hostname: cidHostname } = new URL(id);
return assertedGroups[idx] && cidHostname === hostname;
}).shift();
} catch (e) {
// noop
}
if (remoteCid || recipientCids.length) {
// Overrides passed-in value, respect addressing from main post over booster
options.cid = recipientCids.shift();
options.cid = remoteCid || recipientCids.shift();
}
// mainPid ok to leave as-is

View File

@@ -146,7 +146,9 @@ async function searchInContent(data) {
metadata.pids = metadata.pids.slice(start, start + itemsPerPage);
}
returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, {});
returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, {
extraFields: ['attachments'],
});
await plugins.hooks.fire('filter:search.contentGetResult', { result: returnData, data: data });
delete metadata.pids;
delete metadata.data;

View File

@@ -88,13 +88,20 @@ Digest.send = async function (data) {
return emailsSent;
}
let errorLogged = false;
const date = new Date();
await batch.processArray(data.subscribers, async (uids) => {
let userData = await user.getUsersFields(uids, ['uid', 'email', 'email:confirmed', 'username', 'userslug', 'lastonline']);
userData = userData.filter(u => u && u.email && (meta.config.includeUnverifiedEmails || u['email:confirmed']));
let userData = await user.getUsersFields(uids, [
'uid', 'email', 'email:confirmed', 'username', 'userslug', 'lastonline',
]);
userData = userData.filter(
u => u && u.email && (meta.config.includeUnverifiedEmails || u['email:confirmed'])
);
if (!userData.length) {
return;
}
await Promise.all(userData.map(async (userObj) => {
const userSettings = await user.getMultipleUserSettings(userData.map(u => u.uid));
await Promise.all(userData.map(async (userObj, index) => {
const userSetting = userSettings[index];
const [publicRooms, notifications, topics] = await Promise.all([
getUnreadPublicRooms(userObj.uid),
user.notifications.getUnreadInterval(userObj.uid, data.interval),
@@ -118,9 +125,8 @@ Digest.send = async function (data) {
});
emailsSent += 1;
const now = new Date();
await emailer.send('digest', userObj.uid, {
subject: `[[email:digest.subject, ${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}]]`,
subject: `[[email:digest.subject, ${date.toLocaleDateString(userSetting.userLang)}]]`,
username: userObj.username,
userslug: userObj.userslug,
notifications: unreadNotifs,

View File

@@ -4,7 +4,7 @@
<div class="group-area">
{{{ each users.groups }}}
<div class="group-card float-start m-1" data-group-name="{users.groups.nameEscaped}">
<a href="{config.relative_path}/admin/manage/groups/{users.groups.nameEncoded}"><span class="badge p-2" style="color:{users.groups.textColor}; background-color: {users.groups.labelColor};">{{{ if users.groups.icon }}}<i class="fa {users.groups.icon}"></i> {{{ end }}}{users.groups.displayName} <i class="ms-2 remove-group-icon fa fa-times" role="button"></i></span></a>
<a href="{config.relative_path}/admin/manage/groups/{users.groups.slug}"><span class="badge p-2" style="color:{users.groups.textColor}; background-color: {users.groups.labelColor};">{{{ if users.groups.icon }}}<i class="fa {users.groups.icon}"></i> {{{ end }}}{users.groups.displayName} <i class="ms-2 remove-group-icon fa fa-times" role="button"></i></span></a>
</div>
{{{ end }}}
</div>

View File

@@ -451,7 +451,7 @@ describe('Notes', () => {
assert.strictEqual(cid, -1);
});
it('should create a new topic in cid -1 even if a remote category is addressed', async () => {
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({
audience: [remoteCid],
@@ -462,6 +462,23 @@ describe('Notes', () => {
assert(await posts.exists(id));
const cid = await posts.getCidByPid(id);
assert.strictEqual(cid, remoteCid);
});
it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async () => {
const { id: remoteCid } = helpers.mocks.group({
id: `https://example.com/${utils.generateUUID()}`,
});
const { note, id } = helpers.mocks.note({
audience: [remoteCid],
});
const { activity } = helpers.mocks.create(note);
await activitypub.inbox.create({ body: activity });
assert(await posts.exists(id));
const cid = await posts.getCidByPid(id);
assert.strictEqual(cid, -1);
});