retypeset-odyssey 0.1.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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -0
  3. package/astro.config.ts +18 -0
  4. package/default-config.yaml +136 -0
  5. package/discover-collections.ts +160 -0
  6. package/integration.ts +394 -0
  7. package/package.json +105 -0
  8. package/patches/@qwik.dev__partytown@0.11.2.patch +98 -0
  9. package/public/_redirects +819 -0
  10. package/public/feeds/atom-style.xsl +105 -0
  11. package/public/feeds/rss-style.xsl +105 -0
  12. package/public/fonts/EarlySummer-VF-Split/00785494587e3487ac63a0e7e4fa30f0.woff2 +0 -0
  13. package/public/fonts/EarlySummer-VF-Split/08e5d941a4c76fad7b68e7a937ebb21f.woff2 +0 -0
  14. package/public/fonts/EarlySummer-VF-Split/1268e5072156188d601f1eeb4473655d.woff2 +0 -0
  15. package/public/fonts/EarlySummer-VF-Split/12a385475353c815d7a5add53ee51e37.woff2 +0 -0
  16. package/public/fonts/EarlySummer-VF-Split/12b11ca08223c65a21fc731d59dcfc11.woff2 +0 -0
  17. package/public/fonts/EarlySummer-VF-Split/16d6676d3cb645c520ee6df8a1f89afd.woff2 +0 -0
  18. package/public/fonts/EarlySummer-VF-Split/2912a75ffef95e7a5ae9e2b2311ad61d.woff2 +0 -0
  19. package/public/fonts/EarlySummer-VF-Split/298d96ea561e419a4104bc9fc18499ce.woff2 +0 -0
  20. package/public/fonts/EarlySummer-VF-Split/2a2c71acc17ec39f6780835899e53096.woff2 +0 -0
  21. package/public/fonts/EarlySummer-VF-Split/2a7e2d0e59d3f638074c50fab39fdef1.woff2 +0 -0
  22. package/public/fonts/EarlySummer-VF-Split/36931fc4370e1670ed76af5d3feccba2.woff2 +0 -0
  23. package/public/fonts/EarlySummer-VF-Split/3a68fdc792e4a9e0399a04e32d0cc2e3.woff2 +0 -0
  24. package/public/fonts/EarlySummer-VF-Split/4054d6a4d6b37719b51e0f71da6e7cd9.woff2 +0 -0
  25. package/public/fonts/EarlySummer-VF-Split/429cb25f825c3cbde6bfac5b36ae9675.woff2 +0 -0
  26. package/public/fonts/EarlySummer-VF-Split/42a9efc11298368ecdc1b85ab46f0b4f.woff2 +0 -0
  27. package/public/fonts/EarlySummer-VF-Split/432018d2bdc9df92a7662056eb2b1261.woff2 +0 -0
  28. package/public/fonts/EarlySummer-VF-Split/44a6fb782f2a01560faa0f95248b60ef.woff2 +0 -0
  29. package/public/fonts/EarlySummer-VF-Split/45367b060e8ba0aa2507e6b91b86620b.woff2 +0 -0
  30. package/public/fonts/EarlySummer-VF-Split/571db7564bda7c1a93542881b8976f4b.woff2 +0 -0
  31. package/public/fonts/EarlySummer-VF-Split/58d55eeef4cf455e86a1142b1f3110d3.woff2 +0 -0
  32. package/public/fonts/EarlySummer-VF-Split/59ea41e77309160a0f63cdc76a010202.woff2 +0 -0
  33. package/public/fonts/EarlySummer-VF-Split/5d19d9174e568db4755981aa2e4ab380.woff2 +0 -0
  34. package/public/fonts/EarlySummer-VF-Split/5e811eb3b4175ee93d7ec000bf4631c2.woff2 +0 -0
  35. package/public/fonts/EarlySummer-VF-Split/6268e0cd5d66d6fe05b331f259e7b9e4.woff2 +0 -0
  36. package/public/fonts/EarlySummer-VF-Split/6549844aa3d833ca06a68a8e839db465.woff2 +0 -0
  37. package/public/fonts/EarlySummer-VF-Split/714b459658a7321ceeb1e1386ce165c2.woff2 +0 -0
  38. package/public/fonts/EarlySummer-VF-Split/7511d97a469915013683eae06cb21cd9.woff2 +0 -0
  39. package/public/fonts/EarlySummer-VF-Split/7784b4ebe543d13f62f6f6e05beb0b2e.woff2 +0 -0
  40. package/public/fonts/EarlySummer-VF-Split/77c9bea70b3c6ab24e1497d5468c825b.woff2 +0 -0
  41. package/public/fonts/EarlySummer-VF-Split/789ebea9e81df623e930b86de98fbfab.woff2 +0 -0
  42. package/public/fonts/EarlySummer-VF-Split/885bb7ab0717e8a47fc17f953adcdbf1.woff2 +0 -0
  43. package/public/fonts/EarlySummer-VF-Split/896c58aff69a9a857764cee0663bc56d.woff2 +0 -0
  44. package/public/fonts/EarlySummer-VF-Split/8fb6fc01c59d1e3ad1910b58dec7f5e7.woff2 +0 -0
  45. package/public/fonts/EarlySummer-VF-Split/95be5462b91b9a0458797cdc89d94cb5.woff2 +0 -0
  46. package/public/fonts/EarlySummer-VF-Split/9a5b2724f983ca0fc0d5ff8d10c41396.woff2 +0 -0
  47. package/public/fonts/EarlySummer-VF-Split/9ffe17f9c0e4cc4356cb3f08ffdb9c6d.woff2 +0 -0
  48. package/public/fonts/EarlySummer-VF-Split/EarlySummer-VF-Subset.woff2 +0 -0
  49. package/public/fonts/EarlySummer-VF-Split/EarlySummerSerif License.txt +91 -0
  50. package/public/fonts/EarlySummer-VF-Split/a097ef49be62cd2565aca45600e1e3ac.woff2 +0 -0
  51. package/public/fonts/EarlySummer-VF-Split/a17a1ae6063088e5b3a48c06b816929a.woff2 +0 -0
  52. package/public/fonts/EarlySummer-VF-Split/a83fdcfc5ecf2f6996704b0c02758689.woff2 +0 -0
  53. package/public/fonts/EarlySummer-VF-Split/a8cf15ff9b71e59407d8406866ff6f99.woff2 +0 -0
  54. package/public/fonts/EarlySummer-VF-Split/af530ed51dd519e4456f8a5e259e908b.woff2 +0 -0
  55. package/public/fonts/EarlySummer-VF-Split/b195a8924915deec4aa9c3ec777cc93f.woff2 +0 -0
  56. package/public/fonts/EarlySummer-VF-Split/b4b6bb5df9239dd67b52ca858fd2a506.woff2 +0 -0
  57. package/public/fonts/EarlySummer-VF-Split/b7592e1e027923f19e0e55dfdac69668.woff2 +0 -0
  58. package/public/fonts/EarlySummer-VF-Split/b965859f69d8ccceaf0e2d6292afbcfb.woff2 +0 -0
  59. package/public/fonts/EarlySummer-VF-Split/bbe9333f1ff242bd96ecb23ff9e723b1.woff2 +0 -0
  60. package/public/fonts/EarlySummer-VF-Split/be758580e295339ea98f0240b9869f24.woff2 +0 -0
  61. package/public/fonts/EarlySummer-VF-Split/c07099e1d025617f6d40966986e1941b.woff2 +0 -0
  62. package/public/fonts/EarlySummer-VF-Split/c1b593dda62fdeb7dde3af02016da282.woff2 +0 -0
  63. package/public/fonts/EarlySummer-VF-Split/c89f0335910a68a0958f2846108370e8.woff2 +0 -0
  64. package/public/fonts/EarlySummer-VF-Split/ca49aa409fdedd3f2f894cd20a16640a.woff2 +0 -0
  65. package/public/fonts/EarlySummer-VF-Split/ccd4a28d2f63797e0183c87792e20b75.woff2 +0 -0
  66. package/public/fonts/EarlySummer-VF-Split/d2718da923fce8e7ea229d65e306e92c.woff2 +0 -0
  67. package/public/fonts/EarlySummer-VF-Split/d893e9b307d96041e9cfcbd03761b9f4.woff2 +0 -0
  68. package/public/fonts/EarlySummer-VF-Split/dafaedaee41b75e21479d4ff324b6a34.woff2 +0 -0
  69. package/public/fonts/EarlySummer-VF-Split/db392af65f1867e5fd580eed2195df99.woff2 +0 -0
  70. package/public/fonts/EarlySummer-VF-Split/dc7c73a9e5577143ccd11e05ab55cb39.woff2 +0 -0
  71. package/public/fonts/EarlySummer-VF-Split/de396881189f747eba67685298363242.woff2 +0 -0
  72. package/public/fonts/EarlySummer-VF-Split/df625b213228bba22a7733d4eff8f148.woff2 +0 -0
  73. package/public/fonts/EarlySummer-VF-Split/e6e60b384f220b893ef31a926ece829a.woff2 +0 -0
  74. package/public/fonts/EarlySummer-VF-Split/e6e8ce2c5972ab665630bb705383d0fb.woff2 +0 -0
  75. package/public/fonts/EarlySummer-VF-Split/e963c7ed7104c2d6d68fcb5f952fe2f5.woff2 +0 -0
  76. package/public/fonts/EarlySummer-VF-Split/e966b23b4cd7783f43e31032d41784f4.woff2 +0 -0
  77. package/public/fonts/EarlySummer-VF-Split/edaac57c3856ec13128f4c6c3e00975c.woff2 +0 -0
  78. package/public/fonts/EarlySummer-VF-Split/ee54e0d86edf068c6c9cbddb76a856fe.woff2 +0 -0
  79. package/public/fonts/EarlySummer-VF-Split/f612c78a5544ff2dd3e8296ac3e58344.woff2 +0 -0
  80. package/public/fonts/EarlySummer-VF-Split/f9e539bd9b7bf999c3da82f5403ec3b6.woff2 +0 -0
  81. package/public/fonts/EarlySummer-VF-Split/fa5863b923ac15993c52a619f699ee63.woff2 +0 -0
  82. package/public/fonts/EarlySummer-VF-Split/fc759e56ec6f6e6d3d4cb163d62fb557.woff2 +0 -0
  83. package/public/fonts/Font Subset List/CJK Common Characters.txt +7534 -0
  84. package/public/fonts/Font Subset List/EarlySummer Subset.txt +3 -0
  85. package/public/fonts/Font Subset List/Japanese Kana + Korean Letters.txt +6123 -0
  86. package/public/fonts/Font Subset List/Latin + Cyrillic + Greek + Arabic Glyphs.txt +121 -0
  87. package/public/fonts/Font Subset List/unicode_range.py +49 -0
  88. package/public/fonts/NotoSansSC-Bold.otf +0 -0
  89. package/public/fonts/NotoSansSC-Regular.otf +0 -0
  90. package/public/fonts/STIX-Italic-VF.woff2 +0 -0
  91. package/public/fonts/STIX-VF.woff2 +0 -0
  92. package/public/fonts/Snell-Black-SF.woff2 +0 -0
  93. package/public/fonts/Snell-Bold-SF.woff2 +0 -0
  94. package/public/giscus/theme-dark.css +208 -0
  95. package/public/giscus/theme-light.css +208 -0
  96. package/public/icons/favicon.svg +4 -0
  97. package/public/icons/og-logo.png +0 -0
  98. package/public/robots.txt +4 -0
  99. package/public/sounds/tap_01.wav +0 -0
  100. package/public/sounds/tap_02.wav +0 -0
  101. package/public/sounds/tap_03.wav +0 -0
  102. package/public/sounds/tap_04.wav +0 -0
  103. package/public/sounds/tap_05.wav +0 -0
  104. package/public/sounds/type_01.wav +0 -0
  105. package/public/sounds/type_02.wav +0 -0
  106. package/public/sounds/type_03.wav +0 -0
  107. package/public/sounds/type_04.wav +0 -0
  108. package/public/sounds/type_05.wav +0 -0
  109. package/scripts/apply-lqip.ts +276 -0
  110. package/scripts/format-posts.ts +105 -0
  111. package/scripts/migration/README.md +52 -0
  112. package/scripts/migration/migrate-hexo.ts +185 -0
  113. package/scripts/migration/validate-abbrlinks.ts +161 -0
  114. package/scripts/new-post.ts +52 -0
  115. package/scripts/seo/generate-legacy-redirects.ts +407 -0
  116. package/scripts/update-theme.ts +46 -0
  117. package/src/assets/icons/copy-check.svg +3 -0
  118. package/src/assets/icons/copy-icon.svg +4 -0
  119. package/src/assets/icons/go-back.svg +3 -0
  120. package/src/assets/icons/heading-anchor.svg +4 -0
  121. package/src/assets/icons/lang-en.svg +3 -0
  122. package/src/assets/icons/lang-ja.svg +5 -0
  123. package/src/assets/icons/lang-zh.svg +5 -0
  124. package/src/assets/icons/language-switcher.svg +3 -0
  125. package/src/assets/icons/pin-icon.svg +3 -0
  126. package/src/assets/icons/search-icon.svg +3 -0
  127. package/src/assets/icons/theme-toggle.svg +3 -0
  128. package/src/assets/icons/toc-icon.svg +3 -0
  129. package/src/assets/icons/top-icon.svg +3 -0
  130. package/src/assets/lqip-map.json +10 -0
  131. package/src/components/Button.astro +152 -0
  132. package/src/components/CategoryList.astro +66 -0
  133. package/src/components/Comment/Giscus.astro +119 -0
  134. package/src/components/Comment/Index.astro +30 -0
  135. package/src/components/Comment/Twikoo.astro +114 -0
  136. package/src/components/Comment/Waline.astro +149 -0
  137. package/src/components/FloatingButtons.astro +101 -0
  138. package/src/components/Footer.astro +74 -0
  139. package/src/components/Header.astro +62 -0
  140. package/src/components/JournalList.astro +56 -0
  141. package/src/components/Navbar.astro +69 -0
  142. package/src/components/NoteList.astro +56 -0
  143. package/src/components/Pagination.astro +267 -0
  144. package/src/components/PostDate.astro +80 -0
  145. package/src/components/PostList.astro +87 -0
  146. package/src/components/SearchModal.astro +340 -0
  147. package/src/components/TagList.astro +135 -0
  148. package/src/components/Widgets/BackButton.astro +43 -0
  149. package/src/components/Widgets/CodeCopyButton.astro +47 -0
  150. package/src/components/Widgets/GithubCard.astro +110 -0
  151. package/src/components/Widgets/ImageZoom.astro +135 -0
  152. package/src/components/Widgets/MediaEmbed.astro +127 -0
  153. package/src/components/Widgets/SoundEffect.astro +179 -0
  154. package/src/components/Widgets/TOC.astro +198 -0
  155. package/src/config-schema.ts +164 -0
  156. package/src/config.ts +127 -0
  157. package/src/config.ts.example +205 -0
  158. package/src/content/about/_example-about-en.md +6 -0
  159. package/src/content/about/about-en.md +21 -0
  160. package/src/content/about/about-ja.md +21 -0
  161. package/src/content/about/about-zh.md +24 -0
  162. package/src/content.config.ts +247 -0
  163. package/src/env.d.ts +25 -0
  164. package/src/i18n/config.ts +65 -0
  165. package/src/i18n/lang.ts +70 -0
  166. package/src/i18n/path.ts +160 -0
  167. package/src/i18n/ui.ts +214 -0
  168. package/src/layouts/Head.astro +203 -0
  169. package/src/layouts/Layout.astro +69 -0
  170. package/src/pages/404.astro +20 -0
  171. package/src/pages/[...lang]/[...page].astro +48 -0
  172. package/src/pages/[...lang]/about.astro +28 -0
  173. package/src/pages/[...lang]/atom.xml.ts +14 -0
  174. package/src/pages/[...lang]/categories/index.astro +35 -0
  175. package/src/pages/[...lang]/journals/[slug].astro +89 -0
  176. package/src/pages/[...lang]/journals/index.astro +55 -0
  177. package/src/pages/[...lang]/journals/page/[page].astro +66 -0
  178. package/src/pages/[...lang]/notes/[slug].astro +88 -0
  179. package/src/pages/[...lang]/notes/index.astro +55 -0
  180. package/src/pages/[...lang]/notes/page/[page].astro +66 -0
  181. package/src/pages/[...lang]/posts/[slug].astro +101 -0
  182. package/src/pages/[...lang]/rss.xml.ts +14 -0
  183. package/src/pages/[...lang]/search.astro +65 -0
  184. package/src/pages/[...lang]/tags/[tag].astro +53 -0
  185. package/src/pages/[...lang]/tags/index.astro +36 -0
  186. package/src/pages/_dynamic/list.astro +101 -0
  187. package/src/pages/_dynamic/slug.astro +100 -0
  188. package/src/pages/og/[...image].ts +114 -0
  189. package/src/pages/robots.txt.ts +20 -0
  190. package/src/plugins/rehype-code-copy-button.mjs +82 -0
  191. package/src/plugins/rehype-external-links.mjs +18 -0
  192. package/src/plugins/rehype-heading-anchor.mjs +55 -0
  193. package/src/plugins/rehype-image-processor.mjs +77 -0
  194. package/src/plugins/remark-container-directives.mjs +135 -0
  195. package/src/plugins/remark-leaf-directives.mjs +184 -0
  196. package/src/plugins/remark-reading-time.mjs +11 -0
  197. package/src/styles/comment.css +205 -0
  198. package/src/styles/extension.css +180 -0
  199. package/src/styles/font.css +111 -0
  200. package/src/styles/global.css +91 -0
  201. package/src/styles/lqip.css +71 -0
  202. package/src/styles/markdown.css +276 -0
  203. package/src/styles/transition.css +173 -0
  204. package/src/types/global.d.ts +22 -0
  205. package/src/types/index.d.ts +111 -0
  206. package/src/utils/cache.ts +32 -0
  207. package/src/utils/content.ts +819 -0
  208. package/src/utils/description.ts +147 -0
  209. package/src/utils/dynamic-collections.ts +155 -0
  210. package/src/utils/feed.ts +238 -0
  211. package/src/utils/page.ts +107 -0
  212. package/tsconfig.json +13 -0
  213. package/uno.config.ts +75 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 radishzz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # Retypeset Odyssey
2
+
3
+ A fork of [astro-theme-retypeset](https://github.com/radishzzz/astro-theme-retypeset) — extended to work both as a standalone Astro template **and** as an installable npm package for content-only repositories.
4
+
5
+ Powering [zhenjia.org](https://zhenjia.org).
6
+
7
+ ## What's different from upstream
8
+
9
+ This fork adds the ability to consume the theme as a dependency, so you can keep your content in a separate repository and pull the theme via `pnpm install`. The original "clone and edit" workflow still works unchanged.
10
+
11
+ | Use case | Approach |
12
+ |----------|----------|
13
+ | Personal blog, all in one repo | Fork & clone (same as upstream Retypeset) |
14
+ | Content repo + reusable theme | Install `retypeset-odyssey` as a dependency |
15
+
16
+ Other deltas from upstream: trilingual content support (zh / en / ja) with `.en.md` / `.ja.md` filename convention, tag URL encoding (so tags like `CI/CD` work), legacy Hexo `abbrlink` redirects, GitHub Actions translation pipeline.
17
+
18
+ ## Features
19
+
20
+ - Built with Astro 5 and UnoCSS
21
+ - SEO, Sitemap, OpenGraph (with generated cards), RSS, MDX, LaTeX, Mermaid, TOC
22
+ - i18n with route-level language switching
23
+ - Light / Dark mode with view transitions
24
+ - Pagefind full-text search
25
+ - Responsive, typography-first layout
26
+
27
+ ## Usage A — Standalone (fork & clone)
28
+
29
+ ```bash
30
+ git clone https://github.com/lifeodyssey/retypeset-odyssey.git
31
+ cd retypeset-odyssey
32
+ pnpm install
33
+ pnpm dev
34
+ ```
35
+
36
+ Edit `src/config.ts` for site settings. Put markdown in `content/{posts,notes,journals}/`. Build with `pnpm build`.
37
+
38
+ ## Usage B — As an npm package
39
+
40
+ Set up your own minimal Astro project that consumes this theme. One install, one YAML, one line of Astro config.
41
+
42
+ ```jsonc
43
+ // package.json
44
+ {
45
+ "name": "my-blog",
46
+ "type": "module",
47
+ "scripts": {
48
+ "dev": "astro dev",
49
+ "build": "astro build"
50
+ },
51
+ "dependencies": {
52
+ "astro": "^5.17.1",
53
+ "retypeset-odyssey": "github:lifeodyssey/retypeset-odyssey"
54
+ }
55
+ }
56
+ ```
57
+
58
+ ```ts
59
+ // astro.config.ts
60
+ import { defineConfig } from 'astro/config'
61
+ import retypeset from 'retypeset-odyssey/integration'
62
+
63
+ export default defineConfig({
64
+ integrations: [retypeset()],
65
+ })
66
+ ```
67
+
68
+ ```yaml
69
+ # retypeset.config.yaml — at project root. Any subset of keys; the rest fall back to defaults.
70
+ site:
71
+ title: My Blog
72
+ subtitle: Hello world
73
+ author: Your Name
74
+ url: https://your-domain.com
75
+ global:
76
+ locale: en
77
+ moreLocales: []
78
+ footer:
79
+ links:
80
+ - name: RSS
81
+ url: /atom.xml
82
+ startYear: 2024
83
+ ```
84
+
85
+ ```ts
86
+ // src/content.config.ts
87
+ export { collections } from 'retypeset-odyssey/content-config'
88
+ ```
89
+
90
+ Then put markdown directly in your repo:
91
+
92
+ ```
93
+ content/
94
+ ├── posts/
95
+ │ ├── article.md (zh, default)
96
+ │ ├── article.en.md (English)
97
+ │ └── article.ja.md (Japanese)
98
+ ├── notes/
99
+ └── journals/
100
+ ```
101
+
102
+ Build:
103
+
104
+ ```bash
105
+ pnpm install
106
+ pnpm build # → dist/
107
+ ```
108
+
109
+ ### What the integration does
110
+
111
+ - Loads `default-config.yaml` (shipped with the package), deep-merges your `retypeset.config.yaml` on top, and validates the result with Zod. The merged config is exposed to the theme via a Vite virtual module (`virtual:retypeset/config`) — every theme file that does `import ... from '@/config'` automatically picks up your overrides without any code change on your side.
112
+ - Drives Astro's top-level config from the same YAML: `site`, `base`, `build.format`, `trailingSlash`, `prefetch`, `i18n`, and `image.domains` are all set automatically.
113
+ - Registers UnoCSS (with the theme's `uno.config.ts`), MDX, partytown, sitemap, pagefind, and astro-compress — so you do not import any of them yourself.
114
+ - Injects all theme routes (`posts/[slug]`, `tags/[tag]`, RSS, OG images, etc.) via Astro's `injectRoute` API.
115
+ - Points `publicDir` at the package's own `public/`, so fonts, icons, and other assets ship without copying.
116
+
117
+ ### Configuration reference
118
+
119
+ The full schema lives in [`src/config-schema.ts`](./src/config-schema.ts) and the defaults in [`default-config.yaml`](./default-config.yaml). Top-level groups: `site`, `color`, `global`, `comment`, `seo`, `footer`, `preload`. Any field you do not set in your YAML falls back to the default. Validation errors surface at `astro build` time with a clear path into the YAML.
120
+
121
+ ## Customization
122
+
123
+ - **Site config**: edit `default-config.yaml` (standalone) or write your own `retypeset.config.yaml` next to `astro.config.ts` (package consumers). Any subset of keys is allowed; the rest fall back to the defaults.
124
+ - **About pages**: place markdown in `src/content/about/about-{zh|en|ja}.md`.
125
+ - **Static assets** (favicon, OG logo, fonts): standalone users edit `public/`; package consumers inherit from the installed theme.
126
+
127
+ ## Migrating from Hexo
128
+
129
+ If you're coming from Hexo (or another markdown-first SSG) and want to bring your existing posts over, this theme ships with a one-off migration helper plus a 301-bridge generator for SEO.
130
+
131
+ ### Quick path
132
+
133
+ 1. **Start from the starter template** — [retypeset-starter](https://github.com/lifeodyssey/retypeset-starter) is a minimal consumer repo (one `package.json`, one `astro.config.ts`, one YAML). Click *Use this template* on GitHub, clone the new repo, then `pnpm install`.
134
+
135
+ 2. **Bring your Hexo posts over with the migration script.** Copy `scripts/migration/migrate-hexo.ts` out of the installed theme into your own repo, edit the two source/target path constants at the top, then:
136
+
137
+ ```bash
138
+ pnpm tsx scripts/migrate-hexo.ts
139
+ ```
140
+
141
+ It walks every file under your Hexo `source/_posts/`, converts the frontmatter (`date` → `published`, accepts string-or-array `tags`/`categories`, preserves `abbrlink`, drops Hexo-private fields gracefully), and writes the result to `content/posts/`. Multilingual variants use the `.en.md` / `.ja.md` suffix convention.
142
+
143
+ 3. **(Optional) verify abbrlinks weren't lost.** A sibling `validate-abbrlinks.ts` cross-checks every output file's `abbrlink` against the original Hexo source so you can catch silent drops.
144
+
145
+ 4. **Run `pnpm dev`** and click around. If a post fails frontmatter validation, the integration's Zod schema is permissive — most Hexo frontmatter quirks are accepted — but missing `title` or unparseable `date` will surface as a clear error pointing at the file.
146
+
147
+ ### Preserve your SEO
148
+
149
+ If your Hexo site was deployed under a domain you can't drop (e.g. `username.github.io`), use [`scripts/generate-redirect-pages.mjs`](https://github.com/lifeodyssey/Blog-src/blob/main/scripts/generate-redirect-pages.mjs) (in the content-repo example, not in this package) to generate a per-page 301 bridge: for every old `posts/<abbrlink>.html` URL, it emits a static HTML with `<meta http-equiv="refresh">` + `<link rel="canonical">` pointing at the new domain. Deploy the generated files to the old host and search engine weight transfers over.
150
+
151
+ ### Story version
152
+
153
+ For a long-form walkthrough of why the migration was structured this way, including the trade-offs around frontmatter compatibility, SEO continuity, and `abbrlink` preservation, see [我是怎么用 AI 把博客从 Hexo 迁到 Astro 的](https://zhenjia.org/posts/how-i-migrated-blog-with-ai-v3) (Chinese; the migration was originally documented from a non-frontend developer's perspective).
154
+
155
+ ## Credits
156
+
157
+ Forked from [astro-theme-retypeset](https://github.com/radishzzz/astro-theme-retypeset) by [@radishzzz](https://github.com/radishzzz). Upstream inspirations:
158
+
159
+ - [Typography](https://github.com/moeyua/astro-theme-typography)
160
+ - [Fuwari](https://github.com/saicaca/fuwari)
161
+ - [Redefine](https://github.com/EvanNotFound/hexo-theme-redefine)
162
+ - [AstroPaper](https://github.com/satnaing/astro-paper)
163
+ - [heti](https://github.com/sivan/heti)
164
+ - [EarlySummerSerif](https://github.com/GuiWonder/EarlySummerSerif)
165
+
166
+ ## License
167
+
168
+ Same as upstream: MIT (see `LICENSE`).
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'astro/config'
2
+ import retypeset from './integration'
3
+
4
+ /**
5
+ * Standalone-mode Astro config.
6
+ *
7
+ * When this repo is checked out and used directly (Usage A in README), the
8
+ * integration does all the heavy lifting — it loads `default-config.yaml`
9
+ * (and any `retypeset.config.yaml` placed alongside this file), wires up
10
+ * UnoCSS / MDX / partytown / sitemap / pagefind / compress, sets the
11
+ * top-level Astro config from the YAML, and injects every route.
12
+ *
13
+ * Package consumers (Usage B) should mirror this file — `integrations:
14
+ * [retypeset()]` is all they need.
15
+ */
16
+ export default defineConfig({
17
+ integrations: [retypeset()],
18
+ })
@@ -0,0 +1,136 @@
1
+ # Retypeset Odyssey — default theme configuration.
2
+ #
3
+ # This file is the authoritative source of defaults. A consumer can override
4
+ # any subset of these keys in their own `retypeset.config.yaml` at the project
5
+ # root; values are deep-merged on top of this file.
6
+
7
+ # ---- SITE -------------------------------------------------------------------
8
+ site:
9
+ # Site title.
10
+ title: Life Odyssey
11
+ # Site subtitle (tagline).
12
+ subtitle: 人生若只如初见
13
+ # Site description (for SEO + RSS).
14
+ description: A personal blog about life, technology, and reflections.
15
+ # Use i18n title/subtitle/description from src/i18n/ui.ts instead of the
16
+ # static strings above.
17
+ i18nTitle: true
18
+ # Author name.
19
+ author: Zhenjia
20
+ # Canonical site URL (no trailing slash).
21
+ url: https://zhenjia.org
22
+ # Base path. Use '/' unless the site is served from a subpath.
23
+ base: /
24
+ # Favicon URL. Recommended formats: svg, png, ico.
25
+ favicon: /icons/favicon.svg
26
+
27
+ # ---- COLOR ------------------------------------------------------------------
28
+ color:
29
+ # Default theme mode: light | dark | auto.
30
+ mode: light
31
+ light:
32
+ primary: oklch(25% 0.005 298)
33
+ secondary: oklch(40% 0.005 298)
34
+ background: oklch(96% 0.005 298)
35
+ highlight: oklch(0.93 0.195089 103.2532 / 0.5)
36
+ dark:
37
+ primary: oklch(92% 0.005 298)
38
+ secondary: oklch(77% 0.005 298)
39
+ background: oklch(22% 0.005 298)
40
+ highlight: oklch(0.93 0.195089 103.2532 / 0.2)
41
+
42
+ # ---- GLOBAL -----------------------------------------------------------------
43
+ global:
44
+ # Default language: de | en | es | fr | ja | ko | pl | pt | ru | zh | zh-tw.
45
+ locale: zh
46
+ # Additional languages (do not include the default locale here).
47
+ moreLocales:
48
+ - en
49
+ - ja
50
+ # Post font style: sans | serif.
51
+ fontStyle: sans
52
+ # Date format: YYYY-MM-DD | MM-DD-YYYY | DD-MM-YYYY | MMM D YYYY | D MMM YYYY.
53
+ dateFormat: YYYY-MM-DD
54
+ # Enable table of contents.
55
+ toc: true
56
+ # Enable KaTeX math rendering.
57
+ katex: true
58
+ # Reduce motion.
59
+ reduceMotion: false
60
+
61
+ # ---- COMMENT ----------------------------------------------------------------
62
+ comment:
63
+ # Enable comment system.
64
+ enabled: false
65
+ giscus:
66
+ repo: ''
67
+ repoId: ''
68
+ category: ''
69
+ categoryId: ''
70
+ mapping: pathname
71
+ strict: '0'
72
+ reactionsEnabled: '1'
73
+ emitMetadata: '0'
74
+ inputPosition: bottom
75
+ twikoo:
76
+ envId: ''
77
+ waline:
78
+ serverURL: https://retypeset-comment.radishzz.cc
79
+ emoji:
80
+ - https://unpkg.com/@waline/emojis@1.2.0/tw-emoji
81
+ search: false
82
+ imageUploader: false
83
+
84
+ # ---- SEO --------------------------------------------------------------------
85
+ seo:
86
+ # @twitter ID.
87
+ twitterID: ''
88
+ verification:
89
+ google: ''
90
+ bing: ''
91
+ yandex: ''
92
+ baidu: ''
93
+ # Google Analytics 4 measurement ID (G-XXXXXXXXXX). Not the legacy UA-* form.
94
+ googleAnalyticsID: ''
95
+ # Umami analytics ID (https://cloud.umami.is).
96
+ umamiAnalyticsID: ''
97
+ # Follow.is verification.
98
+ follow:
99
+ feedID: ''
100
+ userID: ''
101
+ # ApiFlash access key (https://apiflash.com/) for generating OG screenshots.
102
+ apiflashKey: ''
103
+
104
+ # ---- FOOTER -----------------------------------------------------------------
105
+ footer:
106
+ links:
107
+ - name: RSS
108
+ url: /atom.xml
109
+ - name: GitHub
110
+ url: https://github.com/lifeodyssey
111
+ # Year the site was first published.
112
+ startYear: 2016
113
+
114
+ # ---- COLLECTIONS ------------------------------------------------------------
115
+ # Built-in collections (`posts`, `notes`, `journals`) are always enabled by
116
+ # default. Any extra top-level folder under `content/` (e.g. `content/tech/`)
117
+ # becomes its own collection automatically, with `/tech` as the list page and
118
+ # `/tech/<slug>` as the detail page. You can disable any collection by setting
119
+ # `enabled: false`. Folders prefixed with `_` or `.` are ignored, so use
120
+ # `content/_drafts/` for in-progress material.
121
+ collections:
122
+ posts:
123
+ enabled: true
124
+ notes:
125
+ enabled: true
126
+ journals:
127
+ enabled: true
128
+
129
+ # ---- PRELOAD ----------------------------------------------------------------
130
+ preload:
131
+ # Image hosting URL. Used to optimize remote images and generate LQIP.
132
+ imageHostURL: ''
133
+ # Custom GA proxy endpoint.
134
+ customGoogleAnalyticsJS: ''
135
+ # Custom Umami proxy endpoint.
136
+ customUmamiAnalyticsJS: ''
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Shared folder discovery + collection-enablement resolution.
3
+ *
4
+ * Both `integration.ts` and `src/content.config.ts` call into this module so
5
+ * the set of active collections is computed identically in the two places
6
+ * (Astro statically loads `content.config.ts` in its own Vite environment,
7
+ * so we can't share state between them at runtime — but both can read the
8
+ * same YAML and scan the same `content/` directory).
9
+ *
10
+ * Behaviour:
11
+ *
12
+ * - Built-in collections: `posts`, `notes`, `journals` are always
13
+ * candidates. They appear in the result unless the consumer explicitly
14
+ * sets `collections.<name>.enabled: false` in `retypeset.config.yaml`.
15
+ *
16
+ * - Dynamic collections: every direct child folder of
17
+ * `<projectRoot>/content/` that is NOT a built-in, NOT prefixed with `_`
18
+ * or `.`, AND has a valid URL-segment name becomes its own collection.
19
+ * Each dynamic folder may be opted out the same way (set
20
+ * `collections.<folder>.enabled: false`).
21
+ *
22
+ * - When no `content/` directory exists (e.g. fresh checkout of the
23
+ * theme repo before any content is wired up), only built-in
24
+ * collections are returned and the dynamic-collection list is empty.
25
+ *
26
+ * The slug used for both the collection name and the URL segment is the
27
+ * folder name verbatim. Folders whose name contains characters outside
28
+ * `[a-zA-Z0-9-]` are skipped with a warning — those would need encoding or
29
+ * renaming to be safe in URLs and JS identifiers.
30
+ */
31
+
32
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
33
+ import { isAbsolute, resolve as resolvePath } from 'node:path'
34
+ import yaml from 'yaml'
35
+
36
+ export const BUILT_IN_COLLECTIONS = ['posts', 'notes', 'journals'] as const
37
+ export type BuiltInCollection = typeof BUILT_IN_COLLECTIONS[number]
38
+
39
+ const URL_SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9-]*$/
40
+
41
+ export interface CollectionDiscovery {
42
+ /** Enabled built-in collections, in canonical order. */
43
+ enabledBuiltIns: BuiltInCollection[]
44
+ /** Dynamic folders that should become collections, in scan order. */
45
+ dynamicFolders: string[]
46
+ /** Folders that were skipped because the name is not URL-safe. */
47
+ skippedFolders: string[]
48
+ }
49
+
50
+ interface RawCollectionConfig {
51
+ enabled?: boolean
52
+ }
53
+
54
+ function readCollectionsSection(
55
+ projectRoot: string,
56
+ userConfigPath?: string,
57
+ ): Record<string, RawCollectionConfig> {
58
+ const candidates: string[] = []
59
+
60
+ if (userConfigPath) {
61
+ const abs = isAbsolute(userConfigPath)
62
+ ? userConfigPath
63
+ : resolvePath(projectRoot, userConfigPath)
64
+ candidates.push(abs)
65
+ }
66
+ else {
67
+ candidates.push(resolvePath(projectRoot, 'retypeset.config.yaml'))
68
+ }
69
+
70
+ for (const candidate of candidates) {
71
+ if (!existsSync(candidate))
72
+ continue
73
+
74
+ try {
75
+ const parsed = yaml.parse(readFileSync(candidate, 'utf-8')) as
76
+ | { collections?: Record<string, RawCollectionConfig> }
77
+ | undefined
78
+ return parsed?.collections ?? {}
79
+ }
80
+ catch {
81
+ // Malformed YAML — fall through to defaults.
82
+ return {}
83
+ }
84
+ }
85
+
86
+ return {}
87
+ }
88
+
89
+ function isEnabled(
90
+ collectionsConfig: Record<string, RawCollectionConfig>,
91
+ name: string,
92
+ defaultEnabled: boolean,
93
+ ): boolean {
94
+ const entry = collectionsConfig[name]
95
+ if (entry?.enabled === false)
96
+ return false
97
+ if (entry?.enabled === true)
98
+ return true
99
+ return defaultEnabled
100
+ }
101
+
102
+ /**
103
+ * Walk `<projectRoot>/content/` and resolve which collections are active.
104
+ *
105
+ * @param projectRoot Consumer project root (or theme root in standalone mode).
106
+ * @param userConfigPath Optional explicit path to `retypeset.config.yaml`.
107
+ */
108
+ export function discoverCollections(
109
+ projectRoot: string,
110
+ userConfigPath?: string,
111
+ ): CollectionDiscovery {
112
+ const collectionsConfig = readCollectionsSection(projectRoot, userConfigPath)
113
+ const contentDir = resolvePath(projectRoot, 'content')
114
+
115
+ const enabledBuiltIns = BUILT_IN_COLLECTIONS.filter(name =>
116
+ isEnabled(collectionsConfig, name, true),
117
+ )
118
+
119
+ if (!existsSync(contentDir)) {
120
+ return { enabledBuiltIns, dynamicFolders: [], skippedFolders: [] }
121
+ }
122
+
123
+ const dynamicFolders: string[] = []
124
+ const skippedFolders: string[] = []
125
+
126
+ for (const entry of readdirSync(contentDir)) {
127
+ if (entry.startsWith('_') || entry.startsWith('.'))
128
+ continue
129
+ if ((BUILT_IN_COLLECTIONS as readonly string[]).includes(entry))
130
+ continue
131
+ // `about` lives in src/content/about, but if a consumer puts a top-level
132
+ // `about` folder in content/ that would collide with the built-in route.
133
+ // Skip with a warning, the consumer needs to rename it.
134
+ if (entry === 'about') {
135
+ skippedFolders.push(entry)
136
+ continue
137
+ }
138
+
139
+ const fullPath = resolvePath(contentDir, entry)
140
+ try {
141
+ if (!statSync(fullPath).isDirectory())
142
+ continue
143
+ }
144
+ catch {
145
+ continue
146
+ }
147
+
148
+ if (!URL_SAFE_NAME.test(entry)) {
149
+ skippedFolders.push(entry)
150
+ continue
151
+ }
152
+
153
+ if (!isEnabled(collectionsConfig, entry, true))
154
+ continue
155
+
156
+ dynamicFolders.push(entry)
157
+ }
158
+
159
+ return { enabledBuiltIns, dynamicFolders, skippedFolders }
160
+ }