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