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
@@ -0,0 +1,101 @@
1
+ ---
2
+ import type { CollectionEntry } from 'astro:content'
3
+ import type { Language } from '@/i18n/config'
4
+ import { render } from 'astro:content'
5
+ import Comment from '@/components/Comment/Index.astro'
6
+ import PostDate from '@/components/PostDate.astro'
7
+ import TagList from '@/components/TagList.astro'
8
+ import BackButton from '@/components/Widgets/BackButton.astro'
9
+ import TOC from '@/components/Widgets/TOC.astro'
10
+ import { allLocales } from '@/config'
11
+ import { getLangFromLocale, getLangRouteParam } from '@/i18n/lang'
12
+ import Layout from '@/layouts/Layout.astro'
13
+ import { getPostGroups, getPostSlug } from '@/utils/content'
14
+ import { getPostDescription } from '@/utils/description'
15
+
16
+ export async function getStaticPaths() {
17
+ const groups = await getPostGroups()
18
+
19
+ return allLocales.flatMap(lang => groups
20
+ .map((group) => {
21
+ const post = group.byLang[lang]
22
+ if (!post) {
23
+ return null
24
+ }
25
+
26
+ return {
27
+ params: {
28
+ lang: getLangRouteParam(lang),
29
+ slug: group.slug,
30
+ },
31
+ props: {
32
+ post,
33
+ supportedLangs: group.supportedLangs,
34
+ },
35
+ }
36
+ })
37
+ .filter(Boolean),
38
+ )
39
+ }
40
+
41
+ interface Props {
42
+ post: CollectionEntry<'posts'>
43
+ supportedLangs: Language[]
44
+ }
45
+
46
+ const { post, supportedLangs } = Astro.props as Props
47
+ const currentLang = getLangFromLocale(Astro.currentLocale)
48
+ const slug = getPostSlug(post)
49
+ const description = getPostDescription(post, 'meta')
50
+ const { Content, headings, remarkPluginFrontmatter } = await render(post)
51
+ ---
52
+
53
+ <Layout
54
+ postTitle={post.data.title}
55
+ postDescription={description}
56
+ postSlug={post.id}
57
+ supportedLangs={supportedLangs}
58
+ >
59
+ <article class="heti">
60
+ <div class="relative">
61
+ <BackButton />
62
+ <h1 class="post-title">
63
+ <span
64
+ transition:name={`post-${slug}${currentLang ? `-${currentLang}` : ''}`}
65
+ data-disable-theme-toggle-transition
66
+ >
67
+ {post.data.title}
68
+ </span>
69
+ </h1>
70
+ </div>
71
+
72
+ <div
73
+ id="post-date"
74
+ class="mb-17.2 block c-primary font-time"
75
+ transition:name={`time-${slug}${currentLang ? `-${currentLang}` : ''}`}
76
+ data-disable-theme-toggle-transition
77
+ >
78
+ <PostDate
79
+ date={post.data.published}
80
+ updatedDate={post.data.updated}
81
+ minutes={remarkPluginFrontmatter.minutes}
82
+ />
83
+ </div>
84
+
85
+ {post.data.toc && <TOC headings={headings} />}
86
+
87
+ <div id="post-content">
88
+ <Content />
89
+ <div id="post-copyright" />
90
+ {post.data.tags?.length > 0 && (
91
+ <div class="mt-12.6 uno-decorative-line" />
92
+ <TagList
93
+ tags={post.data.tags}
94
+ lang={currentLang}
95
+ />
96
+ )}
97
+ <Comment />
98
+ </div>
99
+ </article>
100
+ </Layout>
101
+
@@ -0,0 +1,14 @@
1
+ import type { APIContext } from 'astro'
2
+ import { allLocales } from '@/config'
3
+ import { getLangRouteParam } from '@/i18n/lang'
4
+ import { generateRSS } from '@/utils/feed'
5
+
6
+ export function getStaticPaths() {
7
+ return allLocales.map(lang => ({
8
+ params: { lang: getLangRouteParam(lang) },
9
+ }))
10
+ }
11
+
12
+ export async function GET(context: APIContext) {
13
+ return generateRSS(context)
14
+ }
@@ -0,0 +1,65 @@
1
+ ---
2
+ import { allLocales } from '@/config'
3
+ import { getLangFromLocale, getLangRouteParam } from '@/i18n/lang'
4
+ import { ui } from '@/i18n/ui'
5
+ import Layout from '@/layouts/Layout.astro'
6
+
7
+ export async function getStaticPaths() {
8
+ return allLocales.map(lang => ({
9
+ params: { lang: getLangRouteParam(lang) },
10
+ }))
11
+ }
12
+
13
+ const currentLang = getLangFromLocale(Astro.currentLocale)
14
+ const currentUI = ui[currentLang as keyof typeof ui] ?? {}
15
+ ---
16
+
17
+ <Layout>
18
+ <!-- Decorative Line -->
19
+ <div class="uno-decorative-line" />
20
+ <!-- Search -->
21
+ <div class="search-container">
22
+ <h1 class="text-4.5 c-primary font-bold mb-6">{currentUI.search}</h1>
23
+ <div id="search"></div>
24
+ </div>
25
+ </Layout>
26
+
27
+ <link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
28
+ <script is:inline src="/pagefind/pagefind-ui.js" type="text/javascript"></script>
29
+ <script is:inline>
30
+ window.addEventListener('DOMContentLoaded', () => {
31
+ new PagefindUI({
32
+ element: '#search',
33
+ showImages: false,
34
+ showSubResults: true
35
+ });
36
+ });
37
+ </script>
38
+
39
+ <style is:global>
40
+ .pagefind-ui {
41
+ --pagefind-ui-scale: 0.9;
42
+ --pagefind-ui-primary: var(--c-primary);
43
+ --pagefind-ui-text: var(--c-text);
44
+ --pagefind-ui-background: var(--c-bg);
45
+ --pagefind-ui-border: var(--c-secondary);
46
+ --pagefind-ui-border-width: 1px;
47
+ --pagefind-ui-border-radius: 4px;
48
+ --pagefind-ui-font: inherit;
49
+ }
50
+
51
+ .pagefind-ui__search-input {
52
+ font-size: 1rem !important;
53
+ padding: 0.75rem 1rem !important;
54
+ }
55
+
56
+ .pagefind-ui__result-link {
57
+ color: var(--c-primary) !important;
58
+ font-weight: 600 !important;
59
+ }
60
+
61
+ .pagefind-ui__result-excerpt {
62
+ color: var(--c-text) !important;
63
+ opacity: 0.8;
64
+ }
65
+ </style>
@@ -0,0 +1,53 @@
1
+ ---
2
+ import PostList from '@/components/PostList.astro'
3
+ import TagList from '@/components/TagList.astro'
4
+ import { allLocales } from '@/config'
5
+ import { getLangFromLocale, getLangRouteParam } from '@/i18n/lang'
6
+ import Layout from '@/layouts/Layout.astro'
7
+ import { getAllTags, getPostsByTag, getTagSupportedLangs, getTagsWithCounts } from '@/utils/content'
8
+
9
+ export async function getStaticPaths() {
10
+ const paths = await Promise.all(allLocales.map(async (lang) => {
11
+ const langTags = await getAllTags(lang)
12
+ return langTags.map(tag => ({
13
+ // Astro 6 auto-encodes params for URLs and decodes them on route match,
14
+ // so we hand it the raw tag. Slashes are the one exception: a `/` would
15
+ // split the `[tag]` segment in two; pre-encode just that character so
16
+ // tags like `CI/CD` survive routing.
17
+ params: { lang: getLangRouteParam(lang), tag: tag.replace(/\//g, '%2F') },
18
+ props: { tag },
19
+ }))
20
+ }))
21
+ return paths.flat()
22
+ }
23
+
24
+ interface Props {
25
+ tag: string
26
+ }
27
+
28
+ const { tag } = Astro.props
29
+ const currentLang = getLangFromLocale(Astro.currentLocale)
30
+ const posts = await getPostsByTag(tag, currentLang)
31
+ const tagsWithCounts = await getTagsWithCounts(currentLang)
32
+ const supportedLangs = await getTagSupportedLangs(tag)
33
+ ---
34
+
35
+ <Layout supportedLangs={supportedLangs}>
36
+ <!-- Decorative Line -->
37
+ <div class="uno-decorative-line" />
38
+ <!-- Tag List -->
39
+ <TagList
40
+ tags={tagsWithCounts}
41
+ currentTag={tag}
42
+ lang={currentLang}
43
+ cloudStyle={true}
44
+ />
45
+
46
+ <!-- Posts List -->
47
+ <div class="mt-10.5">
48
+ <PostList
49
+ posts={posts}
50
+ lang={currentLang}
51
+ />
52
+ </div>
53
+ </Layout>
@@ -0,0 +1,36 @@
1
+ ---
2
+ import TagList from '@/components/TagList.astro'
3
+ import { allLocales } from '@/config'
4
+ import { getLangFromLocale, getLangRouteParam } from '@/i18n/lang'
5
+ import { ui } from '@/i18n/ui'
6
+ import Layout from '@/layouts/Layout.astro'
7
+ import { getTagsWithCounts } from '@/utils/content'
8
+
9
+ export async function getStaticPaths() {
10
+ return allLocales.map(lang => ({
11
+ params: { lang: getLangRouteParam(lang) },
12
+ }))
13
+ }
14
+
15
+ const currentLang = getLangFromLocale(Astro.currentLocale)
16
+ const tagsWithCounts = await getTagsWithCounts(currentLang)
17
+ const totalTags = tagsWithCounts.length
18
+ const currentUI = ui[currentLang as keyof typeof ui] ?? {}
19
+ ---
20
+
21
+ <Layout postTitle={currentUI.tags || 'Tags'}>
22
+ <section class="mb-7.5">
23
+ <h1 class="mb-2 text-5 font-bold cjk:tracking-wide lg:text-6">
24
+ {currentUI.tags || 'Tags'}
25
+ </h1>
26
+ <p class="mb-6 text-3.5 c-secondary">
27
+ {totalTags} {currentUI.tagsInTotal || 'tags in total'}
28
+ </p>
29
+ <div class="uno-decorative-line" />
30
+ <TagList
31
+ tags={tagsWithCounts}
32
+ lang={currentLang}
33
+ cloudStyle={true}
34
+ />
35
+ </section>
36
+ </Layout>
@@ -0,0 +1,101 @@
1
+ ---
2
+ /**
3
+ * Generic list page for dynamically-discovered content collections.
4
+ *
5
+ * Mounted at `/[...lang]/[collection]`; `getStaticPaths` enumerates every
6
+ * (lang, folder) combination, where `folder` is a directory under
7
+ * `content/` that the integration discovered. Literal-segment routes
8
+ * (`/posts`, `/notes`, `/about`, etc.) take priority over this dynamic
9
+ * pattern in Astro's router, so the built-ins are unaffected.
10
+ */
11
+
12
+ import Pagination from '@/components/Pagination.astro'
13
+ import PostDate from '@/components/PostDate.astro'
14
+ import { allLocales, base, defaultLocale } from '@/config'
15
+ import { getLangFromLocale, getLangRouteParam } from '@/i18n/lang'
16
+ import Layout from '@/layouts/Layout.astro'
17
+ import { getDynamicEntries, getDynamicSlug } from '@/utils/dynamic-collections'
18
+ // eslint-disable-next-line ts/ban-ts-comment
19
+ // @ts-ignore - virtual module provided by the integration
20
+ import { dynamicCollections } from 'virtual:retypeset/dynamic-collections'
21
+
22
+ export async function getStaticPaths() {
23
+ const paths: Array<{
24
+ params: { lang: string | undefined, collection: string }
25
+ props: { collection: string }
26
+ }> = []
27
+ for (const collection of dynamicCollections as string[]) {
28
+ for (const lang of allLocales) {
29
+ paths.push({
30
+ params: {
31
+ lang: getLangRouteParam(lang),
32
+ collection,
33
+ },
34
+ props: { collection },
35
+ })
36
+ }
37
+ }
38
+ return paths
39
+ }
40
+
41
+ const PER_PAGE = 7
42
+
43
+ const { collection } = Astro.props as { collection: string }
44
+ const currentLang = getLangFromLocale(Astro.currentLocale)
45
+ const entries = await getDynamicEntries(collection, currentLang)
46
+ const totalPages = Math.max(1, Math.ceil(entries.length / PER_PAGE))
47
+ const pageEntries = entries.slice(0, PER_PAGE)
48
+ const baseUrl = (currentLang === defaultLocale ? '' : `/${currentLang}`) + `/${collection}/`
49
+ const title = collection.charAt(0).toUpperCase() + collection.slice(1)
50
+ ---
51
+
52
+ <Layout postTitle={title}>
53
+ <section class="mb-7.5">
54
+ <h1 class="mb-2 text-5 font-bold cjk:tracking-wide lg:text-6">
55
+ {title}
56
+ </h1>
57
+ <div class="uno-decorative-line" />
58
+
59
+ {pageEntries.length > 0
60
+ ? (
61
+ <ul>
62
+ {pageEntries.map((entry) => {
63
+ const slug = getDynamicSlug(entry)
64
+ const href = (currentLang === defaultLocale ? '' : `/${currentLang}`)
65
+ + `/${collection}/${slug}`
66
+ return (
67
+ <li class="mb-5.5 lg:mb-7.5">
68
+ <h3 class="inline transition-colors hover:c-primary">
69
+ <a
70
+ class="cjk:tracking-wide lg:font-medium lg:text-4.25"
71
+ href={(base ? base : '') + href}
72
+ >
73
+ {entry.data.title}
74
+ </a>
75
+ </h3>
76
+ <div class="py-0.8 text-3.5 font-time">
77
+ <PostDate
78
+ date={entry.data.published}
79
+ updatedDate={entry.data.updated}
80
+ minutes={entry.remarkPluginFrontmatter.minutes}
81
+ />
82
+ </div>
83
+ </li>
84
+ )
85
+ })}
86
+ </ul>
87
+ )
88
+ : (
89
+ <p class="mt-6 text-3.5 c-secondary">
90
+ No entries in this collection yet.
91
+ </p>
92
+ )}
93
+
94
+ <Pagination
95
+ currentPage={1}
96
+ totalPages={totalPages}
97
+ baseUrl={(base ? base : '') + baseUrl}
98
+ pagePrefix="page"
99
+ />
100
+ </section>
101
+ </Layout>
@@ -0,0 +1,100 @@
1
+ ---
2
+ /**
3
+ * Generic detail page for dynamically-discovered content collections.
4
+ *
5
+ * Mounted at `/[...lang]/[collection]/[slug]`. `getStaticPaths` walks every
6
+ * (lang, collection, slug) triple that exists in the dynamic folders, so
7
+ * Astro will not try to render this template for any URL the integration
8
+ * didn't anticipate.
9
+ */
10
+
11
+ import type { Language } from '@/i18n/config'
12
+ import { render } from 'astro:content'
13
+ import PostDate from '@/components/PostDate.astro'
14
+ import BackButton from '@/components/Widgets/BackButton.astro'
15
+ import TOC from '@/components/Widgets/TOC.astro'
16
+ import { allLocales } from '@/config'
17
+ import { getLangRouteParam } from '@/i18n/lang'
18
+ import Layout from '@/layouts/Layout.astro'
19
+ import {
20
+ type DynamicEntry,
21
+ getDynamicGroups,
22
+ } from '@/utils/dynamic-collections'
23
+ // eslint-disable-next-line ts/ban-ts-comment
24
+ // @ts-ignore - virtual module provided by the integration
25
+ import { dynamicCollections } from 'virtual:retypeset/dynamic-collections'
26
+
27
+ export async function getStaticPaths() {
28
+ const paths: Array<{
29
+ params: { lang: string | undefined, collection: string, slug: string }
30
+ props: { entry: DynamicEntry, supportedLangs: Language[], collection: string }
31
+ }> = []
32
+
33
+ for (const collection of dynamicCollections as string[]) {
34
+ const groups = await getDynamicGroups(collection)
35
+ for (const lang of allLocales) {
36
+ for (const group of groups) {
37
+ const entry = group.byLang[lang]
38
+ if (!entry)
39
+ continue
40
+ paths.push({
41
+ params: {
42
+ lang: getLangRouteParam(lang),
43
+ collection,
44
+ slug: group.slug,
45
+ },
46
+ props: {
47
+ entry,
48
+ supportedLangs: group.supportedLangs,
49
+ collection,
50
+ },
51
+ })
52
+ }
53
+ }
54
+ }
55
+ return paths
56
+ }
57
+
58
+ interface Props {
59
+ entry: DynamicEntry
60
+ supportedLangs: Language[]
61
+ collection: string
62
+ }
63
+
64
+ const { entry, supportedLangs, collection } = Astro.props as Props
65
+ const { Content, headings, remarkPluginFrontmatter } = await render(entry)
66
+ ---
67
+
68
+ <Layout
69
+ postTitle={entry.data.title}
70
+ postSlug={`${collection}/${entry.id}`}
71
+ supportedLangs={supportedLangs}
72
+ >
73
+ <article class="heti">
74
+ <div class="relative">
75
+ <BackButton />
76
+ <h1 class="post-title">
77
+ <span data-disable-theme-toggle-transition>
78
+ {entry.data.title}
79
+ </span>
80
+ </h1>
81
+ </div>
82
+
83
+ <div
84
+ class="mb-17.2 block c-primary font-time"
85
+ data-disable-theme-toggle-transition
86
+ >
87
+ <PostDate
88
+ date={entry.data.published}
89
+ updatedDate={entry.data.updated}
90
+ minutes={remarkPluginFrontmatter.minutes}
91
+ />
92
+ </div>
93
+
94
+ {entry.data.toc && <TOC headings={headings} />}
95
+
96
+ <div id="post-content">
97
+ <Content />
98
+ </div>
99
+ </article>
100
+ </Layout>
@@ -0,0 +1,114 @@
1
+ import type { CollectionEntry } from 'astro:content'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname, join } from 'node:path'
4
+ import { OGImageRoute } from 'astro-og-canvas'
5
+ import { getCollection } from 'astro:content'
6
+ import { getJournalDescription, getNoteDescription, getPostDescription } from '@/utils/description'
7
+
8
+ // Resolve font paths from the theme package's installed location.
9
+ // Use createRequire so it works whether the theme is the project root (./public/fonts)
10
+ // or installed under node_modules/retypeset-odyssey/public/fonts.
11
+ const themeRequire = createRequire(import.meta.url)
12
+ let themeRoot: string
13
+ try {
14
+ themeRoot = dirname(themeRequire.resolve('retypeset-odyssey/package.json'))
15
+ }
16
+ catch {
17
+ // Fallback: theme IS the project root (template/standalone use case)
18
+ themeRoot = process.cwd()
19
+ }
20
+ const FONT_BOLD = join(themeRoot, 'public/fonts/NotoSansSC-Bold.otf')
21
+ const FONT_REGULAR = join(themeRoot, 'public/fonts/NotoSansSC-Regular.otf')
22
+ const OG_LOGO = join(themeRoot, 'public/icons/og-logo.png')
23
+
24
+ // eslint-disable-next-line antfu/no-top-level-await
25
+ const posts = await getCollection('posts')
26
+ // eslint-disable-next-line antfu/no-top-level-await
27
+ let notes: CollectionEntry<'notes'>[] = []
28
+ try {
29
+ // eslint-disable-next-line antfu/no-top-level-await
30
+ notes = await getCollection('notes')
31
+ }
32
+ catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error)
34
+ if (!message.includes('The collection "notes" does not exist')) {
35
+ throw error
36
+ }
37
+ }
38
+
39
+ // eslint-disable-next-line antfu/no-top-level-await
40
+ let journals: CollectionEntry<'journals'>[] = []
41
+ try {
42
+ // eslint-disable-next-line antfu/no-top-level-await
43
+ journals = await getCollection('journals')
44
+ }
45
+ catch (error) {
46
+ const message = error instanceof Error ? error.message : String(error)
47
+ if (!message.includes('The collection "journals" does not exist')) {
48
+ throw error
49
+ }
50
+ }
51
+
52
+ // Create slug-to-metadata lookup object for blog posts
53
+ type OGPage = {
54
+ title: string
55
+ description: string
56
+ }
57
+
58
+ const pages = Object.fromEntries([
59
+ ...posts.map((post: CollectionEntry<'posts'>) => [
60
+ post.id,
61
+ {
62
+ title: post.data.title,
63
+ description: getPostDescription(post, 'og'),
64
+ },
65
+ ]),
66
+ ...notes.map((note: CollectionEntry<'notes'>) => [
67
+ `notes/${note.id}`,
68
+ {
69
+ title: note.data.title,
70
+ description: getNoteDescription(note, 'og'),
71
+ },
72
+ ]),
73
+ ...journals.map((journal: CollectionEntry<'journals'>) => [
74
+ `journals/${journal.id}`,
75
+ {
76
+ title: journal.data.title,
77
+ description: getJournalDescription(journal, 'og'),
78
+ },
79
+ ]),
80
+ ]) as Record<string, OGPage>
81
+
82
+ // Configure Open Graph image generation route
83
+ // eslint-disable-next-line antfu/no-top-level-await
84
+ export const { getStaticPaths, GET } = await OGImageRoute({
85
+ param: 'image',
86
+ pages,
87
+ getImageOptions: (_path, page) => ({
88
+ title: page.title,
89
+ description: page.description,
90
+ logo: {
91
+ path: OG_LOGO,
92
+ size: [250],
93
+ },
94
+ border: {
95
+ color: [242, 241, 245],
96
+ width: 20,
97
+ },
98
+ font: {
99
+ title: {
100
+ families: ['Noto Sans SC'],
101
+ weight: 'Bold',
102
+ color: [34, 33, 36],
103
+ lineHeight: 1.5,
104
+ },
105
+ description: {
106
+ families: ['Noto Sans SC'],
107
+ color: [72, 71, 74],
108
+ lineHeight: 1.5,
109
+ },
110
+ },
111
+ fonts: [FONT_BOLD, FONT_REGULAR],
112
+ bgGradient: [[242, 241, 245]],
113
+ }),
114
+ })
@@ -0,0 +1,20 @@
1
+ import type { APIRoute } from 'astro'
2
+ import { base } from '@/config'
3
+
4
+ export const GET: APIRoute = ({ site }) => {
5
+ const sitemapURL = new URL('sitemap-index.xml', site)
6
+
7
+ const robotsTxt = [
8
+ 'User-agent: *',
9
+ 'Allow: /',
10
+ `Disallow: ${base}/~partytown/`,
11
+ '',
12
+ `Sitemap: ${sitemapURL.href}`,
13
+ ].join('\n')
14
+
15
+ return new Response(robotsTxt, {
16
+ headers: {
17
+ 'Content-Type': 'text/plain; charset=utf-8',
18
+ },
19
+ })
20
+ }
@@ -0,0 +1,82 @@
1
+ import { SKIP, visit } from 'unist-util-visit'
2
+
3
+ const copyIcon = {
4
+ type: 'element',
5
+ tagName: 'svg',
6
+ properties: {
7
+ 'className': ['icon-copy'],
8
+ 'viewBox': '0 0 24 24',
9
+ 'fill': 'currentColor',
10
+ 'aria-hidden': 'true',
11
+ },
12
+ children: [
13
+ {
14
+ type: 'element',
15
+ tagName: 'path',
16
+ properties: { d: 'M6.9.8v18h14.5V.8zm12.8 16h-11v-14h11z' },
17
+ children: [],
18
+ },
19
+ {
20
+ type: 'element',
21
+ tagName: 'path',
22
+ properties: { d: 'M4.3 21.2V5.6l-1.7.5v17.1h14.3l.6-2z' },
23
+ children: [],
24
+ },
25
+ ],
26
+ }
27
+
28
+ const checkIcon = {
29
+ type: 'element',
30
+ tagName: 'svg',
31
+ properties: {
32
+ 'className': ['icon-check'],
33
+ 'viewBox': '0 0 24 24',
34
+ 'fill': 'currentColor',
35
+ 'aria-hidden': 'true',
36
+ },
37
+ children: [
38
+ {
39
+ type: 'element',
40
+ tagName: 'path',
41
+ properties: { d: 'm23.1 6.4-1.3-1.3L9.4 16.6l-6.3-5.4-1.2 1.2L9.4 20z' },
42
+ children: [],
43
+ },
44
+ ],
45
+ }
46
+
47
+ export function rehypeCodeCopyButton() {
48
+ return (tree) => {
49
+ visit(tree, 'element', (node, index, parent) => {
50
+ if (node.tagName !== 'pre' || node.children?.[0]?.tagName !== 'code' || !parent) {
51
+ return
52
+ }
53
+
54
+ if (node._hasCopyButton) {
55
+ return
56
+ }
57
+
58
+ node._hasCopyButton = true
59
+
60
+ parent.children[index] = {
61
+ type: 'element',
62
+ tagName: 'div',
63
+ properties: { className: ['code-block-wrapper'] },
64
+ children: [
65
+ {
66
+ type: 'element',
67
+ tagName: 'button',
68
+ properties: {
69
+ 'className': ['code-copy-button'],
70
+ 'type': 'button',
71
+ 'aria-label': 'Copy code',
72
+ },
73
+ children: [copyIcon, checkIcon],
74
+ },
75
+ node,
76
+ ],
77
+ }
78
+
79
+ return SKIP
80
+ })
81
+ }
82
+ }