feat: 支持 Obsidian 风格 callout,并把脚注文案中文化

Callout
- 接入 satteri-callouts(HAST 插件),用 Obsidian 语法:
    > [!tip] 标题
    > 内容
    > [!warning]- 默认折叠
  类型名大小写不敏感,支持嵌套、可折叠、标题内联 Markdown。
- 样式没有用它自带的主题 CSS(Obsidian 原版是高饱和蓝/青/紫,还靠
  mix-blend-mode 叠底色,在暖纸底上会发脏),改为新建 src/styles/callouts.css,
  复用主题已有的 --admonitions-color-* token,与 .admonition(免责声明块)
  统一成「3px 左边框 + 彩色标题、无底色」。
- 类型映射:tip/hint→tip,note/info/todo/abstract/summary/tldr→note,
  important/example→important,warning/attention/question/help/faq→warning,
  caution→caution,success/check/done→(新增的)success,
  danger/error/failure/fail/missing/bug→destructive,quote/cite→muted-foreground。
- global.css 补一个 --admonitions-color-success(浅色 oklch(0.62 0.16 150) /
  深色 oklch(0.7 0.18 150)),与 tip(180)/note(250)/important(310) 同明度彩度。

脚注中文化
features.gfm.footnotes 设 label「脚注」、backLabel「返回第 {reference} 条」、
backContent「↑」,替换默认的 Footnotes / Back to reference。

正文
- 周末愉快.md:`::: tip 下周计划`(冒号后有空格,从来不是合法指令,
  一直渲染成普通段落)改成 `> [!tip] 下周计划`,任务列表一并加 `>` 前缀。
- 关于价值感和归属感的思考.md 的 `> [!tip] Tip` 本来就合法,一行未改,
  现在自动变成真 callout。

顺带修
autolink-headings 不再给脚注区那个 sr-only 的「脚注」标题加 # 锚点。

验证
- 两处 callout 均正确渲染;callout 内的图片仍带 data-lightbox-src
  (说明 imageLightbox 与 satteriCallouts 的执行顺序没问题)。
- 临时加了一篇带脚注和折叠 callout 的文章验证:脚注文案、返回链接、
  折叠态 <details>/<summary> 结构都正确,验证后已删除。
- 整页对比基线:37/41 一致,4 处差异中 2 处是本次有意的 callout 生效,
  另 2 处是已知的智能标点引号方向(Sätteri 的结果更正确)。
- pnpm build 41 页 + pagefind 通过。
This commit is contained in:
2026-09-17 18:01:44 +08:00
parent 5e808a9cd4
commit f4a49ef94c
7 changed files with 211 additions and 27 deletions
+4 -7
View File
@@ -17,13 +17,10 @@ draft: false
其实现在看来,一个有趣的网站固然重要,但是毕竟是一个内容输出的平台,没有足够多的原创内容支持,也只能是一个测试站。
另外要想清楚一点,持续的创作和更新其实才是最难的工作。
::: tip 下周计划
- [ ] 阅读累计4小时
- [ ] 汇总整理近期拍摄的照片
- [ ] 写一篇原创文章
:::
> [!tip] 下周计划
> - [ ] 阅读累计4小时
> - [ ] 汇总整理近期拍摄的照片
> - [ ] 写一篇原创文章
@@ -32,6 +32,15 @@ export const autolinkHeadings = defineHastPlugin({
if (typeof id !== "string" || id.length === 0) return
if (hasAnchor(node)) return
// 脚注区那个视觉隐藏的 "脚注" 标题不需要 # 锚点
let parent = ctx.parent(node)
while (parent) {
if (parent.type === "element" && parent.properties?.["data-footnotes"] !== undefined) {
return
}
parent = ctx.parent(parent)
}
ctx.appendChild(node, {
type: "element",
tagName: "a",
+159
View File
@@ -0,0 +1,159 @@
/*
* Obsidian 风格 callout 的样式(DOM 由 satteri-callouts 生成)。
*
* 刻意**不引入** satteri-callouts 自带的主题 CSS:它那套是 Obsidian 原版配色
* (蓝/青/紫高饱和色),而且靠 mix-blend-mode 叠底色 —— 在「暖纸底 + 朱红」这套
* 配色上会发灰发脏。这里改为复用主题已有的 --admonitions-color-* token,
* 与 .admonition(免责声明块)保持同一种视觉语言:3px 左边框 + 彩色标题、无底色。
*
* 用法(frontmatter 里写):
* > [!tip] 标题
* > 内容
*
* > [!warning]- 默认折叠的
* > 展开后才看到
*/
.callout {
/* 未指定类型时的兜底色 */
--callout-accent: var(--admonitions-color-note);
border-left: 3px solid var(--callout-accent);
background: transparent;
padding: 0.75rem 1rem;
margin: 1.25rem 0;
color: var(--foreground);
}
/* ---------- 类型 → 主题配色 ---------- */
[data-callout="tip"],
[data-callout="hint"] {
--callout-accent: var(--admonitions-color-tip);
}
[data-callout="note"],
[data-callout="info"],
[data-callout="todo"],
[data-callout="abstract"],
[data-callout="summary"],
[data-callout="tldr"] {
--callout-accent: var(--admonitions-color-note);
}
[data-callout="important"],
[data-callout="example"] {
--callout-accent: var(--admonitions-color-important);
}
[data-callout="warning"],
[data-callout="attention"],
[data-callout="question"],
[data-callout="help"],
[data-callout="faq"] {
--callout-accent: var(--admonitions-color-warning);
}
[data-callout="caution"] {
--callout-accent: var(--admonitions-color-caution);
}
[data-callout="success"],
[data-callout="check"],
[data-callout="done"] {
--callout-accent: var(--admonitions-color-success);
}
[data-callout="danger"],
[data-callout="error"],
[data-callout="failure"],
[data-callout="fail"],
[data-callout="missing"],
[data-callout="bug"] {
--callout-accent: var(--destructive);
}
[data-callout="quote"],
[data-callout="cite"] {
--callout-accent: var(--muted-foreground);
}
/* ---------- 标题 ---------- */
.callout-title {
display: flex;
align-items: center;
gap: 0.375rem;
color: var(--callout-accent);
font-weight: 600;
font-size: 0.9375rem;
line-height: 1.5;
}
.callout-title-icon {
display: flex;
flex: 0 0 auto;
align-items: center;
}
.callout-title-icon svg {
width: 16px;
height: 16px;
}
.callout-title-text {
color: inherit;
font-weight: inherit;
}
/* 折叠版用的是 <summary>,去掉浏览器默认的三角标记 */
.callout-title::-webkit-details-marker {
display: none;
}
.callout-title::marker {
content: "";
}
/* ---------- 内容 ---------- */
/* prose 给段落 1.25rem 的上下边距,在 callout 里太松,收紧一点 */
.callout-content p {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.callout-content > :first-child {
margin-top: 0.5rem;
}
.callout-content > :last-child {
margin-bottom: 0;
}
/* ---------- 折叠态 ---------- */
.callout[data-collapsible="true"] .callout-title {
cursor: pointer;
}
.callout[data-collapsible="true"] .callout-fold-icon {
display: flex;
align-items: center;
margin-inline-start: auto;
}
.callout-fold-icon svg {
width: 16px;
height: 16px;
}
.callout[data-collapsible="true"] > .callout-title .callout-fold-icon svg {
transform: rotate(-90deg);
transition: transform 150ms ease-in-out;
}
.callout[data-collapsible="true"][open] > .callout-title .callout-fold-icon svg {
transform: none;
}
+3
View File
@@ -1,6 +1,7 @@
@import "./transition.css";
@import "tailwindcss/index.css";
@import "tw-animate-css";
@import "./callouts.css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@@ -88,6 +89,7 @@
--admonitions-color-important: oklch(0.62 0.16 310);
--admonitions-color-warning: oklch(0.7 0.18 70);
--admonitions-color-caution: oklch(0.62 0.2 var(--primary-hue, 25));
--admonitions-color-success: oklch(0.62 0.16 150);
/* Code block colors - light mode */
/* 暖色阴槽 #f2ece1。原来是 0.97/0.003 的中性灰白,在暖纸底(#f8f3e9)上
就是一块冷白补丁,很跳。现在带上同样的暖色相。
@@ -149,6 +151,7 @@
--admonitions-color-important: oklch(0.7 0.18 310);
--admonitions-color-warning: oklch(0.75 0.2 70);
--admonitions-color-caution: oklch(0.7 0.22 var(--primary-hue, 25));
--admonitions-color-success: oklch(0.7 0.18 150);
/* Code block colors - dark mode */
--codeblock-bg: oklch(0.20 0.015 250);
--codeblock-topbar-bg: oklch(0.24 0.015 250);