1
0
mirror of https://github.com/flarum/core.git synced 2025-08-04 15:37:51 +02:00

feat(pm): messages anchor link (#4175)

This commit is contained in:
Sami Mazouz
2025-02-08 18:30:35 +01:00
committed by GitHub
parent 333bbb11e2
commit db1e36d545
19 changed files with 324 additions and 69 deletions

View File

@@ -1,8 +1,7 @@
import app from '../../common/app';
import Model from '../Model';
import { ApiQueryParamsPlural, ApiResponsePlural } from '../Store';
import type Model from '../Model';
import type { ApiQueryParamsPlural, ApiResponsePlural } from '../Store';
import type Mithril from 'mithril';
import setRouteWithForcedRefresh from '../utils/setRouteWithForcedRefresh';
export type SortMapItem =
| string
@@ -15,9 +14,9 @@ export type SortMap = {
[key: string]: SortMapItem;
};
export interface Page<TModel> {
export interface Page<TModel extends Model> {
number: number;
items: TModel[];
items: ApiResponsePlural<TModel> | TModel[];
hasPrev?: boolean;
hasNext?: boolean;
@@ -73,7 +72,7 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
}
public loadPrev(): Promise<void> {
if (this.loadingPrev || this.getLocation().page === 1) return Promise.resolve();
if (this.loadingPrev || !this.hasPrev()) return Promise.resolve();
this.loadingPrev = true;
@@ -140,7 +139,7 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
delete params.include;
}
return app.store.find<T[]>(this.type, params).then((results) => {
return app.store.find<T[]>(this.type, this.mutateRequestParams(params, page)).then((results) => {
const usedPerPage = results.payload?.meta?.perPage;
const usedTotal = results.payload?.meta?.page?.total;
@@ -160,6 +159,35 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
});
}
protected mutateRequestParams(params: ApiQueryParamsPlural, page: number): ApiQueryParamsPlural {
/*
* Support use of page[near]=
*/
if (params.page?.near && this.hasItems()) {
delete params.page?.near;
const nextPage = this.location.page < page;
const offsets = this.getPages().map((page) => {
if ('payload' in page.items) {
return page.items.payload.meta?.page?.offset || 0;
}
return 0;
});
const minOffset = Math.min(...offsets);
const maxOffset = Math.max(...offsets);
const limit = this.pageSize || PaginatedListState.DEFAULT_PAGE_SIZE;
params.page ||= {};
params.page.offset = nextPage ? maxOffset + limit : Math.max(minOffset - limit, 0);
}
return params;
}
/**
* Get the parameters that should be passed in the API request.
* Do not include page offset unless subclass overrides loadPage.

View File

@@ -33,7 +33,6 @@ use Tobyz\JsonApiServer\Pagination\Pagination;
use Tobyz\JsonApiServer\Schema\Concerns\HasMeta;
use function Tobyz\JsonApiServer\json_api_response;
use function Tobyz\JsonApiServer\parse_sort_string;
class Index extends Endpoint
{
@@ -69,24 +68,18 @@ class Index extends Endpoint
protected function setUp(): void
{
$this->route('GET', '/')
->query(function ($query, ?Pagination $pagination, Context $context): Context {
->query(function ($query, ?Pagination $pagination, Context $context, array $filters, ?array $sort, int $offset, ?int $limit): Context {
$collection = $context->collection;
// This model has a searcher API, so we'll use that instead of the default.
// The searcher API allows swapping the default search engine for a custom one.
/** @var SearchManager $search */
$search = $context->api->getContainer()->make(SearchManager::class);
$modelClass = $collection instanceof AbstractDatabaseResource ? $collection->model() : null;
if ($query instanceof Builder && $search->searchable($modelClass)) {
$actor = $context->getActor();
$extracts = $this->defaultExtracts($context);
$filters = $this->extractFilterValue($context, $extracts);
$sort = $this->extractSortValue($context, $extracts);
$limit = $this->extractLimitValue($context, $extracts);
$offset = $this->extractOffsetValue($context, $extracts);
$sortIsDefault = ! $context->queryParam('sort');
$results = $search->query(
@@ -100,8 +93,8 @@ class Index extends Endpoint
else {
$context = $context->withQuery($query);
$this->applySorts($query, $context);
$this->applyFilters($query, $context);
$this->applySorts($query, $context, $sort);
$this->applyFilters($query, $context, $filters);
if ($pagination && method_exists($pagination, 'apply')) {
$pagination->apply($query);
@@ -129,8 +122,20 @@ class Index extends Endpoint
$pagination = ($this->paginationResolver)($context);
$extracts = $this->defaultExtracts($context);
$filters = $this->extractFilterValue($context, $extracts);
$sort = $this->extractSortValue($context, $extracts);
$limit = $this->extractLimitValue($context, $extracts);
$offset = $this->extractOffsetValue($context, $extracts);
if ($pagination instanceof OffsetPagination) {
$pagination->offset = $offset;
$pagination->limit = $limit;
}
if ($this->query) {
$context = ($this->query)($query, $pagination, $context);
$context = ($this->query)($query, $pagination, $context, $filters, $sort, $offset, $limit);
if (! $context instanceof Context) {
throw new RuntimeException('The Index endpoint query closure must return a Context instance.');
@@ -139,8 +144,8 @@ class Index extends Endpoint
/** @var Context $context */
$context = $context->withQuery($query);
$this->applySorts($query, $context);
$this->applyFilters($query, $context);
$this->applySorts($query, $context, $sort);
$this->applyFilters($query, $context, $filters);
if ($pagination) {
$pagination->apply($query);
@@ -205,9 +210,9 @@ class Index extends Endpoint
return $this;
}
final protected function applySorts($query, Context $context): void
final protected function applySorts($query, Context $context, ?array $sort): void
{
if (! ($sortString = $context->queryParam('sort', $this->defaultSort))) {
if (! $sort) {
return;
}
@@ -219,7 +224,7 @@ class Index extends Endpoint
$sorts = $collection->resolveSorts();
foreach (parse_sort_string($sortString) as [$name, $direction]) {
foreach ($sort as $name => $direction) {
foreach ($sorts as $field) {
if ($field->name === $name && $field->isVisible($context)) {
$field->apply($query, $direction, $context);
@@ -233,18 +238,12 @@ class Index extends Endpoint
}
}
final protected function applyFilters($query, Context $context): void
final protected function applyFilters($query, Context $context, array $filters): void
{
if (! ($filters = $context->queryParam('filter'))) {
if (empty($filters)) {
return;
}
if (! is_array($filters)) {
throw (new BadRequestException('filter must be an array'))->setSource([
'parameter' => 'filter',
]);
}
$collection = $context->collection;
if (! $collection instanceof Listable) {

View File

@@ -40,8 +40,10 @@ class ListTest extends TestCase
$this->request('GET', '/api/groups')
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$body = $response->getBody()->getContents();
$this->assertEquals(200, $response->getStatusCode(), $body);
$data = json_decode($body, true);
// The four default groups created by the installer
$this->assertEquals(['1', '2', '3', '4'], Arr::pluck($data['data'], 'id'));