Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(flags): add pagination to flags list #3931

Merged
merged 12 commits into from
Nov 23, 2023
12 changes: 6 additions & 6 deletions extensions/flags/js/dist-typings/forum/states/FlagListState.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 38 additions & 32 deletions extensions/flags/js/src/forum/components/FlagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import HeaderListItem from 'flarum/forum/components/HeaderListItem';
import type Mithril from 'mithril';
import type Post from 'flarum/common/models/Post';
import type FlagListState from '../states/FlagListState';
import type Flag from '../models/Flag';
import { Page } from 'flarum/common/states/PaginatedListState';

export interface IFlagListAttrs extends ComponentAttrs {
state: FlagListState;
Expand All @@ -16,49 +18,53 @@ export interface IFlagListAttrs extends ComponentAttrs {
export default class FlagList<CustomAttrs extends IFlagListAttrs = IFlagListAttrs> extends Component<CustomAttrs, FlagListState> {
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
super.oninit(vnode);
this.state = this.attrs.state;
}

view() {
const flags = this.state.cache || [];
const state = this.attrs.state;

return (
<HeaderList
className="FlagList"
title={app.translator.trans('flarum-flags.forum.flagged_posts.title')}
hasItems={flags.length}
loading={this.state.loading}
hasItems={state.hasItems()}
loading={state.isLoading()}
emptyText={app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}
loadMore={() => state.hasNext() && !state.isLoadingNext() && state.loadNext()}
>
<ul className="HeaderListGroup-content">
OrdinaryJellyfish marked this conversation as resolved.
Show resolved Hide resolved
{!this.state.loading &&
flags.map((flag) => {
const post = flag.post() as Post;

return (
<li>
<HeaderListItem
className="Flag"
avatar={<Avatar user={post.user() || null} />}
icon="fas fa-flag"
content={app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
username: username(post.user()),
em: <em />,
discussion: post.discussion().title(),
})}
excerpt={post.contentPlain()}
datetime={flag.createdAt()}
href={app.route.post(post)}
onclick={(e: MouseEvent) => {
app.flags.index = post;
e.redraw = false;
}}
/>
</li>
);
})}
</ul>
{this.content(state)}
</HeaderList>
);
}

content(state: FlagListState) {
if (!state.isLoading() && state.hasItems()) {
return state.getPages().map((page: Page<Flag>) => {
return page.items.map((flag: Flag) => {
const post = flag.post() as Post;

return (
<HeaderListItem
className="Flag"
avatar={<Avatar user={post.user() || null} />}
icon="fas fa-flag"
content={app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
username: username(post.user()),
em: <em />,
discussion: post.discussion().title(),
})}
excerpt={post.contentPlain()}
datetime={flag.createdAt()}
href={app.route.post(post)}
onclick={(e: MouseEvent) => {
e.redraw = false;
}}
/>
);
});
});
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class FlagsDropdown<CustomAttrs extends IFlagsDropdownAttrs = IFl
}

getUnreadCount() {
return app.flags.cache ? app.flags.cache.length : app.forum.attribute<number>('flagCount');
return app.forum.attribute<number>('flagCount');
}

getNewCount() {
Expand Down
36 changes: 15 additions & 21 deletions extensions/flags/js/src/forum/states/FlagListState.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
import type ForumApplication from 'flarum/forum/ForumApplication';
import type Flag from '../models/Flag';
import type Post from 'flarum/common/models/Post';
import PaginatedListState from 'flarum/common/states/PaginatedListState';

export default class FlagListState {
export default class FlagListState extends PaginatedListState<Flag> {
public app: ForumApplication;
public loading = false;
public cache: Flag[] | null = null;
public index: Post | false | null = null;

constructor(app: ForumApplication) {
super({}, 1, null);
this.app = app;
}

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

/**
* Load flags into the application's cache if they haven't already
* been loaded.
*/
load() {
if (this.cache && !this.app.session.user!.attribute<number>('newFlagCount')) {
return;
load(): Promise<void> {
if (this.app.session.user?.attribute<number>('newFlagCount')) {
this.pages = [];
this.location = { page: 1 };
}

this.loading = true;
m.redraw();
if (this.pages.length > 0) {
return Promise.resolve();
}

this.app.store
.find<Flag[]>('flags')
.then((flags) => {
this.app.session.user!.pushAttributes({ newFlagCount: 0 });
this.cache = flags.sort((a, b) => b.createdAt()!.getTime() - a.createdAt()!.getTime());
})
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});
return super.loadNext();
}
}
44 changes: 38 additions & 6 deletions extensions/flags/src/Api/Controller/ListFlagsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Flarum\Flags\Api\Serializer\FlagSerializer;
use Flarum\Flags\Flag;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;

Expand All @@ -28,26 +29,57 @@ class ListFlagsController extends AbstractListController
'post.discussion'
];

public function __construct(
protected UrlGenerator $url
) {
}

protected function data(ServerRequestInterface $request, Document $document): iterable
{
$actor = RequestUtil::getActor($request);
$include = $this->extractInclude($request);

$actor->assertRegistered();

$actor->read_flags_at = Carbon::now();
$actor->save();

$flags = Flag::whereVisibleTo($actor)
->latest('flags.created_at')
->groupBy('post_id')
->get();
$limit = $this->extractLimit($request);
$offset = $this->extractOffset($request);
$include = $this->extractInclude($request);

if (in_array('post.user', $include)) {
$include[] = 'post.user.groups';
}

$this->loadRelations($flags, $include);
$primaries = Flag::whereVisibleTo($actor)
->groupBy('post_id')
->orderBy('created_at', 'DESC')
->skip($offset)
->take($limit + 1);

$flags = Flag::whereVisibleTo($actor)
->select('flags.*')
->joinSub($primaries, 'p', 'flags.id', '=', 'p.id')
->latest()
->get();
OrdinaryJellyfish marked this conversation as resolved.
Show resolved Hide resolved

$this->loadRelations($flags, $include, $request);

$flags = $flags->all();

$areMoreResults = false;

if (count($flags) > $limit) {
array_pop($flags);
$areMoreResults = true;
}

$this->addPaginationData(
$document,
$request,
$this->url->to('api')->route('flags.index'),
$areMoreResults ? null : 0
);

return $flags;
}
Expand Down