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,18 @@
1
+ import { visit } from 'unist-util-visit'
2
+
3
+ export function rehypeExternalLinks() {
4
+ return (tree) => {
5
+ visit(tree, 'element', (node) => {
6
+ if (node.tagName === 'a' && node.properties?.href) {
7
+ if (/^(?:https?:|\/\/)/.test(node.properties.href)) {
8
+ node.properties.target = '_blank'
9
+ node.properties.rel = ['noopener', 'noreferrer']
10
+
11
+ // Add Umami outbound link tracking
12
+ node.properties.dataUmamiEvent = 'outbound-link-click'
13
+ node.properties.dataUmamiEventUrl = node.properties.href
14
+ }
15
+ }
16
+ })
17
+ }
18
+ }
@@ -0,0 +1,55 @@
1
+ import { SKIP, visit } from 'unist-util-visit'
2
+
3
+ export function rehypeHeadingAnchor() {
4
+ return (tree) => {
5
+ visit(tree, 'element', (node) => {
6
+ if (['h1', 'h2', 'h3', 'h4'].includes(node.tagName) && node.properties?.id) {
7
+ // Extract heading text
8
+ let headingText = ''
9
+ visit(node, 'text', (textNode) => {
10
+ headingText += textNode.value
11
+ })
12
+
13
+ // Create anchor link
14
+ node.children.push({
15
+ type: 'element',
16
+ tagName: 'a',
17
+ properties: {
18
+ href: `#${node.properties.id}`,
19
+ className: ['heading-anchor-link'],
20
+ ariaLabel: headingText
21
+ ? `Link to ${headingText.replace(/["']/g, char => char === '"' ? '"' : ''')}`
22
+ : undefined,
23
+ },
24
+ children: [{
25
+ type: 'element',
26
+ tagName: 'svg',
27
+ properties: {
28
+ 'viewBox': '0 0 24 24',
29
+ 'aria-hidden': 'true',
30
+ 'fill': 'currentColor',
31
+ },
32
+ children: [
33
+ {
34
+ type: 'element',
35
+ tagName: 'path',
36
+ properties: {
37
+ d: 'M2.6 21.4c2 2 5.9 2.9 8.9 0l3.5-3.5-1-1-3.5 3.5c-1.4 1.4-4.2 1.9-6.4-.3s-1.8-5-.3-6.4l3.5-3.5-1-1-3.5 3.5c-3 3-2 6.9 0 8.9ZM21.4 2.6c2 2 2.9 5.9 0 8.9L17.9 15l-1-1 3.5-3.5c1.4-1.4 1.9-4.2-.3-6.4s-5-1.8-6.4-.3l-3.5 3.5-1-1 3.5-3.5c3-3 6.9-2 8.9 0Z',
38
+ },
39
+ },
40
+ {
41
+ type: 'element',
42
+ tagName: 'path',
43
+ properties: {
44
+ d: 'm8.01 14.97 6.93-6.93 1.061 1.06-6.93 6.93z',
45
+ },
46
+ },
47
+ ],
48
+ }],
49
+ })
50
+
51
+ return [SKIP]
52
+ }
53
+ })
54
+ }
55
+ }
@@ -0,0 +1,77 @@
1
+ import { visit } from 'unist-util-visit'
2
+
3
+ function createFigure(imgNode, isInGallery = false) {
4
+ const altText = imgNode.properties?.alt
5
+ const shouldSkipCaption = !altText || altText.startsWith('_')
6
+ if (shouldSkipCaption && !isInGallery) {
7
+ return imgNode
8
+ }
9
+
10
+ const children = [imgNode]
11
+
12
+ if (!shouldSkipCaption) {
13
+ children.push({
14
+ type: 'element',
15
+ tagName: 'figcaption',
16
+ properties: {},
17
+ children: [{ type: 'text', value: altText }],
18
+ })
19
+ }
20
+
21
+ return {
22
+ type: 'element',
23
+ tagName: 'figure',
24
+ properties: isInGallery ? { className: ['gallery-item'] } : {},
25
+ children,
26
+ }
27
+ }
28
+
29
+ export function rehypeImageProcessor() {
30
+ return (tree) => {
31
+ visit(tree, 'element', (node, index, parent) => {
32
+ // Skip non-paragraph elements, empty paragraphs, and orphaned nodes
33
+ if (node.tagName !== 'p' || !node.children || node.children.length === 0 || !parent) {
34
+ return
35
+ }
36
+
37
+ // Collect images from paragraph
38
+ const imgNodes = []
39
+ for (const child of node.children) {
40
+ if (child.tagName === 'img') {
41
+ imgNodes.push(child)
42
+ }
43
+ else if (child.type !== 'text' || child.value.trim() !== '') {
44
+ return // Skip paragraphs with non-image content
45
+ }
46
+ }
47
+
48
+ if (imgNodes.length === 0) {
49
+ return
50
+ }
51
+
52
+ const isInGallery = parent?.properties?.className?.includes('gallery-container')
53
+
54
+ // Gallery container: convert images to figures
55
+ if (isInGallery) {
56
+ const figures = imgNodes.map(imgNode => createFigure(imgNode, true))
57
+ parent.children.splice(index, 1, ...figures)
58
+ return
59
+ }
60
+
61
+ // Single image: convert to figure in non-gallery containers
62
+ if (imgNodes.length === 1) {
63
+ const figure = createFigure(imgNodes[0], false)
64
+ if (figure !== imgNodes[0]) {
65
+ // Only replace if conversion happened
66
+ node.tagName = 'figure'
67
+ node.properties = figure.properties
68
+ node.children = figure.children
69
+ }
70
+ return
71
+ }
72
+
73
+ // Multiple images: unwrap in non-gallery containers
74
+ parent.children.splice(index, 1, ...imgNodes)
75
+ })
76
+ }
77
+ }
@@ -0,0 +1,135 @@
1
+ import { visit } from 'unist-util-visit'
2
+
3
+ const admonitionTypes = {
4
+ note: 'NOTE',
5
+ tip: 'TIP',
6
+ important: 'IMPORTANT',
7
+ warning: 'WARNING',
8
+ caution: 'CAUTION',
9
+ }
10
+
11
+ // Extract text from directive label
12
+ function getLabelText(node) {
13
+ return node?.children
14
+ ?.map(child => child.type === 'text' ? child.value : '')
15
+ .join('')
16
+ .trim() || ''
17
+ }
18
+
19
+ // Admonition Blocks
20
+ function createAdmonition(node, type, title) {
21
+ const titleSpan = `<span class="admonition-title">${title}</span>`
22
+
23
+ node.data ??= {}
24
+ node.data.hName = 'blockquote'
25
+ node.data.hProperties = {
26
+ className: `admonition-${type}`,
27
+ }
28
+
29
+ node.children.unshift({
30
+ type: 'html',
31
+ value: titleSpan,
32
+ })
33
+ }
34
+
35
+ // Collapsible Sections
36
+ function createFoldSection(node, title) {
37
+ const summary = `<summary>${title}</summary>`
38
+
39
+ node.data ??= {}
40
+ node.data.hName = 'details'
41
+
42
+ node.children.unshift({
43
+ type: 'html',
44
+ value: summary,
45
+ })
46
+ }
47
+
48
+ // Gallery Containers
49
+ function createGallery(node) {
50
+ node.data ??= {}
51
+ node.data.hName = 'div'
52
+ node.data.hProperties = {
53
+ className: ['gallery-container'],
54
+ }
55
+ }
56
+
57
+ export function remarkContainerDirectives() {
58
+ const githubAdmonitionRegex = new RegExp(
59
+ `^\\s*\\[!(${Object.values(admonitionTypes).join('|')})\\]\\s*`,
60
+ 'i',
61
+ )
62
+
63
+ return (tree) => {
64
+ // Handle :::type[title] syntax
65
+ visit(tree, 'containerDirective', (node) => {
66
+ const type = node.name
67
+ const labelNode = node.children?.[0]
68
+
69
+ // Admonition Blocks
70
+ if (admonitionTypes[type]) {
71
+ // Optional [title] for admonitions
72
+ let title = admonitionTypes[type]
73
+
74
+ if (labelNode?.data?.directiveLabel) {
75
+ const customTitle = getLabelText(labelNode)
76
+ if (customTitle) {
77
+ title = customTitle
78
+ }
79
+ node.children.shift()
80
+ }
81
+
82
+ createAdmonition(node, type, title)
83
+ return
84
+ }
85
+
86
+ // Collapsible Sections
87
+ if (type === 'fold') {
88
+ // Require non-empty [title]
89
+ const title = getLabelText(labelNode)
90
+ if (!labelNode?.data?.directiveLabel || !title) {
91
+ console.warn(`:::fold syntax requires [title] brackets with non-empty content`)
92
+ return
93
+ }
94
+
95
+ node.children.shift()
96
+ createFoldSection(node, title)
97
+ return
98
+ }
99
+
100
+ // Gallery Containers
101
+ if (type === 'gallery') {
102
+ // Remove label if exists
103
+ if (labelNode?.data?.directiveLabel) {
104
+ node.children.shift()
105
+ }
106
+
107
+ createGallery(node)
108
+ }
109
+ })
110
+
111
+ // Handle > [!TYPE] syntax
112
+ visit(tree, 'blockquote', (node) => {
113
+ const firstTextNode = node.children?.[0]?.children?.[0]
114
+ if (firstTextNode?.type !== 'text') {
115
+ return
116
+ }
117
+
118
+ const match = firstTextNode.value.match(githubAdmonitionRegex)
119
+ if (!match) {
120
+ return
121
+ }
122
+
123
+ const type = match[1].toLowerCase()
124
+ const title = admonitionTypes[type]
125
+
126
+ if (!title) {
127
+ return
128
+ }
129
+
130
+ firstTextNode.value = firstTextNode.value.substring(match[0].length)
131
+
132
+ createAdmonition(node, type, title)
133
+ })
134
+ }
135
+ }
@@ -0,0 +1,184 @@
1
+ import { visit } from 'unist-util-visit'
2
+
3
+ const embedHandlers = {
4
+ // GitHub Repository Card
5
+ github: (node) => {
6
+ const repo = node.attributes?.repo ?? ''
7
+ if (!repo) {
8
+ console.warn(`Missing GitHub repository`)
9
+ return false
10
+ }
11
+
12
+ const [owner, name] = repo.split('/')
13
+ if (!owner || !name) {
14
+ console.warn(`Invalid GitHub repository format: "${repo}"`)
15
+ return false
16
+ }
17
+
18
+ return `
19
+ <a href="https://github.com/${repo}" class="no-heti gc-container" target="_blank" rel="noopener noreferrer" data-repo="${repo}">
20
+ <div class="gc-title-bar">
21
+ <div class="gc-owner-avatar" style="background-size: cover; background-position: center;" aria-hidden="true"></div>
22
+ <span class="gc-repo-title">
23
+ <span>${owner}<span class="gc-slash" aria-hidden="true">/</span><strong>${name}</strong></span>
24
+ </span>
25
+ <svg class="gc-github-icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
26
+ <path d="M12 1C5.9225 1 1 5.9225 1 12C1 16.8675 4.14875 20.9787 8.52125 22.4362C9.07125 22.5325 9.2775 22.2025 9.2775 21.9137C9.2775 21.6525 9.26375 20.7862 9.26375 19.865C6.5 20.3737 5.785 19.1912 5.565 18.5725C5.44125 18.2562 4.905 17.28 4.4375 17.0187C4.0525 16.8125 3.5025 16.3037 4.42375 16.29C5.29 16.2762 5.90875 17.0875 6.115 17.4175C7.105 19.0812 8.68625 18.6137 9.31875 18.325C9.415 17.61 9.70375 17.1287 10.02 16.8537C7.5725 16.5787 5.015 15.63 5.015 11.4225C5.015 10.2262 5.44125 9.23625 6.1425 8.46625C6.0325 8.19125 5.6475 7.06375 6.2525 5.55125C6.2525 5.55125 7.17375 5.2625 9.2775 6.67875C10.1575 6.43125 11.0925 6.3075 12.0275 6.3075C12.9625 6.3075 13.8975 6.43125 14.7775 6.67875C16.8813 5.24875 17.8025 5.55125 17.8025 5.55125C18.4075 7.06375 18.0225 8.19125 17.9125 8.46625C18.6138 9.23625 19.04 10.2125 19.04 11.4225C19.04 15.6437 16.4688 16.5787 14.0213 16.8537C14.42 17.1975 14.7638 17.8575 14.7638 18.8887C14.7638 20.36 14.75 21.5425 14.75 21.9137C14.75 22.2025 14.9563 22.5462 15.5063 22.4362C19.8513 20.9787 23 16.8537 23 12C23 5.9225 18.0775 1 12 1Z"></path>
27
+ </svg>
28
+ </div>
29
+ <p class="gc-repo-description">Loading repository data...</p>
30
+ <div class="gc-info-bar">
31
+ <svg class="gc-info-icon" height="16" width="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
32
+ <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path>
33
+ </svg>
34
+ <span class="gc-stars-count" aria-label="Stars count">--</span>
35
+ <svg class="gc-info-icon" height="16" width="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
36
+ <path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path>
37
+ </svg>
38
+ <span class="gc-forks-count" aria-label="Forks count">--</span>
39
+ <svg class="gc-info-icon" height="16" width="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
40
+ <path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.289.737c-.265.15-.564.23-.869.23h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.288-.737c.265-.15.564-.23.869-.23h.984V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path>
41
+ </svg>
42
+ <span class="gc-license-info" aria-label="License">--</span>
43
+ </div>
44
+ </a>
45
+ `
46
+ },
47
+
48
+ // Youtube
49
+ youtube: (node) => {
50
+ const videoId = node.attributes?.id ?? ''
51
+ if (!videoId) {
52
+ console.warn(`Missing YouTube video ID`)
53
+ return false
54
+ }
55
+
56
+ return `
57
+ <figure>
58
+ <lite-youtube videoid="${videoId}"></lite-youtube>
59
+ </figure>
60
+ `
61
+ },
62
+
63
+ // Bilibili
64
+ bilibili: (node) => {
65
+ const bvid = node.attributes?.id ?? ''
66
+ if (!bvid) {
67
+ console.warn(`Missing Bilibili video ID`)
68
+ return false
69
+ }
70
+
71
+ return `
72
+ <figure>
73
+ <iframe
74
+ class="bilibili-player"
75
+ src="//player.bilibili.com/player.html?isOutside=true&bvid=${bvid}&p=1&autoplay=0&muted=0"
76
+ title="Bilibili video player"
77
+ allowfullscreen
78
+ loading="lazy"
79
+ ></iframe>
80
+ </figure>
81
+ `
82
+ },
83
+
84
+ // Tweet Card
85
+ tweet: (node) => {
86
+ const url = node.attributes?.url ?? ''
87
+ if (!url) {
88
+ console.warn(`Missing Tweet URL`)
89
+ return false
90
+ }
91
+
92
+ const tweetUrl = url.replace(/(\w+:\/\/)?x\.com\//g, '$1twitter.com/')
93
+
94
+ return `
95
+ <figure>
96
+ <blockquote class="twitter-tweet" data-dnt="true">
97
+ <a href="${tweetUrl}"></a>
98
+ </blockquote>
99
+ </figure>
100
+ `
101
+ },
102
+
103
+ // CodePen
104
+ codepen: (node) => {
105
+ const url = node.attributes?.url ?? ''
106
+ if (!url) {
107
+ console.warn(`Missing CodePen URL`)
108
+ return false
109
+ }
110
+
111
+ const match = url.match(/codepen\.io\/([^/]+)\/pen\/([^/?#]+)/)
112
+ if (!match) {
113
+ console.warn(`Invalid CodePen URL: ${url}`)
114
+ return false
115
+ }
116
+
117
+ const [, user, slug] = match
118
+
119
+ return `
120
+ <figure>
121
+ <iframe
122
+ class="codepen-embed"
123
+ src="https://codepen.io/${user}/embed/${slug}?default-tab=result"
124
+ title="CodePen Embed"
125
+ loading="lazy"
126
+ ></iframe>
127
+ </figure>
128
+ `
129
+ },
130
+
131
+ // Spotify
132
+ spotify: (node) => {
133
+ const url = node.attributes?.url ?? ''
134
+ if (!url) {
135
+ console.warn(`Missing Spotify URL`)
136
+ return false
137
+ }
138
+
139
+ const match = url.match(/open\.spotify\.com\/(track|album|playlist|artist|episode|show)\/([^/?#]+)/)
140
+ if (!match) {
141
+ console.warn(`Invalid Spotify URL: ${url}`)
142
+ return false
143
+ }
144
+
145
+ const [, type, id] = match
146
+ const height = ['track', 'episode', 'show'].includes(type) ? 152 : 352
147
+
148
+ return `
149
+ <figure>
150
+ <iframe
151
+ class="spotify-embed"
152
+ src="https://open.spotify.com/embed/${type}/${id}"
153
+ title="Spotify Embed"
154
+ height="${height}"
155
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
156
+ loading="lazy"
157
+ ></iframe>
158
+ </figure>
159
+ `
160
+ },
161
+ }
162
+
163
+ export function remarkLeafDirectives() {
164
+ return (tree) => {
165
+ visit(tree, 'leafDirective', (node) => {
166
+ const handler = embedHandlers[node.name]
167
+ if (!handler) {
168
+ return
169
+ }
170
+
171
+ const htmlContent = handler(node)
172
+ if (!htmlContent) {
173
+ return
174
+ }
175
+
176
+ node.type = 'html'
177
+ node.value = htmlContent
178
+
179
+ delete node.name
180
+ delete node.attributes
181
+ delete node.children
182
+ })
183
+ }
184
+ }
@@ -0,0 +1,11 @@
1
+ import { toString } from 'mdast-util-to-string'
2
+ import getReadingTime from 'reading-time'
3
+
4
+ export function remarkReadingTime() {
5
+ return (tree, { data }) => {
6
+ const textOnPage = toString(tree)
7
+ const readingTime = getReadingTime(textOnPage)
8
+
9
+ data.astro.frontmatter.minutes = Math.max(1, Math.round(readingTime.minutes))
10
+ }
11
+ }
@@ -0,0 +1,205 @@
1
+ /* Twikoo */
2
+ #twikoo svg,
3
+ #twikoo .tk-action-count {
4
+ --at-apply: 'c-secondary/80'
5
+ }
6
+ #twikoo .tk-comments-container {
7
+ --at-apply: 'text-sm leading-6'
8
+ }
9
+ #twikoo .tk-comments-container .tk-avatar {
10
+ --at-apply: 'mt-0.8'
11
+ }
12
+ #twikoo .tk-avatar,
13
+ #twikoo .tk-avatar-img {
14
+ --at-apply: 'lg:(h-12 w-12)'
15
+ }
16
+ #twikoo .tk-main .tk-avatar,
17
+ #twikoo .tk-main .tk-avatar-img {
18
+ --at-apply: 'h-7.7 w-7.7 lg:(h-9.2 w-9.2)'
19
+ }
20
+ #twikoo .el-input__count,
21
+ #twikoo .tk-submit-action-icon.__markdown,
22
+ #twikoo .tk-action-link:has(svg path[d*="256 32C114.6 32 0 125.1"]) .tk-action-count,
23
+ #twikoo .tk-icon.__comments:has(svg path[d^="M440.65 12.57"]),
24
+ #twikoo .tk-extras,
25
+ #twikoo .tk-footer {
26
+ --at-apply: 'hidden'
27
+ }
28
+
29
+ /* Input fields */
30
+ #twikoo .tk-meta-input {
31
+ --at-apply: 'border border-secondary/25 rounded'
32
+ }
33
+ #twikoo .tk-meta-input .el-input + .el-input {
34
+ --at-apply: 'ml-0 mt-0'
35
+ }
36
+ #twikoo .tk-meta-input > * {
37
+ --at-apply: 'border-none bg-transparent'
38
+ }
39
+ #twikoo .el-input-group__prepend {
40
+ --at-apply: 'border-none bg-transparent px-3 text-3 c-secondary'
41
+ }
42
+ #twikoo .el-input:has(input[name="mail"]) .el-input-group__prepend,
43
+ #twikoo.el-input:has(input[name="link"]) .el-input-group__prepend {
44
+ --at-apply: 'lg:pl-0'
45
+ }
46
+ #twikoo .el-input__inner {
47
+ --at-apply: 'my-0.5 border-none p-0 text-3'
48
+ }
49
+ #twikoo .el-input__inner::placeholder {
50
+ --at-apply: 'c-secondary/25'
51
+ }
52
+ #twikoo .el-textarea__inner {
53
+ --at-apply: 'border-secondary/25 py-2 leading-6'
54
+ }
55
+ #twikoo .OwO-body {
56
+ --at-apply: 'border-secondary/25 rounded bg-background c-secondary'
57
+ }
58
+ #twikoo .OwO-item {
59
+ --at-apply: 'hover:shadow-none'
60
+ }
61
+ #twikoo .OwO-items-show {
62
+ --at-apply: 'overflow-x-hidden overflow-y-auto'
63
+ }
64
+ #twikoo .tk-cancel,
65
+ #twikoo .tk-preview {
66
+ --at-apply: 'border-secondary/25 bg-transparent c-secondary font-normal active:(border-secondary/80 c-primary) hover:(border-secondary/80 c-primary)'
67
+ }
68
+ #twikoo .tk-send {
69
+ --at-apply: 'bg-primary font-normal'
70
+ }
71
+ #twikoo .tk-send span {
72
+ --at-apply: 'c-background'
73
+ }
74
+ #twikoo .el-button--primary {
75
+ --at-apply: 'border-none'
76
+ }
77
+
78
+ /* Margins */
79
+ #twikoo .el-loading-spinner svg {
80
+ --at-apply: 'mx-auto'
81
+ }
82
+ #twikoo .el-loading-spinner .path {
83
+ --at-apply: 'stroke-note'
84
+ }
85
+ #twikoo .tk-submit.tk-fade-in {
86
+ --at-apply: 'mt-6'
87
+ }
88
+ #twikoo .tk-row.actions {
89
+ --at-apply: 'mb-0 lg:ml-16'
90
+ }
91
+ #twikoo .tk-comments-container .tk-row.actions {
92
+ --at-apply: 'lg:ml-14'
93
+ }
94
+ #twikoo .tk-preview-container {
95
+ --at-apply: 'ml-14 mt-4 min-h-8 border-secondary/25 py-2 text-sm leading-6'
96
+ }
97
+ #twikoo .tk-comments-title {
98
+ --at-apply: 'mb-0 mt-7'
99
+ }
100
+ #twikoo .tk-comment {
101
+ --at-apply: 'mt-8'
102
+ }
103
+ #twikoo .tk-nick {
104
+ --at-apply: 'mr-1'
105
+ }
106
+ #twikoo .tk-tag {
107
+ --at-apply: 'mr-1 border-note rounded bg-transparent px-0.3em text-3 c-note leading-5'
108
+ }
109
+ #twikoo .tk-replies .tk-comment {
110
+ --at-apply: 'mt-6'
111
+ }
112
+
113
+ /* Waline >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
114
+ #waline .wl-login-info {
115
+ --at-apply: 'mr-3 mt-0'
116
+ }
117
+ #waline .wl-avatar {
118
+ --at-apply: 'border-none'
119
+ }
120
+ #waline .wl-logout-btn {
121
+ --at-apply: 'z-99'
122
+ }
123
+ #waline .wl-login-nick:not(:has(img)) {
124
+ --at-apply: 'mt-1.4 leading-3.6';
125
+ }
126
+ #waline .wl-panel {
127
+ --at-apply: 'm-0 border-secondary/25 rounded-lg bg-transparent'
128
+ }
129
+ #waline .wl-header {
130
+ --at-apply: 'p-0';
131
+ }
132
+ #waline .wl-header-item {
133
+ --at-apply: 'border-b border-primary/25 border-solid';
134
+ }
135
+ #waline .wl-header label {
136
+ --at-apply: 'text-3';
137
+ }
138
+ #waline .wl-header input {
139
+ --at-apply: 'text-2.8';
140
+ }
141
+ #waline .wl-card,
142
+ #waline .wl-header.item3 {
143
+ --at-apply: 'border-b-0';
144
+ }
145
+ #waline .wl-card .wl-quote {
146
+ --at-apply: 'mt-4 border-is-none';
147
+ }
148
+ #waline .wl-editor {
149
+ --at-apply: 'min-h-20';
150
+ }
151
+ #waline .wl-editor::placeholder {
152
+ --at-apply: 'c-primary/25';
153
+ }
154
+ #waline .wl-footer {
155
+ --at-apply: 'm-2';
156
+ }
157
+ #waline .wl-text-number,
158
+ #waline .wl-action[title="Markdown Guide"],
159
+ #waline .wl-sort,
160
+ #waline .wl-gallery::-webkit-scrollbar {
161
+ --at-apply: 'hidden';
162
+ }
163
+ #waline .wl-emoji-popup {
164
+ --at-apply: 'start-0 max-w-532px border-secondary/25';
165
+ }
166
+ #waline .wl-emoji-popup .wl-tab-wrapper {
167
+ scrollbar-width: thin;
168
+ }
169
+ #waline .wl-gif-popup {
170
+ --at-apply: 'border-secondary/25';
171
+ }
172
+ #waline .wl-gif-popup input {
173
+ --at-apply: 'border-secondary/25 bg-background';
174
+ }
175
+ #waline .wl-gif-popup input::placeholder {
176
+ --at-apply: 'text-3.5 c-secondary/30';
177
+ }
178
+ #waline .wl-gallery {
179
+ --at-apply: 'scrollbar-hidden';
180
+ }
181
+ #waline .wl-btn {
182
+ --at-apply: 'duration-150 hover:border-secondary/80';
183
+ }
184
+ #waline .wl-meta-head {
185
+ --at-apply: 'px-0 py-3';
186
+ }
187
+ #waline .wl-card-item {
188
+ --at-apply: 'px-0 py-1';
189
+ }
190
+ #waline .wl-user-avatar {
191
+ --at-apply: 'mt-1';
192
+ }
193
+ #waline .wl-content p {
194
+ --at-apply: 'text-3.5 leading-6';
195
+ }
196
+ #waline .wl-time {
197
+ --at-apply: 'c-primary/75';
198
+ }
199
+ #waline .wl-edit,
200
+ #waline .wl-delete {
201
+ --at-apply: 'mr-0.4';
202
+ }
203
+ #waline .wl-like {
204
+ --at-apply: 'mr-1.2';
205
+ }