mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-29 02:39:55 +01:00
Merge branch 'master' into develop
This commit is contained in:
210
CHANGELOG.md
210
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
|
||||
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
@@ -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.35",
|
||||
"nodebb-theme-lavender": "7.1.19",
|
||||
"nodebb-theme-peace": "2.2.49",
|
||||
"nodebb-theme-persona": "14.1.25",
|
||||
@@ -203,4 +203,4 @@
|
||||
"url": "https://github.com/barisusakli"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -42,19 +45,17 @@ define('forum/groups/list', [
|
||||
return false;
|
||||
};
|
||||
|
||||
function renderSearchResults(data) {
|
||||
app.parseAndTranslate('partials/paginator', {
|
||||
pagination: data.pagination,
|
||||
}).then(function (html) {
|
||||
$('.pagination-container').replaceWith(html);
|
||||
});
|
||||
|
||||
const groupsEl = $('#groups-list');
|
||||
app.parseAndTranslate('partials/groups/list', {
|
||||
groups: data.groups,
|
||||
}).then(function (html) {
|
||||
groupsEl.empty().append(html);
|
||||
});
|
||||
async function renderSearchResults(data) {
|
||||
const [paginationHtml, groupsHtml] = await Promise.all([
|
||||
app.parseAndTranslate('partials/paginator', {
|
||||
pagination: data.pagination,
|
||||
}),
|
||||
app.parseAndTranslate('partials/groups/list', {
|
||||
groups: data.groups,
|
||||
}),
|
||||
]);
|
||||
$('.pagination-container').replaceWith(paginationHtml);
|
||||
$('#groups-list').empty().append(groupsHtml);
|
||||
}
|
||||
|
||||
return Groups;
|
||||
|
||||
@@ -464,7 +464,7 @@ module.exports = function (utils, load, warn) {
|
||||
*/
|
||||
Translator.escape = function escape(text) {
|
||||
return typeof text === 'string' ?
|
||||
text.replace(/\[\[/g, '[[').replace(/\]\]/g, ']]') :
|
||||
text.replace(/\[\[([a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+)\]\]/g, '[[$1]]') :
|
||||
text;
|
||||
};
|
||||
|
||||
|
||||
@@ -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']
|
||||
),
|
||||
topics.crossposts.get(tids),
|
||||
]);
|
||||
|
||||
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,15 +137,20 @@ 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));
|
||||
}
|
||||
});
|
||||
topics.forEach((t) => { t.parentCids = undefined; });
|
||||
topics.forEach((t) => {
|
||||
t.parentCids = undefined;
|
||||
t.crossposts = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function bubbleUpChildrenPosts(categoryData) {
|
||||
|
||||
@@ -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',
|
||||
}, {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger hide" id="create-modal-error"></div>
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="create-group-name">[[admin/manage/groups:name]]</label>
|
||||
|
||||
@@ -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]]' },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -87,22 +87,22 @@ describe('Topic tools', () => {
|
||||
});
|
||||
|
||||
it('should throw when attempting to move a topic from a remote category', async () => {
|
||||
assert.rejects(
|
||||
await assert.rejects(
|
||||
topics.tools.move(tid1, {
|
||||
cid: localCid,
|
||||
uid: 'system',
|
||||
}),
|
||||
'[[error:cant-move-topic-to-from-remote-categories]]'
|
||||
{ message: '[[error:no-topic]]' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when attempting to move a topic to a remote category', async () => {
|
||||
assert.rejects(
|
||||
await assert.rejects(
|
||||
topics.tools.move(tid2, {
|
||||
cid: remoteCid,
|
||||
uid: 'system',
|
||||
}),
|
||||
'[[error:cant-move-topic-to-from-remote-categories]]'
|
||||
{ message: '[[error:cant-move-topic-to-from-remote-categories]]' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -334,6 +334,22 @@ describe('Translator static methods', () => {
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should escape all translation patterns within text', (done) => {
|
||||
assert.strictEqual(
|
||||
Translator.escape('some nice text [[global:home]] here and [[global:search]] there'),
|
||||
'some nice text [[global:home]] here and [[global:search]] there'
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not escape markdown links', (done) => {
|
||||
assert.strictEqual(
|
||||
Translator.escape('[link text [test]](https://example.org)'),
|
||||
'[link text [test]](https://example.org)'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.unescape', () => {
|
||||
|
||||
Reference in New Issue
Block a user