diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index d4431e9e35..7cae6bfcd4 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -150,6 +150,7 @@ "load-categories": "Loading Categories", "confirm-move": "Move", + "confirm-crosspost": "Cross-post", "confirm-fork": "Fork", "bookmark": "Bookmark", @@ -162,6 +163,7 @@ "loading-more-posts": "Loading More Posts", "move-topic": "Move Topic", "move-topics": "Move Topics", + "crosspost-topic": "Cross-post Topic", "move-post": "Move Post", "post-moved": "Post moved!", "fork-topic": "Fork Topic", @@ -182,6 +184,7 @@ "topic-id": "Topic ID", "move-posts-instruction": "Click the posts you want to move then enter a topic ID or go to the target topic", "move-topic-instruction": "Select the target category and then click move", + "crosspost-topic-instruction": "Select one or more categories to cross-post to. Topic(s) will be accessible from the original category and all cross-posted categories.", "change-owner-instruction": "Click the posts you want to assign to another user", "manage-editors-instruction": "Manage the users who can edit this post below.", diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index 5bb4d7d362..7e1a001ada 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -117,7 +117,9 @@ define('forum/topic/threadTools', [ }); topicContainer.on('click', '[component="topic/crosspost"]', () => { - console.log('tbd'); + require(['forum/topic/crosspost'], (crosspost) => { + crosspost.init(tid, ajaxify.data.cid); + }); }); topicContainer.on('click', '[component="topic/delete/posts"]', function () { diff --git a/public/src/modules/categorySearch.js b/public/src/modules/categorySearch.js index 305396b9d8..8542131b37 100644 --- a/public/src/modules/categorySearch.js +++ b/public/src/modules/categorySearch.js @@ -77,6 +77,7 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots states: options.states, showLinks: options.showLinks, localOnly: options.localOnly, + hideUncategorized: options.hideUncategorized, }, function (err, { categories }) { if (err) { return alerts.error(err); @@ -94,6 +95,7 @@ define('categorySearch', ['alerts', 'bootstrap', 'api'], function (alerts, boots categoryItems: categories.slice(0, 200), selectedCategory: ajaxify.data.selectedCategory, allCategoriesUrl: ajaxify.data.allCategoriesUrl, + hideAll: options.hideAll, }, function (html) { el.find('[component="category/list"]') .html(html.find('[component="category/list"]').html()); diff --git a/src/api/search.js b/src/api/search.js index 3ae5319d1e..070dc9810b 100644 --- a/src/api/search.js +++ b/src/api/search.js @@ -9,6 +9,7 @@ const messaging = require('../messaging'); const privileges = require('../privileges'); const meta = require('../meta'); const plugins = require('../plugins'); +const utils = require('../utils'); const controllersHelpers = require('../controllers/helpers'); @@ -29,9 +30,12 @@ searchApi.categories = async (caller, data) => { ({ cids, matchedCids } = await findMatchedCids(caller.uid, data)); } else { cids = await loadCids(caller.uid, data.parentCid); - if (meta.config.activitypubEnabled) { + if (!data.hideUncategorized && meta.config.activitypubEnabled) { cids.unshift(-1); } + if (data.localOnly) { + cids = cids.filter(cid => utils.isNumber(cid)); + } } const visibleCategories = await controllersHelpers.getVisibleCategories({ diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 4cacc31cd9..08958ce730 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -123,8 +123,9 @@ topicsController.get = async function getTopic(req, res, next) { p => parseInt(p.index, 10) === parseInt(Math.max(0, postIndex - 1), 10) ); - const [author] = await Promise.all([ + const [author, crossposts] = await Promise.all([ user.getUserFields(topicData.uid, ['username', 'userslug']), + topics.crossposts.get(topicData.tid), buildBreadcrumbs(topicData), addOldCategory(topicData, userPrivileges), addTags(topicData, req, res, currentPage, postAtIndex), @@ -134,6 +135,7 @@ topicsController.get = async function getTopic(req, res, next) { ]); topicData.author = author; + topicData.crossposts = crossposts; topicData.pagination = pagination.create(currentPage, pageCount, req.query); topicData.pagination.rel.forEach((rel) => { rel.href = `${url}/topic/${topicData.slug}${rel.href}`; diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 88b6c80675..ca07aadd7c 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -12,8 +12,20 @@ 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( + cids, ['cid', 'name', 'icon', 'bgColor', 'color', 'slug'] + ); + categoriesData = 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)); return crosspost; }); diff --git a/src/views/modals/crosspost-topic.tpl b/src/views/modals/crosspost-topic.tpl new file mode 100644 index 0000000000..a7ae3be3ea --- /dev/null +++ b/src/views/modals/crosspost-topic.tpl @@ -0,0 +1,15 @@ +
+ [[topic:crosspost-topic-instruction]] +
+ +