From 03a0e72fae84f6fd0fe6d9b46e5334e0a1d21280 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 10:14:55 -0500 Subject: [PATCH 1/5] refactor: split out logic dedicated to calculating unread counts, to a separate local method --- src/middleware/header.js | 92 +++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index 2174f7c461..e363dee7cc 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -79,9 +79,6 @@ middleware.renderHeader = async function renderHeader(req, res, data) { timeagoCode: languages.userTimeagoCode(res.locals.config.userLang), browserTitle: translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title))), navigation: navigation.get(req.uid), - unreadData: topics.getUnreadData({ uid: req.uid }), - unreadChatCount: messaging.getUnreadCount(req.uid), - unreadNotificationCount: user.notifications.getUnreadCount(req.uid), }); const unreadData = { @@ -105,44 +102,15 @@ middleware.renderHeader = async function renderHeader(req, res, data) { templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+ - - const unreadCounts = results.unreadData.counts; - const unreadCount = { - topic: unreadCounts[''] || 0, - newTopic: unreadCounts.new || 0, - watchedTopic: unreadCounts.watched || 0, - unrepliedTopic: unreadCounts.unreplied || 0, - chat: results.unreadChatCount || 0, - notification: results.unreadNotificationCount || 0, - }; - - Object.keys(unreadCount).forEach(function (key) { - if (unreadCount[key] > 99) { - unreadCount[key] = '99+'; - } - }); - - const tidsByFilter = results.unreadData.tidsByFilter; - results.navigation = results.navigation.map(function (item) { - function modifyNavItem(item, route, filter, content) { - if (item && validator.unescape(item.originalRoute) === route) { - unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); - item.content = content; - if (unreadCounts[filter] > 0) { - item.iconClass += ' unread-count'; - } - } - } - modifyNavItem(item, '/unread', '', unreadCount.topic); - modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); - modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); - modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); - return item; - }); - templateValues.browserTitle = results.browserTitle; - templateValues.navigation = results.navigation; - templateValues.unreadCount = unreadCount; + ({ + navigation: templateValues.navigation, + unreadCount: templateValues.unreadCount, + } = await appendUnreadCounts({ + uid: req.uid, + navigation: results.navigation, + unreadData, + })); templateValues.isAdmin = results.user.isAdmin; templateValues.isGlobalMod = results.user.isGlobalMod; templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; @@ -183,6 +151,50 @@ middleware.renderHeader = async function renderHeader(req, res, data) { return await req.app.renderAsync('header', hookReturn.templateValues); }; +async function appendUnreadCounts({ uid, navigation, unreadData }) { + const results = await utils.promiseParallel({ + unreadData: topics.getUnreadData({ uid: uid }), + unreadChatCount: messaging.getUnreadCount(uid), + unreadNotificationCount: user.notifications.getUnreadCount(uid), + }); + + const unreadCounts = results.unreadData.counts; + const unreadCount = { + topic: unreadCounts[''] || 0, + newTopic: unreadCounts.new || 0, + watchedTopic: unreadCounts.watched || 0, + unrepliedTopic: unreadCounts.unreplied || 0, + chat: results.unreadChatCount || 0, + notification: results.unreadNotificationCount || 0, + }; + + Object.keys(unreadCount).forEach(function (key) { + if (unreadCount[key] > 99) { + unreadCount[key] = '99+'; + } + }); + + const tidsByFilter = results.unreadData.tidsByFilter; + navigation = navigation.map(function (item) { + function modifyNavItem(item, route, filter, content) { + if (item && validator.unescape(item.originalRoute) === route) { + unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); + item.content = content; + if (unreadCounts[filter] > 0) { + item.iconClass += ' unread-count'; + } + } + } + modifyNavItem(item, '/unread', '', unreadCount.topic); + modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); + modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); + modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + return item; + }); + + return { navigation, unreadCount }; +} + middleware.renderFooter = async function renderFooter(req, res, templateValues) { const data = await plugins.hooks.fire('filter:middleware.renderFooter', { req: req, From 6cb5888c133355f6b0903356b4dddd2dcfd4de6b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 10:33:48 -0500 Subject: [PATCH 2/5] fix: unescape header navigation originalRoute [breaking] --- src/middleware/header.js | 2 +- src/navigation/index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index e363dee7cc..049d8a43a0 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -177,7 +177,7 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { const tidsByFilter = results.unreadData.tidsByFilter; navigation = navigation.map(function (item) { function modifyNavItem(item, route, filter, content) { - if (item && validator.unescape(item.originalRoute) === route) { + if (item && item.originalRoute === route) { unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); item.content = content; if (unreadCounts[filter] > 0) { diff --git a/src/navigation/index.js b/src/navigation/index.js index ed639afbfa..a581508367 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -1,6 +1,7 @@ 'use strict'; const nconf = require('nconf'); +const validator = require('validator'); const admin = require('./admin'); const groups = require('../groups'); @@ -12,7 +13,7 @@ navigation.get = async function (uid) { let data = await admin.get(); data = data.filter(item => item && item.enabled).map(function (item) { - item.originalRoute = item.route; + item.originalRoute = validator.unescape(item.route); if (!item.route.startsWith('http')) { item.route = relative_path + item.route; From 6a1311b4bca99a35aa39858f0ff7782e4ee2cd42 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 12:17:42 -0500 Subject: [PATCH 3/5] refactor: flags lib to have a separate getFlagIdsWithFilters method added quick filter for unresolved flags --- src/flags.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/flags.js b/src/flags.js index 8bf57257fd..e2c3085c6f 100644 --- a/src/flags.js +++ b/src/flags.js @@ -36,7 +36,11 @@ Flags.init = async function () { if (!Array.isArray(value)) { sets.push(prefix + value); } else if (value.length) { - value.forEach(x => orSets.push(prefix + x)); + if (value.length === 1) { + sets.push(prefix + value.pop()); + } else { + value.forEach(x => orSets.push(prefix + x)); + } } } @@ -67,6 +71,10 @@ Flags.init = async function () { case 'mine': sets.push('flags:byAssignee:' + uid); break; + + case 'unresolved': + prepareSets(sets, orSets, 'flags:byState:', ['open', 'wip']); + break; } }, }, @@ -113,9 +121,13 @@ Flags.get = async function (flagId) { return data.flag; }; -Flags.list = async function (data) { - const filters = data.filters || {}; +Flags.getCount = async function ({ uid, filters }) { + filters = filters || {}; + const flagIds = await Flags.getFlagIdsWithFilters({ filters, uid }); + return flagIds.length; +}; +Flags.getFlagIdsWithFilters = async function ({ filters, uid }) { let sets = []; const orSets = []; @@ -126,7 +138,7 @@ Flags.list = async function (data) { for (var type in filters) { if (filters.hasOwnProperty(type)) { if (Flags._filters.hasOwnProperty(type)) { - Flags._filters[type](sets, orSets, filters[type], data.uid); + Flags._filters[type](sets, orSets, filters[type], uid); } else { winston.warn('[flags/list] No flag filter type found: ' + type); } @@ -152,6 +164,15 @@ Flags.list = async function (data) { } } + return flagIds; +}; + +Flags.list = async function (data) { + const filters = data.filters || {}; + let flagIds = Flags.getFlagIdsWithFilters({ + filters, + uid: data.uid, + }); flagIds = await Flags.sort(flagIds, data.sort); // Create subset for parsing based on page number (n=20) From c07e1e16afac834c37c30a53e207e635804aaa51 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 12:18:07 -0500 Subject: [PATCH 4/5] feat: add unread-count badge if navigator contains /flags route --- src/middleware/header.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/middleware/header.js b/src/middleware/header.js index 049d8a43a0..b6c7651c79 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -10,6 +10,7 @@ var db = require('../database'); var user = require('../user'); var topics = require('../topics'); var messaging = require('../messaging'); +var flags = require('../flags'); var meta = require('../meta'); var plugins = require('../plugins'); var navigation = require('../navigation'); @@ -152,11 +153,25 @@ middleware.renderHeader = async function renderHeader(req, res, data) { }; async function appendUnreadCounts({ uid, navigation, unreadData }) { - const results = await utils.promiseParallel({ + const originalRoutes = navigation.map(nav => nav.originalRoute); + const calls = { unreadData: topics.getUnreadData({ uid: uid }), unreadChatCount: messaging.getUnreadCount(uid), unreadNotificationCount: user.notifications.getUnreadCount(uid), - }); + unreadFlagCount: (async function () { + if (originalRoutes.includes('/flags') && await user.isPrivileged(uid)) { + return flags.getCount({ + uid, + filters: { + quick: 'unresolved', + cid: (await user.isAdminOrGlobalMod(uid)) ? [] : (await user.getModeratedCids(uid)), + }, + }); + } + return 0; + }()), + }; + const results = await utils.promiseParallel(calls); const unreadCounts = results.unreadData.counts; const unreadCount = { @@ -166,6 +181,7 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { unrepliedTopic: unreadCounts.unreplied || 0, chat: results.unreadChatCount || 0, notification: results.unreadNotificationCount || 0, + flags: results.unreadFlagCount || 0, }; Object.keys(unreadCount).forEach(function (key) { @@ -189,6 +205,14 @@ async function appendUnreadCounts({ uid, navigation, unreadData }) { modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + + ['flags'].forEach((prop) => { + if (item && item.originalRoute === `/${prop}` && unreadCount[prop] > 0) { + item.iconClass += ' unread-count'; + item.content = unreadCount.flags; + } + }); + return item; }); From 4ede18ce5f7dd1da9276a6a8530fcc3e14c0aa7f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Jan 2021 13:57:20 -0500 Subject: [PATCH 5/5] fix: broken test caused by errant .pop(), missing await --- src/flags.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flags.js b/src/flags.js index e2c3085c6f..c6a2a90edd 100644 --- a/src/flags.js +++ b/src/flags.js @@ -37,7 +37,7 @@ Flags.init = async function () { sets.push(prefix + value); } else if (value.length) { if (value.length === 1) { - sets.push(prefix + value.pop()); + sets.push(prefix + value[0]); } else { value.forEach(x => orSets.push(prefix + x)); } @@ -169,7 +169,7 @@ Flags.getFlagIdsWithFilters = async function ({ filters, uid }) { Flags.list = async function (data) { const filters = data.filters || {}; - let flagIds = Flags.getFlagIdsWithFilters({ + let flagIds = await Flags.getFlagIdsWithFilters({ filters, uid: data.uid, });