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:
+15
-1
@@ -15,6 +15,7 @@ import { shiftHeadings } from "./src/plugins/satteri/shift-headings.mjs"
|
||||
import { autolinkHeadings } from "./src/plugins/satteri/autolink-headings.mjs"
|
||||
import { imageLightbox } from "./src/plugins/satteri/image-lightbox.mjs"
|
||||
import { satteriKatex } from "satteri-katex"
|
||||
import satteriCallouts from "satteri-callouts"
|
||||
import { site } from "./src/data/site"
|
||||
|
||||
export default defineConfig({
|
||||
@@ -83,7 +84,18 @@ export default defineConfig({
|
||||
processor: satteri({
|
||||
// dashes: false 是为了和迁移前的输出逐字一致:Astro 的 unified 管线
|
||||
// 会把直引号排成弯引号,但不转换 --- (Sätteri 默认会转成破折号)。
|
||||
features: { math: true, smartPunctuation: { dashes: false } },
|
||||
features: {
|
||||
math: true,
|
||||
smartPunctuation: { dashes: false },
|
||||
// 脚注文案中文化(默认是 "Footnotes" / "Back to reference 1")
|
||||
gfm: {
|
||||
footnotes: {
|
||||
label: "脚注",
|
||||
backLabel: "返回第 {reference} 条",
|
||||
backContent: "↑",
|
||||
},
|
||||
},
|
||||
},
|
||||
mdastPlugins: [
|
||||
readingTime,
|
||||
excerpt,
|
||||
@@ -97,6 +109,8 @@ export default defineConfig({
|
||||
satteriHeadingIdsPlugin(),
|
||||
autolinkHeadings,
|
||||
imageLightbox,
|
||||
// Obsidian 语法的 callout:> [!tip] 标题
|
||||
satteriCallouts(),
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"recharts": "2.15.4",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"satteri": "^0.10.5",
|
||||
"satteri-callouts": "^1.0.1",
|
||||
"satteri-katex": "^0.1.1",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
|
||||
Generated
+20
-19
@@ -200,6 +200,9 @@ importers:
|
||||
satteri:
|
||||
specifier: ^0.10.5
|
||||
version: 0.10.5
|
||||
satteri-callouts:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(satteri@0.10.5)
|
||||
satteri-katex:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1(satteri@0.10.5)
|
||||
@@ -3592,9 +3595,6 @@ packages:
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
decode-named-character-reference@1.2.0:
|
||||
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
||||
|
||||
decode-named-character-reference@1.3.0:
|
||||
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
||||
|
||||
@@ -5637,6 +5637,12 @@ packages:
|
||||
resolution: {integrity: sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
satteri-callouts@1.0.1:
|
||||
resolution: {integrity: sha512-hLbcd5HXbZ+8dHZKlYhetHVORwqdBGdSDwy07YYC6Pddb3p7P4UScduR+QNHkv3cPWL/zlPigf/lZwrrzJbamw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
satteri: ^0.9.4 || ^0.10.0
|
||||
|
||||
satteri-katex@0.1.1:
|
||||
resolution: {integrity: sha512-65M00JZKW145vEhvCFWCe0Gd57hzL8KEDQHbJ9ZvuH4FGrgh90KUPxqS8GCnIEC5EIhOfWPP7NbEnBfFn+nf3w==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -6501,7 +6507,7 @@ snapshots:
|
||||
'@types/hast': 3.0.5
|
||||
'@types/mdast': 4.0.4
|
||||
js-yaml: 4.3.2
|
||||
picomatch: 4.0.4
|
||||
picomatch: 4.0.7
|
||||
retext-smartypants: 6.2.0
|
||||
shiki: 4.1.0
|
||||
smol-toml: 1.6.1
|
||||
@@ -10477,11 +10483,6 @@ snapshots:
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decode-named-character-reference@1.2.0:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
optional: true
|
||||
|
||||
decode-named-character-reference@1.3.0:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
@@ -10632,8 +10633,7 @@ snapshots:
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@6.0.1:
|
||||
optional: true
|
||||
entities@6.0.1: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
@@ -11138,7 +11138,6 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
vfile: 6.0.3
|
||||
vfile-message: 4.0.3
|
||||
optional: true
|
||||
|
||||
hast-util-from-parse5@8.0.3:
|
||||
dependencies:
|
||||
@@ -11150,7 +11149,6 @@ snapshots:
|
||||
vfile: 6.0.3
|
||||
vfile-location: 5.0.3
|
||||
web-namespaces: 2.0.1
|
||||
optional: true
|
||||
|
||||
hast-util-has-property@3.0.0:
|
||||
dependencies:
|
||||
@@ -11158,7 +11156,7 @@ snapshots:
|
||||
|
||||
hast-util-is-element@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/hast': 3.0.5
|
||||
|
||||
hast-util-parse-selector@4.0.0:
|
||||
dependencies:
|
||||
@@ -12430,7 +12428,7 @@ snapshots:
|
||||
'@types/unist': 2.0.11
|
||||
character-entities-legacy: 3.0.0
|
||||
character-reference-invalid: 2.0.1
|
||||
decode-named-character-reference: 1.2.0
|
||||
decode-named-character-reference: 1.3.0
|
||||
is-alphanumerical: 2.0.1
|
||||
is-decimal: 2.0.1
|
||||
is-hexadecimal: 2.0.1
|
||||
@@ -12458,7 +12456,6 @@ snapshots:
|
||||
parse5@7.3.0:
|
||||
dependencies:
|
||||
entities: 6.0.1
|
||||
optional: true
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
@@ -13182,6 +13179,12 @@ snapshots:
|
||||
parse-srcset: 1.0.2
|
||||
postcss: 8.5.6
|
||||
|
||||
satteri-callouts@1.0.1(satteri@0.10.5):
|
||||
dependencies:
|
||||
hast-util-from-html: 2.0.3
|
||||
hast-util-to-html: 9.0.5
|
||||
satteri: 0.10.5
|
||||
|
||||
satteri-katex@0.1.1(satteri@0.10.5):
|
||||
dependencies:
|
||||
katex: 0.16.47
|
||||
@@ -13757,7 +13760,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
vfile: 6.0.3
|
||||
optional: true
|
||||
|
||||
vfile-message@4.0.3:
|
||||
dependencies:
|
||||
@@ -13911,8 +13913,7 @@ snapshots:
|
||||
|
||||
vscode-uri@3.2.0: {}
|
||||
|
||||
web-namespaces@2.0.1:
|
||||
optional: true
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
dependencies:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user