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:
@@ -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.
|
||||
|
@@ -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) {
|
||||
|
@@ -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'));
|
||||
|
Reference in New Issue
Block a user