Writing Posts
Relevant source files
The following files were used as context for generating this wiki page:
Writing posts for the Charlotte blog involves creating Markdown files with specific frontmatter metadata. These files are stored in a designated content directory and are processed by Astro's content collections system. The project provides a helper script to streamline post creation and supports a rich set of Markdown extensions for authoring content.
Posts are managed through a combination of file-based organization, frontmatter properties, and site-wide configuration. Features like drafts, pinned posts, and automatic sorting by date are handled by the system's content utilities.
Creating a New Post
A command-line script is provided to quickly scaffold a new post file with the required frontmatter.
Sources: scripts/new-post.js, package.json:10
Usage
To create a new post, run the following command from the project root:
pnpm new-post <filename>
The <filename> will be used as the post's title in the frontmatter and as the filename (with a .md extension automatically added if missing).
Sources: README.md:32-34, scripts/new-post.js:15-23
Script Logic
The new-post.js script performs the following actions:
- Checks if a filename argument is provided.
- Ensures the filename has a
.mdor.mdxextension. - Constructs the full path within
src/content/posts/. - Checks if a file with the same name already exists to prevent overwrites.
- Creates the directory if it doesn't exist.
- Writes a new Markdown file with a pre-filled frontmatter template.
The diagram below illustrates the workflow of the new-post.js script.
Sources: scripts/new-post.js:5-46
Default Template
The script generates a file with the following default frontmatter:
---
title: <filename>
published: YYYY-MM-DD
description: ''
image: ''
tags: []
category: ''
draft: false
lang: ''
---
Sources: scripts/new-post.js:37-45
File Structure
All blog posts must be located within the src/content/posts/ directory. You can either place Markdown files directly in this folder or create subdirectories for better organization, which is useful for grouping a post with its assets like images.
src/content/posts/
├── post-1.md
└── post-2/
├── cover.png
└── index.md
Sources: src/content/posts/2018-10-30/Charlotte-Markdown.md:29-35
Post Frontmatter and Schema
Each post is a Markdown file that begins with a YAML frontmatter block. This metadata is defined by a Zod schema and is used to control how the post is displayed and organized.
Frontmatter Fields
The following table details the available frontmatter fields for a post.
| Attribute | Type | Description | Default |
|---|---|---|---|
title | string | The title of the post. | (Required) |
published | date | The publication date of the post. Used for sorting. | (Required) |
updated | date | (Optional) The date the post was last updated. | undefined |
description | string | A short summary of the post, shown on list pages. | "" |
image | string | Path to the post's cover image. | "" |
tags | string[] | A list of tags associated with the post. | [] |
category | string | The category the post belongs to. | "" |
draft | boolean | If true, the post will not be shown in production builds. | false |
lang | string | Language of the post. | "" |
isPinned | boolean | (Internal) Set to true if the post is pinned. | false |
Sources: src/content/config.ts:4-15, src/content/posts/2018-10-30/Charlotte-Markdown.md:10-20, frontmatter.json:19-75
Content Schema (Zod)
Astro's content collections feature uses a Zod schema to validate the frontmatter of each post. This ensures data integrity across the site.
The diagram below shows the entity relationship for a post's schema.
Sources: src/content/config.ts:3-25
The schema is defined in src/content/config.ts:
const postsCollection = defineCollection({
schema: z.object({
title: z.string(),
published: z.date(),
updated: z.date().optional(),
draft: z.boolean().optional().default(false),
description: z.string().optional().default(""),
image: z.string().optional().default(""),
tags: z.array(z.string()).optional().default([]),
category: z.string().optional().nullable().default(""),
lang: z.string().optional().default(""),
isPinned: z.boolean().optional().default(false),
/* For internal use */
prevTitle: z.string().default(""),
prevSlug: z.string().default(""),
nextTitle: z.string().default(""),
nextSlug: z.string().default(""),
}),
});
Sources: src/content/config.ts:3-25
Post Management
Sorting and Pinning
By default, posts are sorted in reverse chronological order based on the published date. The project also supports pinning specific posts to the top of the list, regardless of their publication date.
- Pinning: To pin posts, add their slugs to the
pinnedPostarray insrc/config.ts. The order of slugs in this array determines the order of the pinned posts. - Sorting Logic: The
getSortedPostsfunction first fetches all posts, sorts them by date, and then applies the pinning logic. Pinned posts are moved to the beginning of the array in the specified order, followed by the remaining posts sorted by date.
// src/config.ts
export const siteConfig: SiteConfig = {
// ...
pinnedPost: ["2025-09-29/Unveiling"], // 置顶文章的 slug 路径
// ...
}
Sources: src/config.ts:60
The following flowchart shows how posts are sorted and pinned.
Sources: src/utils/content-utils.ts:10-74
Drafts
The draft frontmatter property controls post visibility.
- If
draft: true, the post will be visible in the development environment (pnpm dev) but will be excluded from production builds (pnpm build). - If
draft: falseor omitted, the post will be visible in all environments.
This logic is handled when fetching posts in getRawSortedPosts.
Sources: src/utils/content-utils.ts:78-80, src/content/posts/2018-10-30/Charlotte-Markdown.md:19
Extended Markdown Syntax
In addition to standard Markdown, Charlotte supports several custom syntax features for richer content.
Admonitions (Alerts)
Custom blocks to highlight information. They can be created using ::: syntax or GitHub's blockquote syntax.
| Type | Description |
|---|---|
note | Information the user should consider. |
tip | Optional information to help understanding. |
important | Key information necessary to complete a task. |
warning | Critical content requiring immediate attention due to potential risks. |
caution | Negative potential consequences of an action. |
Syntax:
:::note[Custom Title]
This is a note with a custom title.
:::
> [!TIP]
> This is a tip using GitHub's syntax.
Sources: src/content/posts/2018-10-30/Charlotte-Markdown.md:144-187
GitHub Repository Cards
Embed a dynamic card that fetches and displays information about a GitHub repository.
Syntax:
::github{repo="<owner>/<repo>"}
Example:
::github{repo="ChuwuYo/Charlotte"}
Sources: src/content/posts/2018-10-30/Charlotte-Markdown.md:131-139, src/content/spec/projects.md:3
Other Features
- Spoilers: Hide content until it is clicked.
Content :spoiler[is hidden **here**]! - Math Formulas: Both inline (
$..$) and block ($$..$$) math formulas are supported via KaTeX. - Image Paths: Images can be linked via absolute URLs, from the
/publicdirectory (e.g.,/favicon/icon.png), or using relative paths from the Markdown file (e.g.,./cover.png).
Sources: src/content/posts/2018-10-30/Charlotte-Markdown.md:92-129, 191-200