mirror of
https://github.com/kremalicious/blog.git
synced 2024-06-28 16:48:00 +02:00
refactor
This commit is contained in:
parent
d88f242f27
commit
6d4e7f3a2a
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
40
src/components/Footer/Vcard.astro
Normal file
40
src/components/Footer/Vcard.astro
Normal 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 & developer{' '}
|
||||
<a class="fn" rel="author" href={uri}>
|
||||
{name}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<Networks links={links} />
|
|
@ -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;
|
||||
}
|
|
@ -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 & developer{' '}
|
||||
<a className="fn" rel="author" href={uri}>
|
||||
{name}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<Networks links={links} />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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'
|
||||
|
||||
|
|
36
src/components/PhotoTeaser.astro
Normal file
36
src/components/PhotoTeaser.astro
Normal 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>
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
|
@ -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
58
src/content/_schemas.ts
Normal 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
|
|
@ -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 user’s 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 asset’s 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
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
11
src/lib/slugify.ts
Normal 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
|
|
@ -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')
|
||||
|
|
|
@ -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>
|
60
src/pages/archive/[page].astro
Normal file
60
src/pages/archive/[page].astro
Normal 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> -->
|
3
src/pages/archive/index.astro
Normal file
3
src/pages/archive/index.astro
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
return Astro.redirect('/archive/1')
|
||||
---
|
|
@ -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>
|
||||
|
|
35
src/pages/photos/[page].astro
Normal file
35
src/pages/photos/[page].astro
Normal 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>
|
3
src/pages/photos/index.astro
Normal file
3
src/pages/photos/index.astro
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
return Astro.redirect('/photos/1')
|
||||
---
|
|
@ -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"],
|
||||
|
|
Loading…
Reference in New Issue
Block a user