mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-03 11:01:20 +01:00
refactor: users table
hide uid/ips behind dropdown, AP uids break flow stack username/email allow range selection
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
"inactive.12-months": "12 months",
|
||||
|
||||
"users.uid": "uid",
|
||||
"users.user-id": "User ID",
|
||||
"users.username": "username",
|
||||
"users.email": "email",
|
||||
"users.no-email": "(no email)",
|
||||
@@ -63,6 +64,7 @@
|
||||
"users.validation-pending": "Validation Pending",
|
||||
"users.validation-expired": "Validation Expired",
|
||||
"users.ip": "IP",
|
||||
"users.recent-ips": "Recent IPs",
|
||||
"users.postcount": "postcount",
|
||||
"users.reputation": "reputation",
|
||||
"users.flags": "flags",
|
||||
|
||||
@@ -142,10 +142,6 @@ define('admin/manage/users', [
|
||||
unselectAll();
|
||||
}
|
||||
|
||||
$('[component="user/select/all"]').on('click', function () {
|
||||
$('.users-table [component="user/select/single"]').prop('checked', $(this).is(':checked'));
|
||||
});
|
||||
|
||||
$('.manage-groups').on('click', function () {
|
||||
const uids = getSelectedUids();
|
||||
if (!uids.length) {
|
||||
@@ -494,6 +490,10 @@ define('admin/manage/users', [
|
||||
handleDelete('[[admin/manage/users:alerts.confirm-purge]]', '');
|
||||
});
|
||||
|
||||
$('[component="user/select/all"]').on('click', function () {
|
||||
$('.users-table [component="user/select/single"]').prop('checked', $(this).is(':checked'));
|
||||
});
|
||||
|
||||
const tableEl = document.querySelector('.users-table');
|
||||
const actionBtn = document.getElementById('action-dropdown');
|
||||
tableEl.addEventListener('change', (e) => {
|
||||
@@ -508,6 +508,57 @@ define('admin/manage/users', [
|
||||
}
|
||||
});
|
||||
|
||||
let lastSelectedUser;
|
||||
$(tableEl).on('click', '[component="user/select/single"]', function (ev) {
|
||||
function selectRange(clickedUserRow) {
|
||||
function selectIndexRange(start, end, isChecked) {
|
||||
if (start > end) {
|
||||
const tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
}
|
||||
const rows = $('.user-row');
|
||||
for (let i = start; i <= end; i += 1) {
|
||||
rows.eq(i).find('.form-check-input').prop('checked', isChecked).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastSelectedUser) {
|
||||
lastSelectedUser = $('.user-row').first();
|
||||
}
|
||||
|
||||
const isClickedSelected = clickedUserRow.find('[component="user/select/single"]').is(':checked');
|
||||
|
||||
const clickedIndex = clickedUserRow.index();
|
||||
const lastIndex = lastSelectedUser.index();
|
||||
selectIndexRange(clickedIndex, lastIndex, isClickedSelected);
|
||||
}
|
||||
|
||||
const checkBox = $(this);
|
||||
const userRow = checkBox.parents('.user-row');
|
||||
if (ev.shiftKey) {
|
||||
selectRange(userRow);
|
||||
lastSelectedUser = userRow;
|
||||
return true;
|
||||
}
|
||||
|
||||
lastSelectedUser = userRow;
|
||||
});
|
||||
|
||||
$('[data-copy]').on('click', function () {
|
||||
const btn = $(this);
|
||||
navigator.clipboard.writeText(this.getAttribute('data-copy'));
|
||||
btn.find('i')
|
||||
.removeClass('fa-copy')
|
||||
.addClass('fa-check text-success');
|
||||
setTimeout(() => {
|
||||
btn.find('i')
|
||||
.removeClass('fa-check text-success')
|
||||
.addClass('fa-copy');
|
||||
}, 2000);
|
||||
return false;
|
||||
});
|
||||
|
||||
function handleDelete(confirmMsg, path) {
|
||||
const uids = getSelectedUids();
|
||||
if (!uids.length) {
|
||||
|
||||
@@ -105,78 +105,85 @@
|
||||
<table class="table users-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input component="user/select/all" type="checkbox"/></th>
|
||||
<th class="text-end text-muted">[[admin/manage/users:users.uid]]</th>
|
||||
<th class="text-muted">[[admin/manage/users:users.username]]</th>
|
||||
<th class="text-muted">[[admin/manage/users:users.email]]</th>
|
||||
<th class="text-muted">[[admin/manage/users:users.ip]]</th>
|
||||
<th><div><input class="form-check-input border-secondary" component="user/select/all" type="checkbox"/></div></th>
|
||||
<th class="text-muted">[[admin/manage/users:users.username]] / [[admin/manage/users:users.email]]</th>
|
||||
<th data-sort="postcount" class="text-end pointer text-nowrap">[[admin/manage/users:users.postcount]] {{{if sort_postcount}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="reputation" class="text-end pointer text-nowrap">[[admin/manage/users:users.reputation]] {{{if sort_reputation}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="flags" class="text-end pointer text-nowrap">[[admin/manage/users:users.flags]] {{{if sort_flags}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="joindate" class="pointer text-nowrap">[[admin/manage/users:users.joined]] {{{if sort_joindate}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="lastonline" class="pointer text-nowrap">[[admin/manage/users:users.last-online]] {{{if sort_lastonline}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{ each users }}}
|
||||
<tr class="user-row align-middle">
|
||||
<td><input component="user/select/single" data-uid="{users.uid}" type="checkbox"/></th>
|
||||
<td class="text-end text-tabular">{users.uid}</td>
|
||||
<tr class="user-row align-middle hover-parent">
|
||||
<td><div><input class="form-check-input border-secondary" component="user/select/single" data-uid="{users.uid}" type="checkbox"/></div></td>
|
||||
<td>
|
||||
<i title="[[admin/manage/users:users.banned]]" class="ban fa fa-gavel text-danger{{{ if !users.banned }}} hidden{{{ end }}}"></i>
|
||||
<i class="administrator fa fa-shield text-success{{{ if !users.administrator }}} hidden{{{ end }}}"></i>
|
||||
<a href="{config.relative_path}/user/{users.userslug}"> {users.username}</a>
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<em class="text-muted no-email {{{ if (./email || ./emailToConfirm) }}}hidden{{{ end }}} ">[[admin/manage/users:users.no-email]]</em>
|
||||
<div>
|
||||
<i title="[[admin/manage/users:users.banned]]" class="ban fa fa-gavel text-danger{{{ if !users.banned }}} hidden{{{ end }}}"></i>
|
||||
<i class="administrator fa fa-shield text-success{{{ if !users.administrator }}} hidden{{{ end }}}"></i>
|
||||
<a href="{config.relative_path}/user/{users.userslug}"> {users.username}</a>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-1 text-truncate text-muted">
|
||||
<em class="text-muted no-email {{{ if (./email || ./emailToConfirm) }}}hidden{{{ end }}} ">[[admin/manage/users:users.no-email]]</em>
|
||||
|
||||
<span class="validated {{{ if !users.email:confirmed }}} hidden{{{ end }}}">
|
||||
<i class="fa fa-fw fa-check text-success" title="[[admin/manage/users:users.validated]]" data-bs-toggle="tooltip"></i>
|
||||
<span class="email">{{{ if ./email }}}{./email}{{{ end }}}</span>
|
||||
</span>
|
||||
<span class="validated {{{ if !users.email:confirmed }}} hidden{{{ end }}}">
|
||||
<span class="email">{{{ if ./email }}}{./email}{{{ end }}}</span>
|
||||
<i class="fa fa-fw fa-check text-success" title="[[admin/manage/users:users.validated]]" data-bs-toggle="tooltip"></i>
|
||||
|
||||
<span class="validated-by-admin hidden">
|
||||
<i class="fa fa-fw fa-check text-success" title="[[admin/manage/users:users.validated]]" data-bs-toggle="tooltip"></i>
|
||||
<span class="email">{{{ if ./emailToConfirm }}}{./emailToConfirm}{{{ end }}}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="pending {{{ if (!./emailToConfirm || !users.email:pending) }}} hidden{{{ end }}}">
|
||||
<i class="fa fa-fw fa-clock-o text-warning" title="[[admin/manage/users:users.validation-pending]]" data-bs-toggle="tooltip"></i>
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
</span>
|
||||
<span class="validated-by-admin hidden">
|
||||
<span class="email">{{{ if ./emailToConfirm }}}{./emailToConfirm}{{{ end }}}</span>
|
||||
<i class="fa fa-fw fa-check text-success" title="[[admin/manage/users:users.validated]]" data-bs-toggle="tooltip"></i>
|
||||
|
||||
<span class="expired {{{ if (!./emailToConfirm || !users.email:expired) }}} hidden{{{ end }}}">
|
||||
<i class="fa fa-fw fa-times text-danger" title="[[admin/manage/users:users.validation-expired]]" data-bs-toggle="tooltip"></i>
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="notvalidated {{{ if (!./emailToConfirm || (users.email:expired || (users.email:pending || users.email:confirmed))) }}} hidden{{{ end }}}">
|
||||
<i class="fa fa-fw fa-times text-danger" title="[[admin/manage/users:users.not-validated]]" data-bs-toggle="tooltip"></i>
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
</span>
|
||||
<span class="pending {{{ if (!./emailToConfirm || !users.email:pending) }}} hidden{{{ end }}}">
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
<i class="fa fa-fw fa-clock-o text-warning" title="[[admin/manage/users:users.validation-pending]]" data-bs-toggle="tooltip"></i>
|
||||
</span>
|
||||
|
||||
<span class="expired {{{ if (!./emailToConfirm || !users.email:expired) }}} hidden{{{ end }}}">
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
<i class="fa fa-fw fa-times text-danger" title="[[admin/manage/users:users.validation-expired]]" data-bs-toggle="tooltip"></i>
|
||||
</span>
|
||||
|
||||
<span class="notvalidated {{{ if (!./emailToConfirm || (users.email:expired || (users.email:pending || users.email:confirmed))) }}} hidden{{{ end }}}">
|
||||
<span class="email">{./emailToConfirm}</span>
|
||||
<i class="fa fa-fw fa-times text-danger" title="[[admin/manage/users:users.not-validated]]" data-bs-toggle="tooltip"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="text-end text-tabular">{formattedNumber(users.postcount)}</td>
|
||||
<td class="text-end text-tabular" component="user/reputation" data-uid="{users.uid}">{formattedNumber(users.reputation)}</td>
|
||||
<td class="text-end text-tabular">{{{ if users.flags }}}{users.flags}{{{ else }}}0{{{ end }}}</td>
|
||||
<td class="text-nowrap"><span class="timeago" title="{users.joindateISO}"></span></td>
|
||||
<td class="text-nowrap"><span class="timeago" title="{users.lastonlineISO}"></span></td>
|
||||
<td>
|
||||
{{{ if ./ips.length }}}
|
||||
<div class="dropdown">
|
||||
<div class="dropdown hover-visible">
|
||||
<button class="btn btn-light btn-sm" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-fw fa-list text-muted"></i></button>
|
||||
<ul class="dropdown-menu p-1" role="menu">
|
||||
<li><h6 class="dropdown-header">[[admin/manage/users:users.user-id]]</h6></li>
|
||||
<li class="d-flex gap-1">
|
||||
<a class="dropdown-item rounded-1" role="menuitem">{./uid}</a>
|
||||
<button data-copy="{./uid}" class="btn btn-light btn-sm"><i class="fa fa-copy"></i></button>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">[[admin/manage/users:users.recent-ips]]</h6></li>
|
||||
{{{ each ./ips }}}
|
||||
<li class="d-flex gap-1 {{{ if !@last }}}mb-1{{{ end }}}">
|
||||
<a class="dropdown-item rounded-1" role="menuitem">{@value}</a>
|
||||
<button data-ip="{@value}" onclick="navigator.clipboard.writeText(this.getAttribute('data-ip'))" class="btn btn-light btn-sm"><i class="fa fa-copy"></i></button>
|
||||
<button data-copy="{@value}" class="btn btn-light btn-sm"><i class="fa fa-copy"></i></button>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</td>
|
||||
<td class="text-end text-tabular">{formattedNumber(users.postcount)}</td>
|
||||
<td class="text-end text-tabular" component="user/reputation" data-uid="{users.uid}">{formattedNumber(users.reputation)}</td>
|
||||
<td class="text-end text-tabular">{{{ if users.flags }}}{users.flags}{{{ else }}}0{{{ end }}}</td>
|
||||
<td><span class="timeago" title="{users.joindateISO}"></span></td>
|
||||
<td><span class="timeago" title="{users.lastonlineISO}"></span></td>
|
||||
</tr>
|
||||
{{{ end }}}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user