@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 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.7",
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.description)"></p>
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
- title: post.data.title,
9
- description: post.data.description || '',
10
- url: `/posts/${post.id.toLowerCase()}`,
11
- content: post.body?.substring(0, 500) || '', // 取前500字符作为搜索内容
12
- tags: post.data.tags || [],
13
- categories: post.data.categories || []
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: {