@jet-w/astro-blog 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -0
- package/package.json +3 -2
- package/src/components/ui/SearchBox.vue +33 -1
- package/src/pages/search-index.json.ts +46 -8
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @jet-w/astro-blog
|
|
2
|
+
|
|
3
|
+
A modern, feature-rich Astro blog theme with Vue and Tailwind CSS support. Provides a complete blogging solution with multi-language support, rich markdown capabilities, and interactive components.
|
|
4
|
+
|
|
5
|
+
**Official Website**: [https://jet-w.github.io/jet-w.astro-blog/](https://jet-w.github.io/jet-w.astro-blog/)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-Language Support (i18n)** - Built-in internationalization with configurable locales
|
|
10
|
+
- **Blog System** - Posts, tags, categories, archives, and pagination
|
|
11
|
+
- **Search** - Full-text search powered by Fuse.js
|
|
12
|
+
- **RSS Feed** - Automatic RSS feed generation
|
|
13
|
+
- **Dark Mode** - Theme switching support
|
|
14
|
+
- **Rich Markdown** - Code highlighting, math equations (KaTeX), Mermaid diagrams, custom containers
|
|
15
|
+
- **Interactive Components** - Slides, ECharts, video embeds (YouTube, Bilibili)
|
|
16
|
+
- **Responsive Design** - Mobile-friendly layouts with Tailwind CSS
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @jet-w/astro-blog
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Peer Dependencies
|
|
25
|
+
|
|
26
|
+
Make sure you have these packages installed:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install astro @astrojs/mdx @astrojs/rss @astrojs/tailwind @astrojs/vue tailwindcss vue
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Configure Astro
|
|
35
|
+
|
|
36
|
+
Add the integration to your `astro.config.mjs`:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { defineConfig } from 'astro/config';
|
|
40
|
+
import { astroBlog } from '@jet-w/astro-blog';
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
integrations: [astroBlog()],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Set Up Content Collections
|
|
48
|
+
|
|
49
|
+
Create `src/content/config.ts`:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { defineCollection, z } from 'astro:content';
|
|
53
|
+
|
|
54
|
+
const posts = defineCollection({
|
|
55
|
+
type: 'content',
|
|
56
|
+
schema: z.object({
|
|
57
|
+
title: z.string(),
|
|
58
|
+
description: z.string().optional(),
|
|
59
|
+
date: z.date(),
|
|
60
|
+
tags: z.array(z.string()).optional(),
|
|
61
|
+
categories: z.array(z.string()).optional(),
|
|
62
|
+
draft: z.boolean().optional(),
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const collections = { posts };
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Create Your First Post
|
|
70
|
+
|
|
71
|
+
Create a markdown file in `src/content/posts/`:
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
---
|
|
75
|
+
title: Hello World
|
|
76
|
+
date: 2024-01-01
|
|
77
|
+
tags: [hello, first-post]
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
Welcome to my blog!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
### Site Configuration
|
|
86
|
+
|
|
87
|
+
Create `src/config/site.ts`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { defineSiteConfig } from '@jet-w/astro-blog/config';
|
|
91
|
+
|
|
92
|
+
export default defineSiteConfig({
|
|
93
|
+
title: 'My Blog',
|
|
94
|
+
description: 'A blog about...',
|
|
95
|
+
author: 'Your Name',
|
|
96
|
+
lang: 'en',
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### i18n Configuration
|
|
101
|
+
|
|
102
|
+
Create `src/config/i18n.ts`:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { defineI18nConfig } from '@jet-w/astro-blog/config';
|
|
106
|
+
|
|
107
|
+
export default defineI18nConfig({
|
|
108
|
+
defaultLocale: 'en',
|
|
109
|
+
locales: ['en', 'zh-cn'],
|
|
110
|
+
translations: {
|
|
111
|
+
en: {
|
|
112
|
+
'nav.home': 'Home',
|
|
113
|
+
'nav.blog': 'Blog',
|
|
114
|
+
// ...
|
|
115
|
+
},
|
|
116
|
+
'zh-cn': {
|
|
117
|
+
'nav.home': '首页',
|
|
118
|
+
'nav.blog': '博客',
|
|
119
|
+
// ...
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Sidebar Configuration
|
|
126
|
+
|
|
127
|
+
Create `src/config/sidebar.ts`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { defineSidebarConfig } from '@jet-w/astro-blog/config';
|
|
131
|
+
|
|
132
|
+
export default defineSidebarConfig({
|
|
133
|
+
// Manual configuration
|
|
134
|
+
items: [
|
|
135
|
+
{ label: 'Getting Started', link: '/docs/getting-started' },
|
|
136
|
+
{ label: 'Guide', items: [...] },
|
|
137
|
+
],
|
|
138
|
+
// Or use auto-scan
|
|
139
|
+
autoScan: {
|
|
140
|
+
directory: 'docs',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Layouts
|
|
146
|
+
|
|
147
|
+
The theme provides several layouts:
|
|
148
|
+
|
|
149
|
+
- **BaseLayout** - Standard page layout
|
|
150
|
+
- **PageLayout** - Blog post layout with sidebar
|
|
151
|
+
- **AboutLayout** - Profile/about page layout
|
|
152
|
+
- **SlidesLayout** - Presentation slides layout
|
|
153
|
+
|
|
154
|
+
```astro
|
|
155
|
+
---
|
|
156
|
+
import { PageLayout } from '@jet-w/astro-blog/layouts';
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
<PageLayout title="My Page">
|
|
160
|
+
<p>Page content here</p>
|
|
161
|
+
</PageLayout>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Components
|
|
165
|
+
|
|
166
|
+
Import components as needed:
|
|
167
|
+
|
|
168
|
+
```astro
|
|
169
|
+
---
|
|
170
|
+
import { SearchBox, ThemeToggle, LanguageSwitcher } from '@jet-w/astro-blog/components';
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
<SearchBox />
|
|
174
|
+
<ThemeToggle />
|
|
175
|
+
<LanguageSwitcher />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Available Components
|
|
179
|
+
|
|
180
|
+
- **UI**: SearchBox, ThemeToggle, LanguageSwitcher, MobileMenu, Pagination
|
|
181
|
+
- **Blog**: NavigationTabs, RelatedPosts, TagList
|
|
182
|
+
- **Media**: Video, YouTube, Bilibili, Slides
|
|
183
|
+
- **About**: TagCard, SocialLinks, Timeline
|
|
184
|
+
|
|
185
|
+
## Markdown Extensions
|
|
186
|
+
|
|
187
|
+
### Custom Containers
|
|
188
|
+
|
|
189
|
+
```markdown
|
|
190
|
+
:::tip
|
|
191
|
+
This is a tip
|
|
192
|
+
:::
|
|
193
|
+
|
|
194
|
+
:::warning
|
|
195
|
+
This is a warning
|
|
196
|
+
:::
|
|
197
|
+
|
|
198
|
+
:::danger
|
|
199
|
+
This is dangerous
|
|
200
|
+
:::
|
|
201
|
+
|
|
202
|
+
:::details Summary
|
|
203
|
+
Hidden content here
|
|
204
|
+
:::
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Math Equations
|
|
208
|
+
|
|
209
|
+
```markdown
|
|
210
|
+
Inline: $E = mc^2$
|
|
211
|
+
|
|
212
|
+
Block:
|
|
213
|
+
$$
|
|
214
|
+
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
|
|
215
|
+
$$
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Mermaid Diagrams
|
|
219
|
+
|
|
220
|
+
````markdown
|
|
221
|
+
```mermaid
|
|
222
|
+
graph TD
|
|
223
|
+
A[Start] --> B{Decision}
|
|
224
|
+
B -->|Yes| C[OK]
|
|
225
|
+
B -->|No| D[Cancel]
|
|
226
|
+
```
|
|
227
|
+
````
|
|
228
|
+
|
|
229
|
+
### Tabs
|
|
230
|
+
|
|
231
|
+
```markdown
|
|
232
|
+
:::tabs
|
|
233
|
+
@tab JavaScript
|
|
234
|
+
console.log('Hello');
|
|
235
|
+
|
|
236
|
+
@tab Python
|
|
237
|
+
print('Hello')
|
|
238
|
+
:::
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Project Structure
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
src/
|
|
245
|
+
├── components/ # Vue and Astro components
|
|
246
|
+
├── config/ # Configuration files
|
|
247
|
+
├── layouts/ # Layout templates
|
|
248
|
+
├── pages/ # Route pages
|
|
249
|
+
├── plugins/ # Markdown/MDX plugins
|
|
250
|
+
├── styles/ # Global styles
|
|
251
|
+
├── types/ # TypeScript definitions
|
|
252
|
+
└── utils/ # Utility functions
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
[Apache 2.0](./LICENSE)
|
|
258
|
+
|
|
259
|
+
## Author
|
|
260
|
+
|
|
261
|
+
Haiyue
|
|
262
|
+
|
|
263
|
+
## Links
|
|
264
|
+
|
|
265
|
+
- [Official Website](https://jet-w.github.io/jet-w.astro-blog/)
|
|
266
|
+
- [GitHub Repository](https://github.com/jet-w/jet-w.astro-blog)
|
|
267
|
+
- [NPM Package](https://www.npmjs.com/package/@jet-w/astro-blog)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jet-w/astro-blog",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "A modern Astro blog theme with Vue and Tailwind CSS support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"src/types",
|
|
45
45
|
"src/utils",
|
|
46
46
|
"src/config",
|
|
47
|
-
"templates"
|
|
47
|
+
"templates",
|
|
48
|
+
"README.md"
|
|
48
49
|
],
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsup",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
@click="handleResultClick"
|
|
37
37
|
>
|
|
38
38
|
<h4 class="text-sm font-medium text-slate-900 dark:text-slate-100 mb-1" v-html="highlightText(result.title)"></h4>
|
|
39
|
-
<p class="text-xs text-slate-600 dark:text-slate-400 line-clamp-2" v-html="highlightText(result
|
|
39
|
+
<p class="text-xs text-slate-600 dark:text-slate-400 line-clamp-2" v-html="highlightText(getMatchedContent(result))"></p>
|
|
40
40
|
<div class="flex flex-wrap gap-1 mt-2">
|
|
41
41
|
<span
|
|
42
42
|
v-for="tag in result.tags.slice(0, 3)"
|
|
@@ -138,6 +138,38 @@ const handleResultClick = () => {
|
|
|
138
138
|
searchResults.value = []
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// 获取匹配的内容片段,优先显示匹配到的内容上下文
|
|
142
|
+
const getMatchedContent = (result: SearchResult): string => {
|
|
143
|
+
const query = searchQuery.value.toLowerCase()
|
|
144
|
+
|
|
145
|
+
// 如果标题或描述匹配,优先显示描述
|
|
146
|
+
if (result.title.toLowerCase().includes(query) || result.description.toLowerCase().includes(query)) {
|
|
147
|
+
return result.description || result.content.substring(0, 150)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 如果内容匹配,显示匹配位置的上下文
|
|
151
|
+
if (result.content) {
|
|
152
|
+
const contentLower = result.content.toLowerCase()
|
|
153
|
+
const matchIndex = contentLower.indexOf(query)
|
|
154
|
+
|
|
155
|
+
if (matchIndex !== -1) {
|
|
156
|
+
// 提取匹配位置前后的上下文
|
|
157
|
+
const contextStart = Math.max(0, matchIndex - 50)
|
|
158
|
+
const contextEnd = Math.min(result.content.length, matchIndex + query.length + 100)
|
|
159
|
+
let snippet = result.content.substring(contextStart, contextEnd)
|
|
160
|
+
|
|
161
|
+
// 添加省略号
|
|
162
|
+
if (contextStart > 0) snippet = '...' + snippet
|
|
163
|
+
if (contextEnd < result.content.length) snippet = snippet + '...'
|
|
164
|
+
|
|
165
|
+
return snippet
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 默认返回描述或内容开头
|
|
170
|
+
return result.description || result.content.substring(0, 150)
|
|
171
|
+
}
|
|
172
|
+
|
|
141
173
|
const highlightText = (text: string) => {
|
|
142
174
|
if (!searchQuery.value || !text) return text || ''
|
|
143
175
|
|
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro';
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
|
|
4
|
+
// 从 Markdown 内容中提取纯文本
|
|
5
|
+
function extractPlainText(markdown: string): string {
|
|
6
|
+
if (!markdown) return '';
|
|
7
|
+
|
|
8
|
+
return markdown
|
|
9
|
+
// 移除代码块
|
|
10
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
11
|
+
.replace(/`[^`]*`/g, '')
|
|
12
|
+
// 移除 HTML 标签
|
|
13
|
+
.replace(/<[^>]*>/g, '')
|
|
14
|
+
// 移除图片
|
|
15
|
+
.replace(/!\[.*?\]\(.*?\)/g, '')
|
|
16
|
+
// 移除链接但保留文本
|
|
17
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
18
|
+
// 移除标题标记
|
|
19
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
20
|
+
// 移除粗体/斜体标记
|
|
21
|
+
.replace(/\*\*([^*]*)\*\*/g, '$1')
|
|
22
|
+
.replace(/\*([^*]*)\*/g, '$1')
|
|
23
|
+
.replace(/__([^_]*)__/g, '$1')
|
|
24
|
+
.replace(/_([^_]*)_/g, '$1')
|
|
25
|
+
// 移除引用标记
|
|
26
|
+
.replace(/^>\s+/gm, '')
|
|
27
|
+
// 移除列表标记
|
|
28
|
+
.replace(/^[\s]*[-*+]\s+/gm, '')
|
|
29
|
+
.replace(/^[\s]*\d+\.\s+/gm, '')
|
|
30
|
+
// 移除水平线
|
|
31
|
+
.replace(/^[-*_]{3,}$/gm, '')
|
|
32
|
+
// 移除多余空白
|
|
33
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
34
|
+
.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
export const GET: APIRoute = async () => {
|
|
5
38
|
const posts = await getCollection('posts', ({ data }) => !data.draft);
|
|
6
39
|
|
|
7
|
-
const searchIndex = posts.map(post =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
const searchIndex = posts.map(post => {
|
|
41
|
+
// 提取纯文本内容用于搜索
|
|
42
|
+
const plainContent = extractPlainText(post.body || '');
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
title: post.data.title,
|
|
46
|
+
description: post.data.description || '',
|
|
47
|
+
url: `/posts/${post.id.toLowerCase()}`,
|
|
48
|
+
content: plainContent, // 完整的纯文本内容
|
|
49
|
+
tags: post.data.tags || [],
|
|
50
|
+
categories: post.data.categories || []
|
|
51
|
+
};
|
|
52
|
+
});
|
|
15
53
|
|
|
16
54
|
return new Response(JSON.stringify(searchIndex), {
|
|
17
55
|
headers: {
|