mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: ability to mute users
new mute privilege
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
"upload-files": "Upload Files",
|
"upload-files": "Upload Files",
|
||||||
"signature": "Signature",
|
"signature": "Signature",
|
||||||
"ban": "Ban",
|
"ban": "Ban",
|
||||||
|
"mute": "Mute",
|
||||||
"invite": "Invite",
|
"invite": "Invite",
|
||||||
"search-content": "Search Content",
|
"search-content": "Search Content",
|
||||||
"search-users": "Search Users",
|
"search-users": "Search Users",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
"create.password": "Password",
|
"create.password": "Password",
|
||||||
"create.password-confirm": "Confirm Password",
|
"create.password-confirm": "Confirm Password",
|
||||||
|
|
||||||
"temp-ban.length": "Ban Length",
|
"temp-ban.length": "Length",
|
||||||
"temp-ban.reason": "Reason <span class=\"text-muted\">(Optional)</span>",
|
"temp-ban.reason": "Reason <span class=\"text-muted\">(Optional)</span>",
|
||||||
"temp-ban.hours": "Hours",
|
"temp-ban.hours": "Hours",
|
||||||
"temp-ban.days": "Days",
|
"temp-ban.days": "Days",
|
||||||
|
|||||||
@@ -124,6 +124,9 @@
|
|||||||
"already-unbookmarked": "You have already unbookmarked this post",
|
"already-unbookmarked": "You have already unbookmarked this post",
|
||||||
|
|
||||||
"cant-ban-other-admins": "You can't ban other admins!",
|
"cant-ban-other-admins": "You can't ban other admins!",
|
||||||
|
"cant-mute-other-admins": "You can't mute other admins!",
|
||||||
|
"user-muted-for-hours": "You have been muted, you will be able to post in %1 hour(s)",
|
||||||
|
"user-muted-for-minutes": "You have been muted, you will be able to post in %1 minute(s)",
|
||||||
"cant-make-banned-users-admin": "You can't make banned users admin.",
|
"cant-make-banned-users-admin": "You can't make banned users admin.",
|
||||||
"cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
|
"cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
|
||||||
"account-deletion-disabled": "Account deletion is disabled",
|
"account-deletion-disabled": "Account deletion is disabled",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"ban_account": "Ban Account",
|
"ban_account": "Ban Account",
|
||||||
"ban_account_confirm": "Do you really want to ban this user?",
|
"ban_account_confirm": "Do you really want to ban this user?",
|
||||||
"unban_account": "Unban Account",
|
"unban_account": "Unban Account",
|
||||||
|
"mute_account": "Mute Account",
|
||||||
|
"unmute_account": "Unmute Account",
|
||||||
"delete_account": "Delete Account",
|
"delete_account": "Delete Account",
|
||||||
"delete_account_as_admin": "Delete <strong>Account</strong>",
|
"delete_account_as_admin": "Delete <strong>Account</strong>",
|
||||||
"delete_content": "Delete Account <strong>Content</strong>",
|
"delete_content": "Delete Account <strong>Content</strong>",
|
||||||
@@ -171,6 +173,7 @@
|
|||||||
"info.banned-permanently": "Banned permanently",
|
"info.banned-permanently": "Banned permanently",
|
||||||
"info.banned-reason-label": "Reason",
|
"info.banned-reason-label": "Reason",
|
||||||
"info.banned-no-reason": "No reason given.",
|
"info.banned-no-reason": "No reason given.",
|
||||||
|
"info.muted-no-reason": "No reason given.",
|
||||||
"info.username-history": "Username History",
|
"info.username-history": "Username History",
|
||||||
"info.email-history": "Email History",
|
"info.email-history": "Email History",
|
||||||
"info.moderation-note": "Moderation Note",
|
"info.moderation-note": "Moderation Note",
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ paths:
|
|||||||
$ref: 'write/users/uid/follow.yaml'
|
$ref: 'write/users/uid/follow.yaml'
|
||||||
/users/{uid}/ban:
|
/users/{uid}/ban:
|
||||||
$ref: 'write/users/uid/ban.yaml'
|
$ref: 'write/users/uid/ban.yaml'
|
||||||
|
/users/{uid}/mute:
|
||||||
|
$ref: 'write/users/uid/mute.yaml'
|
||||||
/users/{uid}/tokens:
|
/users/{uid}/tokens:
|
||||||
$ref: 'write/users/uid/tokens.yaml'
|
$ref: 'write/users/uid/tokens.yaml'
|
||||||
/users/{uid}/tokens/{token}:
|
/users/{uid}/tokens/{token}:
|
||||||
|
|||||||
61
public/openapi/write/users/uid/mute.yaml
Normal file
61
public/openapi/write/users/uid/mute.yaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
summary: mute a user
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: uid
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: uid of the user to mute
|
||||||
|
example: 2
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
until:
|
||||||
|
type: number
|
||||||
|
description: UNIX timestamp of the mute expiry
|
||||||
|
example: 1585775608076
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
example: the reason for the mute
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successfully muted user
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
summary: unmute a user
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: uid
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
description: uid of the user to unmute
|
||||||
|
example: 2
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successfully unmuted user
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
@@ -54,7 +54,9 @@ define('forum/account/header', [
|
|||||||
components.get('account/ban').on('click', function () {
|
components.get('account/ban').on('click', function () {
|
||||||
banAccount(ajaxify.data.theirid);
|
banAccount(ajaxify.data.theirid);
|
||||||
});
|
});
|
||||||
|
components.get('account/mute').on('click', muteAccount);
|
||||||
components.get('account/unban').on('click', unbanAccount);
|
components.get('account/unban').on('click', unbanAccount);
|
||||||
|
components.get('account/unmute').on('click', unmuteAccount);
|
||||||
components.get('account/delete-account').on('click', handleDeleteEvent.bind(null, 'account'));
|
components.get('account/delete-account').on('click', handleDeleteEvent.bind(null, 'account'));
|
||||||
components.get('account/delete-content').on('click', handleDeleteEvent.bind(null, 'content'));
|
components.get('account/delete-content').on('click', handleDeleteEvent.bind(null, 'content'));
|
||||||
components.get('account/delete-all').on('click', handleDeleteEvent.bind(null, 'purge'));
|
components.get('account/delete-all').on('click', handleDeleteEvent.bind(null, 'purge'));
|
||||||
@@ -177,6 +179,49 @@ define('forum/account/header', [
|
|||||||
}).catch(alerts.error);
|
}).catch(alerts.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function muteAccount() {
|
||||||
|
Benchpress.render('admin/partials/temporary-mute', {}).then(function (html) {
|
||||||
|
bootbox.dialog({
|
||||||
|
className: 'mute-modal',
|
||||||
|
title: '[[user:mute_account]]',
|
||||||
|
message: html,
|
||||||
|
show: true,
|
||||||
|
buttons: {
|
||||||
|
close: {
|
||||||
|
label: '[[global:close]]',
|
||||||
|
className: 'btn-link',
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
label: '[[user:mute_account]]',
|
||||||
|
callback: function () {
|
||||||
|
const formData = $('.mute-modal form').serializeArray().reduce(function (data, cur) {
|
||||||
|
data[cur.name] = cur.value;
|
||||||
|
return data;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const until = formData.length > 0 ? (
|
||||||
|
Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))
|
||||||
|
) : 0;
|
||||||
|
|
||||||
|
api.put('/users/' + ajaxify.data.theirid + '/mute', {
|
||||||
|
until: until,
|
||||||
|
reason: formData.reason || '',
|
||||||
|
}).then(() => {
|
||||||
|
ajaxify.refresh();
|
||||||
|
}).catch(alerts.error);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmuteAccount() {
|
||||||
|
api.del('/users/' + ajaxify.data.theirid + '/mute').then(() => {
|
||||||
|
ajaxify.refresh();
|
||||||
|
}).catch(alerts.error);
|
||||||
|
}
|
||||||
|
|
||||||
function flagAccount() {
|
function flagAccount() {
|
||||||
require(['flags'], function (flags) {
|
require(['flags'], function (flags) {
|
||||||
flags.showFlagModal({
|
flags.showFlagModal({
|
||||||
|
|||||||
@@ -225,6 +225,53 @@ usersAPI.unban = async function (caller, data) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usersAPI.mute = async function (caller, data) {
|
||||||
|
if (!await privileges.users.hasMutePrivilege(caller.uid)) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
} else if (await user.isAdministrator(data.uid)) {
|
||||||
|
throw new Error('[[error:cant-mute-other-admins]]');
|
||||||
|
}
|
||||||
|
await db.setObject(`user:${data.uid}`, {
|
||||||
|
mutedUntil: data.until,
|
||||||
|
mutedReason: data.reason || '[[user:info.muted-no-reason]]',
|
||||||
|
});
|
||||||
|
|
||||||
|
await events.log({
|
||||||
|
type: 'user-mute',
|
||||||
|
uid: caller.uid,
|
||||||
|
targetUid: data.uid,
|
||||||
|
ip: caller.ip,
|
||||||
|
reason: data.reason || undefined,
|
||||||
|
});
|
||||||
|
plugins.hooks.fire('action:user.muted', {
|
||||||
|
callerUid: caller.uid,
|
||||||
|
ip: caller.ip,
|
||||||
|
uid: data.uid,
|
||||||
|
until: data.until > 0 ? data.until : undefined,
|
||||||
|
reason: data.reason || undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
usersAPI.unmute = async function (caller, data) {
|
||||||
|
if (!await privileges.users.hasMutePrivilege(caller.uid)) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.deleteObjectFields(`user:${data.uid}`, ['mutedUntil', 'mutedReason']);
|
||||||
|
|
||||||
|
await events.log({
|
||||||
|
type: 'user-unmute',
|
||||||
|
uid: caller.uid,
|
||||||
|
targetUid: data.uid,
|
||||||
|
ip: caller.ip,
|
||||||
|
});
|
||||||
|
plugins.hooks.fire('action:user.unmuted', {
|
||||||
|
callerUid: caller.uid,
|
||||||
|
ip: caller.ip,
|
||||||
|
uid: data.uid,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
|
async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
|
||||||
const { uid } = caller;
|
const { uid } = caller;
|
||||||
const isSelf = parseInt(uid, 10) === parseInt(data.uid, 10);
|
const isSelf = parseInt(uid, 10) === parseInt(data.uid, 10);
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
|
|||||||
userData.isSelfOrAdminOrGlobalModerator = isSelf || isAdmin || isGlobalModerator;
|
userData.isSelfOrAdminOrGlobalModerator = isSelf || isAdmin || isGlobalModerator;
|
||||||
userData.canEdit = results.canEdit;
|
userData.canEdit = results.canEdit;
|
||||||
userData.canBan = results.canBanUser;
|
userData.canBan = results.canBanUser;
|
||||||
|
userData.canMute = results.canMuteUser;
|
||||||
userData.canFlag = (await privileges.users.canFlag(callerUID, userData.uid)).flag;
|
userData.canFlag = (await privileges.users.canFlag(callerUID, userData.uid)).flag;
|
||||||
userData.canChangePassword = isAdmin || (isSelf && !meta.config['password:disableEdit']);
|
userData.canChangePassword = isAdmin || (isSelf && !meta.config['password:disableEdit']);
|
||||||
userData.isSelf = isSelf;
|
userData.isSelf = isSelf;
|
||||||
@@ -95,6 +96,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
|
|||||||
|
|
||||||
userData.sso = results.sso.associations;
|
userData.sso = results.sso.associations;
|
||||||
userData.banned = Boolean(userData.banned);
|
userData.banned = Boolean(userData.banned);
|
||||||
|
userData.muted = parseInt(userData.mutedUntil, 10) > Date.now();
|
||||||
userData.website = escape(userData.website);
|
userData.website = escape(userData.website);
|
||||||
userData.websiteLink = !userData.website.startsWith('http') ? `http://${userData.website}` : userData.website;
|
userData.websiteLink = !userData.website.startsWith('http') ? `http://${userData.website}` : userData.website;
|
||||||
userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), '');
|
userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), '');
|
||||||
@@ -144,6 +146,7 @@ async function getAllData(uid, callerUID) {
|
|||||||
sso: plugins.hooks.fire('filter:auth.list', { uid: uid, associations: [] }),
|
sso: plugins.hooks.fire('filter:auth.list', { uid: uid, associations: [] }),
|
||||||
canEdit: privileges.users.canEdit(callerUID, uid),
|
canEdit: privileges.users.canEdit(callerUID, uid),
|
||||||
canBanUser: privileges.users.canBanUser(callerUID, uid),
|
canBanUser: privileges.users.canBanUser(callerUID, uid),
|
||||||
|
canMuteUser: privileges.users.canMuteUser(callerUID, uid),
|
||||||
isBlocked: user.blocks.is(uid, callerUID),
|
isBlocked: user.blocks.is(uid, callerUID),
|
||||||
canViewInfo: privileges.global.can('view:users:info', callerUID),
|
canViewInfo: privileges.global.can('view:users:info', callerUID),
|
||||||
hasPrivateChat: messaging.hasPrivateChat(callerUID, uid),
|
hasPrivateChat: messaging.hasPrivateChat(callerUID, uid),
|
||||||
|
|||||||
@@ -111,6 +111,16 @@ Users.unban = async (req, res) => {
|
|||||||
helpers.formatApiResponse(200, res);
|
helpers.formatApiResponse(200, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Users.mute = async (req, res) => {
|
||||||
|
await api.users.mute(req, { ...req.body, uid: req.params.uid });
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Users.unmute = async (req, res) => {
|
||||||
|
await api.users.unmute(req, { ...req.body, uid: req.params.uid });
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
Users.generateToken = async (req, res) => {
|
Users.generateToken = async (req, res) => {
|
||||||
await hasAdminPrivilege(req.uid, 'settings');
|
await hasAdminPrivilege(req.uid, 'settings');
|
||||||
if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10)) {
|
if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10)) {
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ events.types = [
|
|||||||
'user-removeAdmin',
|
'user-removeAdmin',
|
||||||
'user-ban',
|
'user-ban',
|
||||||
'user-unban',
|
'user-unban',
|
||||||
|
'user-mute',
|
||||||
|
'user-unmute',
|
||||||
'user-delete',
|
'user-delete',
|
||||||
'user-deleteAccount',
|
'user-deleteAccount',
|
||||||
'user-deleteContent',
|
'user-deleteContent',
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ privsGlobal.privilegeLabels = [
|
|||||||
{ name: '[[admin/manage/privileges:view-groups]]' },
|
{ name: '[[admin/manage/privileges:view-groups]]' },
|
||||||
{ name: '[[admin/manage/privileges:allow-local-login]]' },
|
{ name: '[[admin/manage/privileges:allow-local-login]]' },
|
||||||
{ name: '[[admin/manage/privileges:ban]]' },
|
{ name: '[[admin/manage/privileges:ban]]' },
|
||||||
|
{ name: '[[admin/manage/privileges:mute]]' },
|
||||||
{ name: '[[admin/manage/privileges:view-users-info]]' },
|
{ name: '[[admin/manage/privileges:view-users-info]]' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ privsGlobal.userPrivilegeList = [
|
|||||||
'view:groups',
|
'view:groups',
|
||||||
'local:login',
|
'local:login',
|
||||||
'ban',
|
'ban',
|
||||||
|
'mute',
|
||||||
'view:users:info',
|
'view:users:info',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,21 @@ privsUsers.canBanUser = async function (callerUid, uid) {
|
|||||||
return data.canBan;
|
return data.canBan;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
privsUsers.canMuteUser = async function (callerUid, uid) {
|
||||||
|
const privsGlobal = require('./global');
|
||||||
|
const [canMute, isTargetAdmin] = await Promise.all([
|
||||||
|
privsGlobal.can('mute', callerUid),
|
||||||
|
privsUsers.isAdministrator(uid),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const data = await plugins.hooks.fire('filter:user.canMuteUser', {
|
||||||
|
canMute: canMute && !isTargetAdmin,
|
||||||
|
callerUid: callerUid,
|
||||||
|
uid: uid,
|
||||||
|
});
|
||||||
|
return data.canMute;
|
||||||
|
};
|
||||||
|
|
||||||
privsUsers.canFlag = async function (callerUid, uid) {
|
privsUsers.canFlag = async function (callerUid, uid) {
|
||||||
const [userReputation, targetPrivileged, reporterPrivileged] = await Promise.all([
|
const [userReputation, targetPrivileged, reporterPrivileged] = await Promise.all([
|
||||||
user.getUserField(callerUid, 'reputation'),
|
user.getUserField(callerUid, 'reputation'),
|
||||||
@@ -126,6 +141,7 @@ privsUsers.canFlag = async function (callerUid, uid) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
privsUsers.hasBanPrivilege = async uid => await hasGlobalPrivilege('ban', uid);
|
privsUsers.hasBanPrivilege = async uid => await hasGlobalPrivilege('ban', uid);
|
||||||
|
privsUsers.hasMutePrivilege = async uid => await hasGlobalPrivilege('mute', uid);
|
||||||
privsUsers.hasInvitePrivilege = async uid => await hasGlobalPrivilege('invite', uid);
|
privsUsers.hasInvitePrivilege = async uid => await hasGlobalPrivilege('invite', uid);
|
||||||
|
|
||||||
async function hasGlobalPrivilege(privilege, uid) {
|
async function hasGlobalPrivilege(privilege, uid) {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ function authenticatedRoutes() {
|
|||||||
setupApiRoute(router, 'put', '/:uid/ban', [...middlewares, middleware.assert.user], controllers.write.users.ban);
|
setupApiRoute(router, 'put', '/:uid/ban', [...middlewares, middleware.assert.user], controllers.write.users.ban);
|
||||||
setupApiRoute(router, 'delete', '/:uid/ban', [...middlewares, middleware.assert.user], controllers.write.users.unban);
|
setupApiRoute(router, 'delete', '/:uid/ban', [...middlewares, middleware.assert.user], controllers.write.users.unban);
|
||||||
|
|
||||||
|
setupApiRoute(router, 'put', '/:uid/mute', [...middlewares, middleware.assert.user], controllers.write.users.mute);
|
||||||
|
setupApiRoute(router, 'delete', '/:uid/mute', [...middlewares, middleware.assert.user], controllers.write.users.unmute);
|
||||||
|
|
||||||
setupApiRoute(router, 'post', '/:uid/tokens', [...middlewares, middleware.assert.user], controllers.write.users.generateToken);
|
setupApiRoute(router, 'post', '/:uid/tokens', [...middlewares, middleware.assert.user], controllers.write.users.generateToken);
|
||||||
setupApiRoute(router, 'delete', '/:uid/tokens/:token', [...middlewares, middleware.assert.user], controllers.write.users.deleteToken);
|
setupApiRoute(router, 'delete', '/:uid/tokens/:token', [...middlewares, middleware.assert.user], controllers.write.users.deleteToken);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const intFields = [
|
|||||||
'uid', 'postcount', 'topiccount', 'reputation', 'profileviews',
|
'uid', 'postcount', 'topiccount', 'reputation', 'profileviews',
|
||||||
'banned', 'banned:expire', 'email:confirmed', 'joindate', 'lastonline',
|
'banned', 'banned:expire', 'email:confirmed', 'joindate', 'lastonline',
|
||||||
'lastqueuetime', 'lastposttime', 'followingCount', 'followerCount',
|
'lastqueuetime', 'lastposttime', 'followingCount', 'followerCount',
|
||||||
'blocksCount', 'passwordExpiry',
|
'blocksCount', 'passwordExpiry', 'mutedUntil',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = function (User) {
|
module.exports = function (User) {
|
||||||
@@ -25,7 +25,7 @@ module.exports = function (User) {
|
|||||||
'aboutme', 'signature', 'uploadedpicture', 'profileviews', 'reputation',
|
'aboutme', 'signature', 'uploadedpicture', 'profileviews', 'reputation',
|
||||||
'postcount', 'topiccount', 'lastposttime', 'banned', 'banned:expire',
|
'postcount', 'topiccount', 'lastposttime', 'banned', 'banned:expire',
|
||||||
'status', 'flags', 'followerCount', 'followingCount', 'cover:url',
|
'status', 'flags', 'followerCount', 'followingCount', 'cover:url',
|
||||||
'cover:position', 'groupTitle',
|
'cover:position', 'groupTitle', 'mutedUntil', 'mutedReason',
|
||||||
];
|
];
|
||||||
|
|
||||||
User.guestData = {
|
User.guestData = {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = function (User) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [userData, isAdminOrMod] = await Promise.all([
|
const [userData, isAdminOrMod] = await Promise.all([
|
||||||
User.getUserFields(uid, ['uid', 'banned', 'joindate', 'email', 'reputation'].concat([field])),
|
User.getUserFields(uid, ['uid', 'banned', 'mutedUntil', 'joindate', 'email', 'reputation'].concat([field])),
|
||||||
privileges.categories.isAdminOrMod(cid, uid),
|
privileges.categories.isAdminOrMod(cid, uid),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -35,6 +35,16 @@ module.exports = function (User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
if (userData.mutedUntil > now) {
|
||||||
|
let muteLeft = ((userData.mutedUntil - now) / (1000 * 60));
|
||||||
|
if (muteLeft > 60) {
|
||||||
|
muteLeft = (muteLeft / 60).toFixed(0);
|
||||||
|
throw new Error(`[[error:user-muted-for-hours, ${muteLeft}]]`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`[[error:user-muted-for-minutes, ${muteLeft.toFixed(0)}]]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (now - userData.joindate < meta.config.initialPostDelay * 1000) {
|
if (now - userData.joindate < meta.config.initialPostDelay * 1000) {
|
||||||
throw new Error(`[[error:user-too-new, ${meta.config.initialPostDelay}]]`);
|
throw new Error(`[[error:user-too-new, ${meta.config.initialPostDelay}]]`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
<thead>
|
<thead>
|
||||||
{{{ if !isAdminPriv }}}
|
{{{ if !isAdminPriv }}}
|
||||||
<tr class="privilege-table-header">
|
<tr class="privilege-table-header">
|
||||||
<th colspan="3"></th>
|
<th class="privilege-filters btn-toolbar" colspan="100">
|
||||||
<th class="arrowed" colspan="6">
|
<!-- IF privileges.columnCountGroupOther -->
|
||||||
[[admin/manage/categories:privileges.section-posting]]
|
<button type="button" data-filter="19,99" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-other]]</button>
|
||||||
</th>
|
<!-- END -->
|
||||||
<th class="arrowed" colspan="7">
|
<button type="button" data-filter="16,18" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-moderation]]</button>
|
||||||
[[admin/manage/categories:privileges.section-viewing]]
|
<button type="button" data-filter="3,8" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-posting]]</button>
|
||||||
</th>
|
<button type="button" data-filter="9,15" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-viewing]]</button>
|
||||||
<th class="arrowed" colspan="2">
|
|
||||||
[[admin/manage/categories:privileges.section-moderation]]
|
|
||||||
</th>
|
</th>
|
||||||
</tr><tr><!-- zebrastripe reset --></tr>
|
</tr><tr><!-- zebrastripe reset --></tr>
|
||||||
{{{ end }}}
|
{{{ end }}}
|
||||||
@@ -65,9 +63,18 @@
|
|||||||
<label>[[admin/manage/privileges:user-privileges]]</label>
|
<label>[[admin/manage/privileges:user-privileges]]</label>
|
||||||
<table class="table table-striped privilege-table">
|
<table class="table table-striped privilege-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
{{{ if !isAdminPriv }}}
|
||||||
<tr class="privilege-table-header">
|
<tr class="privilege-table-header">
|
||||||
<th colspan="15"></th>
|
<th class="privilege-filters btn-toolbar" colspan="100">
|
||||||
|
<!-- IF privileges.columnCountGroupOther -->
|
||||||
|
<button type="button" data-filter="21,99" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-other]]</button>
|
||||||
|
<!-- END -->
|
||||||
|
<button type="button" data-filter="18,20" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-moderation]]</button>
|
||||||
|
<button type="button" data-filter="10,17" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-posting]]</button>
|
||||||
|
<button type="button" data-filter="3,9" class="btn btn-default pull-right">[[admin/manage/categories:privileges.section-viewing]]</button>
|
||||||
|
</th>
|
||||||
</tr><tr><!-- zebrastripe reset --></tr>
|
</tr><tr><!-- zebrastripe reset --></tr>
|
||||||
|
{{{ end }}}
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
|
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
|
||||||
<th class="text-center">[[admin/manage/privileges:select-clear-all]]</th>
|
<th class="text-center">[[admin/manage/privileges:select-clear-all]]</th>
|
||||||
|
|||||||
27
src/views/admin/partials/temporary-mute.tpl
Normal file
27
src/views/admin/partials/temporary-mute.tpl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<form class="form">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="length">[[admin/manage/users:temp-ban.length]]</label>
|
||||||
|
<input class="form-control" id="length" name="length" type="number" min="0" value="1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-8">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="reason">[[admin/manage/users:temp-ban.reason]]</label>
|
||||||
|
<input type="text" class="form-control" id="reason" name="reason" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4 text-center">
|
||||||
|
<div class="form-group units">
|
||||||
|
<label>[[admin/manage/users:temp-ban.hours]]</label>
|
||||||
|
<input type="radio" name="unit" value="0" checked />
|
||||||
|
|
||||||
|
<label>[[admin/manage/users:temp-ban.days]]</label>
|
||||||
|
<input type="radio" name="unit" value="1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
Reference in New Issue
Block a user