diff --git a/astro.config.mjs b/astro.config.mjs index bcc68c6..962fbee 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -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(), ], }), }, diff --git a/package.json b/package.json index e3ee76c..ab70774 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc8f726..f76bde1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/content/posts/生活/周末愉快.md b/src/content/posts/生活/周末愉快.md index f935c64..ac0dfa2 100644 --- a/src/content/posts/生活/周末愉快.md +++ b/src/content/posts/生活/周末愉快.md @@ -17,13 +17,10 @@ draft: false 其实现在看来,一个有趣的网站固然重要,但是毕竟是一个内容输出的平台,没有足够多的原创内容支持,也只能是一个测试站。 另外要想清楚一点,持续的创作和更新其实才是最难的工作。 -::: tip 下周计划 - -- [ ] 阅读累计4小时 - - [ ] 汇总整理近期拍摄的照片 - - [ ] 写一篇原创文章 - -::: +> [!tip] 下周计划 +> - [ ] 阅读累计4小时 +> - [ ] 汇总整理近期拍摄的照片 +> - [ ] 写一篇原创文章 diff --git a/src/plugins/satteri/autolink-headings.mjs b/src/plugins/satteri/autolink-headings.mjs index 33c33c8..16822f9 100644 --- a/src/plugins/satteri/autolink-headings.mjs +++ b/src/plugins/satteri/autolink-headings.mjs @@ -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", diff --git a/src/styles/callouts.css b/src/styles/callouts.css new file mode 100644 index 0000000..9f300bf --- /dev/null +++ b/src/styles/callouts.css @@ -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; +} + +/* 折叠版用的是 ,去掉浏览器默认的三角标记 */ +.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; +} diff --git a/src/styles/global.css b/src/styles/global.css index b691750..fceb9e0 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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);