feat: closes #13961, rename ban-reasons to custom reasons

use them for ban, mute and post queue depending on the type selected
if type is set to all, the reason is displayed in ban/mute and post queue
move reason label + dropdown + textarea  to a partial
This commit is contained in:
Barış Soner Uşaklı
2026-02-07 21:41:11 -05:00
parent 1d17352f67
commit 0eaf2beeb2
23 changed files with 266 additions and 194 deletions

View File

@@ -1266,60 +1266,60 @@ trans.vi = public/language/vi/admin/manage/uploads.json
trans.zh_CN = public/language/zh-CN/admin/manage/uploads.json
trans.zh_TW = public/language/zh-TW/admin/manage/uploads.json
[o:nodebb:p:nodebb:r:admin-manage-user-ban-reasons]
file_filter = public/language/<lang>/admin/manage/ban-reasons.json
source_file = public/language/en-GB/admin/manage/ban-reasons.json
[o:nodebb:p:nodebb:r:admin-manage-user-custom-reasons]
file_filter = public/language/<lang>/admin/manage/custom-reasons.json
source_file = public/language/en-GB/admin/manage/custom-reasons.json
source_lang = en_GB
type = KEYVALUEJSON
trans.ar = public/language/ar/admin/manage/ban-reasons.json
trans.az = public/language/az/admin/manage/ban-reasons.json
trans.bg = public/language/bg/admin/manage/ban-reasons.json
trans.bn = public/language/bn/admin/manage/ban-reasons.json
trans.cs = public/language/cs/admin/manage/ban-reasons.json
trans.da = public/language/da/admin/manage/ban-reasons.json
trans.de = public/language/de/admin/manage/ban-reasons.json
trans.el = public/language/el/admin/manage/ban-reasons.json
trans.en_US = public/language/en-US/admin/manage/ban-reasons.json
trans.en@pirate = public/language/en-x-pirate/admin/manage/ban-reasons.json
trans.es = public/language/es/admin/manage/ban-reasons.json
trans.et = public/language/et/admin/manage/ban-reasons.json
trans.fa_IR = public/language/fa-IR/admin/manage/ban-reasons.json
trans.fi = public/language/fi/admin/manage/ban-reasons.json
trans.fr = public/language/fr/admin/manage/ban-reasons.json
trans.gl = public/language/gl/admin/manage/ban-reasons.json
trans.he = public/language/he/admin/manage/ban-reasons.json
trans.hr = public/language/hr/admin/manage/ban-reasons.json
trans.hu = public/language/hu/admin/manage/ban-reasons.json
trans.hy = public/language/hy/admin/manage/ban-reasons.json
trans.id = public/language/id/admin/manage/ban-reasons.json
trans.it = public/language/it/admin/manage/ban-reasons.json
trans.ja = public/language/ja/admin/manage/ban-reasons.json
trans.ko = public/language/ko/admin/manage/ban-reasons.json
trans.lt = public/language/lt/admin/manage/ban-reasons.json
trans.lv = public/language/lv/admin/manage/ban-reasons.json
trans.ms = public/language/ms/admin/manage/ban-reasons.json
trans.nb = public/language/nb/admin/manage/ban-reasons.json
trans.nl = public/language/nl/admin/manage/ban-reasons.json
trans.nn_NO = public/language/nn-NO/admin/manage/ban-reasons.json
trans.pl = public/language/pl/admin/manage/ban-reasons.json
trans.pt_BR = public/language/pt-BR/admin/manage/ban-reasons.json
trans.pt_PT = public/language/pt-PT/admin/manage/ban-reasons.json
trans.ro = public/language/ro/admin/manage/ban-reasons.json
trans.ru = public/language/ru/admin/manage/ban-reasons.json
trans.rw = public/language/rw/admin/manage/ban-reasons.json
trans.sc = public/language/sc/admin/manage/ban-reasons.json
trans.sk = public/language/sk/admin/manage/ban-reasons.json
trans.sl = public/language/sl/admin/manage/ban-reasons.json
trans.sq_AL = public/language/sq-AL/admin/manage/ban-reasons.json
trans.sr = public/language/sr/admin/manage/ban-reasons.json
trans.sv = public/language/sv/admin/manage/ban-reasons.json
trans.th = public/language/th/admin/manage/ban-reasons.json
trans.tr = public/language/tr/admin/manage/ban-reasons.json
trans.uk = public/language/uk/admin/manage/ban-reasons.json
trans.ur = public/language/ur/admin/manage/ban-reasons.json
trans.vi = public/language/vi/admin/manage/ban-reasons.json
trans.zh_CN = public/language/zh-CN/admin/manage/ban-reasons.json
trans.zh_TW = public/language/zh-TW/admin/manage/ban-reasons.json
trans.ar = public/language/ar/admin/manage/custom-reasons.json
trans.az = public/language/az/admin/manage/custom-reasons.json
trans.bg = public/language/bg/admin/manage/custom-reasons.json
trans.bn = public/language/bn/admin/manage/custom-reasons.json
trans.cs = public/language/cs/admin/manage/custom-reasons.json
trans.da = public/language/da/admin/manage/custom-reasons.json
trans.de = public/language/de/admin/manage/custom-reasons.json
trans.el = public/language/el/admin/manage/custom-reasons.json
trans.en_US = public/language/en-US/admin/manage/custom-reasons.json
trans.en@pirate = public/language/en-x-pirate/admin/manage/custom-reasons.json
trans.es = public/language/es/admin/manage/custom-reasons.json
trans.et = public/language/et/admin/manage/custom-reasons.json
trans.fa_IR = public/language/fa-IR/admin/manage/custom-reasons.json
trans.fi = public/language/fi/admin/manage/custom-reasons.json
trans.fr = public/language/fr/admin/manage/custom-reasons.json
trans.gl = public/language/gl/admin/manage/custom-reasons.json
trans.he = public/language/he/admin/manage/custom-reasons.json
trans.hr = public/language/hr/admin/manage/custom-reasons.json
trans.hu = public/language/hu/admin/manage/custom-reasons.json
trans.hy = public/language/hy/admin/manage/custom-reasons.json
trans.id = public/language/id/admin/manage/custom-reasons.json
trans.it = public/language/it/admin/manage/custom-reasons.json
trans.ja = public/language/ja/admin/manage/custom-reasons.json
trans.ko = public/language/ko/admin/manage/custom-reasons.json
trans.lt = public/language/lt/admin/manage/custom-reasons.json
trans.lv = public/language/lv/admin/manage/custom-reasons.json
trans.ms = public/language/ms/admin/manage/custom-reasons.json
trans.nb = public/language/nb/admin/manage/custom-reasons.json
trans.nl = public/language/nl/admin/manage/custom-reasons.json
trans.nn_NO = public/language/nn-NO/admin/manage/custom-reasons.json
trans.pl = public/language/pl/admin/manage/custom-reasons.json
trans.pt_BR = public/language/pt-BR/admin/manage/custom-reasons.json
trans.pt_PT = public/language/pt-PT/admin/manage/custom-reasons.json
trans.ro = public/language/ro/admin/manage/custom-reasons.json
trans.ru = public/language/ru/admin/manage/custom-reasons.json
trans.rw = public/language/rw/admin/manage/custom-reasons.json
trans.sc = public/language/sc/admin/manage/custom-reasons.json
trans.sk = public/language/sk/admin/manage/custom-reasons.json
trans.sl = public/language/sl/admin/manage/custom-reasons.json
trans.sq_AL = public/language/sq-AL/admin/manage/custom-reasons.json
trans.sr = public/language/sr/admin/manage/custom-reasons.json
trans.sv = public/language/sv/admin/manage/custom-reasons.json
trans.th = public/language/th/admin/manage/custom-reasons.json
trans.tr = public/language/tr/admin/manage/custom-reasons.json
trans.uk = public/language/uk/admin/manage/custom-reasons.json
trans.ur = public/language/ur/admin/manage/custom-reasons.json
trans.vi = public/language/vi/admin/manage/custom-reasons.json
trans.zh_CN = public/language/zh-CN/admin/manage/custom-reasons.json
trans.zh_TW = public/language/zh-TW/admin/manage/custom-reasons.json
[o:nodebb:p:nodebb:r:admin-manage-user-custom-fields]
file_filter = public/language/<lang>/admin/manage/user-custom-fields.json

View File

@@ -1,9 +0,0 @@
{
"title": "Manage Ban Reasons",
"create-reason": "Create Reason",
"edit-reason": "Edit Reason",
"reason-title": "Title",
"reason-body": "Body",
"ban-reasons-saved": "Ban reasons saved successfully",
"delete-reason-confirm-x": "Are you sure you want to delete the ban reason with the title <strong>%1</strong>?"
}

View File

@@ -0,0 +1,16 @@
{
"title": "Manage Custom Reasons",
"create-reason": "Create Reason",
"edit-reason": "Edit Reason",
"reasons-help": "Reasons are predefined explanations used when banning or muting users, or when rejecting posts in the post queue.",
"reason-title": "Title",
"reason-type": "Type",
"reason-body": "Body",
"reason-all": "All",
"reason-ban": "Ban",
"reason-mute": "Mute",
"reason-post-queue": "Post Queue",
"reason-type-help": "The type of action this reason applies to. If 'All' is selected, this reason will be available for all action types.",
"custom-reasons-saved": "Custom reasons saved successfully",
"delete-reason-confirm-x": "Are you sure you want to delete the custom reason with the title <strong>%1</strong>?"
}

View File

@@ -23,7 +23,7 @@
"purge": "Delete <strong>User(s)</strong> and <strong>Content</strong>",
"download-csv": "Download CSV",
"custom-user-fields": "Custom User Fields",
"ban-reasons": "Ban Reasons",
"custom-reasons": "Custom Reasons",
"manage-groups": "Manage Groups",
"set-reputation": "Set Reputation",
"add-group": "Add Group",

View File

@@ -78,6 +78,7 @@
"users-csv-exported": "Users csv exported, click to download",
"post-queue-accepted": "Your queued post has been accepted. Click here to see your post.",
"post-queue-rejected": "Your queued post has been rejected.",
"post-queue-rejected-for-reason": "Your queued post has been rejected for the following reason: \"%1\"",
"post-queue-notify": "Queued post received a notification: \"%1\"",
"email-confirmed": "Email Confirmed",

View File

@@ -260,7 +260,7 @@ define('admin/manage/users', [
alerts.error('[[error:no-users-selected]]');
return false; // specifically to keep the menu open
}
const reasons = await socket.emit('user.getBanReasons');
const reasons = await socket.emit('user.getCustomReasons', { type: 'ban' });
const html = await app.parseAndTranslate('modals/temporary-ban', { reasons });
const modal = bootbox.dialog({
title: '[[user:ban-account]]',

View File

@@ -1,9 +1,9 @@
define('admin/manage/user/ban-reasons', [
define('admin/manage/user/custom-reasons', [
'benchpress', 'bootbox', 'alerts', 'translator', 'jquery-ui/widgets/sortable',
], function (benchpress, bootbox, alerts, translator) {
const manageBanReasons = {};
const manageCustomReasons = {};
manageBanReasons.init = function () {
manageCustomReasons.init = function () {
const table = $('table');
$('#new').on('click', () => showModal());
@@ -16,7 +16,7 @@ define('admin/manage/user/ban-reasons', [
table.on('click', '[data-action="delete"]', function () {
const row = $(this).parents('[data-key]');
const title = row.attr('data-title');
bootbox.confirm(`[[admin/manage/ban-reasons:delete-reason-confirm-x, "${title}"]]`, function (ok) {
bootbox.confirm(`[[admin/manage/custom-reasons:delete-reason-confirm-x, "${title}"]]`, function (ok) {
if (!ok) {
return;
}
@@ -35,11 +35,11 @@ define('admin/manage/user/ban-reasons', [
$('tbody tr[data-key]').each((index, el) => {
reasons.push(getDataFromEl($(el)));
});
socket.emit('admin.user.saveBanReasons', reasons, function (err) {
socket.emit('admin.user.saveCustomReasons', reasons, function (err) {
if (err) {
return alerts.error(err);
}
alerts.success('[[admin/manage/ban-reasons:ban-reasons-saved]]');
alerts.success('[[admin/manage/custom-reasons:custom-reasons-saved]]');
});
});
};
@@ -48,18 +48,19 @@ define('admin/manage/user/ban-reasons', [
return {
key: el.attr('data-key'),
title: el.attr('data-title'),
type: el.attr('data-type'),
body: el.attr('data-body'),
};
}
async function showModal(reason = null) {
const html = await benchpress.render('admin/partials/manage-ban-reasons-modal', reason);
const html = await benchpress.render('admin/partials/manage-custom-reasons-modal', reason);
const modal = bootbox.dialog({
message: html,
onEscape: true,
title: reason ?
'[[admin/manage/ban-reasons:edit-reason]]' :
'[[admin/manage/ban-reasons:create-reason]]',
'[[admin/manage/custom-reasons:edit-reason]]' :
'[[admin/manage/custom-reasons:create-reason]]',
buttons: {
submit: {
label: '[[global:save]]',
@@ -69,7 +70,7 @@ define('admin/manage/user/ban-reasons', [
formData.body = translator.escape(formData.body);
formData.parsedBody = translator.escape(await socket.emit('admin.parseRaw', formData.body));
app.parseAndTranslate('admin/manage/users/ban-reasons', 'reasons', {
app.parseAndTranslate('admin/manage/users/custom-reasons', 'reasons', {
reasons: [formData],
}, (html) => {
if (reason) {
@@ -90,7 +91,7 @@ define('admin/manage/user/ban-reasons', [
}
return manageBanReasons;
return manageCustomReasons;
});

View File

@@ -2,11 +2,11 @@
define('forum/post-queue', [
'categoryFilter', 'categorySelector', 'api', 'alerts', 'bootbox',
'accounts/moderate', 'accounts/delete',
'categoryFilter', 'categorySelector', 'api', 'alerts', 'translator',
'bootbox', 'accounts/moderate', 'accounts/delete',
], function (
categoryFilter, categorySelector, api, alerts, bootbox,
AccountModerate, AccountsDelete
categoryFilter, categorySelector, api, alerts, translator,
bootbox, AccountModerate, AccountsDelete
) {
const PostQueue = {};
@@ -158,70 +158,122 @@ define('forum/post-queue', [
AccountsDelete.purge(uid, ajaxify.go.bind(null, 'post-queue'));
break;
default:
handleQueueActions.call(e.target);
case 'accept':
handleAccept(subselector);
break;
case 'reject':
handleReject(subselector);
break;
case 'notify':
handleNotify(subselector);
break;
default:
throw new Error(`Unknown action: ${action}`);
}
}
});
}
}
async function handleQueueActions() {
// accept, reject, notify
const parent = $(this).parents('[data-id]');
const action = $(this).attr('data-action');
function handleAccept(btn) {
const parent = $(btn).parents('[data-id]');
const id = parent.attr('data-id');
const listContainer = parent.get(0).parentNode;
doAction('accept', id).then(() => removePostQueueElement(parent)).catch(alerts.error);
}
if ((!['accept', 'reject', 'notify'].includes(action)) ||
(action === 'reject' && !await confirmReject(ajaxify.data.canAccept ? '[[post-queue:confirm-reject]]' : '[[post-queue:confirm-remove]]'))) {
async function handleReject(btn) {
const parent = $(btn).parents('[data-id]');
const id = parent.attr('data-id');
const translationString = ajaxify.data.canAccept ?
'[[post-queue:confirm-reject]]' :
'[[post-queue:confirm-remove]]';
const message = await getMessage(translationString);
if (message === false) {
return;
}
doAction('reject', id, message).then(() => removePostQueueElement(parent)).catch(alerts.error);
}
function removePostQueueElement(parent) {
const listContainer = parent.get(0).parentNode;
parent.remove();
if (listContainer.childElementCount === 0) {
if (ajaxify.data.singlePost) {
ajaxify.go('/post-queue' + window.location.search);
} else {
ajaxify.refresh();
}
}
}
async function handleNotify(btn) {
const parent = $(btn).parents('[data-id]');
const id = parent.attr('data-id');
const message = await getMessage('[[post-queue:notify-user]]');
if (message === false) {
return;
}
doAction(action, id).then(function () {
if (action === 'accept' || action === 'reject') {
parent.remove();
}
if (listContainer.childElementCount === 0) {
if (ajaxify.data.singlePost) {
ajaxify.go('/post-queue' + window.location.search);
} else {
ajaxify.refresh();
}
}
}).catch(alerts.error);
return false;
doAction('notify', id, message).catch(alerts.error);
}
async function doAction(action, id) {
function getMessage() {
return new Promise((resolve) => {
const modal = bootbox.dialog({
title: '[[post-queue:notify-user]]',
message: '<textarea class="form-control"></textarea>',
buttons: {
OK: {
label: '[[modules:bootbox.send]]',
callback: function () {
const val = modal.find('textarea').val();
if (val) {
resolve(val);
}
},
async function getMessage(title) {
const reasons = await socket.emit('user.getCustomReasons', { type: 'post-queue' });
const html = await app.parseAndTranslate('partials/custom-reason', { reasons });
return new Promise((resolve) => {
let resolved = false;
const done = (value) => {
if (resolved) {
return;
}
resolved = true;
resolve(value);
};
const modal = bootbox.dialog({
title: title,
message: `<form class="form">${html.html()}</form>`,
show: true,
onEscape: true,
buttons: {
close: {
label: '[[global:close]]',
className: 'btn-link',
callback: function () {
done(false);
},
},
});
submit: {
label: '[[modules:bootbox.confirm]]',
callback: function () {
done(modal.find('[name="reason"]').val());
},
},
},
});
}
modal.on('hidden.bs.modal', () => {
done(false);
});
modal.find('[data-key]').on('click', function () {
const reason = reasons.find(r => String(r.key) === $(this).attr('data-key'));
if (reason && reason.body) {
modal.find('[name="reason"]').val(translator.unescape(reason.body));
}
});
});
}
async function doAction(action, id, message = '') {
const actionsMap = {
accept: () => api.post(`/posts/queue/${id}`, {}),
reject: () => api.del(`/posts/queue/${id}`, {}),
notify: async () => api.post(`/posts/queue/${id}/notify`, { message: await getMessage() }),
reject: () => api.del(`/posts/queue/${id}`, { message }),
notify: () => api.post(`/posts/queue/${id}/notify`, { message }),
};
if (actionsMap[action]) {
const result = actionsMap[action]();

View File

@@ -14,6 +14,7 @@ define('forum/account/moderate', [
throwModal({
tpl: 'modals/temporary-ban',
title: '[[user:ban-account]]',
type: 'ban',
onSubmit: function (formData) {
const until = formData.length > 0 ? (
Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))
@@ -36,6 +37,7 @@ define('forum/account/moderate', [
throwModal({
tpl: 'modals/unban',
title: '[[user:unban-account]]',
type: 'ban',
onSubmit: function (formData) {
api.del('/users/' + encodeURIComponent(theirid) + '/ban', {
reason: formData.reason || '',
@@ -51,6 +53,7 @@ define('forum/account/moderate', [
throwModal({
tpl: 'modals/temporary-mute',
title: '[[user:mute-account]]',
type: 'mute',
onSubmit: function (formData) {
const until = formData.length > 0 ? (
Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))
@@ -84,7 +87,7 @@ define('forum/account/moderate', [
};
async function throwModal(options) {
const reasons = await socket.emit('user.getBanReasons');
const reasons = await socket.emit('user.getCustomReasons', { type: options.type || '' });
const html = await app.parseAndTranslate(options.tpl, { reasons });
const modal = bootbox.dialog({
title: options.title,

View File

@@ -592,7 +592,10 @@ postsAPI.removeQueuedPost = async (caller, data) => {
await canEditQueue(caller.uid, data, 'reject');
const result = await posts.removeFromQueue(data.id);
if (result && caller.uid !== parseInt(result.uid, 10)) {
await sendQueueNotification('post-queue-rejected', result.uid, '/');
const msg = validator.escape(String(data.message ? data.message : ''));
await sendQueueNotification(
msg ? 'post-queue-rejected-for-reason' : 'post-queue-rejected', result.uid, '/', msg
);
}
await logQueueEvent(caller, result, 'reject');
};
@@ -612,7 +615,7 @@ postsAPI.notifyQueuedPostOwner = async (caller, data) => {
await canEditQueue(caller.uid, data, 'notify');
const result = await posts.getFromQueue(data.id);
if (result) {
await sendQueueNotification('post-queue-notify', result.uid, `/post-queue/${data.id}`, validator.escape(String(data.message)));
await sendQueueNotification('post-queue-notify', result.uid, `/post-queue/${data.id}`, validator.escape(String(data.message || '')));
}
};

View File

@@ -316,6 +316,6 @@ usersController.customFields = async function (req, res) {
};
usersController.banReasons = async function (req, res) {
const reasons = await user.bans.getBanReasons();
res.render('admin/manage/users/ban-reasons', { reasons });
const reasons = await user.bans.getCustomReasons();
res.render('admin/manage/users/custom-reasons', { reasons });
};

View File

@@ -196,7 +196,7 @@ Posts.acceptQueuedPost = async (req, res) => {
};
Posts.removeQueuedPost = async (req, res) => {
await api.posts.removeQueuedPost(req, { id: req.params.id });
await api.posts.removeQueuedPost(req, { id: req.params.id, message: req.body.message });
helpers.formatApiResponse(200, res);
};

View File

@@ -23,7 +23,7 @@ module.exports = function (app, name, middleware, controllers) {
helpers.setupAdminPageRoute(app, `/${name}/manage/users`, middlewares, controllers.admin.users.index);
helpers.setupAdminPageRoute(app, `/${name}/manage/users/custom-fields`, middlewares, controllers.admin.users.customFields);
helpers.setupAdminPageRoute(app, `/${name}/manage/users/ban-reasons`, middlewares, controllers.admin.users.banReasons);
helpers.setupAdminPageRoute(app, `/${name}/manage/users/custom-reasons`, middlewares, controllers.admin.users.banReasons);
helpers.setupAdminPageRoute(app, `/${name}/manage/registration`, middlewares, controllers.admin.users.registrationQueue);
helpers.setupAdminPageRoute(app, `/${name}/manage/admins-mods`, middlewares, controllers.admin.adminsMods.get);

View File

@@ -219,11 +219,11 @@ User.saveCustomFields = async function (socket, fields) {
await user.reloadCustomFieldWhitelist();
};
User.saveBanReasons = async function (socket, reasons) {
const keys = await db.getSortedSetRange('ban-reasons', 0, -1);
await db.delete('ban-reasons');
await db.deleteAll(keys.map(k => `ban-reason:${k}`));
User.saveCustomReasons = async function (socket, reasons) {
const keys = await db.getSortedSetRange('custom-reasons', 0, -1);
await db.delete('custom-reasons');
await db.deleteAll(keys.map(k => `custom-reason:${k}`));
const ids = reasons.map((f, i) => i);
await db.sortedSetAdd(`ban-reasons`, ids, ids);
await db.setObjectBulk(reasons.map((reason, i) => [`ban-reason:${i}`, reason]));
await db.sortedSetAdd(`custom-reasons`, ids, ids);
await db.setObjectBulk(reasons.map((reason, i) => [`custom-reason:${i}`, reason]));
};

View File

@@ -174,12 +174,12 @@ SocketUser.editModerationNote = async function (socket, data) {
return await user.getModerationNotesByIds(data.uid, [data.id]);
};
SocketUser.getBanReasons = async function (socket) {
SocketUser.getCustomReasons = async function (socket, { type }) {
const canBan = await privileges.users.hasBanPrivilege(socket.uid);
if (!canBan) {
throw new Error('[[error:no-privileges]]');
}
return await user.bans.getBanReasons();
return await user.bans.getCustomReasons({ type });
};
SocketUser.deleteUpload = async function (socket, data) {

View File

@@ -165,14 +165,18 @@ module.exports = function (User) {
return banObj && banObj.reason ? banObj.reason : '';
};
User.bans.getBanReasons = async function () {
const keys = await db.getSortedSetRange('ban-reasons', 0, -1);
const reasons = (await db.getObjects(keys.map(k => `ban-reason:${k}`))).filter(Boolean);
User.bans.getCustomReasons = async function ({ type = '' } = {}) {
const keys = await db.getSortedSetRange('custom-reasons', 0, -1);
type = type || '';
const reasons = (await db.getObjects(keys.map(k => `custom-reason:${k}`))).filter(Boolean);
await Promise.all(reasons.map(async (reason, i) => {
reason.key = i;
reason.parsedBody = translator.escape(await plugins.hooks.fire('filter:parse.raw', reason.body || ''));
reason.body = translator.escape(reason.body);
}));
if (type !== '') {
return reasons.filter(reason => reason.type === type || reason.type === '');
}
return reasons;
};
};

View File

@@ -83,7 +83,7 @@
<li><a target="_blank" href="#" class="dropdown-item rounded-1 export-csv" role="menuitem">[[admin/manage/users:download-csv]]</a></li>
<li><a class="dropdown-item rounded-1" href="{relative_path}/admin/manage/users/custom-fields">[[admin/manage/users:custom-user-fields]]</a>
</li>
<li><a class="dropdown-item rounded-1" href="{relative_path}/admin/manage/users/ban-reasons">[[admin/manage/users:ban-reasons]]</a>
<li><a class="dropdown-item rounded-1" href="{relative_path}/admin/manage/users/custom-reasons">[[admin/manage/users:custom-reasons]]</a>
</li>
</ul>
</div>

View File

@@ -1,16 +1,18 @@
<div class="manage-users d-flex flex-column gap-2 px-lg-4 h-100">
<div class="d-flex border-bottom py-2 m-0 sticky-top acp-page-main-header align-items-center justify-content-between flex-wrap gap-2">
<div class="">
<h4 class="fw-bold tracking-tight mb-0">[[admin/manage/ban-reasons:title]]</h4>
<h4 class="fw-bold tracking-tight mb-0">[[admin/manage/custom-reasons:title]]</h4>
</div>
<div class="d-flex align-items-center gap-1">
<button id="new" class="btn btn-light btn-sm text-nowrap" type="button">
<i class="fa fa-fw fa-plus"></i> [[admin/manage/ban-reasons:create-reason]]
<i class="fa fa-fw fa-plus"></i> [[admin/manage/custom-reasons:create-reason]]
</button>
<button id="save" class="btn btn-primary btn-sm fw-semibold ff-secondary w-100 text-center text-nowrap">[[admin/admin:save-changes]]</button>
</div>
</div>
<p class="text-secondary">[[admin/manage/custom-reasons:reasons-help]]</p>
<div class="row flex-grow-1">
<div class="col-lg-12 d-flex flex-column gap-2">
<div class="table-responsive flex-grow-1">
@@ -18,18 +20,20 @@
<thead>
<tr>
<th></th>
<th class="text-muted">[[admin/manage/ban-reasons:reason-title]]</th>
<th class="text-muted">[[admin/manage/ban-reasons:reason-body]]</th>
<th class="text-muted">[[admin/manage/custom-reasons:reason-title]]</th>
<th class="text-muted">[[admin/manage/custom-reasons:reason-type]]</th>
<th class="text-muted">[[admin/manage/custom-reasons:reason-body]]</th>
<th></th>
</tr>
</thead>
<tbody>
{{{ each reasons }}}
<tr data-key="{./key}" data-title="{./title}" data-body="{./body}">
<tr data-key="{./key}" data-title="{./title}" data-type="{./type}" data-body="{./body}">
<td class="" style="width: 32px;">
<a href="#" component="sort/handle" class="btn btn-light btn-sm d-none d-md-block ui-sortable-handle" style="cursor:grab;"><i class="fa fa-arrows-up-down text-muted"></i></a>
</td>
<td class="text-nowrap ">{./title}</td>
<td class="text-nowrap">{./title}</td>
<td class="text-nowrap">{{{ if ./type }}}[[admin/manage/custom-reasons:reason-{{./type}}]]{{{ else }}}[[admin/manage/custom-reasons:reason-all]]{{{ end }}}</td>
<td class="">{./parsedBody}</td>
<td class="">
<div class="d-flex justify-content-end gap-1">

View File

@@ -1,11 +0,0 @@
<form>
<div class="mb-3">
<label class="form-label">[[admin/manage/ban-reasons:reason-title]]</label>
<input class="form-control" type="text" name="title" value="{./title}">
</div>
<div class="mb-3">
<label class="form-label">[[admin/manage/ban-reasons:reason-body]]</label>
<textarea rows="8" class="form-control" type="text" name="body">{./body}</textarea>
</div>
</form>

View File

@@ -0,0 +1,22 @@
<form>
<div class="mb-3">
<label class="form-label">[[admin/manage/custom-reasons:reason-title]]</label>
<input class="form-control" type="text" name="title" value="{./title}">
</div>
<div class="mb-3">
<label class="form-label">[[admin/manage/custom-reasons:reason-type]]</label>
<select class="form-select" name="type">
<option value="" {{{ if (type == "") }}}selected{{{ end }}} >[[admin/manage/custom-reasons:reason-all]]</option>
<option value="ban" {{{ if (type == "ban") }}}selected{{{ end }}}>[[admin/manage/custom-reasons:reason-ban]]</option>
<option value="mute" {{{ if (type == "mute") }}}selected{{{ end }}}>[[admin/manage/custom-reasons:reason-mute]]</option>
<option value="post-queue" {{{ if (type == "post-queue") }}}selected{{{ end }}}>[[admin/manage/custom-reasons:reason-post-queue]]</option>
</select>
<p class="form-text">[[admin/manage/custom-reasons:reason-type-help]]</p>
</div>
<div class="mb-3">
<label class="form-label">[[admin/manage/custom-reasons:reason-body]]</label>
<textarea rows="8" class="form-control" type="text" name="body">{./body}</textarea>
</div>
</form>

View File

@@ -23,24 +23,7 @@
</div>
<div class="col-12">
<div class="mb-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<label class="form-label mb-0" for="reason">[[admin/manage/users:temp-ban.reason]]</label>
{{{ if reasons.length }}}
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" id="reasonDropdown" data-bs-toggle="dropdown" aria-expanded="false">
[[admin/manage/users:temp-ban.select-reason]]
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-end p-1" aria-labelledby="reasonDropdown">
{{{ each reasons }}}
<li><a class="dropdown-item rounded-1" href="#" data-key="{./key}">{./title}</a></li>
{{{ end }}}
</ul>
</div>
{{{ end }}}
</div>
<textarea rows="8" type="text" class="form-control" id="reason" name="reason"></textarea>
<!-- IMPORT partials/custom-reason.tpl -->
</div>
</div>
</div>

View File

@@ -24,24 +24,7 @@
<div class="col-12">
<div class="mb-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<label class="form-label mb-0" for="reason">[[admin/manage/users:temp-ban.reason]]</label>
{{{ if reasons.length }}}
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" id="reasonDropdown" data-bs-toggle="dropdown" aria-expanded="false">
[[admin/manage/users:temp-ban.select-reason]]
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-end p-1" aria-labelledby="reasonDropdown">
{{{ each reasons }}}
<li><a class="dropdown-item rounded-1" href="#" data-key="{./key}">{./title}</a></li>
{{{ end }}}
</ul>
</div>
{{{ end }}}
</div>
<textarea rows="8" type="text" class="form-control" id="reason" name="reason"></textarea>
<!-- IMPORT partials/custom-reason.tpl -->
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div>
<div class="d-flex align-items-center justify-content-between mb-2">
<label class="form-label mb-0" for="reason">[[admin/manage/users:temp-ban.reason]]</label>
{{{ if reasons.length }}}
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" id="reasonDropdown" data-bs-toggle="dropdown" aria-expanded="false">
[[admin/manage/users:temp-ban.select-reason]]
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-end p-1" aria-labelledby="reasonDropdown">
{{{ each reasons }}}
<li><a class="dropdown-item rounded-1" href="#" data-key="{./key}">{./title}</a></li>
{{{ end }}}
</ul>
</div>
{{{ end }}}
</div>
<textarea rows="8" type="text" class="form-control" id="reason" name="reason"></textarea>
</div>