mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-02-27 00:51:16 +01:00
feat: flags list sorting, closes #8569
This commit is contained in:
@@ -57,6 +57,11 @@
|
|||||||
"state-rejected": "Rejected",
|
"state-rejected": "Rejected",
|
||||||
"no-assignee": "Not Assigned",
|
"no-assignee": "Not Assigned",
|
||||||
|
|
||||||
|
"sort": "Sort by",
|
||||||
|
"sort-newest": "Newest first",
|
||||||
|
"sort-oldest": "Oldest first",
|
||||||
|
"sort-reports": "Most reports",
|
||||||
|
|
||||||
"modal-title": "Report Inappropriate Content",
|
"modal-title": "Report Inappropriate Content",
|
||||||
"modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.",
|
"modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.",
|
||||||
"modal-reason-spam": "Spam",
|
"modal-reason-spam": "Spam",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart)
|
|||||||
filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]);
|
filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
filtersEl.find('[name="sort"]').val(ajaxify.data.sort);
|
||||||
|
|
||||||
document.getElementById('apply-filters').addEventListener('click', function () {
|
document.getElementById('apply-filters').addEventListener('click', function () {
|
||||||
var payload = filtersEl.serializeArray().filter(function (item) {
|
var payload = filtersEl.serializeArray().filter(function (item) {
|
||||||
|
|||||||
@@ -18,18 +18,23 @@ const modsController = module.exports;
|
|||||||
modsController.flags = {};
|
modsController.flags = {};
|
||||||
|
|
||||||
modsController.flags.list = async function (req, res, next) {
|
modsController.flags.list = async function (req, res, next) {
|
||||||
let validFilters = ['assignee', 'state', 'reporterId', 'type', 'targetUid', 'cid', 'quick', 'page', 'perPage'];
|
const validFilters = ['assignee', 'state', 'reporterId', 'type', 'targetUid', 'cid', 'quick', 'page', 'perPage'];
|
||||||
|
const validSorts = ['newest', 'oldest', 'reports'];
|
||||||
|
|
||||||
// Reset filters if explicitly requested
|
// Reset filters if explicitly requested
|
||||||
if (parseInt(req.query.reset, 10) === 1) {
|
if (parseInt(req.query.reset, 10) === 1) {
|
||||||
delete req.session.flags_filters;
|
delete req.session.flags_filters;
|
||||||
|
delete req.session.flags_sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isAdminOrGlobalMod, moderatedCids, data] = await Promise.all([
|
const results = await Promise.all([
|
||||||
user.isAdminOrGlobalMod(req.uid),
|
user.isAdminOrGlobalMod(req.uid),
|
||||||
user.getModeratedCids(req.uid),
|
user.getModeratedCids(req.uid),
|
||||||
plugins.fireHook('filter:flags.validateFilters', { filters: validFilters }),
|
plugins.fireHook('filter:flags.validateFilters', { filters: validFilters }),
|
||||||
|
plugins.fireHook('filter:flags.validateSort', { sorts: validSorts }),
|
||||||
]);
|
]);
|
||||||
|
const [isAdminOrGlobalMod, moderatedCids,, { sorts }] = results;
|
||||||
|
let [,, { filters }] = results;
|
||||||
|
|
||||||
if (!(isAdminOrGlobalMod || !!moderatedCids.length)) {
|
if (!(isAdminOrGlobalMod || !!moderatedCids.length)) {
|
||||||
return next(new Error('[[error:no-privileges]]'));
|
return next(new Error('[[error:no-privileges]]'));
|
||||||
@@ -39,10 +44,8 @@ modsController.flags.list = async function (req, res, next) {
|
|||||||
res.locals.cids = moderatedCids;
|
res.locals.cids = moderatedCids;
|
||||||
}
|
}
|
||||||
|
|
||||||
validFilters = data.filters;
|
// Parse query string params for filters, eliminate non-valid filters
|
||||||
|
filters = filters.reduce(function (memo, cur) {
|
||||||
// Parse query string params for filters
|
|
||||||
let filters = validFilters.reduce(function (memo, cur) {
|
|
||||||
if (req.query.hasOwnProperty(cur)) {
|
if (req.query.hasOwnProperty(cur)) {
|
||||||
memo[cur] = req.query[cur];
|
memo[cur] = req.query[cur];
|
||||||
}
|
}
|
||||||
@@ -71,15 +74,32 @@ modsController.flags.list = async function (req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pagination doesn't count as a filter
|
// Pagination doesn't count as a filter
|
||||||
if (Object.keys(filters).length === 1 && filters.hasOwnProperty('page')) {
|
if (
|
||||||
|
(Object.keys(filters).length === 1 && filters.hasOwnProperty('page')) ||
|
||||||
|
(Object.keys(filters).length === 2 && filters.hasOwnProperty('page') && filters.hasOwnProperty('perPage'))
|
||||||
|
) {
|
||||||
hasFilter = false;
|
hasFilter = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save filters into session unless removed
|
// Parse sort from query string
|
||||||
|
let sort;
|
||||||
|
if (!req.query.sort && req.session.hasOwnProperty('flags_sort')) {
|
||||||
|
sort = req.session.flags_sort;
|
||||||
|
} else {
|
||||||
|
sort = sorts.includes(req.query.sort) ? req.query.sort : null;
|
||||||
|
}
|
||||||
|
hasFilter = hasFilter || !!sort;
|
||||||
|
|
||||||
|
// Save filters and sorting into session unless removed
|
||||||
req.session.flags_filters = filters;
|
req.session.flags_filters = filters;
|
||||||
|
req.session.flags_sort = sort;
|
||||||
|
|
||||||
const [flagsData, analyticsData, categoriesData] = await Promise.all([
|
const [flagsData, analyticsData, categoriesData] = await Promise.all([
|
||||||
flags.list(filters, req.uid),
|
flags.list({
|
||||||
|
filters: filters,
|
||||||
|
sort: sort,
|
||||||
|
uid: req.uid,
|
||||||
|
}),
|
||||||
analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30),
|
analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30),
|
||||||
categories.buildForSelect(req.uid, 'read'),
|
categories.buildForSelect(req.uid, 'read'),
|
||||||
]);
|
]);
|
||||||
@@ -90,6 +110,7 @@ modsController.flags.list = async function (req, res, next) {
|
|||||||
categories: filterCategories(res.locals.cids, categoriesData),
|
categories: filterCategories(res.locals.cids, categoriesData),
|
||||||
hasFilter: hasFilter,
|
hasFilter: hasFilter,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
|
sort: sort || 'newest',
|
||||||
title: '[[pages:flags]]',
|
title: '[[pages:flags]]',
|
||||||
pagination: pagination.create(flagsData.page, flagsData.pageCount, req.query),
|
pagination: pagination.create(flagsData.page, flagsData.pageCount, req.query),
|
||||||
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:flags]]' }]),
|
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:flags]]' }]),
|
||||||
|
|||||||
39
src/flags.js
39
src/flags.js
@@ -112,8 +112,8 @@ Flags.get = async function (flagId) {
|
|||||||
return data.flag;
|
return data.flag;
|
||||||
};
|
};
|
||||||
|
|
||||||
Flags.list = async function (filters, uid) {
|
Flags.list = async function (data) {
|
||||||
filters = filters || {};
|
const filters = data.filters || {};
|
||||||
|
|
||||||
let sets = [];
|
let sets = [];
|
||||||
const orSets = [];
|
const orSets = [];
|
||||||
@@ -125,7 +125,7 @@ Flags.list = async function (filters, uid) {
|
|||||||
for (var type in filters) {
|
for (var type in filters) {
|
||||||
if (filters.hasOwnProperty(type)) {
|
if (filters.hasOwnProperty(type)) {
|
||||||
if (Flags._filters.hasOwnProperty(type)) {
|
if (Flags._filters.hasOwnProperty(type)) {
|
||||||
Flags._filters[type](sets, orSets, filters[type], uid);
|
Flags._filters[type](sets, orSets, filters[type], data.uid);
|
||||||
} else {
|
} else {
|
||||||
winston.warn('[flags/list] No flag filter type found: ' + type);
|
winston.warn('[flags/list] No flag filter type found: ' + type);
|
||||||
}
|
}
|
||||||
@@ -151,6 +151,8 @@ Flags.list = async function (filters, uid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flagIds = await Flags.sort(flagIds, data.sort);
|
||||||
|
|
||||||
// Create subset for parsing based on page number (n=20)
|
// Create subset for parsing based on page number (n=20)
|
||||||
const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1);
|
const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1);
|
||||||
const pageCount = Math.ceil(flagIds.length / flagsPerPage);
|
const pageCount = Math.ceil(flagIds.length / flagsPerPage);
|
||||||
@@ -174,19 +176,42 @@ Flags.list = async function (filters, uid) {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const data = await plugins.fireHook('filter:flags.list', {
|
const payload = await plugins.fireHook('filter:flags.list', {
|
||||||
flags: flags,
|
flags: flags,
|
||||||
page: filters.page,
|
page: filters.page,
|
||||||
uid: uid,
|
uid: data.uid,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
flags: data.flags,
|
flags: payload.flags,
|
||||||
page: data.page,
|
page: payload.page,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Flags.sort = async function (flagIds, sort) {
|
||||||
|
switch (sort) {
|
||||||
|
// 'newest' is not handled because that is default
|
||||||
|
case 'oldest':
|
||||||
|
flagIds = flagIds.reverse();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reports': {
|
||||||
|
const keys = flagIds.map(id => `flag:${id}:reports`);
|
||||||
|
const heat = await db.sortedSetsCard(keys);
|
||||||
|
const mapped = heat.map((el, i) => ({
|
||||||
|
index: i, heat: el,
|
||||||
|
}));
|
||||||
|
mapped.sort(function (a, b) {
|
||||||
|
return b.heat - a.heat;
|
||||||
|
});
|
||||||
|
flagIds = mapped.map(obj => flagIds[obj.index]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flagIds;
|
||||||
|
};
|
||||||
|
|
||||||
Flags.validate = async function (payload) {
|
Flags.validate = async function (payload) {
|
||||||
const [target, reporter] = await Promise.all([
|
const [target, reporter] = await Promise.all([
|
||||||
Flags.getTarget(payload.type, payload.id, payload.uid),
|
Flags.getTarget(payload.type, payload.id, payload.uid),
|
||||||
|
|||||||
Reference in New Issue
Block a user