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,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <g transform="translate(-0.07,20.92) scale(0.0238,-0.0238)">
3
+ <path d="M425 765C387 765 361 766 334 770V702C359 706 379 707 423 707C434 604 447 543 475 464C510 365 550 290 605 216C530 131 417 53 276 -11C297 -29 307 -42 321 -65C387 -33 443 1 515 53C561 86 581 105 643 168C669 135 705 99 745 63C802 13 851 -21 927 -62C940 -33 950 -18 969 3C846 60 769 118 684 215C724 267 773 350 802 418C841 506 877 627 894 726C900 757 900 757 903 769C877 766 855 765 824 765ZM828 707C797 567 771 491 721 391C695 338 673 301 645 264C594 335 555 408 527 487C503 555 491 617 482 707ZM45 523C120 482 171 445 232 386L271 445C201 502 160 531 84 575ZM92 769C159 728 211 686 270 626L311 679C258 730 204 771 134 814ZM235 297C179 171 107 54 54 7L111 -48C112 -45 127 -24 153 15C194 75 245 167 285 255Z"/>
4
+ </g>
5
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="M19.18 21.1 12.4 1.89h-1.21L4.52 21.1l-2.53.2v.81h6.37v-.81l-2.83-.2 2.02-5.97h7.58l2.02 5.97-3.34.2v.81H22v-.81l-2.83-.2zM7.85 14.33l3.44-10.21 3.54 10.21z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="M23.7,8.1l-2.2-2.2-3.3-3.3L15.9.3C9,6.4,6.5,7,.4,7.5l6.4,6.4,1,1-6.5,6.9.9.9,6.9-6.5,1,1,6.4,6.4c.5-6.1,1.1-8.6,7.2-15.5ZM7.9,12.7l-4.4-4.4c4.1-.5,5.9-1,12-6l1.4,1.4,3.3,3.3,1.4,1.4c-5,6.1-5.5,7.9-6,12l-4.4-4.4-3.3-3.3Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="m23.37 23.12-5.13-5.57c3.28-3.96 2.96-9.92-.9-13.78C13.25-.33 6.8-.45 2.87 3.48s-3.81 10.38.28 14.47c3.86 3.86 9.82 4.19 13.78.9l5.58 5.13zM3.61 17.49C.26 14.14.54 8.48 4.21 4.82c3.67-3.67 9.32-3.95 12.67-.6s3.07 9.01-.6 12.67c-3.67 3.67-9.32 3.95-12.67.6"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="M12.06.61C5.66.61.46 5.71.46 12.11s5.1 11.5 11.6 11.5 11.5-5.1 11.5-11.5S18.46.61 12.06.61m0 21c-6.07 0-10.94-4.2-10.94-9.5s4.87-9.4 10.94-9.4S23 6.91 23 12.21s-4.88 9.4-10.94 9.4"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="M22.2 2.3H1.8V4h19.9zM22.2 20H1.8v1.7h19.9zM15.5 11.2H1.8v1.6H15z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="m.74 3.99.53-1.54h21.46l.53 1.54zm22.62 16.78L12 8.02.64 20.77l.88.68L12 11.15l10.48 10.3z"/>
3
+ </svg>
@@ -0,0 +1,10 @@
1
+ {
2
+ "/_astro/1-dark.BivCfSmu_Z1BFPSa.webp": "55644522",
3
+ "/_astro/1-light.ClVuZxd5_Z1g9Oj3.webp": "bcddfed5",
4
+ "/_astro/2-dark.prgZMrfY_Z1exqGN.webp": "55644523",
5
+ "/_astro/2-light.DLXS38ks_ZXNxHS.webp": "bbddded6",
6
+ "/_astro/3-dark.CGQcMuXC_Z28xNQs.webp": "55644522",
7
+ "/_astro/3-light.8aAAK8Ad_2oyxts.webp": "bbdfdad5",
8
+ "/_astro/4-dark.CKqTknpt_2qCYkU.webp": "55644523",
9
+ "/_astro/4-light.DIUeDw9p_ZrQwsm.webp": "bcdffede"
10
+ }
@@ -0,0 +1,152 @@
1
+ ---
2
+ import type { Language } from '@/i18n/config'
3
+ import LangEnIcon from '@/assets/icons/lang-en.svg'
4
+ import LangJaIcon from '@/assets/icons/lang-ja.svg'
5
+ import LangZhIcon from '@/assets/icons/lang-zh.svg'
6
+ import SearchIcon from '@/assets/icons/search-icon.svg'
7
+ import ThemeToggleIcon from '@/assets/icons/theme-toggle.svg'
8
+ import { allLocales, moreLocales, themeConfig } from '@/config'
9
+ import { getNextGlobalLangPath, getNextSupportedLangPath } from '@/i18n/path'
10
+ import { getPageInfo } from '@/utils/page'
11
+
12
+ interface Props {
13
+ supportedLangs: Language[]
14
+ }
15
+
16
+ const {
17
+ light: { background: lightMode },
18
+ dark: { background: darkMode },
19
+ } = themeConfig.color
20
+
21
+ const { supportedLangs } = Astro.props
22
+ const currentPath = Astro.url.pathname
23
+ const { currentLang } = getPageInfo(currentPath)
24
+
25
+ const effectiveLangs = supportedLangs.length > 0 ? supportedLangs : allLocales
26
+ const showLanguageSwitcher = moreLocales.length > 0 && effectiveLangs.length > 1
27
+
28
+ // Choose a language switch list according to supported languages (when available)
29
+ const nextUrl = supportedLangs.length > 1
30
+ ? getNextSupportedLangPath(currentPath, supportedLangs)
31
+ : getNextGlobalLangPath(currentPath)
32
+
33
+ // Determine which icon to show based on current language
34
+ // Show the icon of the NEXT language user will switch to
35
+ const langIconMap: Record<string, typeof LangEnIcon> = {
36
+ 'zh': LangEnIcon, // zh → en, show "A"
37
+ 'en': LangJaIcon, // en → ja, show "あ"
38
+ 'ja': LangZhIcon, // ja → zh, show "汉"
39
+ }
40
+ const CurrentLangIcon = langIconMap[currentLang] || LangEnIcon
41
+ ---
42
+
43
+ <div
44
+ class:list={[
45
+ 'absolute right-7.25vw top-14.6 flex gap-6 min-[823px]:max-lg:right-[calc(50vw-22rem)]',
46
+ 'lg:(fixed bottom-[min(10.27rem+1.92vw,12rem)] right-[max(5rem,calc(50vw-35rem))] top-auto w-14rem)',
47
+ ]}
48
+ >
49
+ <!-- Search Button -->
50
+ <button
51
+ id="search-button"
52
+ class="aspect-square w-4 c-secondary active:scale-90 hover:c-primary"
53
+ aria-label="Search"
54
+ >
55
+ <SearchIcon aria-hidden="true" fill="currentColor" />
56
+ </button>
57
+
58
+ <!-- Language Switcher -->
59
+ {showLanguageSwitcher && (
60
+ <a
61
+ id="language-switcher"
62
+ href={nextUrl}
63
+ class="aspect-square w-4 c-secondary active:scale-90 hover:c-primary"
64
+ aria-label="Switch website language"
65
+ >
66
+ <CurrentLangIcon
67
+ aria-hidden="true"
68
+ fill="currentColor"
69
+ />
70
+ </a>
71
+ )}
72
+
73
+ <!-- Theme Toggle -->
74
+ <button
75
+ id="theme-toggle-button"
76
+ class="aspect-square w-4 c-secondary active:scale-90 hover:c-primary"
77
+ data-light-mode={lightMode}
78
+ data-dark-mode={darkMode}
79
+ aria-label="Switch light/dark theme"
80
+ >
81
+ <ThemeToggleIcon
82
+ aria-hidden="true"
83
+ fill="currentColor"
84
+ />
85
+ </button>
86
+ </div>
87
+
88
+ <!-- Theme Toggle Script >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
89
+ <script>
90
+ // Apply theme changes
91
+ function applyTheme(isDark: boolean) {
92
+ document.documentElement.classList.toggle('dark', isDark)
93
+
94
+ // Update meta theme-color tag
95
+ const metaThemeColor = document.head.querySelector('meta[name="theme-color"]')
96
+ const themeButton = document.getElementById('theme-toggle-button')
97
+ const lightMode = themeButton?.dataset.lightMode
98
+ const darkMode = themeButton?.dataset.darkMode
99
+
100
+ if (metaThemeColor && lightMode && darkMode) {
101
+ metaThemeColor.setAttribute('content', isDark ? darkMode : lightMode)
102
+ }
103
+
104
+ localStorage.setItem('theme', isDark ? 'dark' : 'light')
105
+ document.dispatchEvent(new Event('theme-changed'))
106
+ }
107
+
108
+ // Toggle theme mode
109
+ function toggleTheme() {
110
+ const isDark = !document.documentElement.classList.contains('dark')
111
+ applyTheme(isDark)
112
+ }
113
+
114
+ // Handle theme toggle click events
115
+ function handleThemeToggle(e: MouseEvent) {
116
+ if (!(e.target instanceof Element)) {
117
+ return
118
+ }
119
+
120
+ if (!e.target.closest('#theme-toggle-button')) {
121
+ return
122
+ }
123
+
124
+ // If reduceMotion is enabled, update theme directly
125
+ if (document.documentElement.classList.contains('reduce-motion')) {
126
+ toggleTheme()
127
+ return
128
+ }
129
+
130
+ // Add temporary markers before view transition
131
+ document.documentElement.style.setProperty('view-transition-name', 'theme-toggle-transition')
132
+ document.documentElement.setAttribute('data-theme-changing', '')
133
+
134
+ // Use View Transitions API for theme toggle
135
+ const transition = document.startViewTransition(toggleTheme)
136
+
137
+ // Remove temporary markers after view transition
138
+ transition.finished.then(() => {
139
+ document.documentElement.style.removeProperty('view-transition-name')
140
+ document.documentElement.removeAttribute('data-theme-changing')
141
+ })
142
+ }
143
+
144
+ // Listen to system theme changes in real time
145
+ function handleSystemChange(event: MediaQueryListEvent) {
146
+ const isDark = event.matches
147
+ applyTheme(isDark)
148
+ }
149
+
150
+ document.addEventListener('click', handleThemeToggle)
151
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handleSystemChange)
152
+ </script>
@@ -0,0 +1,66 @@
1
+ ---
2
+ import type { Language } from '@/i18n/config'
3
+ import type { UserCategoryWithCount } from '@/utils/content'
4
+
5
+ interface Props {
6
+ categories: UserCategoryWithCount[]
7
+ currentCategory?: string
8
+ lang: Language
9
+ }
10
+
11
+ const { categories, currentCategory = '', lang } = Astro.props
12
+
13
+ // Generate category path (user-defined categories)
14
+ function getCategoryPagePath(category: string): string {
15
+ const langPrefix = lang === 'zh' ? '' : `/${lang}`
16
+ return `${langPrefix}/categories/${encodeURIComponent(category)}`
17
+ }
18
+ ---
19
+
20
+ <ul class="category-list no-heti">
21
+ {categories.map(cat => {
22
+ const isActive = currentCategory === cat.name
23
+ return (
24
+ <li class="category-item">
25
+ <a
26
+ href={getCategoryPagePath(cat.name)}
27
+ class:list={[
28
+ 'category-link',
29
+ isActive && 'active',
30
+ ]}
31
+ >
32
+ <span class="category-name">{cat.name}</span>
33
+ <span class="category-count">({cat.count})</span>
34
+ </a>
35
+ </li>
36
+ )
37
+ })}
38
+ </ul>
39
+
40
+ <style>
41
+ .category-list {
42
+ --at-apply: 'list-none p-0 m-0';
43
+ }
44
+
45
+ .category-item {
46
+ --at-apply: 'py-1.5';
47
+ }
48
+
49
+ .category-link {
50
+ --at-apply: 'inline-flex items-center gap-2';
51
+ --at-apply: 'c-secondary/80 transition-colors';
52
+ --at-apply: 'hover:c-primary';
53
+ }
54
+
55
+ .category-link.active {
56
+ --at-apply: 'c-primary font-semibold';
57
+ }
58
+
59
+ .category-name {
60
+ --at-apply: 'text-4';
61
+ }
62
+
63
+ .category-count {
64
+ --at-apply: 'text-3.5 c-secondary/60';
65
+ }
66
+ </style>
@@ -0,0 +1,119 @@
1
+ ---
2
+ import { base, defaultLocale, themeConfig } from '@/config'
3
+ import { giscusLocaleMap } from '@/i18n/config'
4
+
5
+ const {
6
+ repo = '',
7
+ repoId = '',
8
+ category = '',
9
+ categoryId = '',
10
+ mapping = 'pathname',
11
+ strict = '0',
12
+ reactionsEnabled = '1',
13
+ emitMetadata = '0',
14
+ inputPosition = 'bottom',
15
+ } = themeConfig.comment.giscus ?? {}
16
+
17
+
18
+ const currentPath = Astro.url.pathname
19
+
20
+ // Determine language
21
+ const pathWithoutBase = base && currentPath.startsWith(base)
22
+ ? currentPath.slice(base.length)
23
+ : currentPath
24
+ const matchedLang = Object.keys(giscusLocaleMap).find(code => pathWithoutBase.startsWith(`/${code}/`))
25
+ const lang = (matchedLang ?? defaultLocale) as keyof typeof giscusLocaleMap
26
+
27
+ const giscusConfig = {
28
+ repo,
29
+ repoId,
30
+ category,
31
+ categoryId,
32
+ mapping,
33
+ strict,
34
+ reactionsEnabled,
35
+ emitMetadata,
36
+ inputPosition,
37
+ lang: giscusLocaleMap[lang],
38
+ }
39
+ ---
40
+
41
+ <div
42
+ id="giscus"
43
+ class="giscus mt-16"
44
+ data-config={JSON.stringify(giscusConfig)}
45
+ />
46
+
47
+ <!-- Giscus Script >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
48
+ <script>
49
+ let giscusContainer: HTMLElement | null = null
50
+
51
+ function getThemeUrl() {
52
+ const isDark = document.documentElement.classList.contains('dark')
53
+ return isDark
54
+ ? `https://cdn.jsdelivr.net/gh/radishzzz/astro-theme-retypeset@master/public/giscus/theme-dark.css`
55
+ : `https://cdn.jsdelivr.net/gh/radishzzz/astro-theme-retypeset@master/public/giscus/theme-light.css`
56
+ }
57
+
58
+ function updateGiscusTheme() {
59
+ if (!giscusContainer || !giscusContainer.dataset.config) {
60
+ return
61
+ }
62
+
63
+ const giscusWindow = giscusContainer.querySelector<HTMLIFrameElement>('iframe.giscus-frame')?.contentWindow
64
+ if (!giscusWindow) {
65
+ return
66
+ }
67
+
68
+ try {
69
+ giscusWindow.postMessage(
70
+ { giscus: { setConfig: { theme: getThemeUrl() } } },
71
+ 'https://giscus.app',
72
+ )
73
+ }
74
+ catch (error) {
75
+ console.error('[Giscus] Failed to update theme:', error)
76
+ }
77
+ }
78
+
79
+ function setupGiscus() {
80
+ giscusContainer = document.getElementById('giscus')
81
+ if (!giscusContainer || !giscusContainer.dataset.config) {
82
+ return
83
+ }
84
+
85
+ giscusContainer.innerHTML = ''
86
+
87
+ try {
88
+ const config = JSON.parse(giscusContainer.dataset.config)
89
+ const { category, ...restConfig } = config
90
+
91
+ const dataAttributes: Record<string, string> = {
92
+ ...restConfig,
93
+ ...(category && { category }),
94
+ theme: getThemeUrl(),
95
+ }
96
+
97
+ const script = document.createElement('script')
98
+ script.crossOrigin = 'anonymous'
99
+ script.async = true
100
+ Object.assign(script.dataset, dataAttributes)
101
+ script.src = 'https://giscus.app/client.js'
102
+ giscusContainer.appendChild(script)
103
+ }
104
+ catch (error) {
105
+ console.error('[Giscus] Failed to initialize:', error)
106
+ }
107
+ }
108
+
109
+ function cleanupGiscus() {
110
+ if (giscusContainer) {
111
+ giscusContainer.innerHTML = ''
112
+ giscusContainer = null
113
+ }
114
+ }
115
+
116
+ document.addEventListener('astro:page-load', setupGiscus)
117
+ document.addEventListener('astro:before-swap', cleanupGiscus)
118
+ document.addEventListener('theme-changed', updateGiscusTheme)
119
+ </script>
@@ -0,0 +1,30 @@
1
+ ---
2
+ // import Disqus from '@/components/Comment/Disqus.astro'
3
+ import Giscus from '@/components/Comment/Giscus.astro'
4
+ import Twikoo from '@/components/Comment/Twikoo.astro'
5
+ import Waline from '@/components/Comment/Waline.astro'
6
+ import { themeConfig } from '@/config'
7
+
8
+ const { enabled: enableComment } = themeConfig.comment
9
+
10
+ // Disqus
11
+ // const disqusShortname = themeConfig.comment.disqus?.shortname ?? ''
12
+ // const showDisqus = enableComment && Boolean(disqusShortname.trim())
13
+
14
+ // Giscus
15
+ const giscusRepo = themeConfig.comment.giscus?.repo ?? ''
16
+ const showGiscus = enableComment && Boolean(giscusRepo.trim())
17
+
18
+ // Twikoo
19
+ const twikooEnvId = themeConfig.comment.twikoo?.envId ?? ''
20
+ const showTwikoo = enableComment && Boolean(twikooEnvId.trim())
21
+
22
+ // Waline
23
+ const walineURL = themeConfig.comment.waline?.serverURL ?? ''
24
+ const showWaline = enableComment && Boolean(walineURL.trim())
25
+ ---
26
+
27
+ <!-- {showDisqus && <Disqus />} -->
28
+ {showGiscus && <Giscus />}
29
+ {showTwikoo && <Twikoo />}
30
+ {showWaline && <Waline />}
@@ -0,0 +1,114 @@
1
+ ---
2
+ import twikooCSS from 'twikoo/dist/twikoo.css?url'
3
+ import twikooJS from 'twikoo/dist/twikoo.nocss.js?url' // Using twikoo.min.js causes CSS loading failure after page switching
4
+ import { allLocales, base, defaultLocale, themeConfig } from '@/config'
5
+ import { twikooLocaleMap } from '@/i18n/config'
6
+
7
+ const { envId = '' } = themeConfig.comment.twikoo ?? {}
8
+
9
+ // Remove locale prefix from path
10
+ const currentPath = Astro.url.pathname
11
+ const localePattern = new RegExp(`\\/(${allLocales.join('|')})\\/`)
12
+ const twikooPath = currentPath.replace(localePattern, '/')
13
+
14
+ // Determine language
15
+ const pathWithoutBase = base && currentPath.startsWith(base)
16
+ ? currentPath.slice(base.length)
17
+ : currentPath
18
+ const matchedLang = Object.keys(twikooLocaleMap).find(code => pathWithoutBase.startsWith(`/${code}/`))
19
+ const lang = (matchedLang ?? defaultLocale) as keyof typeof twikooLocaleMap
20
+
21
+ const twikooConfig = {
22
+ envId,
23
+ path: twikooPath,
24
+ lang: twikooLocaleMap[lang],
25
+ twikooCSS,
26
+ }
27
+ ---
28
+
29
+ <!-- Class not working on twikoo div -->
30
+ <div
31
+ id="twikoo-wrapper"
32
+ class="no-heti mt-16"
33
+ data-config={JSON.stringify(twikooConfig)}
34
+ >
35
+ <div id="twikoo" />
36
+ </div>
37
+
38
+ <!-- Twikoo Script >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
39
+ <script
40
+ is:inline
41
+ src={twikooJS}
42
+ ></script>
43
+
44
+ <script>
45
+ declare const twikoo: any
46
+
47
+ let twikooObserver: IntersectionObserver | null = null
48
+ let twikooWrapper: HTMLElement | null = null
49
+
50
+ function setupTwikoo() {
51
+ const twikooWrapper = document.getElementById('twikoo-wrapper')
52
+ if (!twikooWrapper || !twikooWrapper.dataset.config) {
53
+ return
54
+ }
55
+
56
+ try {
57
+ const config = JSON.parse(twikooWrapper.dataset.config)
58
+ const { envId, path, lang } = config
59
+
60
+ twikoo.init({
61
+ envId,
62
+ el: '#twikoo',
63
+ // region: 'ap-shanghai', // Specify for Tencent Cloud, omit for Vercel
64
+ path,
65
+ lang,
66
+ })
67
+ }
68
+ catch (error) {
69
+ console.error('[Twikoo] Failed to initialize:', error)
70
+ }
71
+ }
72
+
73
+ // Lazy load Twikoo when the wrapper enters viewport
74
+ function lazySetupTwikoo() {
75
+ twikooWrapper = document.getElementById('twikoo-wrapper')
76
+ if (!twikooWrapper || !twikooWrapper.dataset.config) {
77
+ return
78
+ }
79
+
80
+ const config = JSON.parse(twikooWrapper.dataset.config)
81
+ const { twikooCSS } = config
82
+
83
+ if (!document.getElementById('twikoo-css')) {
84
+ const link = document.createElement('link')
85
+ link.id = 'twikoo-css'
86
+ link.rel = 'stylesheet'
87
+ link.href = twikooCSS
88
+ document.head.appendChild(link)
89
+ }
90
+
91
+ twikooObserver = new IntersectionObserver((entries) => {
92
+ if (entries.some(entry => entry.isIntersecting)) {
93
+ setupTwikoo()
94
+ twikooObserver?.disconnect()
95
+ twikooObserver = null
96
+ }
97
+ }, { rootMargin: '500px' })
98
+
99
+ twikooObserver.observe(twikooWrapper)
100
+ }
101
+
102
+ function cleanupTwikoo() {
103
+ twikooObserver?.disconnect()
104
+ twikooObserver = null
105
+
106
+ if (twikooWrapper) {
107
+ twikooWrapper.innerHTML = ''
108
+ twikooWrapper = null
109
+ }
110
+ }
111
+
112
+ document.addEventListener('astro:page-load', lazySetupTwikoo)
113
+ document.addEventListener('astro:before-swap', cleanupTwikoo)
114
+ </script>
@@ -0,0 +1,149 @@
1
+ ---
2
+ import walineJS from '@waline/client/full?url'
3
+ import walineCSS from '@waline/client/style?url'
4
+ import { allLocales, base, defaultLocale, themeConfig } from '@/config'
5
+ import { walineLocaleMap } from '@/i18n/config'
6
+
7
+ const {
8
+ serverURL = '',
9
+ emoji = [],
10
+ search = false,
11
+ imageUploader = false,
12
+ } = themeConfig.comment.waline ?? {}
13
+
14
+ // Remove locale prefix from path
15
+ const currentPath = Astro.url.pathname
16
+ const localePattern = new RegExp(`\\/(${allLocales.join('|')})\\/`)
17
+ const walinePath = currentPath.replace(localePattern, '/')
18
+
19
+ // Determine language
20
+ const pathWithoutBase = base && currentPath.startsWith(base)
21
+ ? currentPath.slice(base.length)
22
+ : currentPath
23
+ const matchedLang = Object.keys(walineLocaleMap).find(code => pathWithoutBase.startsWith(`/${code}/`))
24
+ const lang = (matchedLang ?? defaultLocale) as keyof typeof walineLocaleMap
25
+
26
+ const walineConfig = {
27
+ serverURL,
28
+ path: walinePath,
29
+ lang: walineLocaleMap[lang],
30
+ emoji,
31
+ search,
32
+ imageUploader,
33
+ walineJS,
34
+ walineCSS,
35
+ }
36
+ ---
37
+
38
+ <div
39
+ id="waline"
40
+ class="no-heti mt-16"
41
+ data-config={JSON.stringify(walineConfig)}
42
+ />
43
+
44
+ <!-- Waline Script >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
45
+ <script>
46
+ import type { WalineInstance } from '@waline/client'
47
+
48
+ let walineObserver: IntersectionObserver | null = null
49
+ let walineInstance: WalineInstance | null = null
50
+
51
+ async function setupWaline() {
52
+ const walineContainer = document.getElementById('waline')
53
+ if (!walineContainer || !walineContainer.dataset.config) {
54
+ return
55
+ }
56
+
57
+ try {
58
+ const config = JSON.parse(walineContainer.dataset.config)
59
+ const { walineJS, walineCSS, ...restConfig } = config
60
+ const { init } = await import(/* @vite-ignore */ walineJS)
61
+
62
+ walineInstance = init({
63
+ el: '#waline',
64
+ dark: 'html.dark',
65
+ requiredMeta: ['nick', 'mail'],
66
+ highlighter: false,
67
+ texRenderer: false,
68
+ noCopyright: true,
69
+ reaction: [],
70
+ ...restConfig,
71
+ })
72
+ }
73
+ catch (error) {
74
+ console.error('[Waline] Failed to initialize:', error)
75
+ }
76
+ }
77
+
78
+ // Lazy load Waline when the container enters viewport
79
+ function lazySetupWaline() {
80
+ const walineContainer = document.getElementById('waline')
81
+ if (!walineContainer || !walineContainer.dataset.config) {
82
+ return
83
+ }
84
+
85
+ const config = JSON.parse(walineContainer.dataset.config)
86
+ const { walineCSS } = config
87
+
88
+ if (!document.getElementById('waline-css')) {
89
+ const link = document.createElement('link')
90
+ link.id = 'waline-css'
91
+ link.rel = 'stylesheet'
92
+ link.href = walineCSS
93
+ document.head.appendChild(link)
94
+ }
95
+
96
+ walineObserver = new IntersectionObserver((entries) => {
97
+ if (entries.some(entry => entry.isIntersecting)) {
98
+ setupWaline()
99
+ walineObserver?.disconnect()
100
+ walineObserver = null
101
+ }
102
+ }, { rootMargin: '500px' })
103
+
104
+ walineObserver.observe(walineContainer)
105
+ }
106
+
107
+ function cleanupWaline() {
108
+ walineObserver?.disconnect()
109
+ walineObserver = null
110
+ walineInstance?.destroy?.()
111
+ walineInstance = null
112
+ }
113
+
114
+ document.addEventListener('astro:page-load', lazySetupWaline)
115
+ document.addEventListener('astro:before-swap', cleanupWaline)
116
+ </script>
117
+
118
+ <!-- Official CSS Variables >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
119
+ <style>
120
+ #waline {
121
+ /* Regular Colors */
122
+ --waline-white: oklch(var(--un-preset-theme-colors-background));
123
+ --waline-light-grey: oklch(var(--un-preset-theme-colors-primary) / 0.25);
124
+ --waline-dark-grey: oklch(var(--un-preset-theme-colors-secondary));
125
+
126
+ /* Theme Colors */
127
+ --waline-theme-color: oklch(var(--un-preset-theme-colors-primary));
128
+ --waline-active-color: oklch(var(--un-preset-theme-colors-primary));
129
+
130
+ /* Layout Colors */
131
+ --waline-color: oklch(var(--un-preset-theme-colors-secondary));
132
+ --waline-bg-color: oklch(var(--un-preset-theme-colors-background));
133
+ --waline-bg-color-light: oklch(var(--un-preset-theme-colors-background));
134
+ --waline-bg-color-hover: oklch(var(--un-preset-theme-colors-background));
135
+ --waline-border-color: oklch(var(--un-preset-theme-colors-primary) / 0.25);
136
+ --waline-disable-bg-color: oklch(var(--un-preset-theme-colors-secondary) / 0.05);
137
+ --waline-disable-color: oklch(var(--un-preset-theme-colors-primary));
138
+
139
+ /* Special Colors */
140
+ --waline-bq-color: oklch(var(--un-preset-theme-colors-primary) / 0.25);
141
+
142
+ /* Information */
143
+ --waline-info-bg-color: oklch(var(--un-preset-theme-colors-background));
144
+ --waline-info-color: oklch(var(--un-preset-theme-colors-primary) / 0.25);
145
+
146
+ /* Rendering Options */
147
+ --waline-avatar-radius: 0.5rem;
148
+ }
149
+ </style>