Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
(new Extend\Frontend('forum'))
->js(__DIR__.'/js/dist/forum.js')
->css(__DIR__.'/resources/less/forum.less')
->jsDirectory(__DIR__.'/js/dist/forum')
->route('/users', 'fof_user_directory', Content\UserDirectory::class),

new Extend\Locales(__DIR__.'/resources/locale'),
Expand All @@ -39,7 +38,7 @@
->default('fof-user-directory.admin.settings.link', false)
->default('fof-user-directory.use-small-cards', false)
->default('fof-user-directory.disable-global-search-source', false)
->default('fof-user-directory.default-sort', 'default')
->default('fof-user-directory.default-sort', '')
->default('fof-user-directory.link-group-mentions', true)
->serializeToForum('userDirectorySmallCards', 'fof-user-directory.use-small-cards', 'boolVal')
->serializeToForum('userDirectoryDisableGlobalSearchSource', 'fof-user-directory.disable-global-search-source', 'boolVal')
Expand Down
16 changes: 0 additions & 16 deletions js/src/common/utils/SortMap.js

This file was deleted.

32 changes: 32 additions & 0 deletions js/src/common/utils/SortMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* The sort options for the user directory.
*
* Extensions can add custom sort options using the `extend` helper:
*
* @example
* import { extend } from 'flarum/common/extend';
* import SortMap from 'ext:fof/user-directory/common/utils/SortMap';
*
* extend(SortMap.prototype, 'sortMap', function(map) {
* map.most_best_answers = '-bestAnswerCount';
* map.least_best_answers = 'bestAnswerCount';
* });
*/
export default class SortMap {
/**
* Get the sort map. Extensions can use `extend` to add custom sort options.
* @returns A map of sort keys to API sort parameters
*/
sortMap(): Record<string, string> {
const map: Record<string, string> = {
username_az: 'username',
username_za: '-username',
newest: '-joinedAt',
oldest: 'joinedAt',
most_discussions: '-discussionCount',
least_discussions: 'discussionCount',
};

return map;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import app from 'flarum/forum/app';
import UserCard from 'flarum/forum/components/UserCard';
import ItemList from 'flarum/common/utils/ItemList';
import humanTime from 'flarum/common/utils/humanTime';
import type Mithril from 'mithril';
import type User from 'flarum/common/models/User';

export interface SmallUserCardAttrs extends Mithril.Attributes {
user: User;
className?: string;
editable?: boolean;
controlsButtonClassName?: string;
}

export default class SmallUserCard extends UserCard {
attrs!: SmallUserCardAttrs;

//Overriding infoItems so that other extensions can separately add items to small cards
infoItems() {
const items = new ItemList();
infoItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
const user = this.attrs.user;

items.add('joined', app.translator.trans('core.forum.user.joined_date_text', { ago: humanTime(user.joinTime()) }));
Expand Down
13 changes: 8 additions & 5 deletions js/src/forum/components/UserDirectoryList.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,26 @@ export default class UserDirectoryList extends Component {
const useSmallCards = app.forum.attribute('userDirectorySmallCards');
let loading;

if (state.isLoading()) {
if (state.isLoadingNext()) {
loading = LoadingIndicator.component();
} else if (state.moreResults) {
} else if (state.hasNext()) {
loading = Button.component(
{
className: 'Button',
onclick: state.loadMore.bind(state),
onclick: () => state.loadNext(),
},
app.translator.trans('fof-user-directory.forum.page.load_more_button')
);
}

if (state.empty()) {
if (state.isEmpty()) {
const text = app.translator.trans('fof-user-directory.forum.page.empty_text');
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
}

// Get all users from paginated pages
const allUsers = state.getAllItems();

return (
<div
className={
Expand All @@ -42,7 +45,7 @@ export default class UserDirectoryList extends Component {
}
>
<ul className="UserDirectoryList-users">
{state.users.map((user) => {
{allUsers.map((user) => {
return (
<li key={user.id()} data-id={user.id()}>
{UserDirectoryListItem.component({ user, params, useSmallCards })}
Expand Down
8 changes: 4 additions & 4 deletions js/src/forum/components/UserDirectoryPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Button from 'flarum/common/components/Button';
import Dropdown from 'flarum/common/components/Dropdown';
import extractText from 'flarum/common/utils/extractText';
import UserDirectoryList from './UserDirectoryList';
import UserDirectoryState from '../states/UserDirectoryState';
import UserDirectoryListState from '../states/UserDirectoryListState';
import CheckableButton from './CheckableButton';
import SearchField from './SearchField';
import Separator from 'flarum/common/components/Separator';
Expand All @@ -22,7 +22,7 @@ export default class UserDirectoryPage extends Page {
oninit(vnode) {
super.oninit(vnode);

this.state = new UserDirectoryState({});
this.state = new UserDirectoryListState({}, 1);

// Initialize the group filters before refreshing params
this.enabledGroupFilters = [];
Expand Down Expand Up @@ -57,7 +57,7 @@ export default class UserDirectoryPage extends Page {
sort: preloadedData ? preloadedData.sort : m.route.param('sort'),
};

this.state.refreshParams(params);
this.state.refreshParams(params, 1);

this.bodyClass = 'User--directory';

Expand Down Expand Up @@ -250,7 +250,7 @@ export default class UserDirectoryPage extends Page {
delete params.qBuilder;

// Update the state
this.state.refreshParams(params);
this.state.refreshParams(params, 1);

// Update the URL
m.route.set(app.route('fof_user_directory', params));
Expand Down
5 changes: 4 additions & 1 deletion js/src/forum/extend.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Extend from 'flarum/common/extenders';
import Text from './models/Text';
import UserDirectoryPage from './components/UserDirectoryPage';

export default [
new Extend.Store() //
.add('fof-user-directory-text', Text),

new Extend.Routes() //
.add('fof_user_directory', '/users', () => import('./components/UserDirectoryPage')),
// We don't code split this yet because it's fiddly with commonjs to import dynamically
// on the forum side, but normally on the admin side.
.add('fof_user_directory', '/users', UserDirectoryPage),
];
108 changes: 108 additions & 0 deletions js/src/forum/states/UserDirectoryListState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import app from 'flarum/forum/app';
import PaginatedListState, {
Page,
PaginatedListParams,
PaginatedListRequestParams,
type SortMap as SortMapType,
} from 'flarum/common/states/PaginatedListState';
import User from 'flarum/common/models/User';
import { ApiResponsePlural } from 'flarum/common/Store';
import SortMap from '../../common/utils/SortMap';

export interface UserDirectoryParams extends PaginatedListParams {
sort?: string;
q?: string;
filter?: Record<string, any>;
qBuilder?: Record<string, any>;
}

/**
* State class for managing the user directory list with pagination.
* Based on Flarum's modern DiscussionListState pattern.
*/
export default class UserDirectoryListState extends PaginatedListState<User, UserDirectoryParams> {
protected qBuilder: Record<string, any> = {};

constructor(params: UserDirectoryParams = {}, page: number = 1) {
super(params, page, null);
}

get type(): string {
return 'users';
}

requestParams(): PaginatedListRequestParams {
const params: PaginatedListRequestParams = {
include: ['groups'],
filter: this.params.filter || {},
};

const sortKey = this.params.sort || app.forum.attribute<string>('userDirectoryDefaultSort');
const sortValue = this.sortMap()[sortKey];
params.sort = typeof sortValue === 'string' ? sortValue : sortValue?.sort;

if (this.params.q) {
if (!params.filter) {
params.filter = {};
}
params.filter.q = this.params.q;
}

return params;
}

protected loadPage(page: number = 1): Promise<ApiResponsePlural<User>> {
const preloadedUsers = app.preloadedApiDocument<User[]>();

if (preloadedUsers) {
this.initialLoading = false;
this.pageSize = preloadedUsers.payload.meta?.perPage || UserDirectoryListState.DEFAULT_PAGE_SIZE;

return Promise.resolve(preloadedUsers);
}

return super.loadPage(page);
}

/**
* Get the sort map for the user directory.
*
* **Note for extension developers**: Do NOT extend this method.
* Instead, extend the `SortMap` class from `common/utils/SortMap`:
*
* @example
* import { extend } from 'flarum/common/extend';
* import SortMap from 'ext:fof/user-directory/common/utils/SortMap';
*
* extend(SortMap.prototype, 'sortMap', function (map) {
* map.my_custom_sort = '-customField';
* });
*/
sortMap(): SortMapType {
return {
default: '',
...new SortMap().sortMap(),
};
}

/**
* Update parameters and refresh the list.
* Handles qBuilder logic for building query strings from filters.
*/
public refreshParams(newParams: UserDirectoryParams, page: number = 1): Promise<void> {
// Process qBuilder if provided
if (newParams.qBuilder) {
Object.assign(this.qBuilder, newParams.qBuilder || {});
newParams.q = Object.values(this.qBuilder).join(' ').trim();
}

// Ensure q is a string
if (!newParams.q) {
newParams.q = '';
} else if (typeof newParams.q !== 'string') {
newParams.q = String(newParams.q);
}

return super.refreshParams(newParams, page);
}
}
Loading
Loading