From 8d995d1eb609837e4e6e4c77cd855766830378fa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 May 2020 20:27:16 -0400 Subject: [PATCH] refactor: flags detail page - Show account moderation history - Ban and delete quick actions Squashed commit of the following: commit 0e782e65f4d48ae814708e510ec9d01bcdd914e0 Author: Julian Lam Date: Tue May 26 20:24:53 2020 -0400 fix(deps): use persona 10.1.41/vanilla 11.1.17 commit 369e073d3c3189d8ce181eb3d573489cbe54d4fc Author: Julian Lam Date: Tue May 26 20:23:24 2020 -0400 fix: allow ban and delete exported methods to have cbs commit b83a086ea31a77ec82d161306c0b9bc115cb2a3a Merge: 525aae1ea 256ee45d3 Author: Julian Lam Date: Tue May 26 08:54:25 2020 -0400 Merge remote-tracking branch 'origin/master' into flags-improvements commit 525aae1ea2e5d0103028a0f0c8dde05f172d088e Author: Julian Lam Date: Tue May 26 08:53:39 2020 -0400 feat: integrate ban history and username changes to flag history list commit 3e68ad28ba266f4c8620a676aa7f463f0a9d1df7 Author: Julian Lam Date: Mon May 25 18:22:53 2020 -0400 feat: allow ban and deletion from flag details page commit a559ea1d8e8883385c2876868d855a0b93516c54 Author: Julian Lam Date: Mon May 25 18:22:00 2020 -0400 feat: export banAccount and deleteAccount methods from accounts module --- install/package.json | 4 +- public/language/en-GB/flags.json | 4 +- public/language/en-GB/user.json | 1 + public/src/client/account/header.js | 26 +++++++++-- public/src/client/flags/detail.js | 37 +++++++++++----- public/src/client/flags/list.js | 7 --- src/flags.js | 68 +++++++++++++++++++++++++++++ 7 files changed, 122 insertions(+), 25 deletions(-) diff --git a/install/package.json b/install/package.json index e812718623..c088470dd5 100644 --- a/install/package.json +++ b/install/package.json @@ -89,9 +89,9 @@ "nodebb-plugin-spam-be-gone": "0.7.0", "nodebb-rewards-essentials": "0.1.3", "nodebb-theme-lavender": "5.0.11", - "nodebb-theme-persona": "10.1.40", + "nodebb-theme-persona": "10.1.41", "nodebb-theme-slick": "1.2.29", - "nodebb-theme-vanilla": "11.1.16", + "nodebb-theme-vanilla": "11.1.17", "nodebb-widget-essentials": "4.1.0", "nodemailer": "^6.4.6", "passport": "^0.4.1", diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 9b8658dceb..09b0fe746d 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -27,7 +27,7 @@ "filter-cid-all": "All categories", "apply-filters": "Apply Filters", - "quick-links": "Quick Links", + "quick-actions": "Quick Actions", "flagged-user": "Flagged User", "view-profile": "View Profile", "start-new-chat": "Start New Chat", @@ -40,7 +40,7 @@ "add-note": "Add Note", "no-notes": "No shared notes.", - "history": "Flag History", + "history": "Account & Flag History", "back": "Back to Flags List", "no-history": "No flag history.", diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index f1c3b8f7fe..54d0fcf272 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -161,6 +161,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", + "info.banned-expiry": "Expiry", "info.banned-permanently": "Banned permanently", "info.banned-reason-label": "Reason", "info.banned-no-reason": "No reason given.", diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 100256e762..7ac50ef653 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -56,6 +56,10 @@ define('forum/account/header', [ components.get('account/block').on('click', toggleBlockAccount); }; + // TODO: These exported methods are used in forum/flags/detail -- refactor?? + AccountHeader.banAccount = banAccount; + AccountHeader.deleteAccount = deleteAccount; + function hidePrivateLinks() { if (!app.user.uid || app.user.uid !== parseInt(ajaxify.data.theirid, 10)) { $('.account-sub-links .plugin-link.private').addClass('hide'); @@ -117,7 +121,9 @@ define('forum/account/header', [ return false; } - function banAccount() { + function banAccount(theirid, onSuccess) { + theirid = theirid || ajaxify.data.theirid; + Benchpress.parse('admin/partials/temporary-ban', {}, function (html) { bootbox.dialog({ className: 'ban-modal', @@ -140,13 +146,18 @@ define('forum/account/header', [ var until = formData.length > 0 ? (Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))) : 0; socket.emit('user.banUsers', { - uids: [ajaxify.data.theirid], + uids: [theirid], until: until, reason: formData.reason || '', }, function (err) { if (err) { return app.alertError(err.message); } + + if (typeof onSuccess === 'function') { + return onSuccess(); + } + ajaxify.refresh(); }); }, @@ -165,18 +176,25 @@ define('forum/account/header', [ }); } - function deleteAccount() { + function deleteAccount(theirid, onSuccess) { + theirid = theirid || ajaxify.data.theirid; + translator.translate('[[user:delete_this_account_confirm]]', function (translated) { bootbox.confirm(translated, function (confirm) { if (!confirm) { return; } - socket.emit('admin.user.deleteUsersAndContent', [ajaxify.data.theirid], function (err) { + socket.emit('admin.user.deleteUsersAndContent', [theirid], function (err) { if (err) { return app.alertError(err.message); } app.alertSuccess('[[user:account-deleted]]'); + + if (typeof onSuccess === 'function') { + return onSuccess(); + } + history.back(); }); }); diff --git a/public/src/client/flags/detail.js b/public/src/client/flags/detail.js index 40ee9c4fe2..d3bca9ebc6 100644 --- a/public/src/client/flags/detail.js +++ b/public/src/client/flags/detail.js @@ -1,15 +1,21 @@ 'use strict'; -define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'benchpress'], function (FlagsList, components, translator, Benchpress) { - var Flags = {}; +define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'benchpress', 'forum/account/header'], function (FlagsList, components, translator, Benchpress, AccountHeader) { + var Detail = {}; - Flags.init = function () { + Detail.init = function () { // Update attributes $('#state').val(ajaxify.data.state).removeAttr('disabled'); $('#assignee').val(ajaxify.data.assignee).removeAttr('disabled'); $('[data-action]').on('click', function () { var action = this.getAttribute('data-action'); + var uid; + try { + uid = $(this).parents('[data-uid]').get(0).getAttribute('data-uid'); + } catch (e) { + // noop + } switch (action) { case 'update': @@ -21,7 +27,7 @@ define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'b return app.alertError(err.message); } app.alertSuccess('[[flags:updated]]'); - Flags.reloadHistory(history); + Detail.reloadHistory(history); }); break; @@ -34,18 +40,29 @@ define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'b return app.alertError(err.message); } app.alertSuccess('[[flags:note-added]]'); - Flags.reloadNotes(payload.notes); - Flags.reloadHistory(payload.history); + Detail.reloadNotes(payload.notes); + Detail.reloadHistory(payload.history); }); break; + + case 'chat': + app.newChat(uid); + break; + + case 'ban': + AccountHeader.banAccount(uid, ajaxify.refresh); + break; + + case 'delete': + AccountHeader.deleteAccount(uid, ajaxify.refresh); + break; } }); FlagsList.enableFilterForm(); - FlagsList.enableChatButtons(); }; - Flags.reloadNotes = function (notes) { + Detail.reloadNotes = function (notes) { Benchpress.parse('flags/detail', 'notes', { notes: notes, }, function (html) { @@ -57,7 +74,7 @@ define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'b }); }; - Flags.reloadHistory = function (history) { + Detail.reloadHistory = function (history) { Benchpress.parse('flags/detail', 'history', { history: history, }, function (html) { @@ -70,5 +87,5 @@ define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'b }); }; - return Flags; + return Detail; }); diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index 9c5dc63976..03f552ac31 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -5,7 +5,6 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart) Flags.init = function () { Flags.enableFilterForm(); - Flags.enableChatButtons(); Flags.handleGraphs(); }; @@ -27,12 +26,6 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart) }); }; - Flags.enableChatButtons = function () { - $('[data-chat]').on('click', function () { - app.newChat(this.getAttribute('data-chat')); - }); - }; - Flags.handleGraphs = function () { var dailyCanvas = document.getElementById('flags:daily'); var dailyLabels = utils.getDaysArray().map(function (text, idx) { diff --git a/src/flags.js b/src/flags.js index d65cb44955..f0936982d6 100644 --- a/src/flags.js +++ b/src/flags.js @@ -432,6 +432,8 @@ Flags.update = async function (flagId, uid, changeset) { Flags.getHistory = async function (flagId) { const uids = []; let history = await db.getSortedSetRevRangeWithScores('flag:' + flagId + ':history', 0, -1); + const flagData = await db.getObjectFields('flag:' + flagId, ['type', 'targetId']); + const targetUid = await Flags.getTargetUid(flagData.type, flagData.targetId); history = history.map(function (entry) { entry.value = JSON.parse(entry.value); @@ -451,8 +453,74 @@ Flags.getHistory = async function (flagId) { datetimeISO: utils.toISOString(entry.score), }; }); + + // Append ban history and username change data + let recentBans = await db.getSortedSetRevRange('uid:' + targetUid + ':bans:timestamp', 0, 19); + const usernameChanges = await user.getHistory('user:' + targetUid + ':usernames'); + const emailChanges = await user.getHistory('user:' + targetUid + ':emails'); + + recentBans = await db.getObjects(recentBans); + history = history.concat(recentBans.reduce((memo, cur) => { + uids.push(cur.fromUid); + memo.push({ + uid: cur.fromUid, + meta: [ + { + key: '[[user:banned]]', + value: cur.reason, + labelClass: 'danger', + }, + { + key: '[[user:info.banned-expiry]]', + value: new Date(parseInt(cur.expire, 10)).toISOString(), + labelClass: 'default', + }, + ], + datetime: parseInt(cur.timestamp, 10), + datetimeISO: utils.toISOString(parseInt(cur.timestamp, 10)), + }); + + return memo; + }, [])).concat(usernameChanges.reduce((memo, changeObj) => { + uids.push(targetUid); + memo.push({ + uid: targetUid, + meta: [ + { + key: '[[user:change_username]]', + value: changeObj.value, + labelClass: 'primary', + }, + ], + datetime: changeObj.timestamp, + datetimeISO: changeObj.timestampISO, + }); + + return memo; + }, [])).concat(emailChanges.reduce((memo, changeObj) => { + uids.push(targetUid); + memo.push({ + uid: targetUid, + meta: [ + { + key: '[[user:change_email]]', + value: changeObj.value, + labelClass: 'primary', + }, + ], + datetime: changeObj.timestamp, + datetimeISO: changeObj.timestampISO, + }); + + return memo; + }, [])); + const userData = await user.getUsersFields(uids, ['username', 'userslug', 'picture']); history.forEach((event, idx) => { event.user = userData[idx]; }); + + // Resort by date + history = history.sort((a, b) => b.datetime - a.datetime); + return history; };