0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Add base to paginate (#11253)

* add base to `paginate()`

* update tests

* remove condicional cache

* add missing base param

* add missing leading slash in tests

* remove default

* fix: add missing trailing slash in pagination root test

* Add feedback from code review

* add changeset

* rebase and run format code

* Update .changeset/twenty-cobras-push.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* add code diff

* fix rebase

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* chore: merge next

* update changeset

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Kevin 2024-08-28 05:31:32 -04:00 committed by GitHub
parent 36e6dee475
commit 4e5cc5aadd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 63 additions and 20 deletions

View file

@ -0,0 +1,32 @@
---
'astro': major
---
Changes the data returned for `page.url.current`, `page.url.next`, `page.url.prev`, `page.url.first` and `page.url.last` to include the value set for `base` in your Astro config.
Previously, you had to manually prepend your configured value for `base` to the URL path. Now, Astro automatically includes your `base` value in `next` and `prev` URLs.
If you are using the `paginate()` function for "previous" and "next" URLs, remove any existing `base` value as it is now added for you:
```diff
---
export async function getStaticPaths({ paginate }) {
const astronautPages = [{
astronaut: 'Neil Armstrong',
}, {
astronaut: 'Buzz Aldrin',
}, {
astronaut: 'Sally Ride',
}, {
astronaut: 'John Glenn',
}];
return paginate(astronautPages, { pageSize: 1 });
}
const { page } = Astro.props;
// `base: /'docs'` configured in `astro.config.mjs`
- const prev = "/docs" + page.url.prev;
+ const prev = page.url.prev;
---
<a id="prev" href={prev}>Back</a>
```

View file

@ -220,7 +220,7 @@ async function getPathsForRoute(
pipeline: BuildPipeline,
builtPaths: Set<string>,
): Promise<Array<string>> {
const { logger, options, routeCache, serverLike } = pipeline;
const { logger, options, routeCache, serverLike, config } = pipeline;
let paths: Array<string> = [];
if (route.pathname) {
paths.push(route.pathname);
@ -232,6 +232,7 @@ async function getPathsForRoute(
routeCache,
logger,
ssr: serverLike,
base: config.base,
}).catch((err) => {
logger.error('build', `Failed to call getStaticPaths for ${route.component}`);
throw err;

View file

@ -109,7 +109,7 @@ export class RenderContext {
slots: Record<string, any> = {},
): Promise<Response> {
const { cookies, middleware, pipeline } = this;
const { logger, serverLike, streaming } = pipeline;
const { logger, serverLike, streaming, manifest } = pipeline;
const props =
Object.keys(this.props).length > 0
@ -121,6 +121,7 @@ export class RenderContext {
pathname: this.pathname,
logger,
serverLike,
base: manifest.base,
});
const apiContext = this.createAPIContext(props);

View file

@ -5,11 +5,14 @@ import type {
Params,
Props,
} from '../../types/public/common.js';
import type { AstroConfig } from '../../types/public/index.js';
import type { RouteData } from '../../types/public/internal.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { joinPaths } from '../path.js';
export function generatePaginateFunction(
routeMatch: RouteData,
base: AstroConfig['base'],
): (...args: Parameters<PaginateFunction>) => ReturnType<PaginateFunction> {
return function paginateUtility(
data: any[],
@ -41,34 +44,36 @@ export function generatePaginateFunction(
...additionalParams,
[paramName]: includesFirstPageNumber || pageNum > 1 ? String(pageNum) : undefined,
};
const current = correctIndexRoute(routeMatch.generate({ ...params }));
const current = addRouteBase(routeMatch.generate({ ...params }), base);
const next =
pageNum === lastPage
? undefined
: correctIndexRoute(routeMatch.generate({ ...params, page: String(pageNum + 1) }));
: addRouteBase(routeMatch.generate({ ...params, page: String(pageNum + 1) }), base);
const prev =
pageNum === 1
? undefined
: correctIndexRoute(
: addRouteBase(
routeMatch.generate({
...params,
page:
!includesFirstPageNumber && pageNum - 1 === 1 ? undefined : String(pageNum - 1),
}),
base,
);
const first =
pageNum === 1
? undefined
: correctIndexRoute(
: addRouteBase(
routeMatch.generate({
...params,
page: includesFirstPageNumber ? '1' : undefined,
}),
base,
);
const last =
pageNum === lastPage
? undefined
: correctIndexRoute(routeMatch.generate({ ...params, page: String(lastPage) }));
: addRouteBase(routeMatch.generate({ ...params, page: String(lastPage) }), base);
return {
params,
props: {
@ -90,12 +95,11 @@ export function generatePaginateFunction(
};
}
function correctIndexRoute(route: string) {
function addRouteBase(route: string, base: AstroConfig['base']) {
// `routeMatch.generate` avoids appending `/`
// unless `trailingSlash: 'always'` is configured.
// This means an empty string is possible for the index route.
if (route === '') {
return '/';
}
return route;
let routeWithBase = joinPaths(base, route);
if (routeWithBase === '') routeWithBase = '/';
return routeWithBase;
}

View file

@ -16,10 +16,11 @@ interface GetParamsAndPropsOptions {
pathname: string;
logger: Logger;
serverLike: boolean;
base: string;
}
export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
const { logger, mod, routeData: route, routeCache, pathname, serverLike } = opts;
const { logger, mod, routeData: route, routeCache, pathname, serverLike, base } = opts;
// If there's no route, or if there's a pathname (e.g. a static `src/pages/normal.astro` file),
// then we know for sure they don't have params and props, return a fallback value.
@ -49,6 +50,7 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
routeCache,
logger,
ssr: serverLike,
base,
});
const matchedStaticPath = findPathItemByKey(staticPaths, params, route, logger);

View file

@ -6,7 +6,7 @@ import type {
PaginateFunction,
Params,
} from '../../types/public/common.js';
import type { RuntimeMode } from '../../types/public/config.js';
import type { AstroConfig, RuntimeMode } from '../../types/public/config.js';
import type { RouteData } from '../../types/public/internal.js';
import type { Logger } from '../logger/core.js';
@ -20,6 +20,7 @@ interface CallGetStaticPathsOptions {
routeCache: RouteCache;
logger: Logger;
ssr: boolean;
base: AstroConfig['base'];
}
export async function callGetStaticPaths({
@ -28,6 +29,7 @@ export async function callGetStaticPaths({
routeCache,
logger,
ssr,
base,
}: CallGetStaticPathsOptions): Promise<GetStaticPathsResultKeyed> {
const cached = routeCache.get(route);
if (!mod) {
@ -57,7 +59,7 @@ export async function callGetStaticPaths({
staticPaths = await mod.getStaticPaths({
// Q: Why the cast?
// A: So users downstream can have nicer typings, we have to make some sacrifice in our internal typings, which necessitate a cast here
paginate: generatePaginateFunction(route) as PaginateFunction,
paginate: generatePaginateFunction(route, base) as PaginateFunction,
});
validateGetStaticPathsResult(staticPaths, logger, route);

View file

@ -66,6 +66,7 @@ export async function matchRoute(
pathname: pathname,
logger,
serverLike,
base: config.base,
});
return {
route: maybeRoute,

View file

@ -17,9 +17,9 @@ describe('Pagination root', () => {
it('correct prev url in root spread', async () => {
const prevMap = {
'/4/': '/3',
'/3/': '/2',
'/2/': '/',
'/4/': '/blog/3',
'/3/': '/blog/2',
'/2/': '/blog/',
'/': undefined,
};

View file

@ -58,10 +58,10 @@ describe('Pagination', () => {
}
if (color === 'blue' && p === '1') {
assert.equal(prevHref, undefined);
assert.equal(nextHref, '/posts/blue/2');
assert.equal(nextHref, '/blog/posts/blue/2');
}
if (color === 'blue' && p === '2') {
assert.equal(prevHref, '/posts/blue/1');
assert.equal(prevHref, '/blog/posts/blue/1');
assert.equal(nextHref, undefined);
}
}),