From 5d9a8681421c01593af5c8466cdce2ff23ea367e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 2 Oct 2020 16:35:20 -0400 Subject: [PATCH] feat: category filter on post queue (#8710) * feat: category filter on post queue category filter module * feat: add spec --- public/openapi/api/post-queue.yaml | 43 ++++++++++++++++++ public/src/client/post-queue.js | 6 ++- public/src/modules/categoryFilter.js | 66 ++++++++++++++++++++++++++++ public/src/modules/topicList.js | 64 ++------------------------- src/controllers/helpers.js | 17 ++++--- src/controllers/mods.js | 13 ++++-- 6 files changed, 137 insertions(+), 72 deletions(-) create mode 100644 public/src/modules/categoryFilter.js diff --git a/public/openapi/api/post-queue.yaml b/public/openapi/api/post-queue.yaml index 794f7e1f15b3..7825ef3e6f99 100644 --- a/public/openapi/api/post-queue.yaml +++ b/public/openapi/api/post-queue.yaml @@ -42,6 +42,49 @@ get: type: number slug: type: string + categories: + type: array + items: + type: object + properties: + bgColor: + type: string + cid: + type: number + color: + type: string + disabledClass: + nullable: true + icon: + type: string + imageClass: + type: string + level: + type: string + link: + type: string + name: + type: string + parentCid: + type: number + slug: + type: string + allCategoriesUrl: + type: string + selectedCategory: + type: object + properties: + icon: + type: string + name: + type: string + bgColor: + type: string + nullable: true + selectedCids: + type: array + items: + type: number posts: type: array items: diff --git a/public/src/client/post-queue.js b/public/src/client/post-queue.js index 2116f75d34a1..cc5261553949 100644 --- a/public/src/client/post-queue.js +++ b/public/src/client/post-queue.js @@ -1,12 +1,16 @@ 'use strict'; -define('forum/post-queue', ['categorySelector'], function (categorySelector) { +define('forum/post-queue', [ + 'categoryFilter', 'categorySelector', +], function (categoryFilter, categorySelector) { var PostQueue = {}; PostQueue.init = function () { $('[data-toggle="tooltip"]').tooltip(); + categoryFilter.init($('[component="category/dropdown"]')); + $('.posts-list').on('click', '[data-action]', function () { var parent = $(this).parents('[data-id]'); var action = $(this).attr('data-action'); diff --git a/public/src/modules/categoryFilter.js b/public/src/modules/categoryFilter.js new file mode 100644 index 000000000000..b28e29ff1963 --- /dev/null +++ b/public/src/modules/categoryFilter.js @@ -0,0 +1,66 @@ +'use strict'; + +define('categoryFilter', ['categorySearch'], function (categorySearch) { + var categoryFilter = {}; + + categoryFilter.init = function (el) { + categorySearch.init(el); + var listEl = el.find('[component="category/list"]'); + + el.on('hidden.bs.dropdown', function () { + var cids = getSelectedCids(el); + var changed = ajaxify.data.selectedCids.length !== cids.length; + ajaxify.data.selectedCids.forEach(function (cid, index) { + if (cid !== cids[index]) { + changed = true; + } + }); + + if (changed) { + var url = window.location.pathname; + var currentParams = utils.params(); + if (cids.length) { + currentParams.cid = cids; + url += '?' + decodeURIComponent($.param(currentParams)); + } + ajaxify.go(url); + } + }); + + listEl.on('click', '[data-cid]', function (ev) { + function selectChildren(parentCid, flag) { + listEl.find('[data-parent-cid="' + parentCid + '"] [component="category/select/icon"]').toggleClass('invisible', flag); + listEl.find('[data-parent-cid="' + parentCid + '"]').each(function (index, el) { + selectChildren($(el).attr('data-cid'), flag); + }); + } + var categoryEl = $(this); + var link = categoryEl.find('a').attr('href'); + if (link && link !== '#' && link.length) { + return; + } + var cid = categoryEl.attr('data-cid'); + if (ev.ctrlKey) { + selectChildren(cid, !categoryEl.find('[component="category/select/icon"]').hasClass('invisible')); + } + categoryEl.find('[component="category/select/icon"]').toggleClass('invisible'); + listEl.find('li').first().find('i').toggleClass('invisible', !!getSelectedCids(el).length); + return false; + }); + }; + + function getSelectedCids(el) { + var cids = []; + el.find('[component="category/list"] [data-cid]').each(function (index, el) { + if (!$(el).find('[component="category/select/icon"]').hasClass('invisible')) { + cids.push(parseInt($(el).attr('data-cid'), 10)); + } + }); + cids.sort(function (a, b) { + return a - b; + }); + return cids; + } + + return categoryFilter; +}); diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index f9260a5effcc..8ddf566d4840 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -4,9 +4,9 @@ define('topicList', [ 'forum/infinitescroll', 'handleBack', 'topicSelect', - 'categorySearch', + 'categoryFilter', 'forum/category/tools', -], function (infinitescroll, handleBack, topicSelect, categorySearch, categoryTools) { +], function (infinitescroll, handleBack, topicSelect, categoryFilter, categoryTools) { var TopicList = {}; var templateName = ''; @@ -38,7 +38,7 @@ define('topicList', [ TopicList.watchForNewPosts(); - TopicList.handleCategorySelection(); + categoryFilter.init($('[component="category/dropdown"]')); if (!config.usePagination) { infinitescroll.init(TopicList.loadMoreTopics); @@ -159,64 +159,6 @@ define('topicList', [ $('#category-no-topics').addClass('hide'); } - TopicList.handleCategorySelection = function () { - function getSelectedCids() { - var cids = []; - $('[component="category/list"] [data-cid]').each(function (index, el) { - if ($(el).find('i.fa-check').length) { - cids.push(parseInt($(el).attr('data-cid'), 10)); - } - }); - cids.sort(function (a, b) { - return a - b; - }); - return cids; - } - - categorySearch.init($('[component="category/dropdown"]')); - - $('[component="category/dropdown"]').on('hidden.bs.dropdown', function () { - var cids = getSelectedCids(); - var changed = ajaxify.data.selectedCids.length !== cids.length; - ajaxify.data.selectedCids.forEach(function (cid, index) { - if (cid !== cids[index]) { - changed = true; - } - }); - - if (changed) { - var url = window.location.pathname; - var currentParams = utils.params(); - if (cids.length) { - currentParams.cid = cids; - url += '?' + decodeURIComponent($.param(currentParams)); - } - ajaxify.go(url); - } - }); - - $('[component="category/list"]').on('click', '[data-cid]', function (ev) { - function selectChildren(parentCid, flag) { - $('[component="category/list"] [data-parent-cid="' + parentCid + '"] [component="category/select/icon"]').toggleClass('fa-check', flag); - $('[component="category/list"] [data-parent-cid="' + parentCid + '"]').each(function (index, el) { - selectChildren($(el).attr('data-cid'), flag); - }); - } - var categoryEl = $(this); - var link = categoryEl.find('a').attr('href'); - if (link && link !== '#' && link.length) { - return; - } - var cid = categoryEl.attr('data-cid'); - if (ev.ctrlKey) { - selectChildren(cid, !categoryEl.find('[component="category/select/icon"]').hasClass('fa-check')); - } - categoryEl.find('[component="category/select/icon"]').toggleClass('fa-check'); - $('[component="category/list"] li').first().find('i').toggleClass('fa-check', !getSelectedCids().length); - return false; - }); - }; - TopicList.loadMoreTopics = function (direction) { if (!topicListEl.length || !topicListEl.children().length) { return; diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 57c1469a3f27..3fbcc67771cd 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -214,15 +214,15 @@ helpers.buildTitle = function (pageTitle) { helpers.getCategories = async function (set, uid, privilege, selectedCid) { const cids = await categories.getCidsByPrivilege(set, uid, privilege); - return await getCategoryData(cids, uid, selectedCid); + return await getCategoryData(cids, uid, selectedCid, privilege); }; -helpers.getCategoriesByStates = async function (uid, selectedCid, states) { +helpers.getCategoriesByStates = async function (uid, selectedCid, states, privilege = 'topics:read') { const cids = await categories.getAllCidsFromSet('categories:cid'); - return await getCategoryData(cids, uid, selectedCid, states); + return await getCategoryData(cids, uid, selectedCid, states, privilege); }; -async function getCategoryData(cids, uid, selectedCid, states) { +async function getCategoryData(cids, uid, selectedCid, states, privilege) { if (selectedCid && !Array.isArray(selectedCid)) { selectedCid = [selectedCid]; } @@ -230,7 +230,7 @@ async function getCategoryData(cids, uid, selectedCid, states) { states = states || [categories.watchStates.watching, categories.watchStates.notwatching]; const [allowed, watchState, categoryData, isAdmin] = await Promise.all([ - privileges.categories.isUserAllowedTo('topics:read', cids, uid), + privileges.categories.isUserAllowedTo(privilege, cids, uid), categories.getWatchState(cids, uid), categories.getCategoriesData(cids), user.isAdministrator(uid), @@ -246,6 +246,11 @@ async function getCategoryData(cids, uid, selectedCid, states) { const hasVisibleChildren = checkVisibleChildren(c, cidToAllowed, cidToWatchState, states); const isCategoryVisible = c && cidToAllowed[c.cid] && !c.link && !c.disabled && states.includes(cidToWatchState[c.cid]); const shouldBeRemoved = !hasVisibleChildren && !isCategoryVisible; + const shouldBeDisaplayedAsDisabled = hasVisibleChildren && !isCategoryVisible; + + if (shouldBeDisaplayedAsDisabled) { + c.disabledClass = true; + } if (shouldBeRemoved && c && c.parent && c.parent.cid && cidToCategory[c.parent.cid]) { cidToCategory[c.parent.cid].children = cidToCategory[c.parent.cid].children.filter(child => child.cid !== c.cid); @@ -254,7 +259,7 @@ async function getCategoryData(cids, uid, selectedCid, states) { return c && !shouldBeRemoved; }); - const categoriesData = categories.buildForSelectCategories(visibleCategories); + const categoriesData = categories.buildForSelectCategories(visibleCategories, ['disabledClass']); let selectedCategory = []; const selectedCids = []; diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 3be5ebf5d8e8..017f6ef4ba97 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -202,15 +202,16 @@ modsController.postQueue = async function (req, res, next) { if (!isPrivileged) { return next(); } - + const cid = req.query.cid; const page = parseInt(req.query.page, 10) || 1; const postsPerPage = 20; - const [ids, isAdminOrGlobalMod, moderatedCids, allCategories] = await Promise.all([ + const [ids, isAdminOrGlobalMod, moderatedCids, allCategories, categoriesData] = await Promise.all([ db.getSortedSetRange('post:queue', 0, -1), user.isAdminOrGlobalMod(req.uid), user.getModeratedCids(req.uid), categories.buildForSelect(req.uid, 'find', ['disabled', 'link', 'slug']), + helpers.getCategoriesByStates(req.uid, cid, null, 'moderate'), ]); allCategories.forEach((c) => { @@ -218,7 +219,9 @@ modsController.postQueue = async function (req, res, next) { }); let postData = await getQueuedPosts(ids); - postData = postData.filter(p => p && (isAdminOrGlobalMod || moderatedCids.includes(String(p.category.cid)))); + postData = postData.filter(p => p && + (!categoriesData.selectedCids.length || categoriesData.selectedCids.includes(p.category.cid)) && + (isAdminOrGlobalMod || moderatedCids.includes(String(p.category.cid)))); ({ posts: postData } = await plugins.fireHook('filter:post-queue.get', { posts: postData, @@ -234,6 +237,8 @@ modsController.postQueue = async function (req, res, next) { title: '[[pages:post-queue]]', posts: postData, allCategories: allCategories, + ...categoriesData, + allCategoriesUrl: 'post-queue' + helpers.buildQueryString(req.query, 'cid', ''), pagination: pagination.create(page, pageCount), breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:post-queue]]' }]), }); @@ -268,7 +273,7 @@ async function addMetaData(postData) { } postData.topic = { cid: 0 }; if (postData.data.cid) { - postData.topic = { cid: postData.data.cid }; + postData.topic = { cid: parseInt(postData.data.cid, 10) }; } else if (postData.data.tid) { postData.topic = await topics.getTopicFields(postData.data.tid, ['title', 'cid']); }