@@ -135,7 +135,7 @@ export default class EditUserModal extends Modal {
items.add(
'groups',
-
+
{Object.keys(this.groups)
.map((id) => app.store.getById('groups', id))
@@ -164,7 +164,7 @@ export default class EditUserModal extends Modal {
type: 'submit',
loading: this.loading,
},
- app.translator.trans('core.forum.edit_user.submit_button')
+ app.translator.trans('core.lib.edit_user.submit_button')
)}
,
-10
diff --git a/js/src/forum/compat.js b/js/src/forum/compat.js
index 4d6a0f5db..a2bb3ffe8 100644
--- a/js/src/forum/compat.js
+++ b/js/src/forum/compat.js
@@ -51,7 +51,6 @@ import PostPreview from './components/PostPreview';
import EventPost from './components/EventPost';
import DiscussionHero from './components/DiscussionHero';
import PostMeta from './components/PostMeta';
-import EditUserModal from './components/EditUserModal';
import SearchSource from './components/SearchSource';
import DiscussionRenamedPost from './components/DiscussionRenamedPost';
import DiscussionComposer from './components/DiscussionComposer';
@@ -70,6 +69,10 @@ import Search from './components/Search';
import DiscussionListItem from './components/DiscussionListItem';
import LoadingPost from './components/LoadingPost';
import PostsUserPage from './components/PostsUserPage';
+/**
+ * @deprecated
+ */
+import EditUserModal from '../common/components/EditUserModal';
import DiscussionPageResolver from './resolvers/DiscussionPageResolver';
import BasicEditorDriver from '../common/utils/BasicEditorDriver';
import routes from './routes';
@@ -128,6 +131,9 @@ export default Object.assign(compat, {
'components/EventPost': EventPost,
'components/DiscussionHero': DiscussionHero,
'components/PostMeta': PostMeta,
+ /**
+ * @deprecated Used for backwards compatibility now that the EditUserModal has moved to common. Remove in beta 17.
+ */
'components/EditUserModal': EditUserModal,
'components/SearchSource': SearchSource,
'components/DiscussionRenamedPost': DiscussionRenamedPost,
diff --git a/js/src/forum/utils/UserControls.js b/js/src/forum/utils/UserControls.js
index 8841df6a4..b4512e3dc 100644
--- a/js/src/forum/utils/UserControls.js
+++ b/js/src/forum/utils/UserControls.js
@@ -1,6 +1,6 @@
import Button from '../../common/components/Button';
import Separator from '../../common/components/Separator';
-import EditUserModal from '../components/EditUserModal';
+import EditUserModal from '../../common/components/EditUserModal';
import UserPage from '../components/UserPage';
import ItemList from '../../common/utils/ItemList';
diff --git a/less/admin.less b/less/admin.less
index 22e4d39c2..772207002 100644
--- a/less/admin.less
+++ b/less/admin.less
@@ -10,3 +10,4 @@
@import "admin/ExtensionWidget";
@import "admin/AppearancePage";
@import "admin/MailPage";
+@import "admin/UsersListPage.less";
diff --git a/less/admin/UsersListPage.less b/less/admin/UsersListPage.less
new file mode 100644
index 000000000..d9d2b26d4
--- /dev/null
+++ b/less/admin/UsersListPage.less
@@ -0,0 +1,125 @@
+.UserListPage {
+ // Pad bottom of page to make nav area look less squashed
+ padding-bottom: 24px;
+
+ &-grid {
+ width: 100%;
+ position: relative;
+ border-radius: @border-radius;
+
+ // Use CSS custom properties to define the number of columns in the grid
+ grid-template-columns: repeat(var(--columns), max-content);
+
+ // Ensure mobile scrollbar isn't on top of content
+ padding-bottom: 4px;
+
+ // Table refreshing overlay
+ &--loadingPage {
+ &::after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(128, 128, 128, 0.2);
+ }
+
+ .LoadingIndicator-container {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ &--loaded,
+ &--loadingPage {
+ display: grid;
+ overflow-x: auto;
+ }
+
+ &-header {
+ font-weight: bold;
+ border-bottom: 1px solid @muted-more-color;
+ padding: 8px 16px;
+ background: @control-bg;
+ }
+
+ &--rowItem {
+ padding: 4px 16px;
+ display: flex;
+ align-items: center;
+
+ &[data-column-name="editUser"] {
+ padding: 0;
+ position: relative;
+ }
+ }
+
+ &--shadedRow {
+ background: darken(@body-bg, 3%);
+
+ & when (@config-dark-mode = true) {
+ background: lighten(@body-bg, 5%);
+ }
+ }
+ }
+
+ &-gridPagination {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 16px;
+ }
+}
+
+// Handles styling of default UserList columns
+.UserList {
+ &-joinDate {
+ cursor: help;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ }
+
+ &-editModalBtn {
+ width: 100%;
+ height: 100%;
+
+ border-radius: 0;
+ padding: 0;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ &-email {
+ display: flex;
+ flex-grow: 1;
+
+ &[data-email-shown="false"] {
+ .UserList-emailAddress {
+ user-select: none;
+ filter: blur(4px);
+ cursor: pointer;
+ }
+ }
+
+ &Address {
+ flex-grow: 1;
+ margin-right: 4px;
+ transition: filter 0.2s ease-out;
+ }
+
+ &IconBtn {
+ margin-left: 12px;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+}
diff --git a/less/forum/EditUserModal.less b/less/common/EditUserModal.less
similarity index 100%
rename from less/forum/EditUserModal.less
rename to less/common/EditUserModal.less
diff --git a/less/common/common.less b/less/common/common.less
index 6d126bc29..6b530e0c0 100644
--- a/less/common/common.less
+++ b/less/common/common.less
@@ -17,6 +17,7 @@
@import "Button";
@import "Checkbox";
@import "Dropdown";
+@import "EditUserModal";
@import "Form";
@import "FormControl";
@import "LoadingIndicator";
diff --git a/less/forum.less b/less/forum.less
index 70e4532c7..16c1c1604 100644
--- a/less/forum.less
+++ b/less/forum.less
@@ -7,7 +7,6 @@
@import "forum/DiscussionList";
@import "forum/DiscussionListItem";
@import "forum/DiscussionPage";
-@import "forum/EditUserModal";
@import "forum/Hero";
@import "forum/IndexPage";
@import "forum/LogInButton";
diff --git a/locale/core.yml b/locale/core.yml
index 8ead6d66d..d34bfbc76 100644
--- a/locale/core.yml
+++ b/locale/core.yml
@@ -163,6 +163,8 @@ core:
email_title: => core.admin.email.description
permissions_button: => core.admin.permissions.title
permissions_title: => core.admin.permissions.description
+ userlist_button: => core.admin.users.title
+ userlist_title: => core.admin.users.description
search_placeholder: Search Extensions
# These translations are used in the Permissions page of the admin interface.
@@ -217,6 +219,46 @@ core:
remove_button: => core.ref.remove
upload_button: Choose an Image...
+ # These translations are used for the users list on the admin dashboard.
+ users:
+ description: A paginated list of all users on your forum.
+
+ grid:
+ columns:
+ edit_user:
+ button: => core.ref.edit
+ title: => core.ref.edit_user
+ tooltip: Edit {username}
+
+ email:
+ title: => core.ref.email
+ visibility_hide: Hide email address
+ visibility_show: Show email address
+
+ group_badges:
+ no_badges: None
+ title: Groups
+
+ join_time:
+ title: Joined
+
+ user_id:
+ title: ID
+
+ username:
+ profile_link_tooltip: Visit {username}'s profile
+ title: => core.ref.username
+
+ invalid_column_content: Invalid
+
+ pagination:
+ back_button: Previous page
+ next_button: Next page
+ page_counter: Page {current} of {total}
+
+ title: => core.ref.users
+ total_users: "Total users: {count}"
+
# Translations in this namespace are used by the forum user interface.
forum:
@@ -288,21 +330,6 @@ core:
replied_text: "{username} replied {ago}"
started_text: "{username} started {ago}"
- # These translations are used in the Edit User modal dialog (admin function).
- edit_user:
- activate_button: Activate User
- email_heading: => core.ref.email
- email_label: => core.ref.email
- groups_heading: Groups
- nothing_available: There is nothing available for you to edit at this time.
- password_heading: => core.ref.password
- password_label: => core.ref.password
- set_password_label: Set new password
- submit_button: => core.ref.save_changes
- title: Edit User
- username_heading: => core.ref.username
- username_label: => core.ref.username
-
# These translations are used in the Forgot Password modal dialog.
forgot_password:
dismiss_button: => core.ref.okay
@@ -474,6 +501,20 @@ core:
dropdown:
toggle_dropdown_accessible_label: Toggle dropdown menu
+ # These translations are used in the Edit User modal dialog (admin function).
+ edit_user:
+ activate_button: Activate User
+ email_heading: => core.ref.email
+ email_label: => core.ref.email
+ groups_heading: Groups
+ password_heading: => core.ref.password
+ password_label: => core.ref.password
+ set_password_label: Set new password
+ submit_button: => core.ref.save_changes
+ title: => core.ref.edit_user
+ username_heading: => core.ref.username
+ username_label: => core.ref.username
+
# These translations are displayed as error messages.
error:
dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"
@@ -624,6 +665,7 @@ core:
delete_forever: Delete Forever
discussions: Discussions # Referenced by flarum-statistics.yml
edit: Edit
+ edit_user: Edit User
email: Email
icon: Icon
icon_text: "Enter the name of any
FontAwesome icon class,
including the
fas fa-
prefix."
diff --git a/src/Admin/Content/AdminPayload.php b/src/Admin/Content/AdminPayload.php
index 21347271e..8c4237c8e 100644
--- a/src/Admin/Content/AdminPayload.php
+++ b/src/Admin/Content/AdminPayload.php
@@ -14,6 +14,7 @@ use Flarum\Frontend\Document;
use Flarum\Group\Permission;
use Flarum\Settings\Event\Deserializing;
use Flarum\Settings\SettingsRepositoryInterface;
+use Flarum\User\User;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\ConnectionInterface;
@@ -81,5 +82,18 @@ class AdminPayload
$document->payload['phpVersion'] = PHP_VERSION;
$document->payload['mysqlVersion'] = $this->db->selectOne('select version() as version')->version;
+
+ /**
+ * Used in the admin user list. Implemented as this as it matches the API in flarum/statistics.
+ * If flarum/statistics ext is enabled, it will override this data with its own stats.
+ *
+ * This allows the front-end code to be simpler and use one single source of truth to pull the
+ * total user count from.
+ */
+ $document->payload['modelStatistics'] = [
+ 'users' => [
+ 'total' => User::count()
+ ]
+ ];
}
}