mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	feat: category filter on post queue (#8710)
* feat: category filter on post queue category filter module * feat: add spec
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							f14b49457c
						
					
				
				
					commit
					5d9a868142
				
			| @@ -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: | ||||
|   | ||||
| @@ -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'); | ||||
|   | ||||
							
								
								
									
										66
									
								
								public/src/modules/categoryFilter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								public/src/modules/categoryFilter.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| }); | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 = []; | ||||
|   | ||||
| @@ -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']); | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user