This commit is contained in:
2025-11-28 17:20:08 +08:00
parent 0b0650f374
commit f1d413afae
6 changed files with 49 additions and 403 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
dist/
.astro/

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM oven/bun:latest AS build
ADD * /app/
WORKDIR /app
RUN bun install && \
bun run astro build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server/entry.mjs"]

295
README.md
View File

@@ -1,295 +0,0 @@
![Showcase Card](/public/static/twitter-card.png)
<div align="center">
## astro-erudite
![Stargazers]
[![License]](LICENSE)
</div>
astro-erudite is an opinionated, unstyled static blogging template built with [Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), and [shadcn/ui](https://ui.shadcn.com/). Extraordinarily loosely based off the [Astro Micro](https://astro-micro.vercel.app/) theme by [trevortylerlee](https://github.com/trevortylerlee).
| ![Preview 1](/public/static/preview-1.png) | ![Preview 2](/public/static/preview-2.png) |
| ------------------------------------------ | ------------------------------------------ |
| ![Preview 3](/public/static/preview-3.png) | ![Preview 4](/public/static/preview-4.png) |
> [!NOTE]
> To learn more about why this template exists, read [The State of Static Blogs in 2024](https://astro-erudite.vercel.app/blog/the-state-of-static-blogs), where I share my take on what constitutes a great blogging template and my goals while developing this one.
---
## Community Examples
Below are some fantastic examples of websites based on this template. If you wish to add your site to this list, open a pull request!
| Site | Author | Description/Features | Source |
| ---------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| [enscribe.dev](https://enscribe.dev) | [@jktrn](https://github.com/jktrn) | Heavily modified bento-style homepage with client interactivity, with custom MDX components! | [](https://github.com/jktrn/enscribe.dev) |
| [emile.sh](https://emile.sh) | [@echoghi](https://github.com/echoghi) | A minimalist personal blog using the [flexoki](https://stephango.com/flexoki) theme | [](https://github.com/echoghi/v5) |
| [decentparadox.me](https://decentparadox.me) | [@decentparadox](https://github.com/decentparadox) | A heavily customized personal portfolio with a sci-fi theme! | [](https://github.com/decentparadox/decentparadox.me) |
| [flocto.github.io](https://flocto.github.io/) | [@flocto](https://github.com/flocto) | A slightly modified personal blog | [](https://github.com/flocto/flocto.github.io) |
| [dumbprism.me](https://www.dumbprism.me/) | [@dumbprism](https://github.com/dumbprism) | A customized portfolio inspired by enscribe's bento grid style adding my gist of UI | [](https://github.com/dumbprism/dumbprism-portfolio) |
| [hyuki.dev](https://hyuki.dev/) | [@snow0406](https://github.com/snow0406) | A minimalist blog with a blue color scheme, focusing on simplicity! | [](https://github.com/Snow0406/hyuki.dev) |
| [ldd.cc](https://ldd.cc/) | [@xJoyLu](https://github.com/xjoylu) | The cream of the idlers. | [](https://ldd.cc/) |
| [rezarezvan.com](https://rezarezvan.com/) | [@rezaarezvan](https://github.com/rezaarezvan) | A academic blog with personal touches :). | [](https://rezarezvan.com/) |
| [blog.z0x.ca](https://blog.z0x.ca/) | [@z0x](https://z0x.ca) | _Very_ minimal version of erudite, stripping it down to the bare essentials | [](https://git.z0x.ca/z0x/blog.z0x.ca/) |
| [angelaytchan.net](https://angelaytchan.net/) | [@wispyplant](https://github.com/wispyplant) | An artist portfolio and activities archive | [](https://github.com/wispyplant/wispyplant.github.io) |
| [kaezr.xyz](https://kaezr.xyz/) | [@kaezrr](https://github.com/kaezrr) | A minimal porfolio and blog website with slight tweaks to the original. | [](https://github.com/kaezrr/webfolio) |
| [worldwidewong](https://worldwidewong.vercel.app) | [@brendanwong-web](https://github.com/brendanwong-web) | A slightly funky portfolio, blog, and resume site with an added photo gallery. | [](https://github.com/brendanwong-web/worldwidewong) |
| [bgajjala.dev](https://bgajjala.dev) | [@bgajjala8](https://github.com/bgajjala8) | A minimal blog featuring a paper-color inspired color scheme | [](https://github.com/bgajjala8/bgajjala.dev) |
| [ankitz007.vercel.app](https://ankitz007.vercel.app) | [@ankitz007](https://github.com/ankitz007) | A personal blog with a few modifications and updates to the original. | [](https://github.com/ankitz007/webfolio) |
| [sadman.ca](https://sadman.ca) | [@sadmanca](https://github.com/sadmanca) | A customized personal blog with: Goodreads reading progress tracker, SVG thumbnails, custom heading styles, and dynamic media grids (books, movies, etc.) | [](https://github.com/sadmanca/blogv3) |
| [marcel-to.vercel.app](https://marcel-to.vercel.app) | [@Marcel-TO](https://github.com/Marcel-TO) | A contentdriven personal portfolio showcasing software projects, deepdive blog series and multi-part project documentation. | [](https://github.com/Marcel-TO/marcel-to-website) |
| [merox.dev](https://merox.dev) | [@meroxdotdev](https://github.com/meroxdotdev) | A technical blog focused on DevOps automation and homelab infrastructure | [](https://github.com/meroxdotdev/merox) |
## Features
- [Astro](https://astro.build/)'s [Islands](https://docs.astro.build/en/concepts/islands/) architecture for selective hydration and client-side interactivity while maintaining fast static site rendering.
- [shadcn/ui](https://ui.shadcn.com/) with [Tailwind](https://tailwindcss.com/) color conventions for automatic light and dark theme styling. Features accessible, theme-aware UI components for navigation, buttons, and more.
- [Expressive Code](https://expressive-code.com/) for enhanced code block styling, syntax highlighting, and code block titles.
- Blog authoring with [MDX](https://mdxjs.com/) for component-rich content and $\LaTeX$ math rendering via [KaTeX](https://katex.org/).
- Astro [View Transitions](https://docs.astro.build/en/guides/view-transitions/) in <abbr title="Single Page Application">SPA</abbr> mode for smooth route animations.
- SEO optimization with granular metadata and [Open Graph](https://ogp.me/) tag control for each post.
- [RSS](https://en.wikipedia.org/wiki/RSS) feed and sitemap generation.
- Subpost support for breaking long content into digestible parts and organizing related series.
- Author profiles with a dedicated authors page and multi-author post support.
- Project tags with a dedicated tags page for post categorization and discovery.
- Custom Callout component variants for enhanced technical writing.
## Technology Stack
This is a list of the various technologies used to build this template:
| Category | Technology Name |
| ---------- | ------------------------------------------------------------------------------------------ |
| Framework | [Astro](https://astro.build/) |
| Styling | [Tailwind](https://tailwindcss.com) |
| Components | [shadcn/ui](https://ui.shadcn.com/) |
| Content | [MDX](https://mdxjs.com/) |
| Codeblocks | [Expressive Code](https://expressive-code.com/), [Shiki](https://github.com/shikijs/shiki) |
| Graphics | [Figma](https://www.figma.com/) |
| Deployment | [Vercel](https://vercel.com) |
## Getting Started
1. Hit &ldquo;Use this template&rdquo;, the big green button on the top right, to create a new repository in your own GitHub account with this template.
2. Clone the repository:
```bash
git clone https://github.com/[YOUR_USERNAME]/[YOUR_REPO_NAME].git
cd [YOUR_REPO_NAME]
```
3. Install dependencies:
```bash
npm install
```
4. Start the development server:
```bash
npm run dev
```
5. Open your browser and visit `http://localhost:1234` to get started. The following commands are also available:
| Command | Description |
| ------------------ | --------------------------------------------------------------- |
| `npm run start` | Alias for `npm run dev` |
| `npm run build` | Run type checking and build the project |
| `npm run preview` | Previews the built project |
| `npm run astro` | Run Astro CLI commands |
| `npm run prettier` | Blanket format all files using [Prettier](https://prettier.io/) |
## Customization
### Site Configuration
Edit the `src/consts.ts` file to update your site's metadata, navigation links, and social links:
```ts
export const SITE: Site = {
title: 'astro-erudite',
description: // ...
href: 'https://astro-erudite.vercel.app',
featuredPostCount: 2,
postsPerPage: 3,
}
export const NAV_LINKS: SocialLink[] = [
{
href: '/blog',
label: 'blog',
},
// ...
]
export const SOCIAL_LINKS: SocialLink[] = [
{
href: 'https://github.com/jktrn',
label: 'GitHub',
},
// ...
]
```
### Color Palette
Colors are defined in `src/styles/global.css` in [OKLCH format](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch), using the [shadcn/ui](https://ui.shadcn.com/) convention:
```css
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
}
[data-theme='dark'] {
/* ... */
}
```
### Favicons
Favicons are generated using [RealFaviconGenerator](https://realfavicongenerator.net/). To adjust the favicons, replace the files in the `public/` directory (such as `favicon.ico`, `favicon.svg`, `apple-touch-icon.png`, etc.) with your own. After updating the favicon files, you'll also need to adjust the references in `src/components/Favicons.astro` to match your new favicon filenames and paths:
```html
<!-- Replace these with the generated meta tags -->
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="astro-erudite" />
<link rel="manifest" href="/site.webmanifest" />
```
## Adding Content
### Blog Posts
Add new blog posts as MDX files in the `src/content/blog/` directory. Use the following frontmatter structure:
```yml
---
title: 'Your Post Title'
description: 'A brief description of your post!'
date: 2024-01-01
tags: ['tag1', 'tag2']
image: './image.png'
authors: ['author1', 'author2']
draft: false
---
```
The blog post schema is defined as follows:
| Field | Type (Zod) | Requirements | Required |
| ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `title` | `string` | Should be ≤60 characters. | Yes |
| `description` | `string` | Should be ≤155 characters. | Yes |
| `date` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Yes |
| `order` | `number` | Sort order for subposts with the same `date`. Defaults to `0` if not provided. | Optional |
| `image` | `image()` | Should be exactly 1200px &times; 630px. | Optional |
| `tags` | `string[]` | Preferably use kebab-case for these. | Optional |
| `authors` | `string[]` | If the author has a profile, use the id associated with their Markdown file in `src/content/authors/` (e.g. if their file is named `jane-doe.md`, use `jane-doe` in the array). | Optional |
| `draft` | `boolean` | Defaults to `false` if not provided. | Optional |
### Authors
Add author information in `src/content/authors/` as Markdown files. A file named `[author-name].md` can be associated with a blog post if `"author-name"` (the id) is added to the `authors` field:
```yml
---
name: 'enscribe'
pronouns: 'he/him'
avatar: 'https://gravatar.com/avatar/9bfdc4ec972793cf05cb91efce5f4aaaec2a0da1bf4ec34dad0913f1d845faf6.webp?size=256'
bio: 'd(-_-)b'
website: 'https://enscribe.dev'
twitter: 'https://twitter.com/enscry'
github: 'https://github.com/jktrn'
mail: 'jason@enscribe.dev'
---
```
The author schema is defined as follows:
| Field | Type (Zod) | Requirements | Required |
| ---------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| `name` | `string` | n/a | Yes |
| `pronouns` | `string` | n/a | Optional |
| `avatar` | `string.url()` or `string.startsWith('/')` | Should be either a valid URL or a path starting with `/`. Preferably use [Gravatar](https://en.gravatar.com/site/implement/images/) with the `?size=256` size parameter. | Yes |
| `bio` | `string` | n/a | Optional |
| `mail` | `string.email()` | Must be a valid email address. | Optional |
| `website` | `string.url()` | Must be a valid URL. | Optional |
| `twitter` | `string.url()` | Must be a valid URL. | Optional |
| `github` | `string.url()` | Must be a valid URL. | Optional |
| `linkedin` | `string.url()` | Must be a valid URL. | Optional |
| `discord` | `string.url()` | Must be a valid URL. | Optional |
> [!TIP]
> You can add as many social media links as you want, as long as you adjust the schema! Make sure you also support the new field in the `src/components/SocialIcons.astro` component.
### Projects
Add projects in `src/content/projects/` as Markdown files:
```yml
---
name: 'Project A'
description: 'This is an example project description! You should replace this with a description of your own project.'
tags: ['Framework A', 'Library B', 'Tool C', 'Resource D']
image: '/static/1200x630.png'
link: 'https://example.com'
startDate: '2024-01-01'
endDate: '2024-01-01'
---
```
The project schema is defined as follows:
| Field | Type (Zod) | Requirements | Required |
| ------------- | --------------- | --------------------------------------- | -------- |
| `name` | `string` | n/a | Yes |
| `description` | `string` | n/a | Yes |
| `tags` | `string[]` | n/a | Yes |
| `image` | `image()` | Should be exactly 1200px &times; 630px. | Yes |
| `link` | `string.url()` | Must be a valid URL. | Yes |
| `startDate` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Optional |
| `endDate` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Optional |
## License
This project is open source and available under the [MIT License](LICENSE).
---
### Star History
<a href="https://star-history.com/#jktrn/astro-erudite&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date" />
</picture>
</a>
---
Built with &hearts; by [enscribe](https://enscribe.dev)!
[Stargazers]: https://img.shields.io/github/stars/jktrn/astro-erudite?color=fafafa&logo=github&logoColor=fff&style=for-the-badge
[License]: https://img.shields.io/github/license/jktrn/astro-erudite?color=0a0a0a&logo=github&logoColor=fff&style=for-the-badge

View File

@@ -20,8 +20,6 @@ import tailwindcss from '@tailwindcss/vite'
import node from '@astrojs/node'; import node from '@astrojs/node';
import vercel from '@astrojs/vercel';
export default defineConfig({ export default defineConfig({
site: 'https://nvme0n1p.dev', site: 'https://nvme0n1p.dev',
output: 'server', output: 'server',
@@ -114,5 +112,8 @@ export default defineConfig({
remarkPlugins: [remarkMath, remarkEmoji], remarkPlugins: [remarkMath, remarkEmoji],
}, },
adapter: vercel(), adapter: node({
mode: 'standalone',
}),
}) })

View File

@@ -70,34 +70,8 @@ export async function getAllAuthors(): Promise<CollectionEntry<'authors'>[]> {
return getCollectionSafe('authors') return getCollectionSafe('authors')
} }
export const POSTS_API_URL = 'https://notion-api.nvme0n1p.dev/v2/posts' export function normalizePost(post: NotionPost): CollectionEntry<'blog'> {
const dateString =
export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
const fallback = async () => {
const posts = await getCollectionSafe('blog')
return posts
.filter((post) => !post.data.draft && !isSubpost(post.id))
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.map((post) => ({
...post,
data: {
...post.data,
banner: post.data.banner ?? post.data.image,
authors: [DEFAULT_AUTHOR_ID],
},
}))
}
try {
const res = await fetch(POSTS_API_URL)
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`)
const payload = (await res.json()) as { posts?: NotionPost[] }
const posts = (payload.posts ?? []).filter(
(post) => post.Published ?? true,
)
const normalized = posts.map((post) => {
const dateString =
post['Published Date'] ?? post.created_time ?? new Date().toISOString() post['Published Date'] ?? post.created_time ?? new Date().toISOString()
const date = new Date(dateString) const date = new Date(dateString)
const id = post.slug || post.id const id = post.slug || post.id
@@ -114,37 +88,39 @@ export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
tags: Array.isArray(post.Tags) ? post.Tags : [], tags: Array.isArray(post.Tags) ? post.Tags : [],
draft: !(post.Published ?? true), draft: !(post.Published ?? true),
authors: [DEFAULT_AUTHOR_ID], authors: [DEFAULT_AUTHOR_ID],
banner,
image, image,
}, },
body: '', body: '',
} }
}) }
export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
try {
const res = await fetch("https://notion-api.nvme0n1p.dev/v2/posts")
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`)
const payload = (await res.json()) as { posts?: NotionPost[] }
const posts = (payload.posts ?? []).filter(
(post) => post.Published ?? true,
)
const normalized = posts.map(normalizePost)
return normalized return normalized
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.filter((post) => !isSubpost(post.id)) as unknown as CollectionEntry<'blog'>[] .filter((post) => !isSubpost(post.id)) as unknown as CollectionEntry<'blog'>[]
} catch (error) { } catch (error) {
console.error('getAllPosts remote fetch failed, using fallback:', error) console.error('getAllPosts remote fetch failed, using fallback:', error)
return fallback() return []
} }
} }
export async function getAllPostsAndSubposts(): Promise< export async function getAllPostsAndSubposts(): Promise<
CollectionEntry<'blog'>[] CollectionEntry<'blog'>[]
> { > {
// 远程源不区分子文章,直接沿用 getAllPosts 结果
return getAllPosts() return getAllPosts()
} }
export async function getAllProjects(): Promise<CollectionEntry<'projects'>[]> {
const projects = await getCollectionSafe('projects')
return projects.sort((a, b) => {
const dateA = a.data.startDate?.getTime() || 0
const dateB = b.data.startDate?.getTime() || 0
return dateB - dateA
})
}
export async function getAllTags(): Promise<Map<string, number>> { export async function getAllTags(): Promise<Map<string, number>> {
const posts = await getAllPosts() const posts = await getAllPosts()
@@ -163,41 +139,6 @@ export async function getAdjacentPosts(currentId: string): Promise<{
}> { }> {
const allPosts = await getAllPosts() const allPosts = await getAllPosts()
if (isSubpost(currentId)) {
const parentId = getParentId(currentId)
const allPosts = await getAllPosts()
const parent = allPosts.find((post) => post.id === parentId) || null
const posts = await getCollectionSafe('blog')
const subposts = posts
.filter(
(post) =>
isSubpost(post.id) &&
getParentId(post.id) === parentId &&
!post.data.draft,
)
.sort((a, b) => {
const dateDiff = a.data.date.valueOf() - b.data.date.valueOf()
if (dateDiff !== 0) return dateDiff
const orderA = a.data.order ?? 0
const orderB = b.data.order ?? 0
return orderA - orderB
})
const currentIndex = subposts.findIndex((post) => post.id === currentId)
if (currentIndex === -1) {
return { newer: null, older: null, parent }
}
return {
newer:
currentIndex < subposts.length - 1 ? subposts[currentIndex + 1] : null,
older: currentIndex > 0 ? subposts[currentIndex - 1] : null,
parent,
}
}
const parentPosts = allPosts.filter((post) => !isSubpost(post.id)) const parentPosts = allPosts.filter((post) => !isSubpost(post.id))
const currentIndex = parentPosts.findIndex((post) => post.id === currentId) const currentIndex = parentPosts.findIndex((post) => post.id === currentId)
@@ -287,15 +228,13 @@ export function groupPostsByYear(
} }
export async function hasSubposts(postId: string): Promise<boolean> { export async function hasSubposts(postId: string): Promise<boolean> {
const subposts = await getSubpostsForParent(postId) return false
return subposts.length > 0
} }
export function isSubpost(postId: string): boolean { export function isSubpost(postId: string): boolean {
return postId.includes('/') return false
} }
export const FRIENDS_API_URL = 'https://notion-api.nvme0n1p.dev/v2/links'
export async function fetchRemotePost( export async function fetchRemotePost(
slug: string, slug: string,
): Promise<NotionPost | null> { ): Promise<NotionPost | null> {
@@ -314,16 +253,11 @@ export async function fetchRemotePostContent(
slug: string, slug: string,
): Promise<RemotePostPayload | null> { ): Promise<RemotePostPayload | null> {
try { try {
const res = await fetch(`${POSTS_API_URL}/${encodeURI(slug)}`) const res = await fetch(`https://notion-api.nvme0n1p.dev/v2/posts/${encodeURI(slug)}`)
if (!res.ok) throw new Error(`Failed to fetch post content: ${res.status}`) if (!res.ok) throw new Error(`Failed to fetch post content: ${res.status}`)
const data = (await res.json()) as Partial<RemotePostPayload> const data = (await res.json()) as Partial<RemotePostPayload>
if (!data.post || !data.blockMap) return null if (!data.post || !data.blockMap) return null
data.post = normalizePost(data.post)
const post = data.post as any
if (post?.banner && !post?.image) {
post.image = post.banner
}
return data as RemotePostPayload return data as RemotePostPayload
} catch (error) { } catch (error) {
console.error(`fetchRemotePostContent error for slug "${slug}":`, error) console.error(`fetchRemotePostContent error for slug "${slug}":`, error)
@@ -332,28 +266,24 @@ export async function fetchRemotePostContent(
} }
export async function getFriendLinks(): Promise<LinkEntry[]> { export async function getFriendLinks(): Promise<LinkEntry[]> {
const fallback: LinkEntry[] = []
try { try {
const res = await fetch(FRIENDS_API_URL) const res = await fetch("https://notion-api.nvme0n1p.dev/v2/links")
if (!res.ok) throw new Error(`Failed to fetch links: ${res.status}`) if (!res.ok) throw new Error(`Failed to fetch links: ${res.status}`)
const data = (await res.json()) as LinkEntry[] const data = (await res.json()) as LinkEntry[]
if (!Array.isArray(data)) return fallback if (!Array.isArray(data)) throw new Error('Invalid links data format')
return data return data
} catch (error) { } catch (error) {
console.error('getFriendLinks error:', error) console.error('getFriendLinks error:', error)
return fallback return []
} }
} }
export async function getParentPost( export async function getParentPost(
subpostId: string, subpostId: string,
): Promise<CollectionEntry<'blog'> | null> { ): Promise<CollectionEntry<'blog'> | null> {
if (!isSubpost(subpostId)) {
return null
}
const parentId = getParentId(subpostId) const parentId = getParentId(subpostId)
const allPosts = await getAllPosts() const allPosts = await getAllPosts()

View File

@@ -38,25 +38,18 @@ export async function getStaticPaths() {
})) }))
} }
const post = Astro.props
const currentPostId = Astro.params.id const currentPostId = Astro.params.id
const isRemotePost = post.body === ''
let Content: any = null let Content: any = null
let headings let headings
let remoteContent = null let remoteContent = null
if (isRemotePost) {
const remote = await fetchRemotePostContent(currentPostId)
remoteContent = remote ? renderRemoteBlockMap(remote.blockMap, remote.post.id) : null
headings = remoteContent?.headings ?? []
} else {
const rendered = await render(post)
Content = rendered.Content
headings = rendered.headings
}
const authors = await parseAuthors(post.data.authors ?? []) const remote = await fetchRemotePostContent(currentPostId)
remoteContent = remote ? renderRemoteBlockMap(remote.blockMap, remote.post.id) : null
headings = remoteContent?.headings ?? []
const post = remote?.post
const authors = await parseAuthors(post.authors ?? [])
const isCurrentSubpost = isSubpost(currentPostId) const isCurrentSubpost = isSubpost(currentPostId)
const navigation = await getAdjacentPosts(currentPostId) const navigation = await getAdjacentPosts(currentPostId)