@hutusi/amytis 1.5.6 → 1.7.0

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/CLAUDE.md +3 -2
  3. package/GEMINI.md +13 -6
  4. package/README.md +1 -1
  5. package/TODO.md +21 -76
  6. package/bun.lock +18 -3
  7. package/content/about.mdx +1 -0
  8. package/content/about.zh.mdx +21 -0
  9. package/content/flows/2026/02/20.md +16 -0
  10. package/content/links.mdx +42 -0
  11. package/content/links.zh.mdx +41 -0
  12. package/content/posts/2026-02-20-i18n-routing-considerations.mdx +150 -0
  13. package/content/posts/multimedia-showcase/index.mdx +261 -0
  14. package/content/privacy.mdx +32 -0
  15. package/content/privacy.zh.mdx +32 -0
  16. package/docs/ARCHITECTURE.md +11 -2
  17. package/docs/CONTRIBUTING.md +4 -2
  18. package/docs/deployment.md +9 -1
  19. package/eslint.config.mjs +2 -0
  20. package/package.json +5 -4
  21. package/public/next-image-export-optimizer-hashes.json +0 -3
  22. package/scripts/copy-assets.ts +1 -1
  23. package/site.config.ts +126 -44
  24. package/src/app/[slug]/page.tsx +0 -10
  25. package/src/app/archive/page.tsx +38 -10
  26. package/src/app/books/[slug]/page.tsx +18 -0
  27. package/src/app/flows/[year]/[month]/[day]/page.tsx +21 -4
  28. package/src/app/layout.tsx +48 -21
  29. package/src/app/page.tsx +135 -72
  30. package/src/app/posts/[slug]/page.tsx +6 -12
  31. package/src/app/search.json/route.ts +4 -0
  32. package/src/app/series/[slug]/page.tsx +18 -0
  33. package/src/app/subscribe/page.tsx +17 -0
  34. package/src/app/tags/[tag]/page.tsx +9 -26
  35. package/src/app/tags/page.tsx +3 -8
  36. package/src/components/AuthorCard.tsx +43 -0
  37. package/src/components/Comments.tsx +20 -4
  38. package/src/components/ExternalLinks.tsx +6 -2
  39. package/src/components/Footer.tsx +35 -26
  40. package/src/components/LanguageProvider.tsx +0 -5
  41. package/src/components/LanguageSwitch.tsx +117 -6
  42. package/src/components/LocaleSwitch.tsx +33 -0
  43. package/src/components/Navbar.tsx +31 -8
  44. package/src/components/PostNavigation.tsx +55 -0
  45. package/src/components/PostSidebar.tsx +172 -126
  46. package/src/components/ReadingProgressBar.tsx +6 -21
  47. package/src/components/RelatedPosts.tsx +1 -1
  48. package/src/components/Search.tsx +420 -70
  49. package/src/components/SelectedBooksSection.tsx +12 -6
  50. package/src/components/ShareBar.tsx +115 -0
  51. package/src/components/SimpleLayoutHeader.tsx +5 -14
  52. package/src/components/SubscribePage.tsx +298 -0
  53. package/src/components/TagContentTabs.tsx +103 -0
  54. package/src/components/TagPageHeader.tsx +7 -13
  55. package/src/components/TagSidebar.tsx +142 -0
  56. package/src/components/TagsIndexClient.tsx +156 -0
  57. package/src/hooks/useScrollY.ts +41 -0
  58. package/src/i18n/translations.ts +110 -2
  59. package/src/layouts/PostLayout.tsx +34 -7
  60. package/src/layouts/SimpleLayout.tsx +53 -15
  61. package/src/lib/markdown.ts +71 -15
  62. package/src/lib/search-utils.test.ts +163 -0
  63. package/src/lib/search-utils.ts +39 -0
  64. package/src/types/pagefind.d.ts +42 -0
  65. package/src/components/TableOfContents.tsx +0 -158
@@ -0,0 +1,261 @@
1
+ ---
2
+ title: "Multimedia Showcase: Video, Podcasts & Embeds"
3
+ date: "2026-02-20"
4
+ excerpt: "A comprehensive test of multimedia embedding support — YouTube, Vimeo, podcasts, livestreams, HTML5 native media, and audio platforms."
5
+ category: "Showcase"
6
+ tags: ["multimedia", "video", "audio", "podcast", "embed", "test"]
7
+ coverImage: "text:Media"
8
+ toc: true
9
+ ---
10
+
11
+ This post tests how Amytis renders multimedia content embedded via raw HTML iframes and native HTML5 media elements. Since `rehype-raw` is enabled, standard HTML embed codes work directly inside Markdown.
12
+
13
+ ---
14
+
15
+ ## YouTube Video
16
+
17
+ Responsive 16:9 YouTube embed using a padded wrapper:
18
+
19
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
20
+ <iframe
21
+ src="https://www.youtube.com/embed/jNQXAC9IVRw?rel=0"
22
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
23
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
24
+ allowfullscreen
25
+ title="Me at the zoo — first YouTube video ever uploaded (2005)"
26
+ ></iframe>
27
+ </div>
28
+
29
+ *"Me at the zoo" — the very first video uploaded to YouTube (April 23, 2005).*
30
+
31
+ ---
32
+
33
+ ## Vimeo Video
34
+
35
+ Vimeo also supports responsive 16:9 embedding:
36
+
37
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
38
+ <iframe
39
+ src="https://player.vimeo.com/video/76979871?color=ff9933&title=0&byline=0&portrait=0"
40
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
41
+ allow="autoplay; fullscreen; picture-in-picture"
42
+ allowfullscreen
43
+ title="Big Buck Bunny — Blender Foundation (Vimeo)"
44
+ ></iframe>
45
+ </div>
46
+
47
+ *Big Buck Bunny (2008) — open-source animated short by the Blender Foundation.*
48
+
49
+ ---
50
+
51
+ ## Podcast Embeds
52
+
53
+ ### Spotify Podcast
54
+
55
+ Spotify provides a fixed-height iframe embed for individual episodes:
56
+
57
+ <iframe
58
+ src="https://open.spotify.com/embed/episode/0b8q6Q7fKqZLlSc0Lh1WQe?utm_source=generator&theme=0"
59
+ style="border-radius: 12px; margin: 1rem 0;"
60
+ width="100%"
61
+ height="152"
62
+ frameborder="0"
63
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
64
+ loading="lazy"
65
+ title="Spotify Podcast Episode"
66
+ ></iframe>
67
+
68
+ You can also embed the full show player at a taller height (352px) to show the episode list:
69
+
70
+ <iframe
71
+ src="https://open.spotify.com/embed/show/5as3aKmN2k11yfDDDSrvaZ?utm_source=generator"
72
+ style="border-radius: 12px; margin: 1rem 0;"
73
+ width="100%"
74
+ height="352"
75
+ frameborder="0"
76
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
77
+ loading="lazy"
78
+ title="Spotify Show Player"
79
+ ></iframe>
80
+
81
+ ### Apple Podcasts
82
+
83
+ Apple Podcasts uses a similar iframe embed pattern:
84
+
85
+ <iframe
86
+ src="https://embed.podcasts.apple.com/us/podcast/syntax-tasty-web-development-treats/id1253186678?itsct=podcast_box_player&amp;itscg=30200&amp;ls=1&amp;theme=auto"
87
+ height="175px"
88
+ frameborder="0"
89
+ sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
90
+ allow="autoplay *; encrypted-media *; clipboard-write"
91
+ style="width: 100%; max-width: 660px; overflow: hidden; border-radius: 10px; margin: 1rem 0; display: block;"
92
+ title="Apple Podcasts — Syntax.fm"
93
+ ></iframe>
94
+
95
+ ---
96
+
97
+ ## Livestream Embeds
98
+
99
+ ### YouTube Live
100
+
101
+ A YouTube Live channel or active live video can be embedded exactly like a regular video. Use the channel's live URL (`/live`) or a live event ID:
102
+
103
+ ```html
104
+ <!-- Replace CHANNEL_ID with the YouTube channel ID -->
105
+ <iframe
106
+ src="https://www.youtube.com/embed/live_stream?channel=CHANNEL_ID"
107
+ ...
108
+ ></iframe>
109
+ ```
110
+
111
+ Below is NASA's public live stream:
112
+
113
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
114
+ <iframe
115
+ src="https://www.youtube.com/embed/xAieE-QtOeM?autoplay=0"
116
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
117
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
118
+ allowfullscreen
119
+ title="NASA TV Live"
120
+ ></iframe>
121
+ </div>
122
+
123
+ ### Twitch Stream
124
+
125
+ Twitch requires your site's hostname in the `parent` query parameter (so the embed works in production):
126
+
127
+ ```html
128
+ <!-- Replace CHANNEL_NAME with a Twitch channel, and your-domain.com with your domain -->
129
+ <iframe
130
+ src="https://player.twitch.tv/?channel=CHANNEL_NAME&parent=your-domain.com"
131
+ height="378"
132
+ width="100%"
133
+ allowfullscreen
134
+ title="Twitch Stream"
135
+ ></iframe>
136
+ ```
137
+
138
+ Twitch also supports embedding past broadcasts (VODs) and clips:
139
+
140
+ ```html
141
+ <!-- VOD -->
142
+ <iframe src="https://player.twitch.tv/?video=VOD_ID&parent=your-domain.com" ...></iframe>
143
+ <!-- Clip -->
144
+ <iframe src="https://clips.twitch.tv/embed?clip=CLIP_SLUG&parent=your-domain.com" ...></iframe>
145
+ ```
146
+
147
+ ---
148
+
149
+ ## HTML5 Native Media
150
+
151
+ Native `<video>` and `<audio>` elements work without any third-party embeds.
152
+
153
+ ### HTML5 Video
154
+
155
+ <video
156
+ controls
157
+ style="width: 100%; border-radius: 12px; margin: 1rem 0; background: #000;"
158
+ poster="https://peach.blender.org/wp-content/uploads/title_anouncement.jpg"
159
+ >
160
+ <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" type="video/mp4" />
161
+ Your browser does not support the HTML5 video element.
162
+ </video>
163
+
164
+ *Elephants Dream (2006) — first open-source animated film by the Blender Foundation.*
165
+
166
+ Attributes you can use:
167
+
168
+ | Attribute | Description |
169
+ | :--- | :--- |
170
+ | `controls` | Show playback controls |
171
+ | `autoplay` | Start playing automatically (use with `muted`) |
172
+ | `muted` | Mute by default (required for autoplay) |
173
+ | `loop` | Loop the video |
174
+ | `poster` | Preview image shown before play |
175
+ | `preload` | `auto` \| `metadata` \| `none` |
176
+
177
+ ### HTML5 Audio
178
+
179
+ <audio
180
+ controls
181
+ style="width: 100%; margin: 1rem 0;"
182
+ >
183
+ <source src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" type="audio/mpeg" />
184
+ Your browser does not support the HTML5 audio element.
185
+ </audio>
186
+
187
+ *SoundHelix royalty-free sample track.*
188
+
189
+ Multiple formats improve cross-browser support:
190
+
191
+ ```html
192
+ <audio controls>
193
+ <source src="audio.ogg" type="audio/ogg" />
194
+ <source src="audio.mp3" type="audio/mpeg" />
195
+ Your browser does not support the HTML5 audio element.
196
+ </audio>
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Audio Platforms
202
+
203
+ ### SoundCloud
204
+
205
+ SoundCloud exposes an iframe embed from any track's "Share → Embed" menu:
206
+
207
+ <iframe
208
+ width="100%"
209
+ height="166"
210
+ scrolling="no"
211
+ frameborder="no"
212
+ allow="autoplay"
213
+ style="border-radius: 8px; margin: 1rem 0;"
214
+ src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"
215
+ title="SoundCloud track"
216
+ ></iframe>
217
+
218
+ The full visual player (300px height) shows the waveform:
219
+
220
+ ```html
221
+ <iframe
222
+ width="100%"
223
+ height="300"
224
+ src="https://w.soundcloud.com/player/?url=TRACK_URL&visual=true"
225
+ ...
226
+ ></iframe>
227
+ ```
228
+
229
+ ### Bandcamp
230
+
231
+ Bandcamp provides both album and track embed codes:
232
+
233
+ ```html
234
+ <!-- Album embed -->
235
+ <iframe
236
+ style="border: 0; width: 100%; height: 120px;"
237
+ src="https://bandcamp.com/EmbeddedPlayer/album=ALBUM_ID/size=large/bgcol=ffffff/linkcol=0687f5/"
238
+ seamless
239
+ ></iframe>
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Embed Best Practices
245
+
246
+ When embedding third-party media, keep these in mind:
247
+
248
+ - **Responsive sizing**: Wrap iframes in a `padding-bottom: 56.25%; position: relative` container to maintain 16:9 ratio.
249
+ - **`loading="lazy"`**: Add this to below-the-fold embeds to defer loading and improve page speed.
250
+ - **`title` attribute**: Always provide a descriptive `title` for screen reader accessibility.
251
+ - **Privacy-enhanced mode**: YouTube supports `youtube-nocookie.com` to reduce tracking when embeds don't auto-play.
252
+ - **`sandbox` attribute**: Use for untrusted embeds to restrict what the iframe can do.
253
+ - **Twitch `parent` parameter**: Twitch requires your site's hostname in `parent` for embeds to work outside localhost.
254
+
255
+ ```html
256
+ <!-- YouTube privacy-enhanced embed -->
257
+ <iframe
258
+ src="https://www.youtube-nocookie.com/embed/VIDEO_ID?rel=0"
259
+ ...
260
+ ></iframe>
261
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: "Privacy Policy"
3
+ date: "2026-02-20"
4
+ excerpt: "How this site collects and handles your data."
5
+ layout: "simple"
6
+ ---
7
+
8
+ # Privacy Policy
9
+
10
+ Your privacy matters. This page explains what data is collected when you visit this site and how it is used.
11
+
12
+ ## Analytics
13
+
14
+ This site uses **Umami**, a privacy-focused, open-source analytics platform. Umami does not use cookies, does not track users across websites, and does not collect personally identifiable information. The data collected is limited to anonymous page-view statistics such as referrer, browser type, and country — used solely to understand which content is most useful.
15
+
16
+ No data is sold or shared with third parties.
17
+
18
+ ## Comments
19
+
20
+ Post comments are powered by **Giscus**, which uses GitHub Discussions. To leave a comment you must sign in with a GitHub account. Your comment and GitHub username are stored by GitHub and are subject to [GitHub's Privacy Policy](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).
21
+
22
+ If you do not have a GitHub account or prefer not to use it, you can always reach out directly by email.
23
+
24
+ ## External Links
25
+
26
+ This site may contain links to external websites. Once you leave this site, the privacy practices of those third-party sites apply and are outside this site's control.
27
+
28
+ ## Contact
29
+
30
+ If you have any questions about this privacy policy, feel free to reach out at the email address listed in the footer.
31
+
32
+ *Last updated: February 2026.*
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: "隐私政策"
3
+ date: "2026-02-20"
4
+ excerpt: "本站如何收集和处理您的数据。"
5
+ layout: "simple"
6
+ ---
7
+
8
+ # 隐私政策
9
+
10
+ 您的隐私对我们很重要。本页面说明您在访问本站时收集了哪些数据,以及这些数据如何被使用。
11
+
12
+ ## 数据分析
13
+
14
+ 本站使用 **Umami**,一个注重隐私的开源分析平台。Umami 不使用 Cookie,不跨网站追踪用户,也不收集任何个人身份信息。所收集的数据仅限于匿名页面访问统计,如来源网站、浏览器类型和所在国家,仅用于了解哪些内容最受欢迎。
15
+
16
+ 数据不会被出售或与任何第三方共享。
17
+
18
+ ## 评论
19
+
20
+ 文章评论由 **Giscus** 提供支持,基于 GitHub Discussions 实现。留言需要使用 GitHub 账号登录。您的评论和 GitHub 用户名由 GitHub 存储,受 [GitHub 隐私政策](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement)约束。
21
+
22
+ 如果您没有 GitHub 账号或不希望使用,也可以通过页脚中的电子邮件直接联系我。
23
+
24
+ ## 外部链接
25
+
26
+ 本站可能包含指向外部网站的链接。一旦您离开本站,这些第三方网站的隐私实践将适用,超出本站的控制范围。
27
+
28
+ ## 联系我们
29
+
30
+ 如果您对本隐私政策有任何疑问,请通过页脚中的电子邮件地址联系我们。
31
+
32
+ *最后更新:2026 年 2 月。*
@@ -50,6 +50,10 @@ src/app/
50
50
  [author]/page.tsx # Posts by author
51
51
  archive/
52
52
  page.tsx # Chronological archive
53
+ subscribe/
54
+ page.tsx # Subscription options
55
+ search.json/
56
+ route.ts # Supplementary structured search data
53
57
  [slug]/page.tsx # Static pages (about, etc.)
54
58
  ```
55
59
 
@@ -60,6 +64,7 @@ src/app/
60
64
  - **`Footer`** - Social links, copyright, site metadata, and language switch.
61
65
  - **`Hero`** - Homepage hero with collapsible intro.
62
66
  - **`BookLayout`** - Dedicated layout for book chapters with sidebar navigation.
67
+ - **`PageHeader`** - Consistent header for simple pages (Archive, Tags, About).
63
68
 
64
69
  ### Content Display
65
70
  - **`PostList`** - Card-based post listing.
@@ -67,6 +72,8 @@ src/app/
67
72
  - **`SeriesCatalog`** - Timeline-style post listing for series.
68
73
  - **`PostCard`** - Individual post preview card.
69
74
  - **`CoverImage`** - Optimized image component using `next-image-export-optimizer` with dynamic desaturated gradients.
75
+ - **`ShareBar`** - Social sharing buttons for posts and books.
76
+ - **`TagContentTabs`** - Tabbed interface for filtering posts vs flows by tag.
70
77
 
71
78
  ### Content Rendering
72
79
  - **`MarkdownRenderer`** - Core rendering component using `react-markdown` with plugins (GFM, Math, Raw HTML).
@@ -75,9 +82,10 @@ src/app/
75
82
 
76
83
  ### Navigation & Discovery
77
84
  - **`TableOfContents`** - Sticky TOC with scroll-based tracking.
78
- - **`Search`** - Client-side fuzzy search modal (Cmd/Ctrl+K).
85
+ - **`Search`** - Full-text search modal (Cmd/Ctrl+K) powered by Pagefind. Supports type filter tabs (All/Post/Flow/Book), recent searches, keyboard navigation, debounced input, focus trap, and ARIA accessibility. Search syntax: `"exact phrase"`, `word1 word2` (AND), `-exclude`.
79
86
  - **`Pagination`** - Previous/Next page navigation.
80
87
  - **`ReadingProgressBar`** - Top-of-page progress bar for book chapters.
88
+ - **`SubscribePage`** - Centralized page for all subscription options.
81
89
 
82
90
  ## Data Access Layer (`src/lib/markdown.ts`)
83
91
 
@@ -100,4 +108,5 @@ Theming is handled by `next-themes` and Tailwind CSS v4. `src/app/globals.css` d
100
108
 
101
109
  1. **`scripts/copy-assets.ts`** - Copies images from content directories (`posts/`, `series/`, `books/`, `flows/`) to `public/posts/` for static hosting.
102
110
  2. **`next build`** - Next.js static export to `out/`.
103
- 3. **`next-image-export-optimizer`** - Generates optimized WebP variants.
111
+ 3. **`next-image-export-optimizer`** - Generates optimized WebP variants (production build only).
112
+ 4. **`pagefind --site out`** - Crawls the exported HTML and builds a full-text search index in `out/pagefind/`. The index is loaded at runtime by `src/components/Search.tsx` via a dynamic import. Pure utility functions used by the search component and the search index route live in `src/lib/search-utils.ts`.
@@ -13,6 +13,8 @@
13
13
  ```
14
14
  Open [http://localhost:3000](http://localhost:3000).
15
15
 
16
+ > **Search in dev:** The search index is generated by `pagefind` at build time. Run `bun run build:dev` once to generate `public/pagefind/`. After that, the search modal (Cmd/Ctrl+K) will work while `bun dev` is running. Re-run `bun run build:dev` whenever content changes.
17
+
16
18
  ## Writing Content
17
19
 
18
20
  ### Creating Posts
@@ -74,8 +76,8 @@ bun run test:e2e # Run end-to-end tests
74
76
  ## Building
75
77
 
76
78
  ```bash
77
- bun run build # Production build
78
- bun run build:dev # Development build (faster)
79
+ bun run build # Production build (includes image optimization and Pagefind index)
80
+ bun run build:dev # Development build (faster, no image optimization; also generates Pagefind index)
79
81
  ```
80
82
 
81
83
  ## Code Style
@@ -17,7 +17,7 @@ bun run build:dev
17
17
  bun run clean && bun run build
18
18
  ```
19
19
 
20
- The `out/` directory contains plain HTML, CSS, JS, and images — ready to be served by any static file server.
20
+ The `out/` directory contains plain HTML, CSS, JS, images, and the Pagefind search index (`out/pagefind/`) — ready to be served by any static file server.
21
21
 
22
22
  ### Preview Locally
23
23
 
@@ -190,6 +190,11 @@ server {
190
190
  add_header Cache-Control "public";
191
191
  }
192
192
 
193
+ # Cache Pagefind search index (regenerated on each build, so short TTL is fine)
194
+ location /pagefind/ {
195
+ add_header Cache-Control "public, max-age=3600";
196
+ }
197
+
193
198
  # trailingSlash is true, so pages are slug/index.html
194
199
  # Redirect URLs without trailing slash to add one (except files)
195
200
  location / {
@@ -228,6 +233,9 @@ yourdomain.com {
228
233
  @postassets path /posts/*
229
234
  header @postassets Cache-Control "public, max-age=2592000"
230
235
 
236
+ @pagefind path /pagefind/*
237
+ header @pagefind Cache-Control "public, max-age=3600"
238
+
231
239
  handle_errors {
232
240
  rewrite * /404/index.html
233
241
  file_server
package/eslint.config.mjs CHANGED
@@ -12,6 +12,8 @@ const eslintConfig = defineConfig([
12
12
  "out/**",
13
13
  "build/**",
14
14
  "next-env.d.ts",
15
+ // Generated at build time — not authored code
16
+ "public/pagefind/**",
15
17
  ]),
16
18
  ]);
17
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hutusi/amytis",
3
- "version": "1.5.6",
3
+ "version": "1.7.0",
4
4
  "description": "A high-performance digital garden and blog engine with Next.js 16 and Tailwind CSS v4",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,8 @@
14
14
  "packageManager": "bun@1.3.4",
15
15
  "scripts": {
16
16
  "dev": "next dev",
17
- "build": "bun scripts/copy-assets.ts && next build && next-image-export-optimizer",
18
- "build:dev": "bun scripts/copy-assets.ts && next build",
17
+ "build": "bun scripts/copy-assets.ts && next build && next-image-export-optimizer && pagefind --site out",
18
+ "build:dev": "bun scripts/copy-assets.ts && next build && pagefind --site out --output-path public/pagefind",
19
19
  "validate": "bun run lint && bun run test && bun run build:dev",
20
20
  "clean": "rm -rf .next out public/posts public/books public/flows",
21
21
  "new": "bun scripts/new-post.ts",
@@ -35,7 +35,6 @@
35
35
  "dependencies": {
36
36
  "@giscus/react": "^3.1.0",
37
37
  "@tailwindcss/typography": "^0.5.19",
38
- "fuse.js": "^7.1.0",
39
38
  "github-slugger": "^2.0.0",
40
39
  "gray-matter": "^4.0.3",
41
40
  "image-size": "^2.0.2",
@@ -45,6 +44,7 @@
45
44
  "next-themes": "^0.4.6",
46
45
  "react": "19.2.4",
47
46
  "react-dom": "19.2.4",
47
+ "react-icons": "^5.5.0",
48
48
  "react-markdown": "^10.1.0",
49
49
  "react-syntax-highlighter": "^16.1.0",
50
50
  "rehype-katex": "^7.0.1",
@@ -66,6 +66,7 @@
66
66
  "babel-plugin-react-compiler": "1.0.0",
67
67
  "eslint": "^9.0.0",
68
68
  "eslint-config-next": "16.1.6",
69
+ "pagefind": "^1.4.0",
69
70
  "pdf-to-img": "^5.0.0",
70
71
  "tailwindcss": "^4.1.18",
71
72
  "typescript": "^5.9.3"
@@ -1,7 +1,4 @@
1
1
  {
2
2
  "posts/02-routing-mastery/assets/m-p-model.png": "fDmvlEkZnE-UCvPK4gmDkJD7SU8coOTl4iw5hpsmcWI=",
3
- "posts/advanced-markdown/images/m-p-model.png": "BdYG3Wq+FcOnoSrZeJeb6x2V-LsB-gwvms3WQJFq+Wg=",
4
- "posts/part-1/images/m-p-model.png": "94OaqjvkCmDcz+Iuhw14vHXYUzYl3Y8+wwoU1CQZ6l0=",
5
- "posts/part-2-rich-content/images/m-p-model.png": "Vp5bOs9N2OHHjMM2PFntkkCl1uY7DleLymwrSHbecOA=",
6
3
  "/screenshot.png": "dGCNrjp1oMIL3NrVh1VDW0K+7Pp-cI5KH6tW4-6o9zg="
7
4
  }
@@ -51,7 +51,7 @@ function getSlugFromFilename(filename: string): string {
51
51
  const dateRegex = /^(\d{4}-\d{2}-\d{2})-(.*)$/;
52
52
  const match = nameWithoutExt.match(dateRegex);
53
53
 
54
- if (match && !siteConfig.includeDateInUrl) {
54
+ if (match && !siteConfig.posts?.includeDateInUrl) {
55
55
  return match[2];
56
56
  }
57
57
  return nameWithoutExt;