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.
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/astro.config.ts +18 -0
- package/default-config.yaml +136 -0
- package/discover-collections.ts +160 -0
- package/integration.ts +394 -0
- package/package.json +105 -0
- package/patches/@qwik.dev__partytown@0.11.2.patch +98 -0
- package/public/_redirects +819 -0
- package/public/feeds/atom-style.xsl +105 -0
- package/public/feeds/rss-style.xsl +105 -0
- package/public/fonts/EarlySummer-VF-Split/00785494587e3487ac63a0e7e4fa30f0.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/08e5d941a4c76fad7b68e7a937ebb21f.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/1268e5072156188d601f1eeb4473655d.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/12a385475353c815d7a5add53ee51e37.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/12b11ca08223c65a21fc731d59dcfc11.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/16d6676d3cb645c520ee6df8a1f89afd.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/2912a75ffef95e7a5ae9e2b2311ad61d.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/298d96ea561e419a4104bc9fc18499ce.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/2a2c71acc17ec39f6780835899e53096.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/2a7e2d0e59d3f638074c50fab39fdef1.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/36931fc4370e1670ed76af5d3feccba2.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/3a68fdc792e4a9e0399a04e32d0cc2e3.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/4054d6a4d6b37719b51e0f71da6e7cd9.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/429cb25f825c3cbde6bfac5b36ae9675.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/42a9efc11298368ecdc1b85ab46f0b4f.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/432018d2bdc9df92a7662056eb2b1261.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/44a6fb782f2a01560faa0f95248b60ef.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/45367b060e8ba0aa2507e6b91b86620b.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/571db7564bda7c1a93542881b8976f4b.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/58d55eeef4cf455e86a1142b1f3110d3.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/59ea41e77309160a0f63cdc76a010202.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/5d19d9174e568db4755981aa2e4ab380.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/5e811eb3b4175ee93d7ec000bf4631c2.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/6268e0cd5d66d6fe05b331f259e7b9e4.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/6549844aa3d833ca06a68a8e839db465.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/714b459658a7321ceeb1e1386ce165c2.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/7511d97a469915013683eae06cb21cd9.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/7784b4ebe543d13f62f6f6e05beb0b2e.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/77c9bea70b3c6ab24e1497d5468c825b.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/789ebea9e81df623e930b86de98fbfab.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/885bb7ab0717e8a47fc17f953adcdbf1.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/896c58aff69a9a857764cee0663bc56d.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/8fb6fc01c59d1e3ad1910b58dec7f5e7.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/95be5462b91b9a0458797cdc89d94cb5.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/9a5b2724f983ca0fc0d5ff8d10c41396.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/9ffe17f9c0e4cc4356cb3f08ffdb9c6d.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/EarlySummer-VF-Subset.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/EarlySummerSerif License.txt +91 -0
- package/public/fonts/EarlySummer-VF-Split/a097ef49be62cd2565aca45600e1e3ac.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/a17a1ae6063088e5b3a48c06b816929a.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/a83fdcfc5ecf2f6996704b0c02758689.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/a8cf15ff9b71e59407d8406866ff6f99.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/af530ed51dd519e4456f8a5e259e908b.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/b195a8924915deec4aa9c3ec777cc93f.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/b4b6bb5df9239dd67b52ca858fd2a506.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/b7592e1e027923f19e0e55dfdac69668.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/b965859f69d8ccceaf0e2d6292afbcfb.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/bbe9333f1ff242bd96ecb23ff9e723b1.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/be758580e295339ea98f0240b9869f24.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/c07099e1d025617f6d40966986e1941b.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/c1b593dda62fdeb7dde3af02016da282.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/c89f0335910a68a0958f2846108370e8.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/ca49aa409fdedd3f2f894cd20a16640a.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/ccd4a28d2f63797e0183c87792e20b75.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/d2718da923fce8e7ea229d65e306e92c.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/d893e9b307d96041e9cfcbd03761b9f4.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/dafaedaee41b75e21479d4ff324b6a34.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/db392af65f1867e5fd580eed2195df99.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/dc7c73a9e5577143ccd11e05ab55cb39.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/de396881189f747eba67685298363242.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/df625b213228bba22a7733d4eff8f148.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/e6e60b384f220b893ef31a926ece829a.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/e6e8ce2c5972ab665630bb705383d0fb.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/e963c7ed7104c2d6d68fcb5f952fe2f5.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/e966b23b4cd7783f43e31032d41784f4.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/edaac57c3856ec13128f4c6c3e00975c.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/ee54e0d86edf068c6c9cbddb76a856fe.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/f612c78a5544ff2dd3e8296ac3e58344.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/f9e539bd9b7bf999c3da82f5403ec3b6.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/fa5863b923ac15993c52a619f699ee63.woff2 +0 -0
- package/public/fonts/EarlySummer-VF-Split/fc759e56ec6f6e6d3d4cb163d62fb557.woff2 +0 -0
- package/public/fonts/Font Subset List/CJK Common Characters.txt +7534 -0
- package/public/fonts/Font Subset List/EarlySummer Subset.txt +3 -0
- package/public/fonts/Font Subset List/Japanese Kana + Korean Letters.txt +6123 -0
- package/public/fonts/Font Subset List/Latin + Cyrillic + Greek + Arabic Glyphs.txt +121 -0
- package/public/fonts/Font Subset List/unicode_range.py +49 -0
- package/public/fonts/NotoSansSC-Bold.otf +0 -0
- package/public/fonts/NotoSansSC-Regular.otf +0 -0
- package/public/fonts/STIX-Italic-VF.woff2 +0 -0
- package/public/fonts/STIX-VF.woff2 +0 -0
- package/public/fonts/Snell-Black-SF.woff2 +0 -0
- package/public/fonts/Snell-Bold-SF.woff2 +0 -0
- package/public/giscus/theme-dark.css +208 -0
- package/public/giscus/theme-light.css +208 -0
- package/public/icons/favicon.svg +4 -0
- package/public/icons/og-logo.png +0 -0
- package/public/robots.txt +4 -0
- package/public/sounds/tap_01.wav +0 -0
- package/public/sounds/tap_02.wav +0 -0
- package/public/sounds/tap_03.wav +0 -0
- package/public/sounds/tap_04.wav +0 -0
- package/public/sounds/tap_05.wav +0 -0
- package/public/sounds/type_01.wav +0 -0
- package/public/sounds/type_02.wav +0 -0
- package/public/sounds/type_03.wav +0 -0
- package/public/sounds/type_04.wav +0 -0
- package/public/sounds/type_05.wav +0 -0
- package/scripts/apply-lqip.ts +276 -0
- package/scripts/format-posts.ts +105 -0
- package/scripts/migration/README.md +52 -0
- package/scripts/migration/migrate-hexo.ts +185 -0
- package/scripts/migration/validate-abbrlinks.ts +161 -0
- package/scripts/new-post.ts +52 -0
- package/scripts/seo/generate-legacy-redirects.ts +407 -0
- package/scripts/update-theme.ts +46 -0
- package/src/assets/icons/copy-check.svg +3 -0
- package/src/assets/icons/copy-icon.svg +4 -0
- package/src/assets/icons/go-back.svg +3 -0
- package/src/assets/icons/heading-anchor.svg +4 -0
- package/src/assets/icons/lang-en.svg +3 -0
- package/src/assets/icons/lang-ja.svg +5 -0
- package/src/assets/icons/lang-zh.svg +5 -0
- package/src/assets/icons/language-switcher.svg +3 -0
- package/src/assets/icons/pin-icon.svg +3 -0
- package/src/assets/icons/search-icon.svg +3 -0
- package/src/assets/icons/theme-toggle.svg +3 -0
- package/src/assets/icons/toc-icon.svg +3 -0
- package/src/assets/icons/top-icon.svg +3 -0
- package/src/assets/lqip-map.json +10 -0
- package/src/components/Button.astro +152 -0
- package/src/components/CategoryList.astro +66 -0
- package/src/components/Comment/Giscus.astro +119 -0
- package/src/components/Comment/Index.astro +30 -0
- package/src/components/Comment/Twikoo.astro +114 -0
- package/src/components/Comment/Waline.astro +149 -0
- package/src/components/FloatingButtons.astro +101 -0
- package/src/components/Footer.astro +74 -0
- package/src/components/Header.astro +62 -0
- package/src/components/JournalList.astro +56 -0
- package/src/components/Navbar.astro +69 -0
- package/src/components/NoteList.astro +56 -0
- package/src/components/Pagination.astro +267 -0
- package/src/components/PostDate.astro +80 -0
- package/src/components/PostList.astro +87 -0
- package/src/components/SearchModal.astro +340 -0
- package/src/components/TagList.astro +135 -0
- package/src/components/Widgets/BackButton.astro +43 -0
- package/src/components/Widgets/CodeCopyButton.astro +47 -0
- package/src/components/Widgets/GithubCard.astro +110 -0
- package/src/components/Widgets/ImageZoom.astro +135 -0
- package/src/components/Widgets/MediaEmbed.astro +127 -0
- package/src/components/Widgets/SoundEffect.astro +179 -0
- package/src/components/Widgets/TOC.astro +198 -0
- package/src/config-schema.ts +164 -0
- package/src/config.ts +127 -0
- package/src/config.ts.example +205 -0
- package/src/content/about/_example-about-en.md +6 -0
- package/src/content/about/about-en.md +21 -0
- package/src/content/about/about-ja.md +21 -0
- package/src/content/about/about-zh.md +24 -0
- package/src/content.config.ts +247 -0
- package/src/env.d.ts +25 -0
- package/src/i18n/config.ts +65 -0
- package/src/i18n/lang.ts +70 -0
- package/src/i18n/path.ts +160 -0
- package/src/i18n/ui.ts +214 -0
- package/src/layouts/Head.astro +203 -0
- package/src/layouts/Layout.astro +69 -0
- package/src/pages/404.astro +20 -0
- package/src/pages/[...lang]/[...page].astro +48 -0
- package/src/pages/[...lang]/about.astro +28 -0
- package/src/pages/[...lang]/atom.xml.ts +14 -0
- package/src/pages/[...lang]/categories/index.astro +35 -0
- package/src/pages/[...lang]/journals/[slug].astro +89 -0
- package/src/pages/[...lang]/journals/index.astro +55 -0
- package/src/pages/[...lang]/journals/page/[page].astro +66 -0
- package/src/pages/[...lang]/notes/[slug].astro +88 -0
- package/src/pages/[...lang]/notes/index.astro +55 -0
- package/src/pages/[...lang]/notes/page/[page].astro +66 -0
- package/src/pages/[...lang]/posts/[slug].astro +101 -0
- package/src/pages/[...lang]/rss.xml.ts +14 -0
- package/src/pages/[...lang]/search.astro +65 -0
- package/src/pages/[...lang]/tags/[tag].astro +53 -0
- package/src/pages/[...lang]/tags/index.astro +36 -0
- package/src/pages/_dynamic/list.astro +101 -0
- package/src/pages/_dynamic/slug.astro +100 -0
- package/src/pages/og/[...image].ts +114 -0
- package/src/pages/robots.txt.ts +20 -0
- package/src/plugins/rehype-code-copy-button.mjs +82 -0
- package/src/plugins/rehype-external-links.mjs +18 -0
- package/src/plugins/rehype-heading-anchor.mjs +55 -0
- package/src/plugins/rehype-image-processor.mjs +77 -0
- package/src/plugins/remark-container-directives.mjs +135 -0
- package/src/plugins/remark-leaf-directives.mjs +184 -0
- package/src/plugins/remark-reading-time.mjs +11 -0
- package/src/styles/comment.css +205 -0
- package/src/styles/extension.css +180 -0
- package/src/styles/font.css +111 -0
- package/src/styles/global.css +91 -0
- package/src/styles/lqip.css +71 -0
- package/src/styles/markdown.css +276 -0
- package/src/styles/transition.css +173 -0
- package/src/types/global.d.ts +22 -0
- package/src/types/index.d.ts +111 -0
- package/src/utils/cache.ts +32 -0
- package/src/utils/content.ts +819 -0
- package/src/utils/description.ts +147 -0
- package/src/utils/dynamic-collections.ts +155 -0
- package/src/utils/feed.ts +238 -0
- package/src/utils/page.ts +107 -0
- package/tsconfig.json +13 -0
- 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`).
|
package/astro.config.ts
ADDED
|
@@ -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
|
+
}
|