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:
parent
36e6dee475
commit
4e5cc5aadd
9 changed files with 63 additions and 20 deletions
32
.changeset/twenty-cobras-push.md
Normal file
32
.changeset/twenty-cobras-push.md
Normal 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>
|
||||
```
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -66,6 +66,7 @@ export async function matchRoute(
|
|||
pathname: pathname,
|
||||
logger,
|
||||
serverLike,
|
||||
base: config.base,
|
||||
});
|
||||
return {
|
||||
route: maybeRoute,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue