初始化
This commit is contained in:
@@ -1,20 +1,12 @@
|
||||
 
|
||||
Eidolon 是一款轻量、简洁且美观的 Astro 主题。基于 MIT License 开源。
|
||||
|
||||
---
|
||||
|
||||
这里是 **⌈路明笔记⌋** 的代码仓库。
|
||||
|
||||
[GitHub](https://github.com/virelyx258/Blog) | [云仓](https://src.luming.cool/riseforever2026/Blog)
|
||||
|
||||
之所以在自己的服务器上托管一份,是因为 GitHub 直连很不稳定,导致我无法愉快地更新博客。自托管后,无需挂代理即可轻松更新。
|
||||
[GitHub](https://github.com/virelyx258/Astro-Theme-Eidolon) | [云仓](https://src.luming.cool/riseforever2026/Eidolon)
|
||||
|
||||
两个仓库互为镜像关系,同步更新。
|
||||
|
||||
## 主题
|
||||
本主题的设计思想参照了 Typecho 主题 [Mirages](https://get233.com/archives/mirages-intro.html)。
|
||||
|
||||
Eidolon,基于 [Astro](https://astro.build) 进行构建的主题,设计思想参照了 Typecho 主题 [Mirages](https://get233.com/archives/mirages-intro.html)。
|
||||
|
||||
我曾是 Mirages 主题用户,后为追求极致速度,便产生了移植主题到 Astro 的想法。
|
||||
我自己曾是 Mirages 主题用户,后为追求极致速度,便产生了移植主题到 Astro 的想法。
|
||||
|
||||
一款外观相似、免费的主题,如果开源,可能会对原付费主题的销售造成影响。但细想想,我们其实不在一个赛道。喜欢使用 Typecho 博客系统、追求简约的用户,一定还会选择 Mirages 的。本主题的开源,只是为 Astro 用户提供另一种选择。
|
||||
|
||||
@@ -28,10 +20,4 @@ Eidolon,基于 [Astro](https://astro.build) 进行构建的主题,设计思
|
||||
- 支持多样的短代码;
|
||||
- 原生支持友链卡片。
|
||||
|
||||
欢迎使用。如果能帮助到你,欢迎去 [GitHub 仓库](https://github.com/virelyx258/astro_theme_) 给我点个 Star。
|
||||
|
||||
## 版权协议
|
||||
|
||||
本站的文章基于 [CC-BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans) 协议公开。
|
||||
|
||||
转载请注明原文链接。[协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans)不允许对本站文章的再混合、转换、或者二次创作。若认为有必要二创,请邮件沟通。
|
||||
欢迎使用。如果能帮助到你,欢迎去 [GitHub 仓库](https://github.com/virelyx258/Astro-Theme-Eidolon) 给我点个 Star。
|
||||
|
||||
+17
-3
@@ -6,9 +6,23 @@ import remarkMath from 'remark-math';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import shortcodes from './src/lib/shortcodes.ts';
|
||||
import externalLinks from './src/lib/external-links.ts';
|
||||
import responsiveTables from './src/lib/responsive-tables.ts';
|
||||
import noticeShortcode from './src/lib/notice-shortcode.ts';
|
||||
import { siteConfig } from './src/site.config.ts';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { copyFileSync, readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const rootGif = resolve(process.cwd(), '88x31.gif');
|
||||
|
||||
const copyRootGif = {
|
||||
name: 'copy-root-gif',
|
||||
hooks: {
|
||||
'astro:build:done': ({ dir }) => {
|
||||
copyFileSync(rootGif, resolve(fileURLToPath(dir), '88x31.gif'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let githubData = {};
|
||||
try {
|
||||
@@ -20,7 +34,7 @@ try {
|
||||
|
||||
export default defineConfig({
|
||||
site: siteConfig.site.url,
|
||||
integrations: [sitemap()],
|
||||
integrations: [sitemap(), copyRootGif],
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
},
|
||||
@@ -34,7 +48,7 @@ export default defineConfig({
|
||||
},
|
||||
processor: unified({
|
||||
remarkPlugins: [remarkMath, [shortcodes, { githubData, siteUrl: siteConfig.site.url }]],
|
||||
rehypePlugins: [rehypeKatex, [externalLinks, { siteUrl: siteConfig.site.url }]]
|
||||
rehypePlugins: [rehypeKatex, noticeShortcode, responsiveTables, [externalLinks, { siteUrl: siteConfig.site.url }]]
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
RiseForever
|
||||
RSV
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
RiseForever
|
||||
RSV
|
||||
@@ -0,0 +1,2 @@
|
||||
RiseForever
|
||||
RSV
|
||||
@@ -1,17 +1,33 @@
|
||||
---
|
||||
import { siteConfig } from '@/site.config';
|
||||
import lightSticker from '@/images/Written-By-Human-white.svg';
|
||||
import darkSticker from '@/images/Written-By-Human-black.svg';
|
||||
|
||||
const containsChinese = (value: string) => /[\u3400-\u9fff]/.test(value);
|
||||
const blogDomain = new URL(siteConfig.site.url).hostname;
|
||||
const blogsClubBadge = `https://www.blogsclub.org/badge/${blogDomain}/theme`;
|
||||
---
|
||||
|
||||
<footer class="w-full border-t border-line bg-raised px-5 py-5 text-center font-mono text-xs leading-6 text-subtle" data-pagefind-ignore="all">
|
||||
<div class="mx-auto max-w-[73.125rem]">
|
||||
|
||||
<!-- 页脚标签 Start
|
||||
<div class="footer-badges">
|
||||
<a href="https://notbyai.fyi" target="_blank" rel="noreferrer" aria-label="Not By AI">
|
||||
<img class="footer-badge-image" src={lightSticker.src} data-light-src={lightSticker.src} data-dark-src={darkSticker.src} width="131" height="42" alt="Not By AI" />
|
||||
</a>
|
||||
<a href="https://www.blogsclub.org/rank.html" target="_blank" rel="noreferrer" aria-label="博阅榜">
|
||||
<img class="footer-badge-image blogs-club-badge" src={`${blogsClubBadge}/simple`} data-light-src={`${blogsClubBadge}/simple`} data-dark-src={`${blogsClubBadge}/simple-dark`} height="42" alt="博阅榜" loading="lazy" />
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
|
||||
{siteConfig.footer.links.length > 0 && <nav aria-label="页脚导航">{siteConfig.footer.links.map((item, index) => <Fragment>{index > 0 && ' · '}<a data-text-link href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined}><span class:list={{ 'footer-chinese': containsChinese(item.label) }} lang={containsChinese(item.label) ? 'zh-CN' : undefined}>{item.label}</span></a></Fragment>)}</nav>}
|
||||
{siteConfig.footer.copyright && <p class="m-0">{siteConfig.footer.copyright}</p>}
|
||||
<p class="m-0">Powered By Astro · Theme <a data-text-link href="https://src.luming.cool/riseforever2026/Eidolon">Eidolon</a></p>
|
||||
{siteConfig.footer.copyright && <p class="m-0">{siteConfig.footer.copyright}</p>}
|
||||
{siteConfig.footer.note && <p class="footer-chinese m-0" lang="zh-CN">{siteConfig.footer.note}</p>}
|
||||
{siteConfig.footer.theme && <p class="m-0"><a data-text-link href={siteConfig.footer.theme.href} target={siteConfig.footer.theme.external ? '_blank' : undefined} rel={siteConfig.footer.theme.external ? 'noreferrer' : undefined}>{siteConfig.footer.theme.label}</a></p>}
|
||||
<p class="m-0">Powered By Astro. Theme <a data-text-link href="https://src.luming.cool/riseforever2026/Eidolon">Eidolon</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
@@ -19,4 +35,33 @@ const containsChinese = (value: string) => /[\u3400-\u9fff]/.test(value);
|
||||
font-family: var(--mirages-font-sans);
|
||||
}
|
||||
|
||||
.footer-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-badges a { line-height: 0; }
|
||||
.footer-badge-image { display: block; }
|
||||
.blogs-club-badge { height: 42px !important; width: auto; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const root = document.documentElement;
|
||||
const systemTheme = matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const syncBadges = () => {
|
||||
const isDark = root.dataset.theme === 'dark' || (root.dataset.theme === 'auto' && systemTheme.matches);
|
||||
document.querySelectorAll<HTMLImageElement>('.footer-badge-image').forEach((badge) => {
|
||||
badge.src = isDark ? badge.dataset.darkSrc ?? badge.src : badge.dataset.lightSrc ?? badge.src;
|
||||
});
|
||||
};
|
||||
|
||||
syncBadges();
|
||||
systemTheme.addEventListener('change', syncBadges);
|
||||
const themeObserver = new MutationObserver(syncBadges);
|
||||
themeObserver.observe(root, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
document.addEventListener('astro:page-load', syncBadges);
|
||||
</script>
|
||||
|
||||
@@ -122,8 +122,8 @@ const position = banner?.position ?? siteConfig.banner.position;
|
||||
.masthead-separator { font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; }
|
||||
.masthead-date strong { font-family: var(--mirages-font-ui); font-weight: 400; }
|
||||
.masthead-category { font-family: var(--mirages-font-ui); }
|
||||
.about-avatar { animation: avatar-rotate 8s ease-in-out infinite alternate; }
|
||||
@keyframes avatar-rotate { from { transform: rotate(-2deg); } to { transform: rotate(2deg); } }
|
||||
.about-avatar { transform: rotate(0deg); transition: transform 0.5s cubic-bezier(0.33, 1, 0.68, 1); }
|
||||
.about-avatar:hover { transform: rotate(-32deg); }
|
||||
@media (min-width: 48rem) { section { height: var(--desktop-height); } }
|
||||
@media (prefers-reduced-motion: reduce) { .about-avatar { animation: none; transform: none; } }
|
||||
@media (prefers-reduced-motion: reduce) { .about-avatar { transition: none; transform: none; } .about-avatar:hover { transform: none; } }
|
||||
</style>
|
||||
|
||||
@@ -168,7 +168,6 @@ const { overlay = false } = Astro.props;
|
||||
.navbar-over-banner {
|
||||
border-color: color-mix(in srgb, var(--color-border) 70%, transparent);
|
||||
background: color-mix(in srgb, var(--color-bg) 82%, transparent);
|
||||
box-shadow: 0 2px 10px color-mix(in srgb, var(--color-text) 12%, transparent);
|
||||
}
|
||||
|
||||
.navbar-over-banner [data-navbar-tools] > [data-toolbar-item] > a:hover,
|
||||
|
||||
@@ -114,7 +114,6 @@
|
||||
min-height: 3px;
|
||||
max-height: 3px;
|
||||
background: var(--color-accent);
|
||||
box-shadow: 0 3px 8px rgb(0 0 0 / 35%);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
transition: transform 240ms linear;
|
||||
|
||||
@@ -187,7 +187,7 @@ const totalWordsLabel = totalWords >= 10_000
|
||||
const availableCellSize = (heatmapScroll.clientWidth - 3.25 * baseRootFontSize) / 53;
|
||||
|
||||
if (scale <= 1.01 || baseCellSize === 0) {
|
||||
baseCellSize = Math.min(12, Math.max(8, availableCellSize));
|
||||
baseCellSize = Math.max(8, availableCellSize);
|
||||
}
|
||||
heatmapGrid.style.setProperty('--heatmap-cell-size', `${baseCellSize * scale}px`);
|
||||
heatmapViewport?.classList.toggle('has-overflow', heatmapGrid.getBoundingClientRect().width > heatmapScroll.clientWidth + 1);
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
cover: "https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=80"
|
||||
title: "关于"
|
||||
pubDate: "2026-07-22T01:28:00.000Z"
|
||||
updatedDate: "2026-07-27T05:26:00.000Z"
|
||||
categories:
|
||||
tags:
|
||||
slug: "about"
|
||||
comments: false
|
||||
---
|
||||
|
||||
## 关于⌈我⌋
|
||||
|
||||
我是 RiseForever,生于河南。
|
||||
|
||||
准高二了。喜欢开发软件、科技数码。是一名 Xiaomi Vela 快应用开发者。
|
||||
|
||||
性格偏多疑敏感,善于察觉身边人的微小情绪。平时喜欢独处,不喜欢与任何人一起逛街、看电影,因为我感觉受到束缚。
|
||||
|
||||
如果你想知道我是一个怎样的人,请随便翻翻[我的文章](/archives),里面写的比我说的更清楚。
|
||||
|
||||
### 联系
|
||||
|
||||
电子邮箱:hi[at]riseforever.cn(请将“[at]”替换为“@”)
|
||||
|
||||
GitHub:[virelyx258](https://github.com/virelyx258)
|
||||
|
||||
请不要试图添加我的即时通讯账户。无论有什么想探讨的,随时欢迎电邮交流。
|
||||
|
||||
### 使用的工具
|
||||
|
||||
[tools title = "设计"]
|
||||
[Canva](https://www.canva.cn)+(https://image.luming.cool/i/2026/08/07/6a75ba231df9b.webp)/(图像设计)
|
||||
[即时设计](https://js.design)+(https://image.luming.cool/i/2026/08/07/6a75c0b9bbf29.webp)/(UI 设计)
|
||||
[Microsoft 365](https://m365.cloud.microsoft/)+(https://image.luming.cool/i/2026/08/07/6a75bd0318e8e.webp)/(文档处理)
|
||||
[剪映](https://www.capcut.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bf6a337d1.webp)/(视频剪辑)
|
||||
[/tools]
|
||||
|
||||
[tools title = "生产力"]
|
||||
[Chrome](https://google.cn/chrome/)+(https://image.luming.cool/i/2026/08/07/6a75c029d170b.webp)/(网页浏览)
|
||||
[Edge](https://explore.microsoft.com/zh-cn/edge?ep=2185&form=MA14LR&es=375&cs=2324297850)+(https://image.luming.cool/i/2026/08/07/6a75c06af3bd2.webp)/(网页浏览)
|
||||
[Typora](https://typoraio.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bfad4d232.webp)/(Markdown 编辑)
|
||||
|
||||
[/tools]
|
||||
|
||||
[tools title = "开发"]
|
||||
[AIoT IDE](https://iot.mi.com/vela/quickapp/)+(https://image.luming.cool/i/2026/08/07/6a75bd500971b.webp)/(快应用编写 & 调试)
|
||||
[VS Code](https://code.visualstudio.com/)+(https://image.luming.cool/i/2026/08/07/6a75c0ba31e58.webp)/(全能 IDE)
|
||||
[记事本](#)+(https://image.luming.cool/i/2026/08/07/6a75c16e34a58.webp)/(你懂的,真的便捷)
|
||||
[/tools]
|
||||
|
||||
[tools title = "环境"]
|
||||
[Windows 11](https://news.microsoft.com/windows11-general-availability/)+(https://image.luming.cool/i/2026/08/07/6a75c1d50d47d.webp)/(主力系统)
|
||||
[Debian 13](https://www.debian.org/index.zh-cn.html)+(https://image.luming.cool/i/2026/08/07/6a75c2db6a6e9.webp)/(备用系统)
|
||||
[/tools]
|
||||
|
||||
## 关于⌈这个网站⌋
|
||||
|
||||
严格来说,这个网站的建立时间是在 2024 年 10 月。因为我在那时开始转型记录生活。
|
||||
|
||||
有些话我没法对任何人说,于是就在这里写下。
|
||||
|
||||
### 域名
|
||||
|
||||
起初用的是 riseforever.cn,因为初中时我很想要一个有美好寓意的域名,想了很多,结果只有这个域名可注册。于是我选了它,网名也跟着取成了“RiseForever”。
|
||||
|
||||
初三时,我觉得 riseforever.cn 太长了,且 .CN 当时仍不支持隐私保护,便想着换个域名。当时朋友买了一个“mnb.cool”,让我看到了 .cool 这个后缀。过了几天,我在学校灵机一动——用自己的名字拼写“luming”做前缀,使用“.cool”后缀,这样搭配在视觉上非常和谐。
|
||||
|
||||
那周周末,我回家一搜 luming.cool ——未注册,于是赶紧注册了它。
|
||||
|
||||
| 注册商 | 帝思普(腾讯云) |
|
||||
| -------------: | :------------------------------------------------------------------------------------------------------------- |
|
||||
| 首次购入价格 | ¥48 |
|
||||
| 续费价格 | ¥65 |
|
||||
| 域名年龄 |  |
|
||||
|
||||
[collapse title="riseforever.cn 信息"]
|
||||
|
||||
| 注册商 | 云讯(腾讯云) |
|
||||
| -------------: | :---------------------------------------------------------------------------------------------------------------- |
|
||||
| 首次购入价格 | ¥33 |
|
||||
| 续费价格 | ¥38 |
|
||||
| 域名年龄 |  |
|
||||
|
||||
[/collapse]
|
||||
|
||||
目前保持着 riseforever.cn 和 luming.cool 双域名持有,每年续费大约需要¥103。
|
||||
|
||||
### 服务器
|
||||
|
||||
于 2026 年 7 月 21 日搬迁至 007IDC 的香港 BGP 线路服务器,经实测晚高峰也能跑满带宽 100 Mbps,三网回程经过优化,表现优异,河南联通 ping 延迟仅 31ms。
|
||||
|
||||
首月 9 元,后续叠加优惠,¥17.5/月。
|
||||
|
||||
| 厂商 | [007IDC](https://www.007idc.cn/aff/ZFIKNXMX) |
|
||||
| -----: | :--------------------------------------------- |
|
||||
| CPU | 2H Platinum 8272CL |
|
||||
| 内存 | 2G DDR4 |
|
||||
| 硬盘 | 30GB SSD |
|
||||
| 带宽 | 100 Mbps(峰值) |
|
||||
|
||||
[collapse title="2026 年 1 月 - 6 月"]
|
||||
7iNet 香港特惠秒杀机,¥9.9/月,炸夸响。后因其性能太过诡异而被更换。
|
||||
|
||||
| CPU | 2H E5-2698 v4 |
|
||||
| -----: | :-------------------------------------- |
|
||||
| 内存 | 2G |
|
||||
| 硬盘 | 40GB SSD(写入 43MB/s,读取 156MB/s) |
|
||||
| 带宽 | 10 Mbps(不限流量) |
|
||||
|
||||
[/collapse]
|
||||
|
||||
[collapse title="2026 年以前"]
|
||||
腾讯云新加坡服务器,¥99/年,买了两年,参加活动又送了三个月。后因 IP 线路更换导致延迟过高而被更换。
|
||||
|
||||
| CPU | 2H Platinum 8255C |
|
||||
| -----: | :--------------------------------------- |
|
||||
| 内存 | 2G |
|
||||
| 硬盘 | 50GB SSD(写入 338MB/s,读取 6.2GB/s) |
|
||||
| 带宽 | 30 Mbps(月限流 1TB) |
|
||||
|
||||
[/collapse]
|
||||
|
||||
### CDN
|
||||
|
||||
使用[毅融盾](https://rcdn.hydun.com/)进行图床加速。
|
||||
|
||||
毅融盾是融合了网宿与白山 CDN 资源的企业级 CDN,针对未备案域名进行特调,提供卓越的中国大陆访问速度。且没有三六九等的套餐配置,仅按流量计费,出售的是流量包。
|
||||
|
||||
毅融盾是我所在公司的产品,内部团队可免费使用,不限流量。如果你是外部用户,也可以通过[赞助计划](https://blog.hydun.com/9.html)免费获取 CDN 服务。
|
||||
|
||||
### 主题
|
||||
|
||||
使用自己制作的 Astro 主题 Eidolon。
|
||||
|
||||
它极大程度上复刻了 Typecho 主题 Mirages 的风格和功能,又在此基础上使用了更先进的 TailWind CSS,使得站点响应速度更加优秀。
|
||||
|
||||
~~因为 Mirages 是一款付费主题,如果贸然开源会影响到原主题的销售,且本站主题仍处于开发状态,所以本主题暂不开源。~~
|
||||
|
||||
仔细想想,发现我们其实不是同一个赛道。我做的是 Astro 主题,受众是 Astro 用户,不耽误 Typecho 主题的销售。况且,并不是所有 Typecho 用户都能无感切换到静态博客。
|
||||
|
||||
所以,本主题准备开源。
|
||||
|
||||
[collapse title="在此之前"]
|
||||
|
||||
使用 [Mirages](https://get233.com/archives/mirages-intro.html),一款阅读体验优秀的单栏 Typecho 主题。
|
||||
|
||||
我是在[白熊阿丸的小屋](https://blog.bxaw.name)首次体验这款主题,发现它支持用户手动切换衬线/非衬线字体、字体大小,这能使不同口味的用户都获得最适合自己的阅读体验。并且,这款主题在我的 MacBook Pro 2013 上表现得非常流畅。
|
||||
|
||||
于是在 2026 年 4 月,我选择购入并使用它。
|
||||
|
||||
[/collapse]
|
||||
|
||||
### 使用的第三方服务
|
||||
|
||||
使用 [WeAvatar](https://weavatar.com/) 作为评论区头像系统。
|
||||
|
||||
使用 [LUM ALBUM](https://image.luming.cool) 作为图床。(~~这也算第三方服务嘛~~)
|
||||
|
||||
### 备案
|
||||
|
||||
本站永远都不会考虑备案。
|
||||
|
||||
## 关于⌈我的作品⌋
|
||||
|
||||
[tools title = "Xiaomi Vela 快应用"]
|
||||
|
||||
[真智学](https://astrobox.online/open?source=resv2&id=cn.seedsoft.realzhixue&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c6781929d.webp)/(免费 · 智学网客户端)
|
||||
|
||||
[极物流](https://astrobox.online/open?source=resv2&id=cn.seedsoft.express&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c6910d03b.webp)/(订阅制 · 快递查询工具)
|
||||
|
||||
[Poster](https://astrobox.online/open?source=resv2&id=com.virelyx258.poster&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c7021f80e.webp)/(免费 · 网络请求工具)
|
||||
|
||||
[/tools]
|
||||
|
||||
[tools title = "开源项目"]
|
||||
|
||||
[RStatus](https://github.com/virelyx258/RStatus)+(https://image.luming.cool/i/2026/08/08/6a7729950adcd.webp)/(公开你的互联网在线状态)
|
||||
|
||||
[火苗调试器](https://github.com/virelyx258/Flame-Debugger)+(https://image.luming.cool/i/2026/08/08/6a772a0218e74.webp)/(适用于网课软件的调试工具)
|
||||
|
||||
[/tools]
|
||||
|
||||
## 关于⌈版权协议⌋
|
||||
|
||||
如果没有特殊说明,本站文章都是原创,基于 [CC-BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans) 协议公开。
|
||||
|
||||
转载请注明原文链接。[协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans)原则上不允许对本站文章的再混合、转换、或者二次创作。如果有必要二创,请邮件沟通。
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "友人"
|
||||
description: "本站收藏的一些精品中文博客"
|
||||
pubDate: "2026-07-22T01:28:00.000Z"
|
||||
cover: "https://images.unsplash.com/photo-1490730141103-6cac27aaab94?auto=format&fit=crop&w=1600&q=80"
|
||||
slug: "links"
|
||||
---
|
||||
|
||||
## 单向链接
|
||||
|
||||
[路明笔记](https://www.luming.cool)+(https://image.luming.cool/i/2026/05/10/6a001b4129893.png)/(一名高中生的技术和生活博客。)
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
title: "测试页面"
|
||||
description: "主题初始化后创建的测试页面。"
|
||||
pubDate: "2026-08-11T00:00:00.000Z"
|
||||
categories: []
|
||||
tags: []
|
||||
slug: "test-page"
|
||||
draft: false
|
||||
comments: false
|
||||
toc: true
|
||||
---
|
||||
|
||||
## 测试页面
|
||||
|
||||
这是主题初始化后创建的测试页面。
|
||||
|
||||
你可以在这里检查独立页面的标题、正文、链接和列表排版:
|
||||
|
||||
- [Markdown 排版测试文章](/posts/markdown-test/)
|
||||
- [Astro 官方网站](https://astro.build/)
|
||||
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: "Eidolon 主题基本使用教程"
|
||||
pubDate: "2026-08-13T00:25:27.180Z"
|
||||
categories: ['主题']
|
||||
tags: ['主题']
|
||||
draft: false
|
||||
---
|
||||
|
||||
欢迎使用 Eidolon,本文将带着您快速入手,使用 Eidolon 轻松定制您的个人博客。
|
||||
|
||||
---
|
||||
|
||||
## 站点基础信息
|
||||
|
||||
欲修改站点基础信息(如站点标题、Banner 大标题、副标题、背景图等),您需要编辑 `/src/site.config.ts`。
|
||||
|
||||
其中,`site`块代表站点总体配置。示例格式如下:
|
||||
|
||||
```ts
|
||||
site: {
|
||||
title: 'Eidolon',
|
||||
description: '一款轻量、简洁且美观的 Astro 主题',
|
||||
author: {
|
||||
name: 'Eidolon',
|
||||
avatar: 'https://weavatar.com/avatar/302380667bdaf4e1390800e62494d4af?s=512&r=G',
|
||||
bio: '一款轻量、简洁且美观的 Astro 主题',
|
||||
rotateAvatar: true
|
||||
},
|
||||
locale: 'zh-CN',
|
||||
url: 'https://www.luming.cool',
|
||||
favicon: 'https://image.luming.cool/i/2026/05/10/6a001b4129893.png'
|
||||
},
|
||||
```
|
||||
|
||||
每个字段的含义如下表:
|
||||
|
||||
| 字段 | 含义 | 类型 |
|
||||
| :------------------: | :----------------------------------------------: | :----: |
|
||||
| title | 站点名称,也作为显示在浏览器标签页的标题 | 文本型 |
|
||||
| description | 站点简介,也用于 SEO 描述 | 文本型 |
|
||||
| author: name | 博主昵称 | 文本型 |
|
||||
| author: avatar | 博主头像,填写图片链接 | 文本型 |
|
||||
| author: bio | 博主的个性签名 | 文本型 |
|
||||
| author: rotateAvatar | 是否开启关于页面的头像旋转动画 | 布尔型 |
|
||||
| locale | 站点语言 | 文本型 |
|
||||
| url | 站点的域名 | 文本型 |
|
||||
| favicon | 站点图标,同时作为浏览器标签页图标,填写图片链接 | 文本型 |
|
||||
|
||||
## 导航栏设置
|
||||
|
||||
`navigation`块代表导航项目。示例格式如下:
|
||||
|
||||
```ts
|
||||
navigation: [
|
||||
{ label: '首页', href: '/', external: false },
|
||||
{ label: '归档', href: '/archives/', external: false },
|
||||
{ label: '友人', href: '/links/', external: false },
|
||||
{ label: '关于', href: '/about/', external: false }
|
||||
],
|
||||
```
|
||||
|
||||
每个字段的含义如下表:
|
||||
|
||||
| 字段 | 含义 | 类型 |
|
||||
| :------: | :----------------------------------------------: | :----: |
|
||||
| label | 对外显示的导航名称 | 文本型 |
|
||||
| href | 点击后跳转的链接,支持站内相对地址和站外绝对地址 | 文本型 |
|
||||
| external | 是否为外部链接,它决定链接是否在新标签页打开 | 布尔型 |
|
||||
|
||||
[hint type="info" title="提示"]无论是顶栏还是侧栏,都有一个“分类”选项,以展示博客里存在的所有分类。该项目无法删除。[/hint]
|
||||
|
||||
## Pjax 无刷新加载
|
||||
|
||||
`pjax`块代表无刷新加载开关。示例代码:
|
||||
|
||||
```ts
|
||||
pjax: {
|
||||
enabled: true
|
||||
},
|
||||
```
|
||||
|
||||
本主题默认开启 Pjax 以获得更好的浏览体验。如果存在兼容性问题,可将 `enabled` 属性设置为 false 以关闭。
|
||||
|
||||
## 颜色设置
|
||||
|
||||
`appearance`块代表着主题的样式设置。包括深浅色模式和强调色。
|
||||
|
||||
示例代码如下:
|
||||
|
||||
```ts
|
||||
appearance: {
|
||||
defaultTheme: 'auto',
|
||||
accentColor: '#1abc9c'
|
||||
},
|
||||
```
|
||||
|
||||
每个字段的含义如下表:
|
||||
|
||||
| 字段 | 含义 | 类型 |
|
||||
| :----------: | :----------------------------------------------------------: | :-----------------: |
|
||||
| defaultTheme | 用户首次访问博客时,默认采用的主题类型。`auto`代表自动,`light`代表浅色主题,`sunset`代表日落主题,`dark`代表深色主题。 | 文本型 |
|
||||
| accentColor | 强调色。会影响部分手机浏览器,决定系统状态栏的氛围颜色。 | HEX颜色值(文本型) |
|
||||
|
||||
## Banner 设置
|
||||
|
||||
`Banner`块代表首页的 Banner 设置。示例代码如下:
|
||||
|
||||
```ts
|
||||
banner: {
|
||||
enabled: true,
|
||||
title: 'Eidolon',
|
||||
subtitle: '一款轻量、简洁且美观的 Astro 主题',
|
||||
image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=2000&q=85',
|
||||
position: 'center center',
|
||||
desktopHeightVh: 55,
|
||||
mobileHeightVh: 40,
|
||||
overlay: 0.25,
|
||||
textTone: 'auto'
|
||||
},
|
||||
```
|
||||
|
||||
各字段含义如下表:
|
||||
|
||||
| 字段 | 含义 | 类型 |
|
||||
| :-------------: | :----------------------------------------------------------: | :----: |
|
||||
| enabled | 是否启用 Banner。如果设为 false,则下方所有项目都不起作用。 | 布尔型 |
|
||||
| title | 大标题 | 文本型 |
|
||||
| subtitle | 小标题 | 文本型 |
|
||||
| image | 背景图片,填写图片链接 | 文本型 |
|
||||
| position | 图片的对齐方式,填写格式为 “方向 方向”(方向 空格 方向) | 文本型 |
|
||||
| desktopHeightVh | 桌面端 Banner 高度占比,譬如上例代表 Banner 高度占页面可视高度的 55% | 数值型 |
|
||||
| mobileHeightVh | 移动端 Banner 高度占比 | 数值型 |
|
||||
| overlay | 横幅上方黑色遮罩的透明度。此功能是为了防止 Banner 图过亮导致遮挡文字。0 为完全没有遮罩,1 为暗到纯黑。 | 小数型 |
|
||||
| textTone | Banner 文字的色调。推荐保持 `auto`,这样 Banner 文字就能够根据图片的亮暗来调整文字的颜色。如果实在需要,可选 `light`、`Dark`。 | 文本型 |
|
||||
|
||||
## 文章卡片默认封面图
|
||||
|
||||
`cards`块代表文章卡片设置。示例代码如下:
|
||||
|
||||
```ts
|
||||
cards: {
|
||||
defaultCovers: ['https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=80']
|
||||
},
|
||||
```
|
||||
|
||||
其中,defaultCovers 是一个 JSON 数组,你可以往里面填写一个多个图片的地址。当有文章未设置封面,且文章里没有图片可被自动设为封面时,系统会从 defaultCovers 里随机抽选一个图片作为封面。
|
||||
|
||||
## 页脚
|
||||
|
||||
`footer`块代表页脚部分。示例代码如下:
|
||||
|
||||
```ts
|
||||
footer: {
|
||||
copyright: `© 2023-${new Date().getFullYear()} 你的名字`, // 记得把"你的名字"改成你自己的名字。
|
||||
// 外链(如果有)
|
||||
links: [
|
||||
{ label: 'BlogsClub', href: 'https://blogs.club', external: true }
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
首先,你需要把 Copyright 中的“你的名字”改为你自己的名字。
|
||||
|
||||
其次,如果你有外链需求(比如加入了某博客组织),你可以在 links 这个 JSON 数组里添加外链。`external`代表是否在新标签页打开。
|
||||
|
||||
## 评论系统
|
||||
|
||||
Eidolon 支持使用 Twikoo 作为评论系统。你只需要自行部署 Twikoo 云函数并获取 envID,将其填写到 comments: envID 中即可。
|
||||
|
||||
如果你不需要评论系统,只需将 comments: provider 改为 `none`,然后删除 comments 里剩余的子项即可。
|
||||
|
||||
## 顶栏/侧栏按钮自定义
|
||||
|
||||
Eidolon 支持自定义按钮。该按钮项目会在顶栏和侧栏同步显示。
|
||||
|
||||
示例代码如下:
|
||||
|
||||
```ts
|
||||
toolbarItems: [
|
||||
{ type: 'search', icon: 'search', name: '搜索文章' },
|
||||
{ type: 'rss', icon: 'rss', name: 'RSS 订阅', href: '/rss.xml' },
|
||||
{ type: 'settings', icon: 'settings', name: '阅读设置' }
|
||||
]
|
||||
```
|
||||
|
||||
其中,`search`、`rss`、`settings`这三个 type 都是系统自带的按钮类型,分别对应搜索按钮、RSS 按钮、阅读设置按钮。其分别具有独立的功能,不建议删除。
|
||||
|
||||
如果你要新增带图标的外链按钮,需要经历以下这几步:
|
||||
|
||||
### 选择图标
|
||||
|
||||
Eidolon 使用 [Lucide](https://lucide.dev/icons/) 图标库。先前往该站点选择你要使用的图标,并将其名称复制下来。
|
||||
|
||||
### 注册图标名称
|
||||
|
||||
编辑 `src/types/config.ts`,第 18 行,有一个
|
||||
|
||||
```ts
|
||||
export type ToolbarIcon = 'search' | 'rss' | 'settings' | 'link' | 'tram-front';
|
||||
```
|
||||
|
||||
只需要将你要添加的图标名称,规范地加在 ToolbarIcon 后面即可。
|
||||
|
||||
譬如我新增了一个名为 check 的图标,那么我修改后的代码就是:
|
||||
|
||||
```ts
|
||||
export type ToolbarIcon = 'search' | 'rss' | 'settings' | 'link' | 'tram-front' | 'check';
|
||||
```
|
||||
|
||||
### 导入并建立映射
|
||||
|
||||
编辑 `/src/components/ToolbarIcon.astro`,第 2 行
|
||||
|
||||
```ts
|
||||
import { Link, Rss, Search, TramFront, Type } from '@lucide/astro';
|
||||
```
|
||||
|
||||
在这个数组的最前方添加你的图标名称。同上例,如果我要添加名为 check 的图标,那么实际代码就是:
|
||||
|
||||
```ts
|
||||
import { Check, Link, Rss, Search, TramFront, Type } from '@lucide/astro';
|
||||
```
|
||||
|
||||
是的,在第 2 行添加图标名时,需要将图标名首字母大写。
|
||||
|
||||
接下来看到第 7 行:
|
||||
|
||||
```ts
|
||||
const icons = { link: Link, rss: Rss, search: Search, settings: Type, 'tram-front': TramFront } as const;
|
||||
```
|
||||
|
||||
只需要在数组的末端加入`图标名(首字母小写): 图标名(首字母大写)`,即可。
|
||||
|
||||
同上例,加入 check 这个图标后,代码应为:
|
||||
|
||||
```ts
|
||||
const icons = { link: Link, rss: Rss, search: Search, settings: Type, 'tram-front': TramFront, check: Check } as const;
|
||||
```
|
||||
|
||||
### 添加按钮
|
||||
|
||||
回到 site.config.ts,在 toolbarItems 里加入自定义链接按钮。
|
||||
|
||||
譬如我添加的是开往,图标名是`tram-front`,那么我的代码就是这样的:
|
||||
|
||||
```json
|
||||
{ type: 'link', icon: 'tram-front', name: '开往', href: 'https://www.travellings.cn/plain.html', external: true },
|
||||
```
|
||||
|
||||
其中,type: 'link' 定义了这是一个自定义链接;icon 制定了我想要的图标;name 就是鼠标悬浮之上时显示的气泡标题;href 即自定义外链跳转的的地址;external 即是否在新标签页中打开。
|
||||
|
||||
---
|
||||
|
||||
至此,Eidolon 的自定义教程结束。
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
title: "Markdown 排版测试"
|
||||
description: "用于检查主题中常见 Markdown 元素的完整排版效果。"
|
||||
pubDate: "2026-08-11T00:00:00.000Z"
|
||||
categories: ['测试分类']
|
||||
tags: ['测试标签']
|
||||
slug: "markdown-test"
|
||||
draft: false
|
||||
comments: false
|
||||
math: true
|
||||
mermaid: true
|
||||
toc: true
|
||||
---
|
||||
|
||||
# 一级标题
|
||||
|
||||
这是一篇用于检查主题排版的测试文章,涵盖常用 Markdown、GFM、数学公式和 Mermaid 图表。
|
||||
|
||||
## 二级标题
|
||||
|
||||
### 三级标题
|
||||
|
||||
#### 四级标题
|
||||
|
||||
##### 五级标题
|
||||
|
||||
###### 六级标题
|
||||
|
||||
## 文本样式
|
||||
|
||||
普通文本、**粗体文本**、*斜体文本*、***粗斜体文本***、~~删除线文本~~、`行内代码`。
|
||||
|
||||
这是第一行,行末使用两个空格强制换行。
|
||||
这是第二行。
|
||||
|
||||
特殊字符可以转义:\*星号\*、\# 井号、\[方括号\]。
|
||||
|
||||
## 链接与图片
|
||||
|
||||
这是[行内链接](https://astro.build/),这是使用引用的[参考链接][astro],也可以自动识别 <https://astro.build/> 和 <mail@example.com>。
|
||||
|
||||

|
||||
|
||||
[astro]: https://astro.build/ "Astro"
|
||||
|
||||
## 引用
|
||||
|
||||
> 这是一段引用。
|
||||
>
|
||||
> > 这是嵌套引用,其中包含 **粗体** 和 `代码`。
|
||||
|
||||
## 列表
|
||||
|
||||
- 无序列表第一项
|
||||
- 无序列表第二项
|
||||
- 二级列表
|
||||
- 三级列表
|
||||
|
||||
1. 有序列表第一项
|
||||
2. 有序列表第二项
|
||||
1. 二级有序列表
|
||||
2. 另一项
|
||||
|
||||
- [x] 已完成任务
|
||||
- [ ] 未完成任务
|
||||
|
||||
## 代码
|
||||
|
||||
行内代码示例:`const message = 'Hello, Astro';`。
|
||||
|
||||
```ts
|
||||
interface Post {
|
||||
title: string;
|
||||
published: boolean;
|
||||
}
|
||||
|
||||
const post: Post = {
|
||||
title: 'Markdown 排版测试',
|
||||
published: true
|
||||
};
|
||||
|
||||
console.log(post.title);
|
||||
```
|
||||
|
||||
```diff
|
||||
- const theme = 'old';
|
||||
+ const theme = 'Eidolon';
|
||||
```
|
||||
|
||||
这是使用四个空格缩进的代码块。
|
||||
|
||||
## 表格
|
||||
|
||||
| 左对齐 | 居中 | 右对齐 |
|
||||
| :--- | :---: | ---: |
|
||||
| 文本 | **粗体** | 100 |
|
||||
| `code` | [链接](https://astro.build/) | 200 |
|
||||
|
||||
## 分隔线
|
||||
|
||||
---
|
||||
|
||||
分隔线前后的正文用于检查垂直间距。
|
||||
|
||||
## 脚注
|
||||
|
||||
这句话包含一个脚注。[^note]
|
||||
|
||||
[^note]: 这是脚注内容,其中也可以包含 **Markdown**。
|
||||
|
||||
## 数学公式
|
||||
|
||||
行内公式:$E = mc^2$。
|
||||
|
||||
块级公式:
|
||||
|
||||
$$
|
||||
\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}
|
||||
$$
|
||||
|
||||
## Mermaid 图表
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Markdown] --> B{渲染成功?}
|
||||
B -->|是| C[完成]
|
||||
B -->|否| D[检查样式]
|
||||
```
|
||||
|
||||
## HTML 元素
|
||||
|
||||
<details>
|
||||
<summary>展开原生 HTML 内容</summary>
|
||||
<p>这是一段位于 details 元素中的内容。</p>
|
||||
</details>
|
||||
|
||||
<kbd>Ctrl</kbd> + <kbd>K</kbd>
|
||||
|
||||
## 主题短代码
|
||||
|
||||
[hint type="info" title="信息提示"]
|
||||
这是一个信息提示块,包含 **Markdown 文本**。
|
||||
[/hint]
|
||||
|
||||
[collapse title="折叠内容"]
|
||||
这里是折叠区域中的内容。
|
||||
|
||||
- 列表项目
|
||||
- 另一项目
|
||||
[/collapse]
|
||||
|
||||
[tabs title="选项卡测试"]
|
||||
[tab name="选项一" selected]
|
||||
第一个选项卡的内容。
|
||||
[/tab]
|
||||
[tab name="选项二"]
|
||||
第二个选项卡的内容。
|
||||
[/tab]
|
||||
[/tabs]
|
||||
|
||||
[button href="https://astro.build/"]访问 Astro[/button]
|
||||
|
||||
[tag type="success"]成功标签[/tag]
|
||||
|
||||
## 段落收尾
|
||||
|
||||
最后一段用于观察文章底部留白、正文行高以及中英文混排效果。The quick brown fox jumps over the lazy dog. 0123456789。
|
||||
@@ -10,12 +10,11 @@ import NavigationProgress from '@/components/NavigationProgress.astro';
|
||||
import { ClientRouter } from 'astro:transitions';
|
||||
import '../styles/global.css';
|
||||
|
||||
interface Props { title?: string; description?: string; image?: string; type?: 'website' | 'article'; noindex?: boolean; math?: boolean; navbarOverlay?: boolean; }
|
||||
const { title, description, image, type, noindex, math, navbarOverlay = false } = Astro.props;
|
||||
const hasMasthead = Astro.slots.has('masthead');
|
||||
interface Props { title?: string; description?: string; image?: string; type?: 'website' | 'article'; noindex?: boolean; math?: boolean; navbarOverlay?: boolean; hasMasthead?: boolean; }
|
||||
const { title, description, image, type, noindex, math, navbarOverlay = false, hasMasthead = Astro.slots.has('masthead') } = Astro.props;
|
||||
const appearanceScript = `(function(){var r=document.documentElement,t='mirages-theme',s='mirages-font-scale',f='mirages-font-family',themes=['auto','light','sunset','dark'],fonts=['serif','sans'],stored=Number(localStorage.getItem(s)),scale=stored>=.8&&stored<=1.5?stored*100:stored>=80&&stored<=150?stored:100,theme=localStorage.getItem(t),font=localStorage.getItem(f);scale=Math.round(scale/5)*5;r.dataset.theme=themes.indexOf(theme)>-1?theme:'${siteConfig.appearance.defaultTheme}';r.dataset.font=fonts.indexOf(font)>-1?font:'sans';r.dataset.platform=/Win/.test(navigator.userAgentData?.platform||navigator.platform)?'windows':'other';r.style.fontSize=scale+'%'})();`;
|
||||
const tabsScript = `document.addEventListener('click',function(event){var button=event.target.closest('[data-shortcode-tab]');if(!button)return;var group=button.closest('[data-shortcode-tabs]');var id=button.dataset.shortcodeTab;group.querySelectorAll('[data-shortcode-tab="'+id+'"]').forEach(function(tab){var active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;var panel=group.querySelector('#'+tab.getAttribute('aria-controls'));if(panel)panel.hidden=!active;});});`;
|
||||
const collapseScript = `document.querySelectorAll('.shortcode-collapse').forEach(function(details){details.dataset.collapseReady='';details.classList.toggle('is-expanded',details.open);var body=details.querySelector(':scope>.shortcode-body');if(body instanceof HTMLElement&&details.open)body.style.height='auto';});document.addEventListener('click',function(event){var summary=event.target.closest('.shortcode-collapse>summary');if(!summary)return;var details=summary.parentElement;if(!(details instanceof HTMLDetailsElement))return;event.preventDefault();var body=details.querySelector(':scope>.shortcode-body');if(!(body instanceof HTMLElement))return;var expand=!details.classList.contains('is-expanded');details.classList.toggle('is-expanded',expand);if(matchMedia('(prefers-reduced-motion: reduce)').matches){details.open=expand;body.style.height=expand?'auto':'';delete details.dataset.animating;return;}var height=body.getBoundingClientRect().height;details.open=true;body.style.height=height+'px';void body.offsetHeight;details.dataset.animating='true';body.style.height=(expand?body.scrollHeight:0)+'px';});document.addEventListener('transitionend',function(event){var body=event.target;if(!(body instanceof HTMLElement)||!body.classList.contains('shortcode-body')||event.propertyName!=='height')return;var details=body.parentElement;if(!(details instanceof HTMLDetailsElement))return;var expanded=details.classList.contains('is-expanded');details.open=expanded;body.style.height=expanded?'auto':'';delete details.dataset.animating;});`;
|
||||
const tabsScript = `if(!window.__tabsInit){window.__tabsInit=true;document.addEventListener('click',function(event){var button=event.target.closest('[data-shortcode-tab]');if(!button)return;var group=button.closest('[data-shortcode-tabs]');var id=button.dataset.shortcodeTab;group.querySelectorAll('[data-shortcode-tab=\"'+id+'\"]').forEach(function(tab){var active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;var panel=group.querySelector('#'+tab.getAttribute('aria-controls'));if(panel)panel.hidden=!active;});});}`;
|
||||
const collapseScript = `function initCollapse(){document.querySelectorAll('.shortcode-collapse').forEach(function(details){details.dataset.collapseReady='';details.classList.toggle('is-expanded',details.open);var body=details.querySelector(':scope>.shortcode-body');if(body instanceof HTMLElement&&details.open)body.style.height='auto';});}initCollapse();document.addEventListener('astro:page-load',initCollapse);document.addEventListener('click',function(event){var summary=event.target.closest('.shortcode-collapse>summary');if(!summary)return;var details=summary.parentElement;if(!(details instanceof HTMLDetailsElement))return;event.preventDefault();var body=details.querySelector(':scope>.shortcode-body');if(!(body instanceof HTMLElement))return;var expand=!details.classList.contains('is-expanded');details.classList.toggle('is-expanded',expand);if(matchMedia('(prefers-reduced-motion: reduce)').matches){details.open=expand;body.style.height=expand?'auto':'';delete details.dataset.animating;return;}var height=body.getBoundingClientRect().height;details.open=true;body.style.height=height+'px';void body.offsetHeight;details.dataset.animating='true';body.style.height=(expand?body.scrollHeight:0)+'px';});document.addEventListener('transitionend',function(event){var body=event.target;if(!(body instanceof HTMLElement)||!body.classList.contains('shortcode-body')||event.propertyName!=='height')return;var details=body.parentElement;if(!(details instanceof HTMLDetailsElement))return;var expanded=details.classList.contains('is-expanded');details.open=expanded;body.style.height=expanded?'auto':'';delete details.dataset.animating;});`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -51,10 +50,35 @@ const hasMasthead = Astro.slots.has('masthead');
|
||||
<script>
|
||||
import pangu from 'pangu/browser';
|
||||
|
||||
const applyPanguSpacing = () => pangu.spacingPage();
|
||||
const applyPanguSpacing = () => {
|
||||
document.querySelectorAll('pre, code, .line').forEach((el) => el.classList.add('no-pangu-spacing'));
|
||||
document.querySelectorAll('.prose p, .prose li, .prose blockquote, .prose h1, .prose h2, .prose h3, .prose h4').forEach((el) => {
|
||||
pangu.spacingNode(el);
|
||||
});
|
||||
};
|
||||
|
||||
applyPanguSpacing();
|
||||
document.addEventListener('astro:page-load', applyPanguSpacing);
|
||||
|
||||
const initCodeLineNumbers = () => {
|
||||
document.querySelectorAll<HTMLElement>('.prose .astro-code').forEach((block) => {
|
||||
if (block.querySelector(':scope > .code-line-numbers')) return;
|
||||
const lines = block.querySelectorAll(':scope > code > .line');
|
||||
if (!lines.length) return;
|
||||
const gutter = document.createElement('span');
|
||||
gutter.className = 'code-line-numbers';
|
||||
gutter.setAttribute('aria-hidden', 'true');
|
||||
for (let index = 1; index <= lines.length; index += 1) {
|
||||
const number = document.createElement('span');
|
||||
number.textContent = String(index);
|
||||
gutter.append(number);
|
||||
}
|
||||
block.append(gutter);
|
||||
});
|
||||
};
|
||||
|
||||
initCodeLineNumbers();
|
||||
document.addEventListener('astro:page-load', initCodeLineNumbers);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
const NOTICE_ICON = {
|
||||
type: 'element',
|
||||
tagName: 'span',
|
||||
properties: { className: ['shortcode-notice'], ariaLabel: '警告' },
|
||||
children: [{
|
||||
type: 'element',
|
||||
tagName: 'svg',
|
||||
properties: {
|
||||
ariaHidden: 'true',
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
children: [
|
||||
{ type: 'element', tagName: 'circle', properties: { cx: 12, cy: 12, r: 10 }, children: [] },
|
||||
{ type: 'element', tagName: 'path', properties: { d: 'M12 8v4' }, children: [] },
|
||||
{ type: 'element', tagName: 'path', properties: { d: 'M12 16h.01' }, children: [] },
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
function cloneNoticeIcon() {
|
||||
return structuredClone(NOTICE_ICON);
|
||||
}
|
||||
|
||||
export function applyNoticeShortcodes(tree: any) {
|
||||
const transform = (node: any) => {
|
||||
if (!Array.isArray(node.children) || node.tagName === 'code' || node.tagName === 'pre') return;
|
||||
const output: any[] = [];
|
||||
for (const child of node.children) {
|
||||
if (child.type !== 'text' || !child.value.includes('[!/]')) {
|
||||
transform(child);
|
||||
output.push(child);
|
||||
continue;
|
||||
}
|
||||
const parts = child.value.split('[!/]');
|
||||
parts.forEach((part: string, index: number) => {
|
||||
if (part) output.push({ type: 'text', value: part });
|
||||
if (index < parts.length - 1) output.push({ type: 'text', value: '\u00a0' }, cloneNoticeIcon(), { type: 'text', value: '\u00a0' });
|
||||
});
|
||||
}
|
||||
node.children = output;
|
||||
};
|
||||
transform(tree);
|
||||
}
|
||||
|
||||
export default function noticeShortcode() {
|
||||
return (tree: any) => applyNoticeShortcodes(tree);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { SKIP, visit } from 'unist-util-visit';
|
||||
|
||||
export function applyResponsiveTables(tree: any) {
|
||||
visit(tree, 'element', (node: any, index: number | undefined, parent: any) => {
|
||||
if (node.tagName !== 'table' || index === undefined || !parent) return;
|
||||
const classes = Array.isArray(node.properties?.className) ? node.properties.className : [];
|
||||
if (classes.includes('responsive-table')) return;
|
||||
|
||||
const section = node.children?.find((child: any) => child.tagName === 'thead' || child.tagName === 'tbody');
|
||||
const row = section?.children?.find((child: any) => child.tagName === 'tr');
|
||||
const columns = Math.max(1, row?.children?.filter((child: any) => child.tagName === 'th' || child.tagName === 'td').length ?? 1);
|
||||
node.properties = { ...node.properties, className: [...classes, 'responsive-table'] };
|
||||
parent.children[index] = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['table-scroll'], style: `--table-columns:${columns}` },
|
||||
children: [node],
|
||||
};
|
||||
return SKIP;
|
||||
});
|
||||
}
|
||||
|
||||
export default function responsiveTables() {
|
||||
return (tree: any) => applyResponsiveTables(tree);
|
||||
}
|
||||
+81
-18
@@ -5,10 +5,18 @@ import { toHast } from 'mdast-util-to-hast';
|
||||
import { gfm } from 'micromark-extension-gfm';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import { applyExternalLinks } from './external-links';
|
||||
import { applyResponsiveTables } from './responsive-tables';
|
||||
import { applyNoticeShortcodes } from './notice-shortcode';
|
||||
|
||||
const URL_SCHEME = /^(https?:|mailto:|tel:)/i;
|
||||
const TAG_TYPES = new Set(['primary', 'success', 'warning', 'danger', 'info', 'default']);
|
||||
const HINT_TYPES = new Set(['warn', 'warning', 'error', 'danger', 'success', 'info']);
|
||||
const HINT_ICONS: Record<string, string> = {
|
||||
info: '<circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path>',
|
||||
warning: '<circle cx="12" cy="12" r="10"></circle><path d="M12 8v4"></path><path d="M12 16h.01"></path>',
|
||||
danger: '<path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 8.54 21h6.92a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path>',
|
||||
success: '<path d="m9 12 2 2 4-4"></path><circle cx="12" cy="12" r="10"></circle>',
|
||||
};
|
||||
const BLOCK_NAMES = new Set(['hint', 'tip', 'collapse', 'tabs', 'tools']);
|
||||
const INLINE_NAMES = new Set(['button', 'btn', 'file', 'tag', 'label']);
|
||||
type GithubData = { stars?: string; forks?: string; description?: string; commitDate?: string; htmlUrl?: string; defaultBranch?: string };
|
||||
@@ -61,12 +69,44 @@ function normalizeMarkdownHeadings(source: string): string {
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
function markdown(source: string, preserveHtml = false): string {
|
||||
function markdown(source: string, preserveHtml = true, lineNumbers = true): string {
|
||||
const normalized = normalizeMarkdownHeadings(source);
|
||||
const tree = fromMarkdown(normalized, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });
|
||||
transform(tree, normalized);
|
||||
if (!preserveHtml) visit(tree, 'html', (node: any) => { if (!node.data?.shortcode) node.value = ''; });
|
||||
const hast = toHast(tree, { allowDangerousHtml: true }) as any;
|
||||
if (lineNumbers) {
|
||||
visit(hast, 'element', (node: any) => {
|
||||
if (node.tagName !== 'pre') return;
|
||||
const code = node.children?.find((child: any) => child.type === 'element' && child.tagName === 'code');
|
||||
if (!code) return;
|
||||
const classes = Array.isArray(node.properties?.className) ? node.properties.className : [];
|
||||
if (!classes.includes('astro-code')) classes.push('astro-code');
|
||||
if (!classes.includes('shortcode-code')) classes.push('shortcode-code');
|
||||
if (!classes.includes('no-pangu-spacing')) classes.push('no-pangu-spacing');
|
||||
node.properties = { ...node.properties, className: classes };
|
||||
if (code.children?.every((child: any) => child.type === 'text')) {
|
||||
const value = code.children.map((child: any) => child.value).join('');
|
||||
const text = value.endsWith('\n') ? value.slice(0, -1) : value;
|
||||
const lines = text.split('\n');
|
||||
code.children = lines.map((line: string) => ({
|
||||
type: 'element',
|
||||
tagName: 'span',
|
||||
properties: { className: ['line'] },
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'span',
|
||||
properties: { className: ['line-content'] },
|
||||
children: line ? [{ type: 'text', value: line }] : [{ type: 'text', value: '\n' }],
|
||||
}
|
||||
],
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
applyNoticeShortcodes(hast);
|
||||
applyResponsiveTables(hast);
|
||||
if (siteUrl) applyExternalLinks(hast, siteUrl);
|
||||
return toHtml(hast, { allowDangerousHtml: true });
|
||||
}
|
||||
@@ -77,33 +117,41 @@ export function renderMarkdown(source: string): string {
|
||||
|
||||
function renderBlock(name: string, args: Record<string, string>, body = ''): string {
|
||||
const label = args.title ?? args.name ?? args.text ?? body.trim().split('\n')[0] ?? '';
|
||||
if (name === 'button' || name === 'btn' || name === 'file') {
|
||||
if (name === 'button' || name === 'btn') {
|
||||
const url = safeUrl(args.href ?? args.url ?? args.link);
|
||||
if (!url) return `<span class="shortcode-invalid">${escape(label || '链接不可用')}</span>`;
|
||||
return `<a class="shortcode-button shortcode-${name}" href="${escape(url)}" data-no-text-link>${inline(label || url)}</a>`;
|
||||
}
|
||||
if (name === 'file') {
|
||||
const url = safeUrl(args.href ?? args.url ?? args.link);
|
||||
if (!url) return `<span class="shortcode-invalid">${escape(label || '文件链接不可用')}</span>`;
|
||||
const icon = '<svg class="content-file-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><path d="M10 12v-1"></path><path d="M10 18v-2"></path><path d="M10 7v1"></path><path d="M10 15h.01"></path></svg>';
|
||||
return `<a class="shortcode-file content-file" href="${escape(url)}" target="_blank" rel="noopener noreferrer" data-no-text-link>${icon}<span class="content-file-filename">${inline(label || url)}</span></a>`;
|
||||
}
|
||||
if (name === 'tag' || name === 'label') {
|
||||
const type = TAG_TYPES.has(args.type ?? '') ? args.type : 'default';
|
||||
const outline = args.outline !== undefined ? ' shortcode-tag-outline' : '';
|
||||
return `<span class="shortcode-tag shortcode-tag-${type}${outline}">${inline(label)}</span>`;
|
||||
}
|
||||
if (name === 'hint' || name === 'tip') {
|
||||
const type = HINT_TYPES.has(args.type ?? '') ? args.type : 'info';
|
||||
const title = args.title ? `<strong>${inline(args.title)}</strong>` : '';
|
||||
return `<aside class="shortcode-hint shortcode-hint-${type}" role="note">${title}${markdown(body)}</aside>`;
|
||||
const requestedType = args.type ?? [...HINT_TYPES].find((candidate) => args[candidate] !== undefined);
|
||||
const type = requestedType === 'warn' ? 'warning' : requestedType === 'error' ? 'danger' : HINT_TYPES.has(requestedType ?? '') ? requestedType : 'info';
|
||||
const icon = `<svg class="content-hint-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${HINT_ICONS[type]}</svg>`;
|
||||
const heading = args.title ? `<div class="content-hint-heading">${icon}<p class="content-hint-title">${inline(args.title)}</p></div>` : icon;
|
||||
return `<aside class="shortcode-hint content-hint shortcode-${name} shortcode-hint-${type} hint-${type}${args.title ? ' content-hint-has-title' : ''}" role="note">${heading}${markdown(body, true, true)}</aside>`;
|
||||
}
|
||||
if (name === 'collapse') {
|
||||
const open = args.open !== undefined || args.expanded !== undefined ? ' open' : '';
|
||||
return `<details class="shortcode-collapse"${open}><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body, true)}</div></div></details>`;
|
||||
return `<details class="shortcode-collapse"${open}><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body, true, true)}</div></div></details>`;
|
||||
}
|
||||
if (name === 'tabs') {
|
||||
const parsed = parseBlocks(body);
|
||||
const tabs = parsed.filter((item) => item.name === 'tab');
|
||||
if (!tabs.length) return `<details class="shortcode-collapse"><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body)}</div></div></details>`;
|
||||
if (!tabs.length) return `<details class="shortcode-collapse"><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body, true, true)}</div></div></details>`;
|
||||
const id = `shortcode-tabs-${tabs.length}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const selected = Math.max(0, tabs.findIndex((tab) => tab.args.selected !== undefined));
|
||||
const buttons = tabs.map((tab, index) => `<button type="button" role="tab" aria-controls="${id}-panel-${index}" aria-selected="${index === selected}" id="${id}-tab-${index}" data-shortcode-tab="${id}"${index === selected ? '' : ' tabindex="-1"'}>${inline(tab.args.name ?? tab.args.title ?? `选项 ${index + 1}`)}</button>`).join('');
|
||||
const panels = tabs.map((tab, index) => `<div role="tabpanel" id="${id}-panel-${index}" aria-labelledby="${id}-tab-${index}"${index === selected ? '' : ' hidden'}>${markdown(tab.body)}</div>`).join('');
|
||||
const panels = tabs.map((tab, index) => `<div role="tabpanel" id="${id}-panel-${index}" aria-labelledby="${id}-tab-${index}"${index === selected ? '' : ' hidden'}>${markdown(tab.body, true, true)}</div>`).join('');
|
||||
return `<section class="shortcode-tabs" data-shortcode-tabs="${id}"><div class="shortcode-tab-list" role="tablist" aria-label="${escape(label || '选项卡')}">${buttons}</div>${panels}</section>`;
|
||||
}
|
||||
if (name === 'tools') {
|
||||
@@ -151,8 +199,28 @@ function renderBlock(name: string, args: Record<string, string>, body = ''): str
|
||||
|
||||
function parseBlocks(source: string): Array<{ name: string; args: Record<string, string>; body: string }> {
|
||||
const result: Array<{ name: string; args: Record<string, string>; body: string }> = [];
|
||||
const pattern = /^\[tab([^\]]*)\]\s*\n?([\s\S]*?)^\[\/tab\]\s*$/gm;
|
||||
for (const match of source.matchAll(pattern)) result.push({ name: 'tab', args: attrs(match[1]), body: match[2] });
|
||||
let fence: string | null = null;
|
||||
let opening: { args: Record<string, string>; bodyStart: number } | null = null;
|
||||
for (const line of source.matchAll(/^[^\n]*(?:\n|$)/gm)) {
|
||||
const text = line[0].trim();
|
||||
const start = line.index!;
|
||||
const fenceMatch = text.match(/^(`{3,}|~{3,})/);
|
||||
if (fenceMatch) {
|
||||
if (!fence) fence = fenceMatch[1][0];
|
||||
else if (fenceMatch[1][0] === fence) fence = null;
|
||||
continue;
|
||||
}
|
||||
if (fence) continue;
|
||||
|
||||
const closing = text.match(/^\[\/tab\]\s*$/i);
|
||||
if (closing && opening) {
|
||||
result.push({ name: 'tab', args: opening.args, body: source.slice(opening.bodyStart, start) });
|
||||
opening = null;
|
||||
continue;
|
||||
}
|
||||
const openingMatch = text.match(/^\[tab([^\]]*)\]\s*$/i);
|
||||
if (openingMatch && !opening) opening = { args: attrs(openingMatch[1]), bodyStart: start + line[0].length };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -220,15 +288,9 @@ function findBlocks(source: string): Array<{ name: string; args: Record<string,
|
||||
}
|
||||
|
||||
function replaceInline(source: string): string | null {
|
||||
const noticePattern = /^\[!(?:\/\s*)?\]\s*(.+)$/gm;
|
||||
let replacedNotice = false;
|
||||
source = source.replace(noticePattern, (_match, body) => {
|
||||
replacedNotice = true;
|
||||
return `<span class="shortcode-notice" role="note">${inline(body)}</span>`;
|
||||
});
|
||||
const shortcode = /\[(button|btn|file|tag|label)([^\]]*)\]([\s\S]*?)\[\/\1\]/gi;
|
||||
const shortcode = /\[(button|btn|file|tag|label|hint|tip)([^\]]*)\]([\s\S]*?)\[\/\1\]/gi;
|
||||
const replacedShortcode = shortcode.test(source);
|
||||
if (!replacedNotice && !replacedShortcode) return null;
|
||||
if (!replacedShortcode) return null;
|
||||
return source.replace(shortcode, (_match, name, rawArgs, body) => renderBlock(name.toLowerCase(), attrs(rawArgs), body));
|
||||
}
|
||||
|
||||
@@ -268,6 +330,7 @@ function transform(tree: any, raw = '') {
|
||||
const output: any[] = [];
|
||||
for (const node of root.children) {
|
||||
if (node.type !== 'paragraph' && node.type !== 'html') { output.push(node); continue; }
|
||||
if (node.type === 'html' && node.data?.shortcode) { output.push(node); continue; }
|
||||
const source = node.type === 'html' ? node.value : node.position ? raw.slice(node.position.start.offset, node.position.end.offset) : '';
|
||||
const replaced = replaceInline(source.trim());
|
||||
output.push(replaced ? { type: 'html', value: replaced, data: { shortcode: true } } : node);
|
||||
|
||||
@@ -12,7 +12,7 @@ const tags = deriveTaxonomy(posts, 'tags')
|
||||
const visibleTags = tagLimit === 0 ? tags : tags.slice(0, Math.max(0, tagLimit));
|
||||
---
|
||||
<BaseLayout title="归档" description="按日期和主题整理的文章。">
|
||||
<Fragment slot="masthead"><Masthead banner={{ title: '归档', subtitle: '按日期和主题整理的文章。',image: 'https://image.luming.cool/i/2026/06/06/6a23f222a1641.webp', textTone: 'auto' }} /></Fragment>
|
||||
<Fragment slot="masthead"><Masthead banner={{ title: '归档', subtitle: '按日期和主题整理的文章。',image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=2000&q=85', textTone: 'auto' }} /></Fragment>
|
||||
<div class="article-shell archives-shell pt-8 pb-14 md:pt-10 md:pb-20">
|
||||
<WritingHeatmap posts={posts} />
|
||||
<section class="archives-tags" aria-labelledby="tag-cloud-title" data-pagefind-ignore="all">
|
||||
|
||||
@@ -8,7 +8,7 @@ const posts = await getPublicPosts();
|
||||
const total = Math.max(1, Math.ceil(posts.length / POSTS_PER_PAGE));
|
||||
---
|
||||
|
||||
<BaseLayout navbarOverlay={Boolean(siteConfig.banner.image)}>
|
||||
<Fragment slot="masthead"><Masthead /></Fragment>
|
||||
<BaseLayout hasMasthead={siteConfig.banner.enabled} navbarOverlay={siteConfig.banner.enabled && Boolean(siteConfig.banner.image)}>
|
||||
{siteConfig.banner.enabled && <Fragment slot="masthead"><Masthead /></Fragment>}
|
||||
<PostList posts={posts.slice(0, POSTS_PER_PAGE)} total={total} basePath="" />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import Masthead from '@/components/Masthead.astro';
|
||||
import { renderMarkdown } from '@/lib/shortcodes';
|
||||
|
||||
const page = (await getCollection('pages')).find((entry) => entry.data.slug === 'links');
|
||||
if (!page) throw new Error('Missing links page content.');
|
||||
type Link = { name: string; url: string; avatar: string; description: string };
|
||||
type Section = { name: string; links: Link[]; contentLines: string[] };
|
||||
const sections: Section[] = [{ name: '其他', links: [], contentLines: [] }];
|
||||
let section = sections[0];
|
||||
let shortcodeDepth = 0;
|
||||
const escapeHtml = (value: string) => value.replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character]!);
|
||||
const renderNestedCard = (link: Link) => `<div class="links-nested-card"><article class="link-card"><img class="link-avatar" src="${escapeHtml(link.avatar)}" alt="" width="32" height="32" loading="lazy"><div class="link-copy"><h3><a class="link-name" data-text-link href="${escapeHtml(link.url)}" target="_blank" rel="noreferrer">${escapeHtml(link.name)}</a></h3><p class="link-description">${escapeHtml(link.description)}</p></div></article></div>`;
|
||||
for (const line of (page.body ?? '').split(/\r?\n/)) {
|
||||
if (/^\s*\[(?:collapse|hint|tip|tabs)\b[^\]]*\]\s*$/i.test(line)) {
|
||||
shortcodeDepth += 1;
|
||||
section.contentLines.push(line);
|
||||
continue;
|
||||
}
|
||||
if (/^\s*\[\/(?:collapse|hint|tip|tabs)\]\s*$/i.test(line)) {
|
||||
shortcodeDepth = Math.max(0, shortcodeDepth - 1);
|
||||
section.contentLines.push(line);
|
||||
continue;
|
||||
}
|
||||
const heading = line.match(/^##\s+(.+?)\s*#*$/);
|
||||
if (heading && shortcodeDepth === 0) {
|
||||
section = { name: heading[1].trim(), links: [], contentLines: [] };
|
||||
sections.push(section);
|
||||
continue;
|
||||
}
|
||||
const link = line.match(/^\s*\[([^\]]+)\]\(([^)]+)\)\+\(([^)]+)\)\/\((.*)\)\s*$/)
|
||||
?? line.match(/^\s*\[([^\]]+)\]\(([^)]+)\)\+\(([^)]+)\)\/(.*?)\s*\)?$/);
|
||||
if (link && shortcodeDepth === 0) {
|
||||
section.links.push({ name: link[1], url: link[2], avatar: link[3], description: link[4] });
|
||||
continue;
|
||||
}
|
||||
section.contentLines.push(shortcodeDepth > 0 && link ? renderNestedCard({ name: link[1], url: link[2], avatar: link[3], description: link[4] }) : line);
|
||||
}
|
||||
if (!sections[0].links.length && !sections[0].contentLines.some((line) => line.trim())) sections.shift();
|
||||
---
|
||||
|
||||
<BaseLayout title={page.data.title} description={page.data.description} image={page.data.hero ?? page.data.cover} navbarOverlay={Boolean(page.data.hero ?? page.data.cover)}>
|
||||
<Fragment slot="masthead"><Masthead banner={{ title: page.data.title, subtitle: page.data.description, image: page.data.hero ?? page.data.cover, textTone: page.data.textTone }} /></Fragment>
|
||||
<section class="links-shell" aria-label="友链内容">
|
||||
{sections.map((group) => <section class="links-category" aria-labelledby={`links-category-${group.name}`}>
|
||||
<h2 id={`links-category-${group.name}`}>{group.name}</h2>
|
||||
{group.links.length > 0 && <ul class="links-list">
|
||||
{group.links.map((link) => <li class="link-entry">
|
||||
<article class="link-card">
|
||||
<img class="link-avatar" src={link.avatar} alt="" width="32" height="32" loading="lazy" />
|
||||
<div class="link-copy">
|
||||
<h3><a class="link-name" data-text-link href={link.url} target="_blank" rel="noreferrer">{link.name}</a></h3>
|
||||
<p class="link-description">{link.description}</p>
|
||||
</div>
|
||||
</article>
|
||||
</li>)}
|
||||
</ul>}
|
||||
{group.contentLines.some((line) => line.trim()) && <div class="links-content prose" set:html={renderMarkdown(group.contentLines.join('\n'))}></div>}
|
||||
</section>)}
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.links-shell { width: min(calc(100% - 2.5rem), var(--article-content-width)); margin-inline: auto; padding-block: 2rem 5rem; }
|
||||
.links-category + .links-category { margin-top: 2rem; }
|
||||
.links-category h2 { margin: 0 0 .75rem; padding-bottom: .5rem; border-bottom: 1px solid var(--color-border); font-size: 1.2rem; font-weight: 600; }
|
||||
.links-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .25rem; margin: 0; padding: 0; list-style: none; }
|
||||
.link-entry { min-width: 0; margin: 0; }
|
||||
.link-card { display: flex; align-items: flex-start; gap: .75rem; min-width: 0; padding: .75rem .65rem; border-radius: var(--radius-sm); transition: background-color var(--transition-fast); }
|
||||
.link-entry:hover { background: var(--color-archive-hover); }
|
||||
.link-avatar { width: 2rem; height: 2rem; flex: 0 0 2rem; border-radius: 50%; object-fit: cover; background: var(--color-surface); }
|
||||
.link-copy { min-width: 0; line-height: 1.7; overflow-wrap: anywhere; }
|
||||
.link-copy h3 { margin: 0; font-size: 1rem; font-weight: 600; line-height: 1.45; }
|
||||
.link-name { display: inline; color: var(--color-accent); text-decoration: none; }
|
||||
:global(:root[data-theme='sunset']) .link-name { color: color-mix(in srgb, var(--color-accent-strong) 82%, black); }
|
||||
.link-description, .link-note { margin: .2rem 0 0; color: color-mix(in srgb, var(--color-text) 82%, var(--color-muted)); font-size: .88rem; }
|
||||
.link-note { font-size: .86rem; }
|
||||
.links-content { margin-top: 2rem; }
|
||||
.links-content .links-nested-card { margin-block: 1rem; }
|
||||
.links-content .link-card { margin: 0; }
|
||||
.links-content .link-avatar { display: block; width: 2rem; height: 2rem; margin: 0; }
|
||||
.links-content .link-copy h3 { margin: 0; font-size: 1rem; line-height: 1.45; }
|
||||
.links-content .link-description { margin: .2rem 0 0; }
|
||||
@media (max-width: 37.5rem) {
|
||||
.links-shell { width: min(calc(100% - 1.75rem), var(--article-content-width)); padding-block: 2rem 3.5rem; }
|
||||
.links-list { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@ export async function getStaticPaths() {
|
||||
}
|
||||
const { posts, page, total } = Astro.props;
|
||||
---
|
||||
<BaseLayout title={`文章 - 第 ${page} 页`} navbarOverlay={Boolean(siteConfig.banner.image)}>
|
||||
<Fragment slot="masthead"><Masthead /></Fragment>
|
||||
<BaseLayout title={`文章 - 第 ${page} 页`} hasMasthead={siteConfig.banner.enabled} navbarOverlay={siteConfig.banner.enabled && Boolean(siteConfig.banner.image)}>
|
||||
{siteConfig.banner.enabled && <Fragment slot="masthead"><Masthead /></Fragment>}
|
||||
<PostList posts={posts} current={page} total={total} basePath="" />
|
||||
</BaseLayout>
|
||||
|
||||
+18
-20
@@ -2,12 +2,12 @@ import type { SiteConfig } from './types/config';
|
||||
|
||||
export const siteConfig: SiteConfig = {
|
||||
site: {
|
||||
title: '路明笔记',
|
||||
description: '一名高中生的技术与生活博客',
|
||||
title: 'Eidolon',
|
||||
description: '一款轻量、简洁且美观的 Astro 主题',
|
||||
author: {
|
||||
name: 'RiseForever',
|
||||
avatar: 'https://weavatar.com/avatar/302380667bdaf4e1390800e62494d4af?s=512&r=G',
|
||||
bio: '不慌张,不绝望,不狂妄,不投降。',
|
||||
name: 'Eidolon',
|
||||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=400&h=400&q=85',
|
||||
bio: '一款轻量、简洁且美观的 Astro 主题',
|
||||
rotateAvatar: true
|
||||
},
|
||||
locale: 'zh-CN',
|
||||
@@ -17,7 +17,8 @@ export const siteConfig: SiteConfig = {
|
||||
navigation: [
|
||||
{ label: '首页', href: '/', external: false },
|
||||
{ label: '归档', href: '/archives/', external: false },
|
||||
{ label: '测试页面', href: '/test-page/', external: false }
|
||||
{ label: '友人', href: '/links/', external: false },
|
||||
{ label: '关于', href: '/about/', external: false }
|
||||
],
|
||||
pjax: {
|
||||
enabled: true
|
||||
@@ -28,9 +29,9 @@ export const siteConfig: SiteConfig = {
|
||||
},
|
||||
banner: {
|
||||
enabled: true,
|
||||
title: '路明笔记',
|
||||
subtitle: '一名高中生的技术与生活博客',
|
||||
image: 'https://image.luming.cool/i/2026/07/26/6a6628967fe03.webp',
|
||||
title: 'Eidolon',
|
||||
subtitle: '一款轻量、简洁且美观的 Astro 主题',
|
||||
image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=2000&q=85',
|
||||
position: 'center center',
|
||||
desktopHeightVh: 55,
|
||||
mobileHeightVh: 40,
|
||||
@@ -38,34 +39,31 @@ export const siteConfig: SiteConfig = {
|
||||
textTone: 'auto'
|
||||
},
|
||||
cards: {
|
||||
defaultCovers: ['https://image.luming.cool/i/2026/08/03/6a6f79cc091ac.webp']
|
||||
defaultCovers: ['https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=80']
|
||||
},
|
||||
archives: {
|
||||
tagLimit: 30
|
||||
},
|
||||
footer: {
|
||||
copyright: `© 2023-${new Date().getFullYear()} RiseForever`,
|
||||
copyright: `© 2023-${new Date().getFullYear()} 你的名字`, // 记得把"你的名字"改成你自己的名字。
|
||||
// 外链(如果有)
|
||||
links: [
|
||||
{ label: 'BlogsClub', href: 'https://blogs.quest/luming', external: true },
|
||||
{ label: '笔墨迹', href: 'https://blogscn.fun/blogs/01k7zk4mhndsr6atqd1wm11f7b', external: true },
|
||||
{ label: '十年之约', href: 'https://www.foreverblog.cn/blog/6506.html', external: true },
|
||||
{ label: '博友圈', href: 'https://www.boyouquan.com/blogs/luming.cool', external: true },
|
||||
{ label: '好站网', href: 'https://haozhan.wang/site_detail.php?id=176', external: true },
|
||||
{ label: '集博栈', href: 'https://www.heyblog.net/site/019eff04-b025-76c9-b8f2-388deb7195cd', external: true },
|
||||
{ label: '博客大联盟', href: 'https://bo.ke/luming.cool/', external: true }
|
||||
{ label: 'BlogsClub', href: 'https://blogs.club', external: true }
|
||||
]
|
||||
},
|
||||
// 评论系统采用 Twikoo,请自行部署云函数后,并在下方填写你的云函数 URL
|
||||
comments: {
|
||||
provider: 'twikoo',
|
||||
envId: 'https://twokii.luming.cool/',
|
||||
envId: 'https://link.to.your.twikoo.service/',
|
||||
region: 'cn',
|
||||
lang: 'zh-CN'
|
||||
},
|
||||
// 搜索服务默认使用 PageFind,无需理会
|
||||
search: { provider: 'pagefind', placeholder: '搜索文章' },
|
||||
// 这是顶栏右上角及侧栏底部的工具按钮,可自定义。下方示例已涵盖了站内功能、RSS 复制、外链跳转等场景。
|
||||
toolbarItems: [
|
||||
{ type: 'search', icon: 'search', name: '搜索文章' },
|
||||
{ type: 'rss', icon: 'rss', name: 'RSS 订阅', href: '/rss.xml' },
|
||||
{ type: 'link', icon: 'tram-front', name: '开往', href: 'https://www.travellings.cn/plain.html', external: true },
|
||||
{ type: 'settings', icon: 'settings', name: '阅读设置' }
|
||||
]
|
||||
};
|
||||
|
||||
+134
-33
@@ -131,54 +131,88 @@
|
||||
font-family: var(--mirages-font-code);
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prose .astro-code {
|
||||
--code-line-digits: 1;
|
||||
--code-gutter-background: color-mix(in srgb, var(--color-code) 65%, var(--color-border));
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--color-code);
|
||||
}
|
||||
|
||||
.prose .astro-code code {
|
||||
--code-line-digits: 1;
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: none;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-block: 1rem;
|
||||
counter-reset: code-line;
|
||||
font-size: 0;
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent;
|
||||
scrollbar-width: thin;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.prose .astro-code code:has(.line:nth-child(10)) { --code-line-digits: 2; }
|
||||
.prose .astro-code code:has(.line:nth-child(100)) { --code-line-digits: 3; }
|
||||
.prose .astro-code code:has(.line:nth-child(1000)) { --code-line-digits: 4; }
|
||||
.prose .astro-code code::-webkit-scrollbar { height: 8px; }
|
||||
.prose .astro-code code::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); }
|
||||
.prose .astro-code code::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--color-muted) 78%, transparent); }
|
||||
|
||||
.prose .astro-code:has(.line:nth-child(10)) { --code-line-digits: 2; }
|
||||
.prose .astro-code:has(.line:nth-child(100)) { --code-line-digits: 3; }
|
||||
.prose .astro-code:has(.line:nth-child(1000)) { --code-line-digits: 4; }
|
||||
|
||||
.prose .astro-code .code-line-numbers {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
display: block;
|
||||
width: calc(var(--code-line-digits) * 1ch + 2rem);
|
||||
padding-block: 1rem;
|
||||
font-family: var(--mirages-font-code);
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
background: var(--code-gutter-background);
|
||||
border-right: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
||||
box-sizing: border-box;
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.prose .astro-code .code-line-numbers > span {
|
||||
display: block;
|
||||
height: 1.45em;
|
||||
padding-inline: 1rem;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.prose .astro-code .line {
|
||||
display: flex;
|
||||
display: block;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
min-height: 1.45em;
|
||||
padding-left: calc(var(--code-line-digits) * 1ch + 3rem);
|
||||
box-sizing: border-box;
|
||||
counter-increment: code-line;
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prose .astro-code .line::before {
|
||||
display: inline-block;
|
||||
width: calc(var(--code-line-digits) * 1ch + 2rem);
|
||||
flex: 0 0 calc(var(--code-line-digits) * 1ch + 2rem);
|
||||
margin-right: 1rem;
|
||||
padding-inline: 1rem;
|
||||
background: var(--color-code-gutter);
|
||||
box-sizing: border-box;
|
||||
color: var(--color-muted);
|
||||
content: counter(code-line);
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.72;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prose .astro-code .line:first-child::before {
|
||||
box-shadow: 0 -1rem var(--color-code-gutter);
|
||||
}
|
||||
|
||||
.prose .astro-code .line:last-child::before {
|
||||
box-shadow: 0 1rem var(--color-code-gutter);
|
||||
}
|
||||
|
||||
.prose .astro-code .line:only-child::before {
|
||||
box-shadow: 0 -1rem var(--color-code-gutter), 0 1rem var(--color-code-gutter);
|
||||
content: none;
|
||||
}
|
||||
|
||||
.prose .astro-code span {
|
||||
@@ -186,6 +220,8 @@
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .prose .astro-code {
|
||||
--code-gutter-background: var(--color-code-gutter);
|
||||
--shortcode-code-gutter: var(--color-code-gutter);
|
||||
background-color: var(--color-code);
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
@@ -196,6 +232,8 @@
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root[data-theme='auto'] .prose .astro-code {
|
||||
--code-gutter-background: var(--color-code-gutter);
|
||||
--shortcode-code-gutter: var(--color-code-gutter);
|
||||
background-color: var(--color-code);
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
@@ -205,9 +243,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
.prose .astro-code {
|
||||
.shortcode-code {
|
||||
--shortcode-code-gutter: color-mix(in srgb, var(--color-code) 65%, var(--color-border));
|
||||
padding: 0;
|
||||
background: var(--color-code);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shortcode-code code {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: none;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-block: 1rem;
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent;
|
||||
scrollbar-width: thin;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.shortcode-code code::-webkit-scrollbar { height: 8px; }
|
||||
.shortcode-code code::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); }
|
||||
.shortcode-code code::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--color-muted) 78%, transparent); }
|
||||
|
||||
.shortcode-code .line {
|
||||
display: block;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
min-height: 1.45em;
|
||||
padding-left: 4rem;
|
||||
box-sizing: border-box;
|
||||
font-size: .875rem;
|
||||
line-height: 1.45;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.shortcode-code .shortcode-line-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prose .katex-display { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding-block: .35em; }
|
||||
@@ -215,9 +291,25 @@
|
||||
.prose .mermaid { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding: 1rem 0; }
|
||||
.prose .mermaid svg { max-width: none; height: auto; }
|
||||
|
||||
.prose .table-scroll {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 1.25em 0;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-inline: contain;
|
||||
scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.prose .table-scroll::-webkit-scrollbar { height: 8px; }
|
||||
.prose .table-scroll::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); }
|
||||
|
||||
.prose table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
border-collapse: collapse;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.prose thead {
|
||||
@@ -239,6 +331,15 @@
|
||||
.prose :where(th, td) {
|
||||
padding: 0.55em 0.8em;
|
||||
border: 1px solid var(--color-border);
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.prose .responsive-table {
|
||||
width: max(100%, calc(var(--table-columns, 1) * 8rem));
|
||||
}
|
||||
}
|
||||
|
||||
.prose :where(th, td)[align='left'] { text-align: left; }
|
||||
|
||||
+116
-31
@@ -6,7 +6,7 @@
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-accent);
|
||||
color: var(--color-bg);
|
||||
font-family: var(--mirages-font-ui);
|
||||
font-family: var(--reading-font-family);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.prose .shortcode-button:hover {
|
||||
@@ -14,9 +14,54 @@
|
||||
color: var(--color-bg);
|
||||
}
|
||||
.prose .shortcode-file {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
min-height: 3.5rem;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0.25rem 0 0.25rem 0.5rem;
|
||||
overflow: hidden;
|
||||
padding: 0 1.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
font-family: var(--reading-font-family);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: border-color 0.6s ease;
|
||||
}
|
||||
.prose .shortcode-file:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.prose .content-file-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex: 0 0 1.5rem;
|
||||
color: var(--color-text);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.prose .content-file-filename {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--color-text);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.prose .shortcode-file:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.prose .shortcode-file:hover .content-file-icon,
|
||||
.prose .shortcode-file:hover .content-file-filename {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.prose .shortcode-file,
|
||||
.prose .content-file-icon,
|
||||
.prose .content-file-filename { transition: none; }
|
||||
}
|
||||
.shortcode-tag {
|
||||
display: inline-block;
|
||||
margin: 0.15em 0.3em 0.15em 0;
|
||||
@@ -24,7 +69,7 @@
|
||||
border-radius: 999px;
|
||||
background: var(--color-accent);
|
||||
color: var(--color-bg);
|
||||
font: 0.82em var(--mirages-font-ui);
|
||||
font: 0.82em var(--reading-font-family);
|
||||
}
|
||||
.shortcode-tag-outline {
|
||||
background: transparent;
|
||||
@@ -44,29 +89,60 @@
|
||||
background: #35769b;
|
||||
}
|
||||
.shortcode-hint {
|
||||
--hint-color: var(--color-accent);
|
||||
position: relative;
|
||||
margin: 1.2em 0;
|
||||
padding: 0.7em 1em;
|
||||
border-left: 4px solid var(--color-accent);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem 1.25rem 1.5rem 3.5rem;
|
||||
border: 1px solid color-mix(in srgb, var(--hint-color) 28%, var(--color-border));
|
||||
border-left: 0.25rem solid var(--hint-color);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--hint-color) 7%, var(--color-surface));
|
||||
}
|
||||
.shortcode-hint strong {
|
||||
.content-hint-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
transform: translateY(-50%);
|
||||
color: var(--hint-color);
|
||||
}
|
||||
.content-hint-heading {
|
||||
display: grid;
|
||||
min-height: 1.5rem;
|
||||
grid-template-columns: 1.5rem minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0 0 0.5rem -2.5rem;
|
||||
}
|
||||
.content-hint-heading .content-hint-icon {
|
||||
position: static;
|
||||
transform: none;
|
||||
}
|
||||
.prose .content-hint-title {
|
||||
display: block;
|
||||
margin-bottom: 0.2em;
|
||||
font-family: var(--mirages-font-ui);
|
||||
margin: 0;
|
||||
color: var(--hint-color);
|
||||
font-size: 1.2em;
|
||||
line-height: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.shortcode-hint p {
|
||||
.shortcode-hint p:not(.content-hint-title) {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.shortcode-hint-warning,
|
||||
.shortcode-hint-warn {
|
||||
border-color: #a36b16;
|
||||
.shortcode-hint-warn,
|
||||
.hint-warning {
|
||||
--hint-color: #d99b00;
|
||||
}
|
||||
.shortcode-hint-error,
|
||||
.shortcode-hint-danger {
|
||||
border-color: #ad3d48;
|
||||
.shortcode-hint-danger,
|
||||
.hint-danger {
|
||||
--hint-color: #d34b50;
|
||||
}
|
||||
.shortcode-hint-success {
|
||||
border-color: #287d5b;
|
||||
.shortcode-hint-success,
|
||||
.hint-success {
|
||||
--hint-color: #209b61;
|
||||
}
|
||||
.shortcode-collapse {
|
||||
margin: 1.2em 0;
|
||||
@@ -132,10 +208,6 @@
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.shortcode-body { transition: none; }
|
||||
}
|
||||
.shortcode-body-inner table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.shortcode-body .shortcode-collapse {
|
||||
margin: 1rem 0 0.25rem;
|
||||
}
|
||||
@@ -156,6 +228,7 @@
|
||||
}
|
||||
.shortcode-tabs {
|
||||
margin: 1.2em 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
@@ -170,15 +243,19 @@
|
||||
}
|
||||
.shortcode-tab-list button {
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.7rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: 0;
|
||||
border-radius: var(--radius-xs);
|
||||
background: transparent;
|
||||
color: var(--color-muted);
|
||||
font-family: var(--reading-font-family);
|
||||
transition: background-color 150ms ease, color 150ms ease;
|
||||
}
|
||||
.shortcode-tab-list button[aria-selected="true"] {
|
||||
background: var(--color-bg);
|
||||
background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.shortcode-tabs [role="tabpanel"] {
|
||||
padding: 0.8rem 1rem;
|
||||
@@ -266,7 +343,7 @@
|
||||
line-height: 1.25;
|
||||
}
|
||||
.shortcode-tool-description {
|
||||
color: var(--color-muted);
|
||||
color: color-mix(in srgb, var(--color-text) 82%, var(--color-muted));
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@@ -308,7 +385,7 @@
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
font: 600 1.08rem/1.35 var(--mirages-font-ui);
|
||||
font: 600 1.08rem/1.35 var(--reading-font-family);
|
||||
}
|
||||
.shortcode-github-icon {
|
||||
display: inline-block;
|
||||
@@ -336,7 +413,7 @@
|
||||
justify-content: flex-end;
|
||||
gap: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
font: 0.75rem var(--mirages-font-ui);
|
||||
font: 0.75rem var(--reading-font-family);
|
||||
text-align: right;
|
||||
}
|
||||
.shortcode-github-stat {
|
||||
@@ -365,7 +442,7 @@
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-accent);
|
||||
font: 0.78rem var(--mirages-font-ui);
|
||||
font: 0.78rem var(--reading-font-family);
|
||||
text-decoration: none;
|
||||
}
|
||||
.shortcode-github-read-more:hover,
|
||||
@@ -383,7 +460,7 @@
|
||||
padding: 0.6rem 1rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
color: var(--color-muted);
|
||||
font: 0.75rem var(--mirages-font-ui);
|
||||
font: 0.75rem var(--reading-font-family);
|
||||
}
|
||||
.shortcode-github-commit {
|
||||
min-width: 0;
|
||||
@@ -420,9 +497,17 @@
|
||||
font-style: italic;
|
||||
}
|
||||
.shortcode-notice {
|
||||
display: inline-block;
|
||||
padding: 0.1em 0.45em;
|
||||
border-left: 2px solid var(--color-accent);
|
||||
background: color-mix(in srgb, var(--color-accent) 8%, transparent);
|
||||
color: var(--color-accent);
|
||||
display: inline-flex;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
.shortcode-notice svg {
|
||||
display: block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user