1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-06-28 16:48:00 +02:00
This commit is contained in:
Matthias Kretschmann 2023-08-31 02:03:45 +01:00
parent d88f242f27
commit 6d4e7f3a2a
Signed by: m
GPG Key ID: 606EEEF3C479A91F
30 changed files with 338 additions and 414 deletions

14
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@astrojs/rss": "^3.0.0",
"@astrojs/sitemap": "^3.0.0",
"@rainbow-me/rainbowkit": "^1.0.9",
"astro": "^3.0.2",
"astro": "^3.0.3",
"classnames": "^2.3.2",
"date-fns": "^2.30.0",
"dms2dec": "^1.1.0",
@ -6689,9 +6689,9 @@
}
},
"node_modules/astro": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/astro/-/astro-3.0.2.tgz",
"integrity": "sha512-7ytwERK1CN7LBA/BgUrA9/ktbn0WDRwvkpxZZkIPlbjp3Ojlp6e12L2ZkNIYyIsGzFqXAZZz58pFwy7KG2Qz5g==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/astro/-/astro-3.0.3.tgz",
"integrity": "sha512-bugdGn9wIniVFbfyAHYtF9bc9pZpPaEs3gJAnK/XWROxCBAI2UQjR6lQuWM20iCc3snqu7GDgoW2MdzO7WFZZw==",
"dependencies": {
"@astrojs/compiler": "^2.0.1",
"@astrojs/internal-helpers": "0.2.0",
@ -26660,9 +26660,9 @@
"dev": true
},
"astro": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/astro/-/astro-3.0.2.tgz",
"integrity": "sha512-7ytwERK1CN7LBA/BgUrA9/ktbn0WDRwvkpxZZkIPlbjp3Ojlp6e12L2ZkNIYyIsGzFqXAZZz58pFwy7KG2Qz5g==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/astro/-/astro-3.0.3.tgz",
"integrity": "sha512-bugdGn9wIniVFbfyAHYtF9bc9pZpPaEs3gJAnK/XWROxCBAI2UQjR6lQuWM20iCc3snqu7GDgoW2MdzO7WFZZw==",
"requires": {
"@astrojs/compiler": "^2.0.1",
"@astrojs/internal-helpers": "0.2.0",

View File

@ -27,7 +27,7 @@
"@astrojs/rss": "^3.0.0",
"@astrojs/sitemap": "^3.0.0",
"@rainbow-me/rainbowkit": "^1.0.9",
"astro": "^3.0.2",
"astro": "^3.0.3",
"classnames": "^2.3.2",
"date-fns": "^2.30.0",
"dms2dec": "^1.1.0",

View File

@ -0,0 +1,40 @@
---
import { Image } from 'astro:assets'
import Networks from './Networks'
import avatar from '../../images/avatar.jpg'
import config from '../../../.config/blog.config.mjs'
const { author, rss, jsonfeed } = config
const { mastodon, twitter, github, name, uri } = author
const links = [mastodon, github, twitter, rss, jsonfeed]
---
<style>
.avatar {
/* composes: frame from '../atoms/Image.module.css'; */
border: 2px solid transparent;
border-radius: 50%;
margin-bottom: calc(var(--spacer) / 3);
}
.description {
font-size: var(--font-size-h5);
margin-top: 0;
margin-bottom: calc(var(--spacer) / var(--line-height));
}
.description a {
display: block;
}
</style>
<Image class="avatar" src={avatar} width="80" height="80" alt="avatar" />
<p class="description">
Blog of designer &amp; developer{' '}
<a class="fn" rel="author" href={uri}>
{name}
</a>
</p>
<Networks links={links} />

View File

@ -1,16 +0,0 @@
.avatar {
/* composes: frame from '../atoms/Image.module.css'; */
border: 2px solid transparent;
border-radius: 50%;
margin-bottom: calc(var(--spacer) / 3);
}
.description {
font-size: var(--font-size-h5);
margin-top: 0;
margin-bottom: calc(var(--spacer) / var(--line-height));
}
.description a {
display: block;
}

View File

@ -1,31 +0,0 @@
import type { ReactElement } from 'react'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
import Networks from './Networks'
import styles from './Vcard.module.css'
export default function Vcard(): ReactElement {
const { author, rss, jsonfeed } = useSiteMetadata()
const { mastodon, twitter, github, name, uri } = author
// const avatar = getSrc(data.avatar.edges[0].node)
const links = [mastodon, github, twitter, rss, jsonfeed]
return (
<>
<img
className={styles.avatar}
// src={avatar}
width="80"
height="80"
alt="avatar"
/>
<p className={styles.description}>
Blog of designer &amp; developer{' '}
<a className="fn" rel="author" href={uri}>
{name}
</a>
</p>
<Networks links={links} />
</>
)
}

View File

@ -1,6 +1,6 @@
---
import Icon from '../core/Icon'
import Vcard from './Vcard'
import Vcard from './Vcard.astro'
import styles from './index.module.css'
import config from '@config/blog.config.mjs'

View File

@ -0,0 +1,36 @@
---
import { Image } from 'astro:assets'
import type { CollectionEntry } from 'astro:content'
type Props = CollectionEntry<'photos'>
const { slug } = Astro.props
const { title, image } = Astro.props.data
---
<style>
.photo a {
display: block;
}
.photo figure,
.photo figure > div {
margin: 0;
}
.photo figcaption {
font-size: var(--font-size-base);
padding-left: calc(var(--spacer) / 2);
padding-right: calc(var(--spacer) / 2);
}
</style>
<article class="photo">
{
image ? (
<a href={slug}>
<Image width="150" height="80" src={image} alt={title} />
</a>
) : null
}
</article>

View File

@ -1,14 +0,0 @@
.posts {
display: grid;
gap: calc(var(--spacer) * 2);
}
@media (min-width: 40rem) {
.posts {
grid-template-columns: repeat(2, 1fr);
}
}
.posts h1 {
font-size: var(--font-size-h4);
}

View File

@ -1,23 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import data from '../../../.jest/__fixtures__/posts.json'
import Archive from './Archive'
describe('Archive', () => {
const pageContext = {
tag: 'hello',
slug: '/hello',
currentPageNumber: 2,
numPages: 20
}
it('renders without crashing', () => {
const { container } = render(
<Archive
data={data as unknown as Queries.ArchiveTemplateQuery}
pageContext={pageContext}
/>
)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -1,71 +0,0 @@
import React, { ReactElement } from 'react'
import { graphql } from 'gatsby'
import { PageContext } from '../../@types/Post'
import HeadMeta, { HeadMetaProps } from '../atoms/HeadMeta'
import Pagination from '../molecules/Pagination'
import PostTeaser from '../molecules/PostTeaser'
import styles from './Archive.module.css'
import Page from './Page'
function getMetadata(pageContext: PageContext) {
const { tag, currentPageNumber, numPages } = pageContext
const title = tag ? `#${tag}` : 'Archive'
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
const meta: Partial<HeadMetaProps> = {
title: `${title} ${paginationTitle}`,
description: 'All the articles & links.'
}
return meta
}
export default function Archive({
data,
pageContext
}: {
data: Queries.ArchiveTemplateQuery
pageContext: PageContext
}): ReactElement {
const edges = data.allMarkdownRemark.edges
const meta = getMetadata(pageContext)
const PostsList = edges.map(({ node }) => (
<PostTeaser key={node.id} post={node} />
))
return (
<Page title={meta.title}>
<div className={styles.posts}>{PostsList}</div>
{pageContext.numPages > 1 && <Pagination pageContext={pageContext} />}
</Page>
)
}
export function Head({ pageContext }: { pageContext: PageContext }) {
const meta = getMetadata(pageContext)
return <HeadMeta {...meta} slug={pageContext.slug} />
}
export const archiveQuery = graphql`
query ArchiveTemplate($tag: String, $skip: Int, $limit: Int) {
allMarkdownRemark(
filter: {
fields: { type: { nin: "photo" } }
frontmatter: { tags: { eq: $tag } }
}
sort: { fields: { date: DESC } }
skip: $skip
limit: $limit
) {
edges {
node {
...PostTeaser
}
}
}
}
`

View File

@ -1,26 +0,0 @@
.photos {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacer);
}
@media (min-width: 40rem) {
.photos {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
}
.photo a {
display: block;
}
.photo figure,
.photo figure > div {
margin: 0;
}
.photo figcaption {
font-size: var(--font-size-base);
padding-left: calc(var(--spacer) / 2);
padding-right: calc(var(--spacer) / 2);
}

View File

@ -1,24 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import data from '../../../.jest/__fixtures__/photos.json'
import Photos from '.'
describe('/photos', () => {
it('renders without crashing', () => {
const pageContext = {
slug: '/photos',
currentPageNumber: 2,
numPages: 20
}
const { container } = render(
// @ts-expect-error: only testing first render
<Photos
data={data as unknown as Queries.PhotosTemplateQuery}
pageContext={pageContext}
location={{ pathname: '/photos' } as any}
/>
)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -1,63 +0,0 @@
import React, { ReactElement } from 'react'
// import { Image } from '../../core/Image'
import Pagination from '../../Pagination'
import Page from '../Page'
import styles from './index.module.css'
export const PhotoThumb = ({
photo
}: {
photo: Queries.PhotosTemplateQuery['allMarkdownRemark']['edges'][0]['node']
}): ReactElement => {
const { title, image, slug } = photo.data
return (
<article className={styles.photo}>
{image && (
<a href={slug}>
{/* <Image title={title} image={gatsbyImageData} alt={title} /> */}
</a>
)}
</article>
)
}
interface PhotosPageProps extends PageProps {
data: Queries.PhotosTemplateQuery
pageContext: PageContext
}
function getMetadata(currentPageNumber: number, numPages: number) {
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
const meta: Partial<HeadMetaProps> = {
title: `Photos ${paginationTitle}`,
description: 'Personal photos of designer & developer Matthias Kretschmann.'
}
return meta
}
export default function Photos({
data,
pageContext
}: PhotosPageProps): ReactElement {
const photos = data.allMarkdownRemark.edges
const { currentPageNumber, numPages } = pageContext
const meta = getMetadata(currentPageNumber, numPages)
return (
<Page title={meta.title}>
<section className={styles.photos}>
{photos.map(({ node }) => (
<PhotoThumb key={node.id} photo={node} />
))}
</section>
{numPages > 1 && <Pagination pageContext={pageContext} />}
</Page>
)
}

View File

@ -1,20 +0,0 @@
---
type Props = {
lead: string
}
const { lead } = Astro.props
---
<style>
.lead {
font-size: var(--font-size-large);
margin-bottom: calc(var(--spacer) * var(--line-height));
}
.index {
font-size: var(--font-size-base);
margin-top: calc(var(--spacer) * var(--line-height));
}
</style>
<div class="lead">{lead}</div>

View File

@ -2,12 +2,11 @@
import type { CollectionEntry } from 'astro:content'
import LayoutBase from '@layouts/Base/index.astro'
import Title from './Title.astro'
import Lead from './Lead.astro'
type Props = CollectionEntry<'articles' | 'links' | 'photos'>
const { collection, body } = Astro.props
const { title, date, updated, image, linkurl } = Astro.props.data
const { collection, body, data } = Astro.props
const { title, date, updated, image, linkurl } = data
// Extract lead paragraph from content
// Grab everything before more tag, or just first paragraph
@ -39,6 +38,11 @@ if (collection === 'articles') {
.image {
/* composes: breakout from '../../Layout.module.css'; */
}
.lead {
font-size: var(--font-size-large);
margin-bottom: calc(var(--spacer) * var(--line-height));
}
</style>
<LayoutBase title={title}>
@ -47,7 +51,7 @@ if (collection === 'articles') {
<Title linkurl={linkurl} title={title} date={date} updated={updated} />
</header>
{lead && <Lead lead={lead} />}
{lead && <div class="lead">{lead}</div>}
<div class="hero-image">
{image && <img width={1020} height={510} src={image} alt="" />}

58
src/content/_schemas.ts Normal file
View File

@ -0,0 +1,58 @@
import { z } from 'astro:content'
const schemaShared = {
title: z.string(),
date: z
.string()
.or(z.date())
.optional()
// Transform string to Date object
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
tags: z.array(z.string()).optional(),
draft: z.boolean().optional(),
redirect_from: z.array(z.string()).optional(),
author: z.string().optional(),
featured: z.boolean().optional(),
style: z.string().optional()
}
export const schemaArticles = z
.object({
...schemaShared,
image: z.string().optional(),
download: z.string().optional(),
toc: z.boolean().optional(),
changelog: z.string().optional()
})
.strict()
export const schemaLinks = z
.object({
...schemaShared,
linkurl: z.string()
})
.strict()
export const schemaPhotos = z
.object({
...schemaShared,
image: z.string()
})
.strict()
export type ArticleFrontmatter = z.infer<typeof schemaArticles>
export type LinkFrontmatter = z.infer<typeof schemaLinks>
export type PhotoFrontmatter = z.infer<typeof schemaPhotos>
export type PostFrontmatter =
| ArticleFrontmatter
| LinkFrontmatter
| PhotoFrontmatter

View File

@ -2,7 +2,7 @@
date: 2021-07-29T00:00:00.000Z
title: Ocean Makes Multi-Network Even Easier
image: ocean-makes-multi-network-even-easier-teaser.png
image: ./ocean-makes-multi-network-even-easier-teaser.png
tags:
- oceanprotocol
@ -38,23 +38,23 @@ So we sat down and figured out the best patterns to solve these main pain points
## 🧜‍♀️ Multi-Network Market
![Leeloo agrees words with “multi” in front are better.](multinetwork-01.jpeg)
![Leeloo agrees words with “multi” in front are better.](./multinetwork-01.jpeg)
Ultimately, we arrived at a solution tackling all this, where the main new paradigm is an interface showing assets mixed from multiple networks. All the time and on every screen where assets are listed. This detaches the metadata and financial data source from the users wallet network as it was before.
The displayed networks are now controlled by the new network selector.
![The new network selector and revised menubar in the Ocean Market interface.](multinetwork-02.png)
![The new network selector and revised menubar in the Ocean Market interface.](./multinetwork-02.png)
By default, we auto-select all production networks Ocean Protocol is deployed to. As soon as you interact with this new network switcher, your selection takes over and is saved in your browser so it will be the same the next time you come to the market.
Selecting or de-selecting networks then modifies all Elasticsearch queries going to our new Aquarius, resulting in mixed assets on screen.
![Mixed assets from multiple networks.](multinetwork-03.png)
![Mixed assets from multiple networks.](./multinetwork-03.png)
All assets now indicate which network they belong to, and you are prompted to switch to the assets network when we detect your wallet being connected to another network.
![One remaining place where user wallet switching is still important.](multinetwork-04.png)
![One remaining place where user wallet switching is still important.](./multinetwork-04.png)
And in the case of using MetaMask, we added actions to switch your wallet network directly from the UI, which, as of right now, is pretty much the most streamlined user flow possible to switch networks with MetaMask from a Dapp.
@ -62,15 +62,15 @@ With all this, wallet network switching is now only needed once you want to inte
User wallet network also stays important for publishing an asset, so we based the whole publish form on the currently connected network to define onto which network an asset is published.
![Publish form with network indicator.](multinetwork-05.png)
![Publish form with network indicator.](./multinetwork-05.png)
As for our key market statistics in the footer, we switched it to show consolidated numbers as a sum of all production networks. In its tooltip, you can find the values split up by network.
![New consolidated market statistics based on each network.](multinetwork-06.png)
![New consolidated market statistics based on each network.](./multinetwork-06.png)
More assets on screen and more controls also led to further UI tweaks to get more space available to the actual main content. We completely refactored the main menu layout, added a global search box to it, and moved some warnings around. And, while we were at it, improved the mobile experience for it. ✨✨
![Everything you need from our menu is all there in mobile viewports.](multinetwork-07.png)
![Everything you need from our menu is all there in mobile viewports.](./multinetwork-07.png)
And finally, we also automatically migrate all your existing bookmarks from all the networks and combine them into one list.
@ -101,7 +101,7 @@ In addition to making an interface with mixed assets possible, this also brings
So multiple Aquarius instances are now reduced to one instance, where for every network a specific indexer is started. The [Aquarius API](https://docs.oceanprotocol.com/references/aquarius/) got a new endpoint exposing which chains are indexed under `/api/v1/aquarius/chains/list`.
![`/chains/list` endpoint response exposing indexed chain IDs](multinetwork-08.png)
![`/chains/list` endpoint response exposing indexed chain IDs](./multinetwork-08.png)
### Migration to Multi-Network Aquarius

View File

@ -1,78 +1,19 @@
import { defineCollection, z } from 'astro:content'
import { schemaArticles, schemaLinks, schemaPhotos } from './_schemas'
const articles = defineCollection({
type: 'content', // v2.5.0 and later
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
// Transform string to Date object
date: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
image: z.string().optional(),
tags: z.array(z.string()).optional(),
download: z.string().optional(),
toc: z.boolean().optional(),
changelog: z.string().optional()
})
type: 'content',
schema: schemaArticles
})
const links = defineCollection({
type: 'content', // v2.5.0 and later
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
linkurl: z.string(),
date: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
tags: z.array(z.string()).optional()
})
type: 'content',
schema: schemaLinks
})
const photos = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
image: z.string(),
tags: z.array(z.string()).optional()
})
schema: schemaPhotos
})
export const collections = { articles, links, photos }

View File

@ -1,21 +1,46 @@
import { getCollection, CollectionEntry } from 'astro:content'
import { getCollection, type CollectionEntry } from 'astro:content'
export function getSortedPosts(
posts: CollectionEntry<'articles' | 'links' | 'photos'>[]
) {
return posts
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor((b.data.date as Date).getTime() / 1000) -
Math.floor((a.data.date as Date).getTime() / 1000)
)
}
export function getPostsByTag(
posts: CollectionEntry<'articles' | 'links' | 'photos'>[],
tag: string
) {
return posts.filter((post) => slugifyAll(post.data.tags).includes(tag))
}
export default getPostsByTag
export async function loadAndFormatCollection(
name: 'articles' | 'links' | 'photos'
) {
const posts = await getCollection(name)
const postsCollection = await getCollection(name)
posts.forEach((post: CollectionEntry<'articles' | 'links' | 'photos'>) => {
// remove date from slug
const slug = post.slug.substring(11) as CollectionEntry<
'articles' | 'links' | 'photos'
>['slug']
// use date from frontmatter, or grab from file path
const date = post.data.date ? post.data.date : slug.substring(1, 11)
postsCollection.forEach(
(post: CollectionEntry<'articles' | 'links' | 'photos'>) => {
// remove date from slug
const slug = post.slug.substring(11) as CollectionEntry<
'articles' | 'links' | 'photos'
>['slug']
post.slug = slug
post.data.date = new Date(date)
})
// use date from frontmatter, or grab from file path
const date = post.data.date ? post.data.date : slug.substring(1, 11)
return posts.reverse()
post.slug = slug
post.data.date = new Date(date)
}
)
const posts = getSortedPosts(postsCollection)
return posts
}

11
src/lib/slugify.ts Normal file
View File

@ -0,0 +1,11 @@
import { slug as slugger } from 'github-slugger'
import type { PostFrontmatter } from '@content/_schemas'
export const slugifyStr = (str: string) => slugger(str)
const slugify = (post: PostFrontmatter) =>
post.slug ? slugger(post.slug) : slugger(post.title)
export const slugifyAll = (arr: string[]) => arr.map((str) => slugifyStr(str))
export default slugify

View File

@ -1,6 +1,11 @@
---
import LayoutPost from '@layouts/Post/index.astro'
import { loadAndFormatCollection } from '@lib/astro'
import type { CollectionEntry } from 'astro:content'
type Props = {
entry: CollectionEntry<'articles' | 'links' | 'photos'>
}
export async function getStaticPaths() {
const articles = await loadAndFormatCollection('articles')

View File

@ -1,17 +0,0 @@
---
---
<ul>
{
posts.map((post) => (
<li>
<a href={`/${post.slug}/`}>
<img width={720} height={360} src={post.data.image} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">{post.data.date}</p>
</a>
</li>
))
}
</ul>

View File

@ -0,0 +1,60 @@
---
import { loadAndFormatCollection } from '@lib/astro'
type Props = {
page: {
data: {
post: {
title: string
}
}[]
currentPage: number
totalPages: number
}
}
export async function getStaticPaths({ paginate }) {
const articles = await loadAndFormatCollection('articles')
const links = await loadAndFormatCollection('links')
return paginate([...articles, ...links], { pageSize: 10 })
}
// All paginated data is passed on the "page" prop
const { page } = Astro.props
---
<style>
.posts {
display: grid;
gap: calc(var(--spacer) * 2);
}
@media (min-width: 40rem) {
.posts {
grid-template-columns: repeat(2, 1fr);
}
}
.posts h1 {
font-size: var(--font-size-h4);
}
</style>
<!--Display the current page number. Astro.params.page can also be used!-->
<h1>Page {page.currentPage}</h1>
<ul>
{page.data.map(({ post }) => <li>{post.title}</li>)}
</ul>
<!--
<ul>
{
posts.map((post) => (
<li>
<a href={`/${post.slug}/`}>
<img width={720} height={360} src={post.data.image} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">{post.data.date}</p>
</a>
</li>
))
}
</ul> -->

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/archive/1')
---

View File

@ -1,8 +1,9 @@
---
import LayoutBase from '@components/layouts/Base/index.astro'
import PostTeaser from '@components/PostTeaser.astro'
import { PhotoThumb } from '../components/layouts/Photos'
import { loadAndFormatCollection } from '@lib/astro'
import PhotoTeaser from '@components/PhotoTeaser.astro'
import type { CollectionEntry } from 'astro:content'
const articles = await loadAndFormatCollection('articles')
const links = await loadAndFormatCollection('links')
@ -79,14 +80,20 @@ const photos = await loadAndFormatCollection('photos')
}
</div>
<a href="/archive">All Articles</a>
<a href="/archive/1">All Articles</a>
</section>
<!-- <section class="section">
<div class="photos">
{photos.map(({ node }) => <PhotoThumb photo={node} />)}
</div>
<section class="section">
<div class="photos">
{
photos
.slice(0, 8)
.map((photo) => (
<PhotoTeaser {...(photo as CollectionEntry<'photos'>)} />
))
}
</div>
<PostMore to="/photos">All Photos</PostMore>
</section> -->
<a href="/photos/1">All Photos</a>
</section>
</LayoutBase>

View File

@ -0,0 +1,35 @@
---
import { loadAndFormatCollection } from '@lib/astro'
import LayoutBase from '@layouts/Base/index.astro'
import PhotoTeaser from '@components/PhotoTeaser.astro'
import type { CollectionEntry } from 'astro:content'
export async function getStaticPaths({ paginate }) {
const photos = await loadAndFormatCollection('photos')
return paginate(photos, { pageSize: 10 })
}
// All paginated data is passed on the "page" prop
const { page } = Astro.props
---
<style>
.photos {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacer);
}
@media (min-width: 40rem) {
.photos {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
}
</style>
<LayoutBase>
<div class="photos">
{page.data.map((photo) => <PhotoTeaser photo={photo} />)}
</div>
<!--Display the current page number. Astro.params.page can also be used!-->
<h1>Page {page.currentPage}</h1>
</LayoutBase>

View File

@ -0,0 +1,3 @@
---
return Astro.redirect('/photos/1')
---

View File

View File

@ -10,7 +10,8 @@
"@components/*": ["src/components/*"],
"@layouts/*": ["src/components/layouts/*"],
"@images/*": ["src/images/*"],
"@lib/*": ["src/lib/*"]
"@lib/*": ["src/lib/*"],
"@content/*": ["src/content/*"]
}
}
// "exclude": ["node_modules", "public", "dist", "./**/*.js"],