├── .editorconfig
├── .github
└── workflows
│ └── astro.yml
├── .gitignore
├── LICENSE
├── README-ZH-CN.md
├── README.md
├── astro.config.js
├── package.json
├── pnpm-lock.yaml
├── public
├── alipay.svg
├── avatar.png
├── favicon.svg
├── load-mathjax.js
├── paypal.svg
├── robots.txt
├── spinner.gif
├── toggle-theme.js
└── wechat.svg
├── src
├── components
│ ├── BaseHead.astro
│ ├── BlogAside.astro
│ ├── BlogFooter.astro
│ ├── BusuanziAnalytics.astro
│ ├── Comment.astro
│ ├── CommentAside.astro
│ ├── Donate.astro
│ ├── FeedPostDate.astro
│ ├── FeedPreview.astro
│ ├── Footer.astro
│ ├── Friends.astro
│ ├── GiscusComment.astro
│ ├── GoogleAnalytics.astro
│ ├── Header.astro
│ ├── HeaderLink.astro
│ ├── MenuIcon.astro
│ ├── Pagination.astro
│ ├── PostTitle.astro
│ ├── PostView.astro
│ ├── PostViewTitle.astro
│ ├── Profile.astro
│ ├── ScrollToTop.astro
│ ├── SearchTitle.astro
│ ├── SidebarIcon.astro
│ ├── ThemeIcon.astro
│ ├── Toc.astro
│ ├── UmamiAnalytics.astro
│ └── WalineComment.astro
├── consts.ts
├── content
│ ├── blog
│ │ ├── markdown-elements.md
│ │ └── new-features.md
│ ├── config.ts
│ └── feed
│ │ └── 2024-01-23.md
├── env.d.ts
├── i18n
│ ├── cs.ts
│ ├── en.ts
│ ├── utils.ts
│ ├── zhCn.ts
│ └── zhHant.ts
├── layouts
│ ├── BlogPost.astro
│ └── IndexPage.astro
├── pages
│ ├── 404.astro
│ ├── about
│ │ ├── about.md
│ │ └── index.astro
│ ├── archive
│ │ ├── [page].astro
│ │ └── index.astro
│ ├── blog
│ │ ├── [...slug].astro
│ │ └── [page].astro
│ ├── category
│ │ └── [category].astro
│ ├── feed
│ │ ├── [...slug].astro
│ │ └── [page].astro
│ ├── friends
│ │ └── index.astro
│ ├── index.astro
│ ├── memos
│ │ └── index.astro
│ ├── message
│ │ └── index.astro
│ ├── rss.xml.js
│ ├── search.astro
│ ├── search.json.js
│ └── tags
│ │ ├── [tag].astro
│ │ └── index.astro
├── plugins
│ ├── lazy-load-image.js
│ ├── remark-asides.js
│ ├── remark-button.js
│ ├── remark-collapse.js
│ ├── remark-github-card.js
│ ├── remark-html.js
│ ├── remark-modified-time.mjs
│ └── reset-remark.js
├── styles
│ ├── JetBrainsMono-Regular.ttf
│ ├── donate.css
│ ├── github-markdown.css
│ ├── index.css
│ └── remark-aside.css
├── types
│ └── analyticsTypes.ts
└── utils
│ ├── dealLabel.ts
│ ├── formatDate.ts
│ ├── getCollectionByName.ts
│ ├── getCountByCategory.ts
│ ├── getCountByTagName.ts
│ ├── getPostsByCategory.ts
│ ├── getPostsByTag.ts
│ ├── getPostsByYear.ts
│ ├── getUniqeCategory.ts
│ ├── getUniqueTags.ts
│ ├── getUrl.ts
│ ├── orderBySticky.ts
│ └── sortPostsByDate.ts
├── tailwind.config.js
├── tsconfig.json
└── vercel.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.github/workflows/astro.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying an Astro site to GitHub Pages
2 | #
3 | # To get started with Astro see: https://docs.astro.build/en/getting-started/
4 | #
5 | name: Deploy Astro site to Pages
6 |
7 | on:
8 | # Runs on pushes targeting the default branch
9 | push:
10 | branches: ["main"]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
16 | permissions:
17 | contents: read
18 | pages: write
19 | id-token: write
20 |
21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
23 | concurrency:
24 | group: "pages"
25 | cancel-in-progress: false
26 |
27 | env:
28 | BUILD_PATH: "." # default value when not using subfolders
29 | # BUILD_PATH: subfolder
30 |
31 | jobs:
32 | build:
33 | name: Build
34 | runs-on: ubuntu-latest
35 | steps:
36 | - name: Checkout
37 | uses: actions/checkout@v4
38 | - uses: pnpm/action-setup@v4
39 | name: Install pnpm
40 | with:
41 | version: 10
42 | run_install: false
43 | - name: Install Node.js
44 | uses: actions/setup-node@v4
45 | with:
46 | node-version: 20
47 | cache: 'pnpm'
48 | - name: Install dependencies
49 | run: pnpm install
50 | - name: Setup Pages
51 | id: pages
52 | uses: actions/configure-pages@v5
53 | - name: Install dependencies
54 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
55 | working-directory: ${{ env.BUILD_PATH }}
56 | - name: Build with Astro
57 | run: |
58 | ${{ steps.detect-package-manager.outputs.runner }} npm run build \
59 | --site "${{ steps.pages.outputs.origin }}" \
60 | --base "${{ steps.pages.outputs.base_path }}"
61 | working-directory: ${{ env.BUILD_PATH }}
62 | - name: Upload artifact
63 | uses: actions/upload-pages-artifact@v3
64 | with:
65 | path: ${{ env.BUILD_PATH }}/dist
66 |
67 | deploy:
68 | environment:
69 | name: github-pages
70 | url: ${{ steps.deployment.outputs.page_url }}
71 | needs: build
72 | runs-on: ubuntu-latest
73 | name: Deploy
74 | steps:
75 | - name: Deploy to GitHub Pages
76 | id: deployment
77 | uses: actions/deploy-pages@v4
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | # generated types
4 | .astro/
5 | .vscode
6 | .idea
7 |
8 | # dependencies
9 | node_modules/
10 |
11 | # logs
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 |
18 | # environment variables
19 | .env
20 | .env.production
21 |
22 | # macOS-specific files
23 | .DS_Store
24 |
25 | # patches
26 | patches/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Choi Cirry
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README-ZH-CN.md:
--------------------------------------------------------------------------------
1 | # Astro Theme YI
2 |
3 | [[English]](./README.md) | [[简体中文]](./README-ZH-CN.md)
4 |
5 | 预览地址:[Astro-Theme-Yi](https://astro-yi-nu.vercel.app/)
6 |
7 | 更多配置内容,请参阅文章: [Astro-YI Write Skill](https://cirry.cn/blog/frontend/astro/config-and-write-skill)
8 |
9 | 一款以内容为主的Astro博客主题————Yi,快速和简洁。
10 |
11 | 
12 |
13 | 
14 |
15 | 
16 |
17 | ### 🔥 特色功能
18 | - [x] 支持GithubPages
19 | - [x] 支持多端显示
20 | - [x] 支持暗黑显示
21 | - [x] 集成Memos
22 | - [x] 支持国际化
23 | - [x] 支持搜索功能
24 | - [x] 友好的SEO
25 | - [x] 支持站点地图和rss
26 | - [x] 支持草稿箱
27 | - [x] 支持waline评论系统
28 | - [x] 支持图片懒加载和预览
29 | - [x] 支持文章永久固定链接
30 | - [x] 支持meriand
31 | - [x] 支持mathjax
32 | - [x] 支持expressive-code
33 |
34 | ......等等
35 |
36 | ### Vercel一键部署
37 |
38 | [](https://vercel.com/import/project?template=https%3A%2F%2Fgithub.com%2Fcirry%2Fastro-yi)
39 |
40 | 试试吧,点击上面的按钮,就可以一键部署了。
41 |
42 | ### 👨🏻💻 手动部署
43 |
44 | 推荐使用:`nodejs >= 18`,`pnpm >= 8`。
45 |
46 | ```bash
47 | git clone https://github.com/cirry/astro-yi.git
48 | cd astro-yi
49 | npm install -g pnpm
50 | pnpm i
51 | npm run dev # preview
52 | ```
53 |
54 | 将您最喜欢的文章写在 `src/content/blog` 文件夹中,在 `src/content/feed` 文件夹中编写您想发布的动态内容。
55 |
56 | ```bash
57 | npm run build # build
58 | ```
59 |
60 | 打包完成后,在根目录中会生成一个 dist 文件夹。将 'dist' 文件夹上传到 Web 服务器目录中,即可完成部署。
61 |
62 | ### github pages部署
63 |
64 | 只需在`/src/consts.ts`中修改`site`对象中的`url`和`baseUrl`属性:
65 |
66 | ```js
67 | export const site = {
68 | // ...
69 | url: 'https://cirry.github.io', // 必填 你的网站主域名
70 | baseUrl: 'yi.github.io', // 必填,仓库名称
71 | // ...
72 | }
73 | ```
74 |
75 | ## 配置
76 |
77 | 本博客的唯一的配置文件就是:`src/consts.ts`,您可以根据自己的需求进行一些修改。
78 |
79 | ```ts
80 | /**
81 | * Site information
82 | * title:网站标题
83 | * description:网站描述
84 | * author:作者
85 | * motto:格言
86 | * url:网站地址
87 | * baseUrl: 当使用github pages时,必须填入仓库名称,用/开头
88 | * recentBlogSize:最近文章数量
89 | * archivePageSize:归档页面每页显示的数量
90 | * postPageSize:文章页面每页显示的数量
91 | * feedPageSize:动态页面每页显示数量
92 | * beian:备案号
93 | */
94 | export const site = {
95 | title: 'Astro Theme Yi',
96 | favicon: '/favicon.svg',
97 | description: 'Welcome to my independent blog website! ',
98 | author: "Cirry",
99 | avatar: '/avatar.png',
100 | motto: '最重要的事情只有一件',
101 | url: 'https://astro-yi-nu.vercel.app',
102 | baseUrl: '', // 用斜杠开头 '/astro-blog'
103 | recentBlogSize: 5,
104 | archivePageSize: 25,
105 | postPageSize: 10,
106 | feedPageSize: 20,
107 | beian: ''
108 | }
109 |
110 | /**
111 | * busuanzi:是否开启不蒜子统计功能
112 | * lang {string} Default website language: English
113 | * codeFoldingStartLines {number} default 16
114 | * ga {string} google analytics code
115 | * memosUrl {string} memos server url
116 | * memosUsername {string} memos login name
117 | * memosPageSize {number} 10
118 | */
119 | export const config = {
120 | lang: 'en', // English: en | 简体中文: zh-cn | 繁體中文: zh-Hant | cs
121 | codeFoldingStartLines: 16, // Need to re-run the project to take effect
122 |
123 | // memos config
124 | memosUrl: '', // https://xxxx.xxx.xx
125 | memosUsername: '', // login name
126 | memosPageSize: 10, // number
127 | }
128 |
129 | /**
130 | * 导航栏
131 | */
132 | export const categories = [
133 | {
134 | name: "首页",
135 | iconClass: "ri-home-4-line",
136 | href: "/",
137 | },
138 | {
139 | name: "博客",
140 | iconClass: "ri-draft-line",
141 | href: "/blog/1",
142 | },
143 | {
144 | name: "动态",
145 | iconClass: "ri-lightbulb-flash-line",
146 | href: "/feed/1",
147 | },
148 | {
149 | name: "归档",
150 | iconClass: "ri-archive-line",
151 | href: "/archive/1",
152 | },
153 | {
154 | name: "留言",
155 | iconClass: "ri-chat-1-line",
156 | href: "/message",
157 | },
158 | {
159 | name: "搜索",
160 | iconClass: "ri-search-line",
161 | href: "/search",
162 | },
163 | {
164 | name: "更多",
165 | iconClass: "ri-more-fill",
166 | href: "javascript:void(0);",
167 | children: [
168 | {
169 | name: '关于本站',
170 | iconClass: 'ri-information-line',
171 | href: '/about',
172 | },
173 | {
174 | name: '友情链接',
175 | iconClass: 'ri-user-5-line',
176 | href: '/friends',
177 | },
178 | ]
179 | }
180 | ]
181 |
182 | /**
183 | * 个人链接地址
184 | */
185 | export const infoLinks = [
186 | {
187 | icon: "ri-bilibili-fill",
188 | name: "bilibili",
189 | outlink: "https://space.bilibili.com/xxxxxxxx",
190 | },
191 | {
192 | icon: 'ri-mail-fill',
193 | name: 'xxxxxxx@gmail.com',
194 | outlink: 'mailto:xxxxxxx@gmail.com',
195 | },
196 | {
197 | icon: 'ri-github-fill',
198 | name: 'github',
199 | outlink: 'https://github.com/cirry',
200 | },
201 | {
202 | icon: 'ri-rss-fill',
203 | name: 'rss',
204 | outlink: 'https://xxxxx.com/rss.xml',
205 | }
206 | ]
207 |
208 | /**
209 | * 赞赏功能
210 | * 请在使用之前更换图像和 PayPal 链接。
211 | * enable {boolean}
212 | * tip {string}
213 | */
214 | export const donate = {
215 | enable: false,
216 | tip: "感谢大佬送来的咖啡☕",
217 | wechatQRCode: "/WeChatQR.png",
218 | alipayQRCode: "/AliPayQR.png",
219 | paypalUrl: "https://paypal.me/cirry0?country.x=C2&locale.x=zh_XC",
220 | }
221 |
222 | /**
223 | * 友情链接配置
224 | */
225 | export const friendshipLinks =
226 | [
227 | {
228 | name: "Cirry's Blog",
229 | url: 'https://cirry.cn',
230 | avatar: "https://cirry.cn/avatar.png",
231 | description: '前端开发的日常'
232 | },
233 | ]
234 |
235 | /**
236 | * 评论功能
237 | * enable 是否开启评论功能
238 | * type 目前支持waline和giscus评论系统
239 | * walineConfig.serverUrl 评论服务器地址
240 | * walineConfig.pageSize 每页评论数量
241 | * walineConfig.wordLimit 评论内容字数限制,默认为空不限制
242 | * walineConfig.count 最近评论侧边栏评论数量
243 | * walineConfig.pageview 是否开启阅读数统计
244 | * walineConfig.reaction 是否开启表情
245 | * walineConfig.requiredMeta 必填字段
246 | */
247 | export const comment = {
248 | enable: false,
249 | type: 'giscus', // waline | giscus,
250 | walineConfig:{
251 | serverUrl: "https://xxxxx.xxxxx.app",
252 | lang: 'en',
253 | pageSize: 20,
254 | wordLimit: '',
255 | count: 5,
256 | pageview: true,
257 | reaction: true,
258 | requiredMeta: ["nick", "mail"],
259 | whiteList: ['/message/', '/friends/'],
260 | },
261 |
262 | // giscus config
263 | giscusConfig: {
264 | 'data-repo': "xxxxxxx",
265 | 'data-repo-id': "xxxxxx",
266 | 'data-category': "Announcements",
267 | 'data-category-id': "xxxxxxxxx",
268 | 'data-mapping': "pathname",
269 | 'data-strict': "0",
270 | 'data-reactions-enabled': "1",
271 | 'data-emit-metadata': "0",
272 | 'data-input-position': "bottom",
273 | 'data-theme': "light",
274 | 'data-lang': "xxxxxxxxxxx",
275 | 'crossorigin': "anonymous",
276 | }
277 | }
278 |
279 | /**
280 | * Analytics Feature Configuration
281 | * enable: {boolean} 这个必须启用才能使用其他的配置项
282 | * This file centralizes the analytics configuration for the application.
283 | * It defines and exports the default settings for Umami and Google Analytics.
284 | * busuanzi {boolean}
285 | */
286 | export const analytics: AnalyticsConfig = {
287 | enable: false,
288 | umamiConfig: {
289 | enable: false,
290 | id: "",
291 | url: ""
292 | },
293 | gaConfig: {
294 | enable: false,
295 | id: ""
296 | },
297 | busuanzi: false,
298 | };
299 | ```
300 |
301 | 请修改您的网站配置、评论系统配置、赞赏功能图片、个人信息链接,当然,您也可以修改其他配置内容。
302 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Astro Theme Yi
2 |
3 | [[English]](./README.md) | [[简体中文]](./README-ZH-CN.md)
4 |
5 | Demo:[Astro-Theme-Yi](https://astro-yi-nu.vercel.app/)
6 |
7 | A content-focused Astro blog theme, Yi, in Chinese, it means fast and concise.
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | ### 🔥 Features
16 |
17 | - [x] Support github pages.
18 | - [x] Supports multi-platform display.
19 | - [x] Supports dark mode.
20 | - [x] Supports Memos.
21 | - [x] supports i18n.
22 | - [x] Supports search functionality.
23 | - [x] SEO-friendly
24 | - [x] Supports sitemap and RSS.
25 | - [x] Supports article drafts.
26 | - [x] Supports Waline/Giscus Comment.
27 | - [x] Image lazy loading and scaling support.
28 | - [x] Supports fixed permalinks for articles.
29 | - [x] Supports meriand
30 | - [x] Supports mathjax
31 | - [x] Supports expressive code
32 |
33 | ......
34 |
35 | ### Vercel Deplyment
36 |
37 | [](https://vercel.com/import/project?template=https%3A%2F%2Fgithub.com%2Fcirry%2Fastro-yi)
38 |
39 | Give it a try! Click the button above, and you can deploy with just one click.
40 |
41 | ### 👨🏻💻 Manual Installation
42 |
43 | Recommended for use `nodejs >= 18`,`pnpm >= 8`。
44 |
45 | ```bash
46 | git clone https://github.com/cirry/astro-yi.git
47 | cd astro-yi
48 | npm install -g pnpm # install pnpm
49 | pnpm i # installs dependencies
50 | npm run dev # preview, starts local dev server at localhost:4321
51 | ```
52 |
53 | ```bash
54 | npm run build # build your production site to ./dist
55 | ```
56 | After the packaging is completed, Upload the `dist` folder to the web server directory.
57 |
58 | ### GitHub Pages deployment
59 |
60 | In /src/consts.ts, modify the contents of the site field:
61 |
62 | ```js
63 | export const site = {
64 | // ...
65 | url: 'https://cirry.github.io', // required, website origin
66 | baseUrl: '/yi.github.io', // When using GitHubPages, you must enter the repository name startwith '/'
67 | // ...
68 | }
69 | ```
70 |
71 | ## Configuration
72 |
73 | The only profile for this blog is: `src/consts.ts`, you can make some modifications according to your needs.
74 |
75 | ```ts
76 | /**
77 | * title {string} website title
78 | * favicon {string} website favicon url
79 | * description {string} website description
80 | * author {string} author
81 | * avatar {string} Avatar used in the profile
82 | * motto {string} used in the profile
83 | * url {string} Website link
84 | * baseUrl {string} When using GitHubPages, you must enter the repository name
85 | * recentBlogSize {number} Number of recent articles displayed in the sidebar
86 | * archivePageSize {number} Number of articles on archive pages
87 | * postPageSize {number} Number of articles on blog pages
88 | * feedPageSize {number} Number of articles on feed pages
89 | * beian {string} Chinese policy
90 | */
91 | export const site = {
92 | title: 'Astro Theme Yi',
93 | favicon: '/favicon.svg',
94 | description: 'Welcome to my independent blog website! ',
95 | author: "xxxxx",
96 | avatar: '/avatar.png',
97 | motto: 'Keep moving.',
98 | url: 'https://astro-yi-nu.vercel.app',
99 | recentBlogSize: 5,
100 | archivePageSize: 25,
101 | postPageSize: 10,
102 | feedPageSize: 20,
103 | beian: ''
104 | }
105 |
106 | /**
107 | * lang {string} Default website language: English
108 | * codeFoldingStartLines {number} default 16
109 | * memosUrl {string} memos server url
110 | * memosUsername {string} memos login name
111 | * memosPageSize {number} 10
112 | */
113 | export const config = {
114 | lang: 'en', // English: en | 简体中文: zh-cn | 繁體中文: zh-Hant | cs
115 | codeFoldingStartLines: 16, // Need to re-run the project to take effect
116 |
117 | // memos config
118 | memosUrl: '', // https://xxxx.xxx.xx
119 | memosUsername: '', // login name
120 | memosPageSize: 10, // number
121 | }
122 |
123 | /**
124 | * Navigator
125 | */
126 | export const categories = [
127 | {
128 | name: "Homepage",
129 | iconClass: "ri-home-4-line",
130 | href: "/",
131 | },
132 | {
133 | name: "Blog",
134 | iconClass: "ri-draft-line",
135 | href: "/blog/1",
136 | },
137 | {
138 | name: "Feed",
139 | iconClass: "ri-lightbulb-flash-line",
140 | href: "/feed/1",
141 | },
142 | {
143 | name: "Archive",
144 | iconClass: "ri-archive-line",
145 | href: "/archive/1",
146 | },
147 | {
148 | name: "Message",
149 | iconClass: "ri-chat-1-line",
150 | href: "/message/",
151 | },
152 | {
153 | name: "Search",
154 | iconClass: "ri-search-line",
155 | href: "/search",
156 | },
157 | {
158 | name: "More",
159 | iconClass: "ri-more-fill",
160 | href: "javascript:void(0);",
161 | children: [
162 | {
163 | name: 'About',
164 | iconClass: 'ri-information-line',
165 | href: '/about',
166 | },
167 | {
168 | name: 'Friends',
169 | iconClass: 'ri-user-5-line',
170 | href: '/friends',
171 | target: '_self', // _self | _blank
172 | },
173 | ]
174 | }
175 | ]
176 |
177 | /**
178 | * Personal link address
179 | */
180 | export const infoLinks = [
181 | {
182 | icon: 'ri-telegram-fill',
183 | name: 'telegram',
184 | outlink: 'xxxxxxx',
185 | },
186 | {
187 | icon: 'ri-twitter-fill',
188 | name: 'twitter',
189 | outlink: 'xxxxxxx',
190 | },
191 | {
192 | icon: 'ri-instagram-fill',
193 | name: 'instagram',
194 | outlink: 'xxxxxxx',
195 | },
196 | {
197 | icon: 'ri-github-fill',
198 | name: 'github',
199 | outlink: 'https://github.com/cirry',
200 | },
201 | {
202 | icon: 'ri-mail-fill',
203 | name: 'xxxxxxx@gmail.com',
204 | outlink: 'mailto:xxxxxxx@gmail.com',
205 | },
206 | {
207 | icon: 'ri-rss-fill',
208 | name: 'rss',
209 | outlink: 'https://xxxxx.com/rss.xml',
210 | }
211 | ]
212 |
213 | /**
214 | * Donation feature
215 | * Please replace the image and paypal link before use.
216 | * enable {boolean}
217 | * tip {string}
218 | */
219 | export const donate = {
220 | enable: false,
221 | tip: "Thanks for the coffee !!!☕",
222 | wechatQRCode: "/WeChatQR.png",
223 | alipayQRCode: "/AliPayQR.png",
224 | paypalUrl: "https://paypal.me/xxxxxxxx",
225 | }
226 |
227 | /**
228 | * Friends Links Page
229 | * name {string}
230 | * url {string}
231 | * avatar {string}
232 | * description {string}
233 | */
234 | export const friendshipLinks =
235 | [
236 | {
237 | name: "Cirry's Blog",
238 | url: 'https://cirry.cn',
239 | avatar: "https://cirry.cn/avatar.png",
240 | description: 'frontend development'
241 | },
242 | ]
243 |
244 | /**
245 | * Comment Feature
246 | * enable {boolean}
247 | * type {string} giscus and waline are currently supported.
248 | * walineConfig.serverUrl {string} server link
249 | * walineConfig.pageSize {number} number of comments per page. default 10
250 | * walineConfig.wordLimit {number} Comment word s limit. When a single number is filled in, it 's the maximum number of comment words. No limit when set to 0
251 | * walineConfig.count {number} recent comment numbers
252 | * walineConfig.pageview {boolean} display the number of page views and comments of the article
253 | * walineConfig.reaction {string | string[]} Add emoji interaction function to the article
254 | * walineConfig.requiredMeta {string[]} Set required fields, default anonymous
255 | */
256 | export const comment = {
257 | enable: false,
258 | type: 'giscus', // waline | giscus,
259 | walineConfig:{
260 | serverUrl: "https://xxxxx.xxxxx.app",
261 | lang: 'en',
262 | pageSize: 20,
263 | wordLimit: '',
264 | count: 5,
265 | pageview: true,
266 | reaction: true,
267 | requiredMeta: ["nick", "mail"],
268 | whiteList: ['/message/', '/friends/'],
269 | },
270 |
271 | // giscus config
272 | giscusConfig: {
273 | 'data-repo': "xxxxxxx",
274 | 'data-repo-id': "xxxxxx",
275 | 'data-category': "Announcements",
276 | 'data-category-id': "xxxxxxxxx",
277 | 'data-mapping': "pathname",
278 | 'data-strict': "0",
279 | 'data-reactions-enabled': "1",
280 | 'data-emit-metadata': "0",
281 | 'data-input-position': "bottom",
282 | 'data-theme': "light",
283 | 'data-lang': "xxxxxxxxxxx",
284 | 'crossorigin': "anonymous",
285 | }
286 | }
287 |
288 | /**
289 | * Analytics Feature Configuration
290 | *
291 | * This file centralizes the analytics configuration for the application.
292 | * It defines and exports the default settings for Umami and Google Analytics.
293 | */
294 | export const analytics: AnalyticsConfig = {
295 | enable: false,
296 | umamiConfig: {
297 | enable: false,
298 | id: "",
299 | url: ""
300 | },
301 | gaConfig: {
302 | enable: false,
303 | id: ""
304 | },
305 | busuanzi: false,
306 | };
307 |
308 | ```
309 |
310 | Please modify your website configuration, comment system configuration, appreciation function image, personal information links, and of course, you can also modify other configuration content.
311 |
312 | ### Write a blog
313 |
314 | With the Yi theme, all you need to do is create a new md document in `src/content/blog` and you're ready to start writing your blog.
315 |
316 | According to Astro's Markdown document standard, each document should have its own frontmatter information and add `---` as the beginning and the end of the document's header in md to mark the frontmatter, which gives us a lot of convenience:.
317 |
318 | 1. for example, if we want to add tags and categories to a document or top some documents, we can add some attributes to the document in Frontmatter, such as `tags`, `sticky` and so on.
319 |
320 | 2. For example, to avoid using Chinese as the blog path and file name, we can set `title` as the title of the md document in Chinese, and the file name in English with `-` as the word connecting symbol.
321 |
322 | In Astro-Yi, You need to set two important properties: **title** and **date**, the following is the simplest frontmatter setup for a Md document:
323 |
324 | ```yaml
325 | ---
326 | title: Display pictures
327 | date: 2024-03-05
328 | ---
329 | ```
330 |
331 | If you feel that this is not quite enough, Yi also provides more properties for you to use, this is a complete example:
332 |
333 | ```yaml
334 | ---
335 | title: Display pictures
336 | description: Display pictures
337 | date: 2024-03-05
338 | tags: [astro]
339 | category: astro
340 | sticky: 100 // Document top weight, the larger the number, the greater the weight
341 | slug: poem/ci // Permanent link to document
342 | mathjax: false // enable formula display
343 | mermaid: false // enable mermaid
344 | draft: false
345 | toc: true
346 | donate: false
347 | comment: false
348 | ogImage: https://xxxxx/xxxxx/xxxxx // cover image
349 | ---
350 | ```
351 |
352 | ### Write a feed
353 |
354 | With the Yi theme, all you need to do is create a new md document in `src/content/feed` and you're ready to start writing.
355 |
356 | feed frontmatter needs to set an important property **date**, the rest of the attributes are optional.
357 |
358 | ```yaml
359 | ---
360 | date: 2024-03-20
361 | ---
362 | ```
363 |
364 | ### Modify icons
365 |
366 | All the icons in the blog are using the open source icon library [remixicon](https://remixicon.cn/), you can replace your favorite icons by yourself.
367 |
368 | ### Note
369 |
370 | In the `astro.config.js` file in the root directory, it is recommended to modify the `site` property to correctly generate the site map.
371 |
372 | ```js
373 | export default defineConfig({
374 | site: 'https://xxxx.com',// Modify to your own website address
375 | // ...
376 | })
377 | ```
378 |
379 | Add a line to the path of your sitemap file at the end of the robots.txt file in the public directory.
380 |
381 |
382 | ```text
383 | Sitemap: [blog-url]/sitemap-0.xml
384 | // ps:Sitemap: https://astro-yi-nu.vercel.app/sitemap-0.xml
385 | ```
386 |
--------------------------------------------------------------------------------
/astro.config.js:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'astro/config';
2 | import mdx from '@astrojs/mdx';
3 | import sitemap from '@astrojs/sitemap';
4 | import tailwind from '@astrojs/tailwind';
5 | import {site} from './src/consts.ts'
6 | import remarkDirective from "remark-directive";
7 | import expressiveCode from "astro-expressive-code";
8 | import {pluginLineNumbers} from '@expressive-code/plugin-line-numbers'
9 | import {pluginCollapsibleSections} from '@expressive-code/plugin-collapsible-sections'
10 |
11 | import {remarkModifiedTime,} from "./src/plugins/remark-modified-time.mjs";
12 | import {resetRemark} from "./src/plugins/reset-remark.js";
13 | import {remarkAsides} from './src/plugins/remark-asides.js'
14 | import {remarkCollapse} from "./src/plugins/remark-collapse.js";
15 | import {remarkGithubCard} from './src/plugins/remark-github-card.js'
16 | import {lazyLoadImage} from "./src/plugins/lazy-load-image.js";
17 | import {remarkButton} from "./src/plugins/remark-button.js";
18 | import {remarkHtml} from "./src/plugins/remark-html.js";
19 |
20 | export default defineConfig({
21 | site: site.url,
22 | base: import.meta.env.PROD ? site.baseUrl : '',
23 | trailingSlash: "never",
24 | integrations: [sitemap(), tailwind(), expressiveCode({
25 | plugins: [pluginLineNumbers(), pluginCollapsibleSections()],
26 | themes: ["github-dark", "github-light"],
27 | styleOverrides: {
28 | codeFontFamily: "jetbrains-mono",
29 | uiFontFamily: "jetbrains-mono",
30 | },
31 | themeCssSelector: (theme) => `[data-theme="${theme.type}"]`
32 | }), mdx()],
33 | markdown: {
34 | remarkPlugins: [remarkModifiedTime, resetRemark, remarkDirective, remarkAsides({}), remarkCollapse({}), remarkGithubCard(), remarkButton(), remarkHtml()],
35 | rehypePlugins: [lazyLoadImage],
36 | }
37 | });
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astro-blog",
3 | "type": "module",
4 | "version": "0.0.14",
5 | "scripts": {
6 | "dev": "astro dev",
7 | "start": "astro dev",
8 | "build": "astro build",
9 | "preview": "astro preview",
10 | "astro": "astro"
11 | },
12 | "dependencies": {
13 | "@astrojs/mdx": "^4.2.6",
14 | "@astrojs/rss": "^4.0.11",
15 | "@astrojs/sitemap": "^3.4.0",
16 | "@astrojs/tailwind": "^6.0.2",
17 | "@expressive-code/plugin-collapsible-sections": "^0.33.5",
18 | "@expressive-code/plugin-line-numbers": "^0.33.5",
19 | "@fancyapps/ui": "^5.0.35",
20 | "@waline/client": "^3.1.3",
21 | "astro": "^5.7.13",
22 | "astro-expressive-code": "^0.33.5",
23 | "child_process": "^1.0.2",
24 | "dayjs": "^1.11.13",
25 | "dompurify": "^3.2.4",
26 | "fuse.js": "^7.1.0",
27 | "hastscript": "^9.0.0",
28 | "lodash-es": "^4.17.21",
29 | "mdast-util-to-string": "^4.0.0",
30 | "reading-time": "^1.5.0",
31 | "remark-directive": "^3.0.0",
32 | "remixicon": "^4.6.0",
33 | "tailwindcss": "^3.4.1",
34 | "tocbot": "^4.25.0",
35 | "typescript": "^5.4.2",
36 | "unist-util-remove": "^4.0.0",
37 | "unist-util-visit": "^5.0.0"
38 | },
39 | "devDependencies": {
40 | "@types/lodash-es": "^4.17.12",
41 | "sass": "^1.72.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/alipay.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cirry/astro-yi/03982e2a1b9a8fb647e4bc2b19db6d46d0dc0b7f/public/avatar.png
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/load-mathjax.js:
--------------------------------------------------------------------------------
1 | window.MathJax = {
2 | tex: {
3 | inlineMath: [['$', '$'], ['\\(', '\\)']]
4 | },
5 | svg: {
6 | fontCache: 'global'
7 | }
8 | };
9 |
10 | (function () {
11 | var script = document.createElement('script');
12 | script.src = 'https://cdn.bootcdn.net/ajax/libs/mathjax/3.2.2/es5/tex-svg.js';
13 | script.async = true;
14 | document.head.appendChild(script);
15 | })();
16 |
--------------------------------------------------------------------------------
/public/paypal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/public/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cirry/astro-yi/03982e2a1b9a8fb647e4bc2b19db6d46d0dc0b7f/public/spinner.gif
--------------------------------------------------------------------------------
/public/toggle-theme.js:
--------------------------------------------------------------------------------
1 | const primaryColorScheme = ""; // "light" | "dark"
2 |
3 | // Get theme data from local storage
4 | const currentTheme = localStorage.getItem("theme");
5 |
6 | function getPreferTheme() {
7 | // return theme value in local storage if it is set
8 | if (currentTheme) return currentTheme;
9 |
10 | // return primary color scheme if it is set
11 | if (primaryColorScheme) return primaryColorScheme;
12 |
13 | // return user device's prefer color scheme
14 | return window.matchMedia("(prefers-color-scheme: dark)").matches
15 | ? "dark"
16 | : "light";
17 | }
18 |
19 | let themeValue = getPreferTheme();
20 |
21 | function setPreference() {
22 | localStorage.setItem("theme", themeValue);
23 | reflectPreference();
24 | }
25 |
26 | function reflectPreference() {
27 | document.firstElementChild.setAttribute("data-theme", themeValue);
28 |
29 | document.querySelector("#theme-btn")?.setAttribute("aria-label", themeValue);
30 | }
31 |
32 | // set early so no page flashes / CSS is made aware
33 | reflectPreference();
34 |
35 | function init() {
36 | // set on load so screen readers can get the latest value on the button
37 | reflectPreference();
38 |
39 | // now this script can find and listen for clicks on the control
40 | document.querySelector("#theme-btn")?.addEventListener("click", () => {
41 | themeValue = themeValue === "light" ? "dark" : "light";
42 | setPreference();
43 | });
44 | document.querySelector("#theme-btn-mobile")?.addEventListener("click", () => {
45 | themeValue = themeValue === "light" ? "dark" : "light";
46 | setPreference();
47 | });
48 | }
49 |
50 |
51 | window.onload = () => {
52 | init()
53 | };
54 |
55 | // sync with system changes
56 | window.matchMedia("(prefers-color-scheme: dark)")
57 | .addEventListener("change", ({matches: isDark}) => {
58 | themeValue = isDark ? "dark" : "light";
59 | setPreference();
60 | });
61 |
--------------------------------------------------------------------------------
/public/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/BaseHead.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import '@/styles/index.css'
3 | import {site} from "@/consts";
4 | import UmamiAnalytics from '@/components/UmamiAnalytics.astro';
5 | import GoogleAnalytics from '@/components/GoogleAnalytics.astro';
6 | import BusuanziAnalytics from '@/components/BusuanziAnalytics.astro';
7 | import getUrl from "@/utils/getUrl";
8 | const canonicalURL = new URL(Astro.url.pathname, Astro.site);
9 | const {description = site.description, mathjax = false, mermaid = false, ogImage:ogimage = ''} = Astro.props
10 | let ogImage = ogimage || new URL(Astro.url.pathname, Astro.site?.href).href;
11 | let title = Astro.props.title ? `${Astro.props.title} - ${site.title}` : site.title;
12 | ---
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {title}
49 |
50 | {
51 | mathjax &&
52 | }
53 |
54 | {
55 | mermaid &&
56 |
57 | }
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/components/BlogAside.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import CommentAside from "@/components/CommentAside.astro";
3 | import {comment} from "@/consts";
4 | import {getCollectionByName} from "@/utils/getCollectionByName";
5 | import getUniqueTags from "@/utils/getUniqueTags";
6 | import getCountByCategory from "@/utils/getCountByCategory";
7 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
8 | import {site} from '@/consts'
9 | import {t} from '@/i18n/utils'
10 | import getUrl from "@/utils/getUrl";
11 | const blogs = await getCollectionByName('blog')
12 | let tagArr = getUniqueTags(blogs);
13 | if(site.asideTagsMaxSize > 0)
14 | tagArr = tagArr.slice(0, site.asideTagsMaxSize)
15 | let categoryCount = getCountByCategory(blogs);
16 | let sortPosts = await sortPostsByDate(blogs);
17 | let resultPosts = sortPosts.splice(0, site.recentBlogSize);
18 | ---
19 |
20 |
40 |
41 | {
42 | tagArr.length > 0 && (
43 |
44 |
45 | {t('sidebar.tags')}
46 |
47 | )
48 | }
49 |
50 | {
51 | tagArr &&
52 | tagArr.map((tag) =>
53 |
{tag}
54 | )
55 | }
56 | {
57 | tagArr &&
58 | site.asideTagsMaxSize > 0 &&
59 | (
60 |
{t('more')} »
61 | )
62 | }
63 |
64 |
65 |
66 |
67 |
68 | {t('sidebar.recentArticle')}
69 |
70 |
83 |
84 |
85 | {comment.enable && comment.type === "waline" && }
86 |
--------------------------------------------------------------------------------
/src/components/BlogFooter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {site} from "@/consts";
3 | import {t} from '@/i18n/utils'
4 | import {formatDate} from "@/utils/formatDate.js";
5 | const {title, date, url} = Astro.props;
6 | ---
7 |
8 |
9 | {title ?
10 |
{t('footer.articleTitle')}:{title}
: ''}
11 | {
12 | site.author ?
13 |
{t('footer.articleAuthor')}:{site.author}
: ''
14 | }
15 |
16 | {
17 | date ?
18 |
{t('footer.releaseTime')}:{formatDate(date)}
: ''
19 | }
20 | {
21 | site.url?
: ''
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/components/BusuanziAnalytics.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {analytics} from "@/consts";
3 | const {enable: allEnable, busuanzi} = analytics;
4 | const shouldLoadGA = allEnable && busuanzi;
5 | ---
6 |
7 | {
8 | shouldLoadGA &&
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Comment.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { comment } from "@/consts";
3 | import GiscusComment from "./GiscusComment.astro";
4 | import WalineComment from "./WalineComment.astro";
5 | ---
6 |
7 | {comment.enable && comment.type === "waline" && }
8 | {comment.enable && comment.type === "giscus" && }
9 |
--------------------------------------------------------------------------------
/src/components/CommentAside.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {comment} from '@/consts'
3 | import {t} from '@/i18n/utils'
4 | ---
5 |
6 | {
7 | comment.enable && comment.type === "waline" && (
8 |
9 |
10 |
11 | {t('sidebar.recentComments')}
12 |
13 |
14 |
15 | )
16 | }
17 |
18 |
40 |
--------------------------------------------------------------------------------
/src/components/Donate.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import "@/styles/donate.css";
3 | import {donate} from "@/consts";
4 | ---
5 |
6 |
28 |
29 |
30 |
54 |
--------------------------------------------------------------------------------
/src/components/FeedPostDate.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const { date, index } = Astro.props;
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
32 |
--------------------------------------------------------------------------------
/src/components/FeedPreview.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import FeedPostDate from "./FeedPostDate.astro";
3 | const {data, body, slug} = Astro.props.post;
4 | const {index} = Astro.props;
5 | import {t} from '@/i18n/utils'
6 | import getUrl from "@/utils/getUrl";
7 | ---
8 |
9 |
10 |
13 | {
14 | body.replace(/^#+/gm, "").replace(/!?\[.*?\]\(.*?\)/g, "")
15 | }
16 |
17 |
18 |
19 | {t('home.readMore')}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/Footer.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {site, analytics} from "@/consts";
3 | import {t} from '@/i18n/utils'
4 | import getUrl from "@/utils/getUrl";
5 | let shouldLoadBusuanzi = analytics.enable && analytics.busuanzi
6 | ---
7 |
8 |
9 | {
10 | shouldLoadBusuanzi && (
11 |
12 |
13 | {t('footer.busuanziSitePV')}{t('footer.busuanziSitePVUnit')}
14 |
15 |
16 |
17 | {t('footer.busuanziSiteUV')}{t('footer.busuanziSiteUVUnit')}
18 |
19 |
20 | )
21 | }
22 |
23 |
24 | Copyright
25 |
26 | {new Date().getFullYear()}
27 |
28 | {
29 | site.beian && (
30 |
31 |
32 | {site.beian}
33 |
34 | )
35 | }
36 |
37 |
{t('footer.sitemap')}
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/components/Friends.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const { name, avatar, description, url } = Astro.props;
3 | ---
4 |
5 |
10 |
11 |
15 |
16 | {name}
17 |
18 |
19 | {description}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/GiscusComment.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { comment } from "@/consts";
3 | ---
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/GoogleAnalytics.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { analytics } from "@/consts";
3 | const { enable: allEnable, gaConfig } = analytics;
4 | const { enable: gaEnable, id } = gaConfig ?? {};
5 | const shouldLoadGA = allEnable && gaEnable && id;
6 | ---
7 |
8 | {shouldLoadGA && (
9 | <>
10 |
11 |
21 | >
22 | )}
23 |
--------------------------------------------------------------------------------
/src/components/Header.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {getCollectionByName} from "@/utils/getCollectionByName";
3 | import getUniqueTags from "@/utils/getUniqueTags";
4 | import getCountByCategory from "@/utils/getCountByCategory";
5 | import HeaderLink from './HeaderLink.astro';
6 | import ThemeIcon from './ThemeIcon.astro'
7 | import MenuIcon from './MenuIcon.astro'
8 | import {site, categories, infoLinks} from '@/consts';
9 | import AsideIcon from "./SidebarIcon.astro";
10 | import {t} from "@/i18n/utils";
11 | import getCountByTagName from "@/utils/getCountByTagName";
12 | import getUrl from "@/utils/getUrl";
13 | const blogs = await getCollectionByName('blog')
14 | let tagArr = getUniqueTags(blogs);
15 | let categoryCount = getCountByCategory(blogs);
16 | let tagCount = getCountByTagName(blogs);
17 | ---
18 |
19 |
20 |
21 |
22 |
23 |
24 |
{site.title}
25 |
26 |
27 |
28 | {
29 | categories.map(category => (
30 | {category.name}
31 | ))
32 | }
33 |
34 |
35 |
36 |
39 |
40 |
41 |
69 |
70 |
})
71 |
{site.motto}
72 |
73 | {
74 | infoLinks.map((infoItem) => (
75 |
76 |
77 |
78 | ))
79 | }
80 |
81 |
82 |
83 | {
84 | Object.keys(categoryCount).length > 0 && (
85 |
86 | {t('sidebar.categories')}
87 |
88 | )
89 | }
90 | {
91 | Object.keys(categoryCount).map((category) => (
92 |
97 |
98 | ))
99 | }
100 | {
101 | tagArr.length > 0 && (
102 |
103 |
104 |
105 | {t('sidebar.tags')}
106 |
107 | )
108 | }
109 | {
110 | tagArr &&
111 | tagArr.map((tag) => (
112 |
115 | ))
116 | }
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/src/components/HeaderLink.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const {href, icon: iconClass, children, target, ...props} = Astro.props;
3 | import getUrl from "@/utils/getUrl";
4 |
5 | const {pathname} = Astro.url;
6 | let hrefMath = href.split('/')
7 | let pathnameMatch = pathname.split('/')
8 | const isActive = hrefMath[1] === pathnameMatch[1]
9 | ---
10 |
34 |
35 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/MenuIcon.astro:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
6 |
9 |
10 |
11 |
34 |
--------------------------------------------------------------------------------
/src/components/Pagination.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {t} from '@/i18n/utils'
3 | import getUrl from "@/utils/getUrl";
4 | let pager = []; // 实际显示的分页组件内容
5 | //prefixUrl : 点击页面跳转的路由地址前缀,比如/blog/1,/tags/1, /category/1等等
6 | const {
7 | currentPage,
8 | totalPage,
9 | prefixUrl = getUrl("/blog/"),
10 | } = Astro.props;
11 |
12 | if (totalPage === 1) {
13 | pager = [];
14 | } else if (totalPage <= 5) {
15 | pager = new Array(totalPage).fill(0).map((i, index) => index + 1);
16 | } else {
17 | if (currentPage > totalPage) {
18 | return
19 | }
20 | let diffNextPages = 2 - (totalPage - currentPage);
21 |
22 | let diffPrevPages = currentPage - 1;
23 |
24 | if (diffNextPages <= 2 && diffNextPages >= 0) {
25 | pager = [
26 | currentPage - 2 - diffNextPages,
27 | currentPage - 1 - diffNextPages,
28 | currentPage - diffNextPages,
29 | currentPage + 1 - diffNextPages,
30 | currentPage + 2 - diffNextPages,
31 | ];
32 | } else if (diffPrevPages <= 1 && diffPrevPages >= 0) {
33 | pager = [1, 2, 3, 4, 5];
34 | } else {
35 | pager = [
36 | currentPage - 2,
37 | currentPage - 1,
38 | currentPage,
39 | currentPage + 1,
40 | currentPage + 2,
41 | ];
42 | }
43 | }
44 |
45 | ---
46 |
47 |
48 | {
49 | pager.length > 4 && (
50 |
51 |
52 |
53 | )
54 | }
55 |
56 | {
57 | pager.map((i) =>
58 | currentPage === i ? (
59 |
64 | {i}
65 |
66 | ) : (
67 |
68 | {i}
69 |
70 | )
71 | )
72 | }
73 | {
74 | pager.length > 4 && (
75 |
79 |
80 |
81 | )
82 | }
83 | {totalPage > 5 && t('pagination.total') + totalPage + t('pagination.unit')}
84 |
85 |
86 |
91 |
--------------------------------------------------------------------------------
/src/components/PostTitle.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import dayjs from "dayjs";
3 | import {formatDate} from "@/utils/formatDate";
4 | import {dealLabel} from "@/utils/dealLabel";
5 | import {t} from '@/i18n/utils'
6 | import getUrl from "@/utils/getUrl";
7 | const {title, date, category, tags, lastModified = '', draft = false, readingTime = {}} = Astro.props
8 | const lastModifiedDate = dayjs(lastModified === '' ? date : lastModified);
9 | const publishDate = dayjs(date);
10 | const mostRecentDate = lastModifiedDate.isAfter(publishDate) ? lastModifiedDate : publishDate;
11 | const mostRecent = mostRecentDate.format('YYYY-MM-DD');
12 | const currentDate = dayjs();
13 | ---
14 |
15 |
16 |
17 |
{title}
18 |
19 |
20 |
21 | {
22 | date &&
23 |
{formatDate(date)}
24 | }
25 |
26 |
27 | {
28 | draft &&
29 |
{t('title.draft')}
30 | }
31 |
32 |
33 | {
34 | category && dealLabel(category).filter(item => item !== 'uncategorized').map((categoryName) =>
35 |
{categoryName}
36 | )
37 | }
38 |
39 |
40 | {
41 | tags && dealLabel(tags).map((tagName) =>
42 |
{tagName}
43 | )
44 | }
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {
55 | readingTime.minutes && readingTime.minutes !== 0 && (
56 |
{Math.ceil(readingTime.minutes)}{t('title.minutes')}
57 |
{readingTime.words}{t('title.words')}
58 | )
59 | }
60 |
61 | {
62 | currentDate.isAfter(mostRecentDate.add(6, 'month')) &&
63 |
{t('post.lastUpdatedTip1')} {formatDate(mostRecent)} {t('post.lastUpdatedTip2')}
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/components/PostView.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import PostViewTitle from '@/components/PostViewTitle.astro'
3 | import getUrl from "@/utils/getUrl";
4 | const {blog} = Astro.props;
5 | const {data, slug} = blog
6 | ---
7 |
8 |
9 |
13 |
14 |
15 | {
16 | data.description ? data.description : ''
17 | }
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/PostViewTitle.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {formatDate} from "@/utils/formatDate";
3 | import {dealLabel} from "@/utils/dealLabel";
4 | import {t} from '@/i18n/utils'
5 | import getUrl from "@/utils/getUrl";
6 | const {title, date, slug, category, tags, sticky = false, draft = false} = Astro.props
7 | ---
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 | {
22 | date &&
23 |
{formatDate(date)}
24 | }
25 |
26 |
27 | {
28 | draft &&
29 |
{t('title.draft')}
30 | }
31 |
32 |
33 | {
34 | category && dealLabel(category).filter(item => item !== 'uncategorized').map((categoryName) =>
35 |
36 |
37 | {categoryName}
38 |
39 | )
40 | }
41 |
42 |
43 | {
44 | tags && dealLabel(tags).map((tagName) =>
45 |
46 | {tagName}
47 | )
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/Profile.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {site, infoLinks} from "@/consts";
3 | import getUrl from "@/utils/getUrl";
4 | ---
5 |
6 |
7 |
})
8 |
{site.motto}
9 |
10 | {
11 | infoLinks.map((infoItem) => (
12 |
13 |
14 |
15 | ))
16 | }
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop.astro:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ---
4 |
5 |
8 |
9 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/SearchTitle.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const { label } = Astro.props;
3 | import {t}from '@/i18n/utils'
4 | ---
5 |
6 | {t('search.labelOne')} {label ==='uncategorized' ? t('sidebar.uncategorized') : label} {t('search.labelTwo')}
7 |
--------------------------------------------------------------------------------
/src/components/SidebarIcon.astro:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ---
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
38 |
--------------------------------------------------------------------------------
/src/components/ThemeIcon.astro:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ---
4 |
8 |
--------------------------------------------------------------------------------
/src/components/Toc.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {t} from '@/i18n/utils'
3 | ---
4 |
8 |
9 |
10 | {t('blog.tableOfContent')}
11 |
12 |
15 |
16 |
17 |
44 |
45 |
72 |
--------------------------------------------------------------------------------
/src/components/UmamiAnalytics.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { analytics } from "@/consts";
3 | const { enable: allEnable, umamiConfig } = analytics;
4 | const { enable: umamiEnable, id, url } = umamiConfig ?? {};
5 | const shouldLoadUmami = allEnable && umamiEnable && id && url;
6 | ---
7 |
8 | {shouldLoadUmami && (
9 |
10 | )}
11 |
--------------------------------------------------------------------------------
/src/components/WalineComment.astro:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ---
4 |
5 |
6 |
7 |
30 |
--------------------------------------------------------------------------------
/src/consts.ts:
--------------------------------------------------------------------------------
1 | // Place any global data in this file.
2 | // You can import this data from anywhere in your site by using the `import` keyword.
3 |
4 | import type {AnalyticsConfig} from "./types/analyticsTypes"
5 |
6 | /**
7 | * title {string} website title
8 | * favicon {string} website favicon url
9 | * description {string} website description
10 | * author {string} author
11 | * avatar {string} Avatar used in the profile
12 | * motto {string} used in the profile
13 | * url {string} Website link
14 | * baseUrl {string} When using GitHubPages, you must enter the repository name, startWith '/', e.g. /repo_name
15 | * recentBlogSize {number} Number of recent articles displayed in the sidebar
16 | * archivePageSize {number} Number of articles on archive pages
17 | * postPageSize {number} Number of articles on blog pages
18 | * feedPageSize {number} Number of articles on feed pages
19 | * beian {string} Chinese policy
20 | * asideTagsMaxSize {number}
21 | * 0: disable,
22 | * > 0: display the limited number of tags in the sidebar
23 | * All tags will be displayed in single page "/tags".
24 | */
25 | export const site = {
26 | title: 'Astro Theme Yi', // required
27 | favicon: '/favicon.svg', // required
28 | description: 'Welcome to my independent blog website! ',
29 | author: "Astro-Yi", // required
30 | avatar: '/avatar.png', // required
31 | url: 'https://astro-yi-nu.vercel.app', // required
32 | baseUrl: '', // When using GitHubPages, you must enter the repository name startWith '/'. e.g. '/astro-blog'
33 | motto: 'Actions speak louder than words.',
34 | recentBlogSize: 5,
35 | archivePageSize: 25,
36 | postPageSize: 10,
37 | feedPageSize: 20,
38 | beian: '',
39 | asideTagsMaxSize: 0,
40 | }
41 |
42 | /**
43 | * busuanzi {boolean} link: https://busuanzi.ibruce.info/
44 | * lang {string} Default website language
45 | * codeFoldingStartLines {number}
46 | * ga {string|false}
47 | * memosUrl {string} memos server url
48 | * memosUsername {string} memos login name
49 | * memosPageSize {number} 10
50 | */
51 | export const config = {
52 | lang: 'en', // en | zh-cn | zh-Hant | cs
53 | codeFoldingStartLines: 16, // Need to re-run the project to take effect
54 |
55 | // memos config
56 | memosUrl: '', // https://xxxx.xxx.xx
57 | memosUsername: '', // login name
58 | memosPageSize: 10, // number
59 | }
60 |
61 | /**
62 | * Navigator
63 | * name {string}
64 | * iconClass {string} icon style
65 | * href {string} link url
66 | * target {string} optional "_self|_blank" open in current window / open in new window
67 | */
68 | export const categories = [
69 | {
70 | name: "Blog",
71 | iconClass: "ri-draft-line",
72 | href: "/blog/1",
73 | },
74 | {
75 | name: "Feed",
76 | iconClass: "ri-lightbulb-flash-line",
77 | href: "/feed/1",
78 | },
79 | // {
80 | // name: "Memos",
81 | // iconClass: "ri-quill-pen-line",
82 | // href: "/memos",
83 | // },
84 | {
85 | name: "Archive",
86 | iconClass: "ri-archive-line",
87 | href: "/archive/1",
88 | },
89 | {
90 | name: "Message",
91 | iconClass: "ri-chat-1-line",
92 | href: "/message",
93 | },
94 | {
95 | name: "Search",
96 | iconClass: "ri-search-line",
97 | href: "/search",
98 | },
99 | {
100 | name: "More",
101 | iconClass: "ri-more-fill",
102 | href: "javascript:void(0);",
103 | children: [
104 | {
105 | name: 'About',
106 | iconClass: 'ri-information-line',
107 | href: '/about',
108 | },
109 | {
110 | name: 'Friends',
111 | iconClass: 'ri-user-5-line',
112 | href: '/friends',
113 | target: '_self',
114 | },
115 | ]
116 | }
117 | ]
118 |
119 | /**
120 | * Personal link address
121 | */
122 | export const infoLinks = [
123 | {
124 | icon: 'ri-telegram-fill',
125 | name: 'telegram',
126 | outlink: '',
127 | },
128 | {
129 | icon: 'ri-twitter-fill',
130 | name: 'twitter',
131 | outlink: '',
132 | },
133 | {
134 | icon: 'ri-instagram-fill',
135 | name: 'instagram',
136 | outlink: '',
137 | },
138 | {
139 | icon: 'ri-github-fill',
140 | name: 'github',
141 | outlink: 'https://github.com/cirry/astro-yi',
142 | },
143 | {
144 | icon: 'ri-rss-fill',
145 | name: 'rss',
146 | outlink: '',
147 | }
148 | ]
149 |
150 | /**
151 | * donate
152 | * enable {boolean}
153 | * tip {string}
154 | * wechatQRCode: Image addresses should be placed in the public directory.
155 | * alipayQRCode: Image addresses should be placed in the public directory.
156 | * paypalUrl {string}
157 | */
158 | export const donate = {
159 | enable: false,
160 | tip: "Thanks for the coffee !!!☕",
161 | wechatQRCode: "/WeChatQR.png",
162 | alipayQRCode: "/AliPayQR.png",
163 | paypalUrl: "https://paypal.me/xxxxxxxxxx",
164 | }
165 |
166 | /**
167 | * Friendship Links Page
168 | * name {string}
169 | * url {string}
170 | * avatar {string}
171 | * description {string}
172 | */
173 | export const friendshipLinks =
174 | [
175 | // {
176 | // name: "Cirry's Blog",
177 | // url: 'https://cirry.cn',
178 | // avatar: "https://cirry.cn/avatar.png",
179 | // description: '前端开发的日常'
180 | // },
181 | ]
182 |
183 | /**
184 | * Comment Feature
185 | * enable {boolean}
186 | * type {string} required waline | giscus
187 | * walineConfig.serverUrl {string} server link
188 | * walineConfig.lang {string} link: https://waline.js.org/guide/features/i18n.html
189 | * walineConfig.pageSize {number} number of comments per page. default 10
190 | * walineConfig.wordLimit {number} Comment word s limit. When a single number is filled in, it 's the maximum number of comment words. No limit when set to 0
191 | * walineConfig.count {number} recent comment numbers
192 | * walineConfig.pageview {boolean} display the number of page views and comments of the article
193 | * walineConfig.reaction {string | string[]} Add emoji interaction function to the article
194 | * walineConfig.requiredMeta {string[]} Set required fields, default anonymous
195 | * walineConfig.whiteList {string[]} set some pages not to display reaction
196 | */
197 | export const comment = {
198 | enable: false,
199 | type: 'giscus', // waline | giscus,
200 | walineConfig: {
201 | serverUrl: "",
202 | lang: 'en',
203 | pageSize: 20,
204 | wordLimit: '',
205 | count: 5,
206 | pageview: true,
207 | reaction: true,
208 | requiredMeta: ["nick", "mail"],
209 | whiteList: ['/message/', '/friends/'],
210 | },
211 |
212 | // giscus config
213 | giscusConfig: {
214 | 'data-repo': "",
215 | 'data-repo-id': "",
216 | 'data-category': "",
217 | 'data-category-id': "",
218 | 'data-mapping': "",
219 | 'data-strict': "",
220 | 'data-reactions-enabled': "",
221 | 'data-emit-metadata': "",
222 | 'data-input-position': "",
223 | 'data-theme': "",
224 | 'data-lang': "",
225 | 'crossorigin': "",
226 | }
227 |
228 | //
229 | }
230 |
231 | /**
232 | * Analytics Feature Configuration
233 | *
234 | * This file centralizes the analytics configuration for the application.
235 | * It defines and exports the default settings for Umami and Google Analytics.
236 | */
237 | export const analytics: AnalyticsConfig = {
238 | enable: false,
239 | umamiConfig: {
240 | enable: false,
241 | id: "",
242 | url: ""
243 | },
244 | gaConfig: {
245 | enable: false,
246 | id: ""
247 | },
248 | busuanzi: false,
249 | };
250 |
--------------------------------------------------------------------------------
/src/content/blog/markdown-elements.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "A post of Markdown elements"
3 | description: "This post is for testing and listing a number of different markdown elements"
4 | date: 2024-03-19
5 | tags: ["test", "markdown"]
6 | ---
7 |
8 | ## This is a H2 Heading
9 |
10 | ### This is a H3 Heading
11 |
12 | #### This is a H4 Heading
13 |
14 | ##### This is a H5 Heading
15 |
16 | ###### This is a H6 Heading
17 |
18 | ## Horizontal Rules
19 |
20 | ---
21 |
22 | ---
23 |
24 | ---
25 |
26 | ## Emphasis
27 |
28 | **This is bold text**
29 |
30 | _This is italic text_
31 |
32 | ~~Strikethrough~~
33 |
34 | ## Quotes
35 |
36 | "Double quotes" and 'single quotes'
37 |
38 | ## Blockquotes
39 |
40 | > Blockquotes can also be nested...
41 | >
42 | > > ...by using additional greater-than signs right next to each other...
43 |
44 | ## References
45 |
46 | An example containing a clickable reference[^1] with a link to the source.
47 |
48 | Second example containing a reference[^2] with a link to the source.
49 |
50 | [^1]: Reference first footnote with a return to content link.
51 | [^2]: Second reference with a link.
52 |
53 | If you check out this example in `src/content/post/markdown-elements/index.md`, you'll notice that the references and the heading "Footnotes" are added to the bottom of the page via the [remark-rehype](https://github.com/remarkjs/remark-rehype#options) plugin.
54 |
55 | ## Lists
56 |
57 | Unordered
58 |
59 | - Create a list by starting a line with `+`, `-`, or `*`
60 | - Sub-lists are made by indenting 2 spaces:
61 | - Marker character change forces new list start:
62 | - Ac tristique libero volutpat at
63 | - Facilisis in pretium nisl aliquet
64 | - Nulla volutpat aliquam velit
65 | - Very easy!
66 |
67 | Ordered
68 |
69 | 1. Lorem ipsum dolor sit amet
70 | 2. Consectetur adipiscing elit
71 | 3. Integer molestie lorem at massa
72 |
73 | 4. You can use sequential numbers...
74 | 5. ...or keep all the numbers as `1.`
75 |
76 | Start numbering with offset:
77 |
78 | 57. foo
79 | 1. bar
80 |
81 | ## Code
82 |
83 | Inline `code`
84 |
85 | Indented code
86 |
87 | // Some comments
88 | line 1 of code
89 | line 2 of code
90 | line 3 of code
91 |
92 | Block code "fences"
93 |
94 | ```
95 | Sample text here...
96 | ```
97 |
98 | Syntax highlighting
99 |
100 | ```js
101 | var foo = function (bar) {
102 | return bar++;
103 | };
104 |
105 | console.log(foo(5));
106 | ```
107 |
108 | ### Expressive code examples
109 |
110 | Adding a title
111 |
112 | ```js title="file.js"
113 | console.log("Title example");
114 | ```
115 |
116 | A bash terminal
117 |
118 | ```bash
119 | echo "A base terminal example"
120 | ```
121 |
122 | Highlighting code lines
123 |
124 | ```js title="line-markers.js" del={2} ins={3-4} {6}
125 | function demo() {
126 | console.log("this line is marked as deleted");
127 | // This line and the next one are marked as inserted
128 | console.log("this is the second inserted line");
129 |
130 | return "this line uses the neutral default marker type";
131 | }
132 | ```
133 |
134 | [Expressive Code](https://expressive-code.com/) can do a ton more than shown here, and includes a lot of [customisation](https://expressive-code.com/reference/configuration/).
135 |
136 | ## Tables
137 |
138 | | Option | Description |
139 | | ------ | ------------------------------------------------------------------------- |
140 | | data | path to data files to supply the data that will be passed into templates. |
141 | | engine | engine to be used for processing templates. Handlebars is the default. |
142 | | ext | extension to be used for dest files. |
143 |
144 | Right aligned columns
145 |
146 | | Option | Description |
147 | | -----: | ------------------------------------------------------------------------: |
148 | | data | path to data files to supply the data that will be passed into templates. |
149 | | engine | engine to be used for processing templates. Handlebars is the default. |
150 | | ext | extension to be used for dest files. |
151 |
152 |
153 | ## Links
154 |
155 | [Content from markdown-it](https://markdown-it.github.io/)
--------------------------------------------------------------------------------
/src/content/blog/new-features.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: 2024-03-24
3 | title: New features
4 | description: More new features specific to this theme.
5 | mermaid: true
6 | mathjax: true
7 | category: [astro, feature]
8 | tags: [astro, feature]
9 | ogImage: https://astro-yi.obs.cn-east-3.myhuaweicloud.com/avatar.png
10 | ---
11 |
12 | ### Support Remixicon
13 |
14 | ```text
15 | :i{class="ri-poker-hearts-fill"}
16 | :i{class="ri-poker-clubs-fill"}
17 | ```
18 |
19 | :i{class="ri-poker-hearts-fill"}
20 | :i{class="ri-poker-clubs-fill"}
21 |
22 | ### Support Button
23 |
24 | ```text
25 | :btn[Google]{href="https://www.google.com"}
26 | ```
27 |
28 | :btn[Google]{href="https://www.google.com"}
29 |
30 | ```text
31 | :::btn{href="#"}
32 | :i{class="ri-share-box-line"} Open in new tab
33 | :::
34 | ```
35 |
36 | :::btn{href="#"}
37 | :i{class="ri-share-box-line"} Open in new tab
38 | :::
39 |
40 | ### Support Github Card
41 |
42 | ```text
43 | ::github{repo="cirry/astro-yi"}
44 | ```
45 |
46 | ::github{repo="cirry/astro-yi"}
47 |
48 | ### Support collapse
49 |
50 | ```bash
51 | :::collapse
52 | Hello World!
53 | :::
54 | ```
55 |
56 | :::collapse
57 | Hello World!
58 | :::
59 |
60 | ### Support admonitions
61 |
62 | ```markdown
63 | :::tip[Customized Title]
64 | hello world
65 | :::
66 |
67 | :::note
68 | note
69 | :::
70 |
71 | :::caution
72 | caution
73 | :::
74 |
75 | :::danger
76 | danger
77 | :::
78 |
79 | ```
80 |
81 | :::tip[Customized Title]
82 | hello world
83 | :::
84 |
85 | :::note
86 | note
87 |
88 | ```js
89 | console.log('hello world')
90 | ```
91 |
92 | :::
93 |
94 | :::caution
95 | caution
96 | :::
97 |
98 | :::danger
99 | danger
100 | :::
101 |
102 | ### Support mermaid
103 |
104 | Use:
105 |
106 | + start with **```mermaid**
107 | + end with **```**
108 | + set markdown frontmatter `mermaid: true`.
109 |
110 | Mermaid Code:
111 |
112 | ```html title="mermaid.md"
113 | classDiagram
114 | note "From Duck till Zebra"
115 | Animal <|-- Duck
116 | note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
117 | Animal <|-- Fish
118 | Animal <|-- Zebra
119 | Animal : +int age
120 | Animal : +String gender
121 | Animal: +isMammal()
122 | Animal: +mate()
123 | class Duck{
124 | +String beakColor
125 | +swim()
126 | +quack()
127 | }
128 | class Fish{
129 | -int sizeInFeet
130 | -canEat()
131 | }
132 | class Zebra{
133 | +bool is_wild
134 | +run()
135 | }
136 | ```
137 |
138 | Result:
139 |
140 | ```mermaid
141 | classDiagram
142 | note "From Duck till Zebra"
143 | Animal <|-- Duck
144 | note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
145 | Animal <|-- Fish
146 | Animal <|-- Zebra
147 | Animal : +int age
148 | Animal : +String gender
149 | Animal: +isMammal()
150 | Animal: +mate()
151 | class Duck{
152 | +String beakColor
153 | +swim()
154 | +quack()
155 | }
156 | class Fish{
157 | -int sizeInFeet
158 | -canEat()
159 | }
160 | class Zebra{
161 | +bool is_wild
162 | +run()
163 | }
164 | ```
165 |
166 | ### Support mathjax
167 |
168 | + set markdown frontmatter `mathjax: true`.
169 |
170 | #### Block Mode
171 |
172 | ```yaml title="Mathjax.md"
173 | ---
174 | mathjax: true
175 | ---
176 | hello!
177 | $$ \sum_{i=0}^N\int_{a}^{b}g(t,i)\text{d}t $$
178 | hello!
179 | ```
180 |
181 | hello!
182 | $$ \sum_{i=0}^N\int_{a}^{b}g(t,i)\text{d}t $$
183 | hello!
184 |
185 | #### Inline Mode
186 |
187 | ```yaml title="Mathjax.md"
188 | ---
189 | mathjax: true
190 | ---
191 | hello! $ \sum_{i=0}^N\int_{a}^{b}g(t,i)\text{d}t $ hello!
192 | ```
193 |
194 | hello! $ \sum_{i=0}^N\int_{a}^{b}g(t,i)\text{d}t $ hello!
195 |
196 | ### Integration with Expressive Code
197 |
198 | For more usage, please refer to the official website [expressive-code](https://expressive-code.com/).
199 |
200 | ```js title="line-markers.js" del={2} ins={3-4} {6}
201 | function demo() {
202 | console.log('this line is marked as deleted')
203 | // This line and the next one are marked as inserted
204 | console.log('this is the second inserted line')
205 |
206 | return 'this line uses the neutral default marker type'
207 | }
208 | ```
209 |
210 | ### Code folding is supported by default
211 |
212 | ```js
213 | var myArr = [1, 2]
214 | console.log(myArr)
215 |
216 | var myObj = {a: 1, b: 2}
217 |
218 | for (let key of myArr) {
219 | console.log(key)
220 | }
221 |
222 | var it = myArr[Symbol.iterator]()
223 | it.next() // {value: 1, done: false}
224 |
225 | // VM704:12 Uncaught TypeError: myObj is not iterable
226 | for (let key of myObj) {
227 | console.log(key)
228 | }
229 |
230 | ```
231 |
--------------------------------------------------------------------------------
/src/content/config.ts:
--------------------------------------------------------------------------------
1 | import {defineCollection, z} from 'astro:content';
2 |
3 | const blog = defineCollection({
4 | type: 'content',
5 | schema: z.object({
6 | title: z.string(),
7 | description: z.string().optional().nullable(),
8 | date: z.date(),
9 | tags: z.array(z.string()).or(z.string()).optional().nullable(),
10 | category: z.array(z.string()).or(z.string()).default('uncategorized').nullable(),
11 | sticky: z.number().default(0).nullable(),
12 | mathjax: z.boolean().default(false).nullable(),
13 | mermaid: z.boolean().default(false).nullable(),
14 | draft: z.boolean().default(false).nullable(),
15 | toc: z.boolean().default(true).nullable(),
16 | donate: z.boolean().default(true).nullable(),
17 | comment: z.boolean().default(true).nullable(),
18 | ogImage: z.string().optional()
19 | }),
20 | });
21 |
22 | const feed = defineCollection({
23 | schema: z.object({
24 | date: z.date().or(z.string()).optional().nullable(),
25 | donate: z.boolean().default(true),
26 | comment: z.boolean().default(true),
27 | })
28 | })
29 |
30 | export const collections = {blog, feed};
31 |
--------------------------------------------------------------------------------
/src/content/feed/2024-01-23.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: 2023-03-24 11:31:13
3 | ---
4 |
5 | Astro-Theme-Yi is released!
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/src/i18n/cs.ts:
--------------------------------------------------------------------------------
1 | import 'dayjs/locale/cs';
2 |
3 | export let cs = {
4 | 'aside.caution': 'varování',
5 | 'aside.danger':'nouze',
6 | 'aside.note': 'vzít na vědomí',
7 | 'aside.tip':'upozornit na něco',
8 | 'blog.tableOfContent':'Obsah',
9 | 'feed.next': 'Další',
10 | 'feed.previous': 'Předchozí',
11 | 'feed.publishedIn':'Publikováno v',
12 | 'footer.articleAuthor':'Autor článku',
13 | 'footer.articleTitle':'Název článku',
14 | 'footer.busuanziSitePV':'Zobrazení stránky:',
15 | 'footer.busuanziSitePVUnit':'',
16 | 'footer.busuanziSiteUV':'Celkový počet návštěvníků:',
17 | 'footer.busuanziSiteUVUnit':'',
18 | 'footer.copyrightOne': 'Oznámení o autorských právech: Tento článek je licencován pod',
19 | 'footer.copyrightThree': '',
20 | 'footer.copyrightTwo': '„Attribution-NonCommercial-ShareAlike 4.0 International“',
21 | 'footer.originalLink':'Původní odkaz',
22 | 'footer.releaseTime':'Čas vydání',
23 | 'footer.sitemap':'Mapa stránek',
24 | 'home.goBack': 'Jít zpět',
25 | 'home.moreArticles': 'Více článků',
26 | 'home.readMore': 'Číst dál',
27 | 'home.sticky': 'Připíchnuto',
28 | 'message.welcomeTips': 'Zanechte stopu!',
29 | 'message.welcome': 'Vítejte',
30 | 'pagination.total':'Celkem',
31 | 'pagination.unit': 'Stránky',
32 | 'post.dateFormat': 'D. MMMM YYYY',
33 | 'post.lastUpdated': 'Poslední aktualizace',
34 | 'post.lastUpdatedTip1':'Tento článek byl naposledy aktualizován',
35 | 'post.lastUpdatedTip2':'a vzhledem k uplynulému času již některé informace nemusí být aktuální.',
36 | 'search.labelOne':'Zobrazení článků pod',
37 | 'search.labelTwo':'',
38 | 'search.placeholder': 'Zadejte klíčová slova názvu nebo abstraktu',
39 | 'search.searchLabelOne':'Nalezeno ',
40 | 'search.searchLabelTwo': ' článek(ů) celkem',
41 | 'search.search': 'Hledat',
42 | 'sidebar.categories': 'Kategorie',
43 | 'sidebar.recentArticle': 'Nedávné články',
44 | 'sidebar.recentComments': 'Nedávné komentáře',
45 | 'sidebar.tags': 'Štítky',
46 | 'sidebar.uncategorized': 'nekategorizované',
47 | 'title.draft': 'návrh',
48 | 'title.minutes':' Minut',
49 | 'title.words':' Slov',
50 | 'more': 'Více'
51 | }
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/i18n/en.ts:
--------------------------------------------------------------------------------
1 | export const en = {
2 | 'aside.caution': 'Caution',
3 | 'aside.danger':'Danger',
4 | 'aside.note': 'Note',
5 | 'aside.tip':'Tip',
6 | 'blog.tableOfContent':'Table of Contents',
7 | 'feed.next': 'Next',
8 | 'feed.previous': 'Previous',
9 | 'feed.publishedIn':'Published in',
10 | 'footer.articleAuthor':'Article author',
11 | 'footer.articleTitle':'Article title',
12 | 'footer.busuanziSitePV':'Page views:',
13 | 'footer.busuanziSitePVUnit':'',
14 | 'footer.busuanziSiteUV':'Total visitors:',
15 | 'footer.busuanziSiteUVUnit':'',
16 | 'footer.copyrightOne': 'Copyright notice: This article is licensed under the',
17 | 'footer.copyrightThree': '',
18 | 'footer.copyrightTwo': '「Attribution-NonCommercial-ShareAlike 4.0 International」',
19 | 'footer.originalLink':'Original link',
20 | 'footer.releaseTime':'Release time',
21 | 'footer.sitemap':'Sitemap',
22 | 'home.goBack': 'Go back',
23 | 'home.moreArticles': 'More articles',
24 | 'home.readMore': 'Read more',
25 | 'home.sticky': 'Sticky',
26 | 'message.welcome': 'Welcome',
27 | 'message.welcomeTips': 'Leave a footprint!',
28 | 'memos.loadMore': 'Load more',
29 | 'pagination.total':'Total',
30 | 'pagination.unit': 'Pages',
31 | 'post.dateFormat': 'MMM D, YYYY',
32 | 'post.lastUpdated': 'Last Updated',
33 | 'post.lastUpdatedTip1':'This article was last updated on',
34 | 'post.lastUpdatedTip2':' and some of the information may no longer be applicable due to the passage of time.',
35 | 'remark.open': 'Open',
36 | 'search.labelOne':'Viewing articles under the ',
37 | 'search.labelTwo':'',
38 | 'search.placeholder': 'Enter title or abstract keywords',
39 | 'search.search': 'Search',
40 | 'search.searchLabelOne':'Found ',
41 | 'search.searchLabelTwo': ' article(s) in total',
42 | 'sidebar.categories': 'Categories',
43 | 'sidebar.recentArticle': 'Recent Articles',
44 | 'sidebar.recentComments': 'Recent Comments',
45 | 'sidebar.tags': 'Tags',
46 | 'sidebar.uncategorized': 'uncategorized',
47 | 'title.draft': 'draft',
48 | 'title.minutes':' Minutes',
49 | 'title.words':' Words',
50 | 'more': 'More',
51 | }
52 |
--------------------------------------------------------------------------------
/src/i18n/utils.ts:
--------------------------------------------------------------------------------
1 | import {en} from './en'
2 | import {zhCn} from './zhCn'
3 | import {cs} from './cs'
4 | import {zhHant} from './zhHant'
5 | import {config} from "../consts";
6 | const ui = {
7 | en, 'zh-cn': zhCn, 'zh-Hant': zhHant, cs
8 | }
9 | export function useTranslations(lang: keyof typeof ui) {
10 | return function t(key: string) {
11 | return ui[lang][key];
12 | }
13 | }
14 |
15 | export const t = useTranslations(config.lang)
16 |
17 |
--------------------------------------------------------------------------------
/src/i18n/zhCn.ts:
--------------------------------------------------------------------------------
1 | export const zhCn = {
2 | 'aside.note': '注意',
3 | 'aside.tip': '提示',
4 | 'aside.caution': '警告',
5 | 'aside.danger': '危险',
6 | 'home.sticky': '置顶',
7 | 'home.goBack': '返回',
8 | 'home.moreArticles': '更多文章',
9 | 'home.readMore': '阅读全文',
10 | 'message.welcome': '欢迎留言',
11 | 'message.welcomeTips': '既然来了就留个脚印吧!',
12 | 'memos.loadMore': '加载更多',
13 | 'post.lastUpdated': '最后更新',
14 | 'post.lastUpdatedTip1':'温馨提示:本文最后更新于',
15 | 'post.lastUpdatedTip2':',部分信息可能因时间推移而不再适用,欢迎反馈。',
16 | 'remark.open': '展开',
17 | 'sidebar.categories': '分类',
18 | 'sidebar.uncategorized': '未分类',
19 | 'sidebar.tags': '标签',
20 | 'sidebar.recentArticle': '最近文章',
21 | 'sidebar.recentComments': '最近评论',
22 | 'footer.articleTitle': '本文标题',
23 | 'footer.articleAuthor': '文章作者',
24 | 'footer.releaseTime': '发布时间',
25 | 'footer.originalLink': '原始链接',
26 | 'footer.copyrightOne': '版权声明:本作品采用',
27 | 'footer.copyrightTwo': '「署名-非商业性使用-相同方式共享 4.0 国际」',
28 | 'footer.copyrightThree': '许可协议进行许可',
29 | 'footer.sitemap': '站点地图',
30 | 'footer.busuanziSitePV': '总访问量',
31 | 'footer.busuanziSitePVUnit': '次',
32 | 'footer.busuanziSiteUV': '总访客数',
33 | 'footer.busuanziSiteUVUnit': '人次',
34 | 'feed.publishedIn': '发表于',
35 | 'pagination.total': '共',
36 | 'pagination.unit': '页',
37 | 'title.minutes': '分钟',
38 | 'title.words': '字',
39 | 'title.draft': '草稿',
40 | 'blog.tableOfContent': '文章目录',
41 | 'search.labelOne': '正在查看',
42 | 'search.labelTwo': '下的文章',
43 | 'search.search': '搜索',
44 | 'search.searchLabelOne': '共找到 ',
45 | 'search.searchLabelTwo': ' 篇文章',
46 | 'search.placeholder': '输入标题或摘要关键字',
47 | 'feed.previous': '上一条动态',
48 | 'feed.next': '下一条动态',
49 | 'more': '更多'
50 | }
51 |
--------------------------------------------------------------------------------
/src/i18n/zhHant.ts:
--------------------------------------------------------------------------------
1 | export const zhHant = {
2 | 'home.sticky': '置頂',
3 | 'home.goBack': '返回',
4 | 'home.moreArticles': '更多文章',
5 | 'home.readMore': '閱讀全文',
6 | 'message.welcome': '歡迎留言',
7 | 'message.welcomeTips': '既然來了就留個腳印吧!',
8 | 'memos.loadMore': '加載更多',
9 | 'post.lastUpdated': '最後更新',
10 | 'post.lastUpdatedTip1':'警告:本文最後更新日期為',
11 | 'post.lastUpdatedTip2':',由於時間推移部分資訊可能不再適用。',
12 | 'sidebar.categories': '分類',
13 | 'sidebar.uncategorized': '未分類',
14 | 'sidebar.tags': '標簽',
15 | 'sidebar.recentArticle': '最近文章',
16 | 'sidebar.recentComments': '最近評論',
17 | 'footer.articleTitle':'本文標題',
18 | 'footer.articleAuthor':'文章作者',
19 | 'footer.releaseTime':'發布時間',
20 | 'footer.originalLink':'原始鏈接',
21 | 'footer.copyrightOne': '版權聲明:本作品采用',
22 | 'footer.copyrightTwo': '「署名-非商業性使用-相同方式共享 4.0 國際」',
23 | 'footer.copyrightThree': '許可協議進行許可',
24 | 'footer.sitemap':'站點地圖',
25 | 'footer.busuanziSitePV':'總訪問量',
26 | 'footer.busuanziSitePVUnit':'次',
27 | 'footer.busuanziSiteUV':'總訪客數',
28 | 'footer.busuanziSiteUVUnit':'人次',
29 | 'feed.publishedIn':'發表於',
30 | 'pagination.total':'共',
31 | 'pagination.unit': '頁',
32 | 'title.minutes':'分鐘',
33 | 'title.words':'字',
34 | 'title.draft': '草稿',
35 | 'blog.tableOfContent':'文章目錄',
36 | 'search.labelOne':'正在查看',
37 | 'search.labelTwo':'下的文章',
38 | 'search.search': '搜索',
39 | 'search.searchLabelOne':'共找到 ',
40 | 'search.searchLabelTwo': ' 篇文章',
41 | 'search.placeholder': '輸入標題或摘要關鍵字',
42 | 'feed.previous': '上一條動態',
43 | 'feed.next': '下一條動態',
44 | 'more': '更多'
45 | }
46 |
--------------------------------------------------------------------------------
/src/layouts/BlogPost.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import BaseHead from '@/components/BaseHead.astro';
3 | import Header from '@/components/Header.astro';
4 | import Footer from '@/components/Footer.astro';
5 | import Profile from '@/components/Profile.astro'
6 | import Comment from '@/components/Comment.astro'
7 | import Toc from '@/components/Toc.astro'
8 | import {comment, config} from "@/consts";
9 | import ScrollToTop from '@/components/ScrollToTop.astro'
10 | import {t} from '@/i18n/utils'
11 |
12 | const {
13 | frontmatter = {
14 | comment: false,
15 | donate: false,
16 | toc: false,
17 | mathjax: false,
18 | mermaid: false,
19 | },
20 | } = Astro.props;
21 | ---
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 | {
43 | frontmatter.comment && comment.enable &&
44 |
45 | }
46 |
47 |
48 |
49 | {
50 | frontmatter.toc &&
51 |
52 | }
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
107 |
--------------------------------------------------------------------------------
/src/layouts/IndexPage.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import BaseHead from '@/components/BaseHead.astro';
3 | import Header from '@/components/Header.astro';
4 | import Footer from '@/components/Footer.astro';
5 | import Profile from '@/components/Profile.astro'
6 | import BlogAside from '@/components/BlogAside.astro'
7 | import Comment from '@/components/Comment.astro'
8 | import Donate from '@/components/Donate.astro'
9 | import {donate, comment, config} from "@/consts";
10 |
11 | const {frontmatter = {comment: false, donate: false}} = Astro.props
12 | ---
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {
24 | donate.enable && frontmatter.donate &&
25 |
26 | }
27 | {
28 | comment.enable && frontmatter.comment &&
29 |
30 | }
31 |
32 |
38 |
39 |
40 |
41 |
42 |
47 |
48 |
--------------------------------------------------------------------------------
/src/pages/404.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | ---
4 |
5 |
6 | 404
7 | Oops,This Page Could Not Be Found!
8 |
9 |
--------------------------------------------------------------------------------
/src/pages/about/about.md:
--------------------------------------------------------------------------------
1 | ---
2 | donate: false
3 | comment: false
4 | ---
5 |
6 | ## About Website
7 | Demo
8 |
9 | ## About Me
10 | Demo
--------------------------------------------------------------------------------
/src/pages/about/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | const posts = Object.values(import.meta.glob("./about.md", { eager: true }));
4 | let {Content, frontmatter} = posts[0];
5 | ---
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/pages/archive/[page].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import Pagination from "@/components/Pagination.astro";
4 | import { sortPostsByDate } from "@/utils/sortPostsByDate";
5 | import getPostsByYear from "@/utils/getPostsByYear";
6 | import {site} from "@/consts";
7 | import { getCollectionByName } from "@/utils/getCollectionByName";
8 | import getUrl from "@/utils/getUrl";
9 | import dayjs from "dayjs";
10 | export async function getStaticPaths({ paginate }) {
11 | let posts = await getCollectionByName("blog");
12 | posts = sortPostsByDate(posts);
13 | return paginate(posts, { pageSize: site.archivePageSize });
14 | }
15 | // 所有分页数据都将传递给 "page" 参数
16 | const { page } = Astro.props;
17 | let resultPosts = getPostsByYear(page.data);
18 | ---
19 |
20 |
21 | {
22 | Object.keys(resultPosts)
23 | .sort((a, b) => Number(b) - Number(a))
24 | .map((year) => (
25 |
42 | ))
43 | }
44 |
49 |
50 |
--------------------------------------------------------------------------------
/src/pages/archive/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import {site} from "@/consts";
4 | import getPostsByYear from "@/utils/getPostsByYear";
5 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
6 | import Pagination from "@/components/Pagination.astro";
7 | import {getCollectionByName} from "@/utils/getCollectionByName";
8 | import getUrl from "@/utils/getUrl";
9 |
10 | import dayjs from "dayjs";
11 | import {ceil, divide} from "lodash-es";
12 |
13 | let posts = await getCollectionByName("blog");
14 | let totalPage = ceil(divide(posts.length, site.archivePageSize));
15 |
16 | let sortedPosts = sortPostsByDate(posts);
17 | sortedPosts = sortedPosts.splice(0, site.archivePageSize);
18 | let resultPosts = getPostsByYear(sortedPosts);
19 | ---
20 |
21 |
22 | {
23 | Object.keys(resultPosts)
24 | .sort((a, b) => Number(b) - Number(a))
25 | .map((year) => (
26 |
41 | ))
42 | }
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/pages/blog/[...slug].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import BlogPost from '@/layouts/BlogPost.astro'
3 | import PostTitle from "@/components/PostTitle.astro";
4 | import BlogFooter from '@/components/BlogFooter.astro'
5 | import {getCollectionByName} from "@/utils/getCollectionByName";
6 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
7 | import Donate from '@/components/Donate.astro'
8 | import {donate} from "@/consts";
9 | import getUrl from "@/utils/getUrl";
10 |
11 | export async function getStaticPaths() {
12 | const blogEntries = await getCollectionByName("blog");
13 | return blogEntries.map(entry => ({
14 | params: {slug: entry.slug}, props: {entry,},
15 | }));
16 | }
17 |
18 | const {entry} = Astro.props;
19 | const {Content, remarkPluginFrontmatter} = await entry.render();
20 |
21 | const lastModified = remarkPluginFrontmatter.lastModified
22 | const readingTime = remarkPluginFrontmatter.readingTime
23 |
24 | const posts = await getCollectionByName("blog");
25 | const sortPosts = sortPostsByDate(posts);
26 |
27 | const currentPostIndex = sortPosts.findIndex(
28 | (postItem) => postItem.data.title === entry.data.title
29 | );
30 | let prevPost, nextPost
31 | if (sortPosts[currentPostIndex - 1]) {
32 | prevPost = sortPosts[currentPostIndex - 1];
33 | }
34 | if (sortPosts[currentPostIndex + 1]) {
35 | nextPost = sortPosts[currentPostIndex + 1];
36 | }
37 | ---
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {
48 | prevPost ? (
49 |
54 |
55 | {prevPost.data.title}
56 |
57 | ) : (
58 |
59 | )
60 | }
61 | {
62 | nextPost ? (
63 |
69 | ) : (
70 |
71 | )
72 | }
73 |
74 |
75 | {
76 | donate.enable && entry.data.donate &&
77 |
78 | }
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/pages/blog/[page].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {site} from "@/consts";
3 | import PostView from '@/components/PostView.astro'
4 | import IndexPage from '@/layouts/IndexPage.astro'
5 | import Pagination from '@/components/Pagination.astro';
6 | import {getCollectionByName} from "@/utils/getCollectionByName";
7 | import {orderBySticky} from "@/utils/orderBySticky";
8 | import getUrl from "@/utils/getUrl";
9 |
10 | export async function getStaticPaths({paginate}) {
11 | let posts = await getCollectionByName("blog");
12 | let sortedPosts = orderBySticky(posts);
13 | return paginate(sortedPosts, {pageSize: site.postPageSize});
14 | }
15 | // 所有分页数据都将传递给 "page" 参数
16 | const {page} = Astro.props;
17 | ---
18 |
19 |
20 |
21 | {
22 | page.data.map((blog, index) =>
23 | )
24 | }
25 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/pages/category/[category].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import getPostsByCategory from "@/utils/getPostsByCategory";
4 | import getUniqueCategory from "@/utils/getUniqeCategory";
5 | import SearchTitle from "@/components/SearchTitle.astro";
6 | import {formatDate} from "@/utils/formatDate";
7 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
8 | import {getCollectionByName} from "@/utils/getCollectionByName";
9 | import getUrl from "@/utils/getUrl";
10 | export async function getStaticPaths() {
11 | const posts = await getCollectionByName("blog");
12 | const category = getUniqueCategory(posts);
13 | return category.map((category) => {
14 | return {
15 | params: {
16 | category,
17 | },
18 | props: {
19 | category,
20 | },
21 | };
22 | });
23 | }
24 | const {category} = Astro.props;
25 | let posts = await getCollectionByName("blog");
26 | const categoryPosts = getPostsByCategory(posts, category);
27 | const resultPosts = sortPostsByDate(categoryPosts);
28 | ---
29 |
30 |
31 |
32 |
33 | {
34 | resultPosts.map((post) => (
35 | -
36 |
37 | {formatDate(post.data.date)}
38 |
39 | {post.data.title}
40 |
41 |
42 |
43 | ))
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/pages/feed/[...slug].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
3 | import {getCollectionByName} from "@/utils/getCollectionByName";
4 | import BlogPost from '@/layouts/BlogPost.astro'
5 | import {comment} from "@/consts";
6 | import {formatDate} from '@/utils/formatDate'
7 | import {t} from '@/i18n/utils'
8 | import getUrl from "@/utils/getUrl";
9 |
10 | export async function getStaticPaths() {
11 | const posts = await getCollectionByName("feed");
12 | return posts.map((post) => ({
13 | params: {slug: post.slug},
14 | props: post,
15 | }));
16 | }
17 |
18 | const post = Astro.props;
19 | const {Content} = await post.render();
20 | const posts = await getCollectionByName("feed");
21 | const sortPosts = sortPostsByDate(posts);
22 |
23 | let postDate = post.data.date ? formatDate(post.data.date) : '';
24 | const currentPostIndex = sortPosts.findIndex(
25 | (postItem) => postItem.data.date === post.data.date
26 | );
27 | let prevPost: any, nextPost: any;
28 | if (sortPosts[currentPostIndex - 1]) {
29 | prevPost = sortPosts[currentPostIndex - 1];
30 | }
31 | if (sortPosts[currentPostIndex + 1]) {
32 | nextPost = sortPosts[currentPostIndex + 1];
33 | }
34 | ---
35 |
36 |
37 | {
38 | (
39 |
40 | {postDate &&
41 |
42 |
43 |
{postDate}
44 |
}
45 | {comment.enable && comment.type === "waline" && comment.pageview && (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | )}
57 |
58 | )
59 | }
60 |
61 |
62 |
63 |
64 |
65 | {
66 | (
67 |
95 | )
96 | }
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/pages/feed/[page].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {site} from "@/consts";
3 | import IndexPage from '@/layouts/IndexPage.astro'
4 | import Pagination from "@/components/Pagination.astro";
5 | import { sortPostsByDate } from "@/utils/sortPostsByDate";
6 | import { getCollectionByName } from "@/utils/getCollectionByName";
7 | import FeedPreview from "@/components/FeedPreview.astro";
8 | import getUrl from "@/utils/getUrl";
9 | export async function getStaticPaths({ paginate }) {
10 | let posts = await getCollectionByName("feed");
11 | posts = sortPostsByDate(posts);
12 | // 将根据宇航员数组生成两个页面
13 | return paginate(posts, { pageSize: site.feedPageSize });
14 | }
15 | // 所有分页数据都将传递给 "page" 参数
16 |
17 | const { page } = Astro.props;
18 | ---
19 |
20 |
21 | {page.data.map((post, index) => )}
22 |
28 |
29 |
--------------------------------------------------------------------------------
/src/pages/friends/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import Friends from "@/components/Friends.astro";
4 | import {friendshipLinks} from "@/consts";
5 | ---
6 |
7 |
8 |
9 | {friendshipLinks ? friendshipLinks.map((friend) => ) : ""}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import PostView from '@/components/PostView.astro'
3 | import IndexPage from '@/layouts/IndexPage.astro'
4 | import {getCollectionByName} from "@/utils/getCollectionByName";
5 | import {orderBySticky} from "@/utils/orderBySticky";
6 | import {site} from '@/consts'
7 | import Pagination from "@/components/Pagination.astro";
8 | import {ceil, divide} from 'lodash-es'
9 |
10 | let currentPage = 1
11 | let blogs = await getCollectionByName("blog");
12 | let totalPage = ceil(divide(blogs.length, site.postPageSize))
13 |
14 | let sortedPosts = orderBySticky(blogs);
15 | if (sortedPosts.length > site.postPageSize) {
16 | sortedPosts = sortedPosts.splice(0, site.postPageSize);
17 | }
18 | ---
19 |
20 |
21 | {
22 | sortedPosts.map(blog =>
23 | )
24 | }
25 | {
26 | totalPage > 1 ?
27 | : ''
31 | }
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/pages/memos/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import {t} from '@/i18n/utils'
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
76 |
--------------------------------------------------------------------------------
/src/pages/message/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import {comment} from "@/consts";
4 | import {t} from '@/i18n/utils'
5 | ---
6 |
7 |
8 |
9 | {t('message.welcome')}
10 |
11 |
12 | {t('message.welcomeTips')}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/pages/rss.xml.js:
--------------------------------------------------------------------------------
1 | import rss from '@astrojs/rss';
2 | import {site} from "../consts";
3 | import getUrl from "../utils/getUrl.js";
4 | import {getCollection} from "astro:content";
5 | import {getCollectionByName} from "@/utils/getCollectionByName";
6 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
7 |
8 | export async function GET(context) {
9 | // const blogs = await getCollection('blog')
10 | const blogs = await getCollectionByName('blog')
11 | let sortPosts = await sortPostsByDate(blogs);
12 | let blog = sortPosts.splice(0, 20);
13 |
14 | return rss({
15 | title: site.title,
16 | description: site.description,
17 | site: site.url,
18 | items: blog.map((post) => ({
19 | title: post.data.title,
20 | pubDate: post.data.date,
21 | description: post.data.description? post.data.description : post.body.substring(0, 140).replace(/#/gi, "") + "...",
22 | // Compute RSS link from post `slug`
23 | // This example assumes all posts are rendered as `/blog/[slug]` routes
24 | link: `${getUrl("/blog/")}${post.slug}/`,
25 | })),
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/search.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import {t} from '@/i18n/utils'
4 | ---
5 |
6 |
27 |
28 |
31 |
32 |
164 |
--------------------------------------------------------------------------------
/src/pages/search.json.js:
--------------------------------------------------------------------------------
1 | import {getCollectionByName} from "../utils/getCollectionByName.js";
2 |
3 | async function getBlogs(context) {
4 | const blog = await getCollectionByName('blog')
5 | return blog.map(blog => {
6 | return {
7 | slug: blog.slug,
8 | title: blog.data.title,
9 | description: blog.data.description,
10 | date: blog.data.date,
11 | category: blog.data.category,
12 | tags: blog.data.tags,
13 | }
14 | })
15 | }
16 |
17 | export async function GET({}) {
18 | return new Response(JSON.stringify(await getBlogs()), {
19 | status: 200,
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | }
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/tags/[tag].astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import getUniqueTags from "@/utils/getUniqueTags";
4 | import getPostsByTag from "@/utils/getPostsByTag";
5 | import SearchTitle from "@/components/SearchTitle.astro";
6 | import {formatDate} from "@/utils/formatDate";
7 | import {sortPostsByDate} from "@/utils/sortPostsByDate";
8 | import {getCollectionByName} from "@/utils/getCollectionByName";
9 | import getUrl from "@/utils/getUrl";
10 | export async function getStaticPaths() {
11 | const posts = await getCollectionByName("blog");
12 | // 过滤没有分类的文章,todo 后期可能会强制需要文章分类
13 | const tags = getUniqueTags(posts).filter((tag) => tag);
14 | return tags.map((tag) => {
15 | return {params: {tag,}, props: {tag,},};
16 | });
17 | }
18 | const {tag} = Astro.props;
19 | let posts = await getCollectionByName("blog");
20 | const tagPosts = getPostsByTag(posts, tag);
21 | const resultPosts = sortPostsByDate(tagPosts);
22 | ---
23 |
24 |
25 |
26 |
27 | {
28 | resultPosts.map((post) => (
29 | -
30 |
31 | {formatDate(post.data.date)}
32 | {post.data.title}
33 |
34 |
35 | ))
36 | }
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/pages/tags/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import IndexPage from "@/layouts/IndexPage.astro";
3 | import {getCollectionByName} from "@/utils/getCollectionByName";
4 | import getUniqueTags from "@/utils/getUniqueTags";
5 | import getUrl from "@/utils/getUrl";
6 | import {t} from '../../i18n/utils'
7 |
8 | const blogs = await getCollectionByName('blog')
9 | let tagArr = getUniqueTags(blogs);
10 | ---
11 |
12 |
13 |
14 | {
15 | (
16 | tagArr
17 | &&
18 | tagArr.map((tag) =>
19 |
{tag}
20 | )
21 | )
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/plugins/lazy-load-image.js:
--------------------------------------------------------------------------------
1 | import {visit} from "unist-util-visit";
2 | import getUrl from "../utils/getUrl.js";
3 |
4 | export function lazyLoadImage() {
5 | return function (tree) {
6 | visit(tree, function (node) {
7 | if (node.tagName === 'img') {
8 | node.properties['data-src'] = node.properties.src
9 | node.properties.src = getUrl('/spinner.gif')
10 | node.properties['data-alt'] = node.properties.alt
11 | node.properties.alt = 'default'
12 | }
13 | })
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/plugins/remark-asides.js:
--------------------------------------------------------------------------------
1 | import {h as _h, s as _s} from "hastscript";
2 | import {remove} from "unist-util-remove";
3 | import {visit} from "unist-util-visit";
4 | import {t} from '../i18n/utils.ts';
5 | const variants = new Set(["note", "tip", "caution", "danger"]);
6 |
7 | function defaultLabel(v) {
8 | switch (v) {
9 | case "note":
10 | return t('aside.note') || 'Note';
11 | case "tip":
12 | return t('aside.tip') || 'Tip';
13 | case "caution":
14 | return t('aside.caution') || 'Caution';
15 | case "danger":
16 | return t('aside.danger') || 'Danger';
17 | default:
18 | return "";
19 | }
20 | }
21 |
22 | /** Hacky function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
23 | function h(el, attrs = {}, children = []) {
24 | const {tagName, properties} = _h(el, attrs);
25 | return {
26 | type: "paragraph",
27 | data: {hName: tagName, hProperties: properties},
28 | children,
29 | };
30 | }
31 |
32 | /** Hacky function that generates an mdast SVG tree ready for conversion to HTML by rehype. */
33 | function s(el, attrs = {}, children = []) {
34 | const {tagName, properties} = _s(el, attrs);
35 | return {
36 | type: "paragraph",
37 | data: {hName: tagName, hProperties: properties},
38 | children,
39 | };
40 | }
41 |
42 | /**
43 | * remark plugin that converts blocks delimited with `:::` into styled
44 | * asides (a.k.a. “callouts”, “admonitions”, etc.). Depends on the
45 | * `remark-directive` module for the core parsing logic.
46 | *
47 | * For example, this Markdown
48 | *
49 | * ```md
50 | * :::tip[Did you know?]
51 | * Astro helps you build faster websites with “Islands Architecture”.
52 | * :::
53 | * ```
54 | *
55 | * will produce this output
56 | *
57 | * ```astro
58 | *
64 | * ```
65 | */
66 | export function remarkAsides(options) {
67 | options = {
68 | label: defaultLabel,
69 | ...options,
70 | };
71 | const isAsideVariant = (s) => variants.has(s);
72 |
73 | const iconPaths = {
74 | // Information icon
75 | note: [
76 | s("path", {
77 | d: "M12 11C11.7348 11 11.4804 11.1054 11.2929 11.2929C11.1054 11.4804 11 11.7348 11 12V16C11 16.2652 11.1054 16.5196 11.2929 16.7071C11.4804 16.8946 11.7348 17 12 17C12.2652 17 12.5196 16.8946 12.7071 16.7071C12.8946 16.5196 13 16.2652 13 16V12C13 11.7348 12.8946 11.4804 12.7071 11.2929C12.5196 11.1054 12.2652 11 12 11ZM12.38 7.08C12.1365 6.97998 11.8635 6.97998 11.62 7.08C11.4973 7.12759 11.3851 7.19896 11.29 7.29C11.2017 7.3872 11.1306 7.49882 11.08 7.62C11.024 7.73868 10.9966 7.86882 11 8C10.9992 8.13161 11.0245 8.26207 11.0742 8.38391C11.124 8.50574 11.1973 8.61656 11.29 8.71C11.3872 8.79833 11.4988 8.86936 11.62 8.92C11.7715 8.98224 11.936 9.00632 12.099 8.99011C12.2619 8.97391 12.4184 8.91792 12.5547 8.82707C12.691 8.73622 12.8029 8.61328 12.8805 8.46907C12.9582 8.32486 12.9992 8.16378 13 8C12.9963 7.73523 12.8927 7.48163 12.71 7.29C12.6149 7.19896 12.5028 7.12759 12.38 7.08ZM12 2C10.0222 2 8.08879 2.58649 6.4443 3.6853C4.79981 4.78412 3.51809 6.3459 2.76121 8.17317C2.00433 10.0004 1.8063 12.0111 2.19215 13.9509C2.578 15.8907 3.53041 17.6725 4.92894 19.0711C6.32746 20.4696 8.10929 21.422 10.0491 21.8079C11.9889 22.1937 13.9996 21.9957 15.8268 21.2388C17.6541 20.4819 19.2159 19.2002 20.3147 17.5557C21.4135 15.9112 22 13.9778 22 12C22 10.6868 21.7413 9.38642 21.2388 8.17317C20.7363 6.95991 19.9997 5.85752 19.0711 4.92893C18.1425 4.00035 17.0401 3.26375 15.8268 2.7612C14.6136 2.25866 13.3132 2 12 2ZM12 20C10.4178 20 8.87104 19.5308 7.55544 18.6518C6.23985 17.7727 5.21447 16.5233 4.60897 15.0615C4.00347 13.5997 3.84504 11.9911 4.15372 10.4393C4.4624 8.88743 5.22433 7.46197 6.34315 6.34315C7.46197 5.22433 8.88743 4.4624 10.4393 4.15372C11.9911 3.84504 13.5997 4.00346 15.0615 4.60896C16.5233 5.21447 17.7727 6.23984 18.6518 7.55544C19.5308 8.87103 20 10.4177 20 12C20 14.1217 19.1572 16.1566 17.6569 17.6569C16.1566 19.1571 14.1217 20 12 20Z",
78 | }),
79 | ],
80 | // Rocket icon
81 | tip: [
82 | s("path", {
83 | "fill-rule": "evenodd",
84 | "clip-rule": "evenodd",
85 | d: "M1.43909 8.85483L1.44039 8.85354L4.96668 5.33815C5.30653 4.99386 5.7685 4.79662 6.2524 4.78972L6.26553 4.78963L12.9014 4.78962L13.8479 3.84308C16.9187 0.772319 20.0546 0.770617 21.4678 0.975145C21.8617 1.02914 22.2271 1.21053 22.5083 1.4917C22.7894 1.77284 22.9708 2.13821 23.0248 2.53199C23.2294 3.94517 23.2278 7.08119 20.1569 10.1521L19.2107 11.0983V17.7338L19.2106 17.7469C19.2037 18.2308 19.0067 18.6933 18.6624 19.0331L15.1456 22.5608C14.9095 22.7966 14.6137 22.964 14.29 23.0449C13.9663 23.1259 13.6267 23.1174 13.3074 23.0204C12.9881 22.9235 12.7011 22.7417 12.4771 22.4944C12.2533 22.2473 12.1006 21.9441 12.0355 21.6171L11.1783 17.3417L6.65869 12.822L4.34847 12.3589L2.38351 11.965C2.05664 11.8998 1.75272 11.747 1.50564 11.5232C1.25835 11.2992 1.07653 11.0122 0.979561 10.6929C0.882595 10.3736 0.874125 10.034 0.955057 9.7103C1.03599 9.38659 1.20328 9.09092 1.43909 8.85483ZM6.8186 10.8724L2.94619 10.096L6.32006 6.73268H10.9583L6.8186 10.8724ZM15.2219 5.21703C17.681 2.75787 20.0783 2.75376 21.1124 2.8876C21.2462 3.92172 21.2421 6.31895 18.783 8.77812L12.0728 15.4883L8.51172 11.9272L15.2219 5.21703ZM13.9042 21.0538L13.1279 17.1811L17.2676 13.0414V17.68L13.9042 21.0538Z",
86 | }),
87 | s("path", {
88 | d: "M9.31827 18.3446C9.45046 17.8529 9.17864 17.3369 8.68945 17.1724C8.56178 17.1294 8.43145 17.1145 8.30512 17.1243C8.10513 17.1398 7.91519 17.2172 7.76181 17.3434C7.62613 17.455 7.51905 17.6048 7.45893 17.7835C6.97634 19.2186 5.77062 19.9878 4.52406 20.4029C4.08525 20.549 3.6605 20.644 3.29471 20.7053C3.35607 20.3395 3.45098 19.9148 3.59711 19.476C4.01221 18.2294 4.78141 17.0237 6.21648 16.5411C6.39528 16.481 6.54504 16.3739 6.65665 16.2382C6.85126 16.0016 6.92988 15.678 6.84417 15.3647C6.83922 15.3466 6.83373 15.3286 6.82767 15.3106C6.74106 15.053 6.55701 14.8557 6.33037 14.7459C6.10949 14.6389 5.84816 14.615 5.59715 14.6994C5.47743 14.7397 5.36103 14.7831 5.24786 14.8294C3.22626 15.6569 2.2347 17.4173 1.75357 18.8621C1.49662 19.6337 1.36993 20.3554 1.30679 20.8818C1.27505 21.1464 1.25893 21.3654 1.25072 21.5213C1.24662 21.5993 1.24448 21.6618 1.24337 21.7066L1.243 21.7226L1.24235 21.7605L1.2422 21.7771L1.24217 21.7827L1.24217 21.7856C1.24217 22.3221 1.67703 22.7579 2.2137 22.7579L2.2155 22.7579L2.22337 22.7578L2.23956 22.7577C2.25293 22.7575 2.27096 22.7572 2.29338 22.7567C2.33821 22.7555 2.40073 22.7534 2.47876 22.7493C2.63466 22.7411 2.85361 22.725 3.11822 22.6932C3.64462 22.6301 4.36636 22.5034 5.13797 22.2464C6.58274 21.7653 8.3431 20.7738 9.17063 18.7522C9.21696 18.639 9.26037 18.5226 9.30064 18.4029C9.30716 18.3835 9.31304 18.364 9.31827 18.3446Z",
89 | }),
90 | ],
91 | // Warning triangle icon
92 | caution: [
93 | s("path", {
94 | d: "M12 16C11.8022 16 11.6089 16.0587 11.4444 16.1686C11.28 16.2784 11.1518 16.4346 11.0761 16.6173C11.0004 16.8001 10.9806 17.0011 11.0192 17.1951C11.0578 17.3891 11.153 17.5673 11.2929 17.7071C11.4327 17.847 11.6109 17.9422 11.8049 17.9808C11.9989 18.0194 12.2 17.9996 12.3827 17.9239C12.5654 17.8482 12.7216 17.72 12.8315 17.5556C12.9413 17.3911 13 17.1978 13 17C13 16.7348 12.8946 16.4805 12.7071 16.2929C12.5196 16.1054 12.2652 16 12 16ZM22.67 17.47L14.62 3.47003C14.3598 3.00354 13.9798 2.61498 13.5192 2.3445C13.0586 2.07401 12.5341 1.9314 12 1.9314C11.4659 1.9314 10.9414 2.07401 10.4808 2.3445C10.0202 2.61498 9.64019 3.00354 9.38 3.47003L1.38 17.47C1.11079 17.924 0.966141 18.441 0.960643 18.9688C0.955144 19.4966 1.089 20.0166 1.34868 20.4761C1.60837 20.9356 1.9847 21.3185 2.43968 21.5861C2.89466 21.8536 3.41218 21.9964 3.94 22H20.06C20.5921 22.0053 21.1159 21.8689 21.5779 21.6049C22.0399 21.341 22.4234 20.9589 22.689 20.4978C22.9546 20.0368 23.0928 19.5134 23.0895 18.9814C23.0862 18.4493 22.9414 17.9277 22.67 17.47ZM20.94 19.47C20.8523 19.626 20.7245 19.7556 20.5697 19.8453C20.4149 19.935 20.2389 19.9815 20.06 19.98H3.94C3.76111 19.9815 3.5851 19.935 3.43032 19.8453C3.27553 19.7556 3.14765 19.626 3.06 19.47C2.97223 19.318 2.92602 19.1456 2.92602 18.97C2.92602 18.7945 2.97223 18.622 3.06 18.47L11.06 4.47003C11.1439 4.30623 11.2714 4.16876 11.4284 4.07277C11.5855 3.97678 11.766 3.92599 11.95 3.92599C12.134 3.92599 12.3145 3.97678 12.4716 4.07277C12.6286 4.16876 12.7561 4.30623 12.84 4.47003L20.89 18.47C20.9892 18.6199 21.0462 18.7937 21.055 18.9732C21.0638 19.1527 21.0241 19.3312 20.94 19.49V19.47ZM12 8.00003C11.7348 8.00003 11.4804 8.10538 11.2929 8.29292C11.1054 8.48046 11 8.73481 11 9.00003V13C11 13.2652 11.1054 13.5196 11.2929 13.7071C11.4804 13.8947 11.7348 14 12 14C12.2652 14 12.5196 13.8947 12.7071 13.7071C12.8946 13.5196 13 13.2652 13 13V9.00003C13 8.73481 12.8946 8.48046 12.7071 8.29292C12.5196 8.10538 12.2652 8.00003 12 8.00003Z",
95 | }),
96 | ],
97 | // Error shield icon
98 | danger: [
99 | s("path", {
100 | d: "M12 7C11.7348 7 11.4804 7.10536 11.2929 7.29289C11.1054 7.48043 11 7.73478 11 8V12C11 12.2652 11.1054 12.5196 11.2929 12.7071C11.4804 12.8946 11.7348 13 12 13C12.2652 13 12.5196 12.8946 12.7071 12.7071C12.8946 12.5196 13 12.2652 13 12V8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7ZM12 15C11.8022 15 11.6089 15.0586 11.4444 15.1685C11.28 15.2784 11.1518 15.4346 11.0761 15.6173C11.0004 15.8 10.9806 16.0011 11.0192 16.1951C11.0578 16.3891 11.153 16.5673 11.2929 16.7071C11.4327 16.847 11.6109 16.9422 11.8049 16.9808C11.9989 17.0194 12.2 16.9996 12.3827 16.9239C12.5654 16.8482 12.7216 16.72 12.8315 16.5556C12.9414 16.3911 13 16.1978 13 16C13 15.7348 12.8946 15.4804 12.7071 15.2929C12.5196 15.1054 12.2652 15 12 15ZM21.71 7.56L16.44 2.29C16.2484 2.10727 15.9948 2.00368 15.73 2H8.27C8.00523 2.00368 7.75163 2.10727 7.56 2.29L2.29 7.56C2.10727 7.75163 2.00368 8.00523 2 8.27V15.73C2.00368 15.9948 2.10727 16.2484 2.29 16.44L7.56 21.71C7.75163 21.8927 8.00523 21.9963 8.27 22H15.73C15.9948 21.9963 16.2484 21.8927 16.44 21.71L21.71 16.44C21.8927 16.2484 21.9963 15.9948 22 15.73V8.27C21.9963 8.00523 21.8927 7.75163 21.71 7.56ZM20 15.31L15.31 20H8.69L4 15.31V8.69L8.69 4H15.31L20 8.69V15.31Z",
101 | }),
102 | ],
103 | };
104 |
105 | const transformer = (tree) => {
106 | visit(tree, (node, index, parent) => {
107 | if (!parent || index === undefined || node.type !== "containerDirective") {
108 | return;
109 | }
110 | const variant = node.name;
111 | if (!isAsideVariant(variant)) return;
112 |
113 | // remark-directive converts a container’s “label” to a paragraph in
114 | // its children, but we want to pass it as the title prop to