Code Blocks (Expressive Code)
Relevant source files
The following files were used as context for generating this wiki page:The Charlotte project utilizes the astro-expressive-code library to render syntax-highlighted code blocks within Markdown content. This system is enhanced with a custom plugin that provides a feature-rich toolbar, including a language badge, an optional title, and a copy-to-clipboard button. The styling and behavior are tailored to match the blog's theme.
The core of this feature is a custom Expressive Code plugin defined in src/plugins/expressive-code/code-toolbar.ts, which programmatically builds and injects a toolbar at the top of each code block. Configuration is managed globally in src/config.ts, and final layout adjustments are handled via CSS in src/styles/markdown.css.
Core Dependencies
The functionality relies on several key packages from the Expressive Code ecosystem, as defined in the project's dependencies.
| Package | Version | Purpose |
|---|---|---|
astro-expressive-code | ^0.41.3 | Astro integration for Expressive Code. |
@expressive-code/core | ^0.41.3 | The core engine for processing and rendering code blocks. |
@expressive-code/plugin-collapsible-sections | ^0.41.3 | Plugin to allow collapsible sections within code blocks. |
@expressive-code/plugin-line-numbers | ^0.41.3 | Plugin to display line numbers. |
Sources: package.json:23-26
Configuration
Global configuration for Expressive Code is defined in src/config.ts within the expressiveCodeConfig object.
// src/config.ts:121-125
export const expressiveCodeConfig: ExpressiveCodeConfig = {
// 注意:某些样式(如背景颜色)正在被覆盖,请参阅 astro.config.mjs 文件
// 请选择深色主题,因为此博客主题目前仅支持深色背景
theme: "github-dark",
};
The corresponding type definition is located in src/types/config.ts.
// src/types/config.ts:88-90
export type ExpressiveCodeConfig = {
theme: string;
};
| Option | Type | Value | Description |
|---|---|---|---|
theme | string | "github-dark" | Sets the syntax highlighting theme. The project is configured to use a dark theme. |
Sources: src/config.ts:121-125, src/types/config.ts:88-90
Custom Code Toolbar Plugin
A custom plugin, pluginCodeToolbar, is defined in src/plugins/expressive-code/code-toolbar.ts to replace the default Expressive Code header with a custom-built toolbar. This plugin is responsible for creating the toolbar's structure, adding its components, and injecting base styles.
Toolbar Structure
The plugin generates a div with the class .code-toolbar. This toolbar contains a language badge, a copy button, and an optional, centrally-aligned title.
The following diagram illustrates the generated DOM structure for the toolbar.
Component Creation
The toolbar and its child elements are constructed using a set of helper functions that generate HAST (HTML Abstract Syntax Tree) element nodes.
| Function | Description |
|---|---|
createCopyIcon() | Creates the SVG element for the default copy icon. |
createSuccessIcon() | Creates the SVG element for the success checkmark icon, shown after a successful copy action. |
createCopyButton() | Creates the <button> element that houses the copy and success icons. |
createLanguageBadge(language) | Creates a <span> element to display the code block's language. |
createToolbar(language, title) | Assembles the final toolbar element by combining the language badge, an optional title, a spacer, and the copy button. |
Sources: src/plugins/expressive-code/code-toolbar.ts:11-176
Plugin Logic Flow
The plugin uses the postprocessRenderedBlock hook to modify the rendered output of each code block. The logic extracts metadata, removes the default header, and prepends the custom toolbar.
This flowchart shows the process within the postprocessRenderedBlock hook.
The title is extracted from the code block's meta string by matching the pattern title="...".
// src/plugins/expressive-code/code-toolbar.ts:318-321
if (codeBlock.meta) {
const titleMatch = codeBlock.meta.match(/title=["'](https://github.com/ChuwuYo/Charlotte/blob/HEAD/%5B%5E%22'%5D%2B)["']/);
if (titleMatch) {
title = titleMatch[1];
}
}
Sources: src/plugins/expressive-code/code-toolbar.ts:311-349
Styling and Layout
The appearance of the code blocks and the custom toolbar is controlled by two main sources: base styles injected by the plugin and global overrides in src/styles/markdown.css.
Injected Plugin Styles
The pluginCodeToolbar injects a block of CSS using the baseStyles property. This CSS defines the core appearance and behavior of the toolbar and its components, including:
- Flexbox layout for the toolbar.
- Styles for the language badge (
.code-toolbar-lang). - Absolute positioning for the title (
.code-toolbar-title) to center it. - Styles for the copy button (
.code-toolbar-copy), includinghover,active,focus-visible, andsuccessstates. - Transitions for icon visibility (copy vs. success).
- Responsive adjustments for smaller screens.
Sources: src/plugins/expressive-code/code-toolbar.ts:182-299
CSS Overrides
The file src/styles/markdown.css provides additional styles to ensure the Expressive Code component integrates correctly with the site's layout. It establishes a flexbox column layout for the main .frame container, which holds both the custom toolbar and the <pre> element.
/* src/styles/markdown.css:55-61 */
.expressive-code .frame {
@apply overflow-hidden;
display: flex;
flex-direction: column;
}
This ensures that the toolbar sits directly on top of the code area, and both elements are contained within the rounded border of the parent .frame.
Sources: src/styles/markdown.css:55-71
Usage in Markdown
To add a code block with a title, use a standard fenced code block and append the title attribute to the language identifier line.
The following example from a post demonstrates how to specify a language (shell) and a title (目录结构).
```shell title="目录结构"
src/content/posts/
├── post-1.md
└── post-2/
├── cover.png
└── index.md
The `pluginCodeToolbar` will automatically parse this meta string to display "目录结构" as the title in the code block's toolbar.
*Sources: [src/content/posts/2018-10-30/Charlotte-Markdown.md:46-52](https://github.com/ChuwuYo/Charlotte/blob/HEAD/src/content/posts/2018-10-30/Charlotte-Markdown.md#L46-L52)*