diff --git a/docs/操作指南.md b/docs/操作指南.md index fe4b0a6..3d50be1 100644 --- a/docs/操作指南.md +++ b/docs/操作指南.md @@ -299,6 +299,19 @@ sudo sysctl fs.inotify.max_user_watches=524288 如果连空态提示都看不到,检查 `src/data/reading.json` 是否存在(这个文件要提交进 git)。 +**改了 markdown 插件却不生效** + +Astro 会把渲染好的文章缓存到 `node_modules/.astro`,而这个缓存**只按 markdown +文件内容算 key,不看插件代码**。所以改完 `src/plugins/satteri/*.mjs` 之后, +已经渲染过的文章不会重新跑插件 —— 你会以为改动没生效,其实是吃了缓存。 + +```bash +rm -rf node_modules/.astro .astro && pnpm build +``` + +(这个坑是给标题锚点补 `aria-label` 时踩到的:插件明明改了, +产物里的 HTML 却还是老样子。) + **`pnpm dev` 启动时满屏刷红** 历史上踩过两个独立的原因,都已修好,这里记下来免得再犯: diff --git a/src/data/reading.json b/src/data/reading.json index 5bd62be..6f2d0ee 100644 --- a/src/data/reading.json +++ b/src/data/reading.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-09-17T08:50:20.126Z", + "updatedAt": "2026-09-17T10:36:53.871Z", "source": "weread", "books": [ { diff --git a/src/plugins/satteri/autolink-headings.mjs b/src/plugins/satteri/autolink-headings.mjs index 16822f9..ee2b0f2 100644 --- a/src/plugins/satteri/autolink-headings.mjs +++ b/src/plugins/satteri/autolink-headings.mjs @@ -32,6 +32,18 @@ export const autolinkHeadings = defineHastPlugin({ if (typeof id !== "string" || id.length === 0) return if (hasAnchor(node)) return + // 取标题自身的文字,用作锚点的无障碍名称(递归收集,标题里可能有 code/strong) + const collectText = (children) => + (children || []) + .map((child) => { + if (child.type === "text") return child.value + if (Array.isArray(child.children)) return collectText(child.children) + return "" + }) + .join("") + .trim() + const headingText = collectText(node.children) + // 脚注区那个视觉隐藏的 "脚注" 标题不需要 # 锚点 let parent = ctx.parent(node) while (parent) { @@ -44,12 +56,22 @@ export const autolinkHeadings = defineHastPlugin({ ctx.appendChild(node, { type: "element", tagName: "a", - properties: { className: ["anchor"], href: `#${id}` }, + properties: { + className: ["anchor"], + href: `#${id}`, + // 锚点内容只有一个 #,不补名称的话读屏会念「#,链接」, + // 连着二十几个标题听下来完全不知道跳到哪 + ...(headingText ? { "aria-label": `链接到「${headingText}」` } : {}), + }, children: [ { type: "element", tagName: "span", - properties: { className: ["anchor-icon"], "data-pagefind-ignore": "" }, + properties: { + className: ["anchor-icon"], + "data-pagefind-ignore": "", + "aria-hidden": "true", + }, children: [{ type: "text", value: "#" }], }, ], diff --git a/src/styles/global.css b/src/styles/global.css index fceb9e0..7b2834c 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -805,6 +805,53 @@ a.card-github:hover .github-logo { } } +/* ——— 文章标题末尾的 # 锚点 ——— + * + * 由 src/plugins/satteri/autolink-headings.mjs 生成: + *

标题#

+ * + * 插件一直在追加这个锚点,但项目里从来没给它写过样式,所以那个 # 是 + * 以纯文本形式常驻显示在每一个标题后面的。这里按通行做法改成「悬停才浮现」。 + */ + +.anchor { + margin-left: 0.35em; + color: var(--muted-foreground); + text-decoration: none; + opacity: 0; + transition: opacity 200ms ease, color 200ms ease; +} + +.anchor-icon { + font-weight: 400; +} + +/* 鼠标悬停标题、或键盘 Tab 聚焦到锚点时浮现 */ +:is(h1, h2, h3, h4, h5, h6):hover > .anchor, +:is(h1, h2, h3, h4, h5, h6):focus-within > .anchor, +.anchor:focus-visible { + opacity: 1; +} + +.anchor:hover { + color: var(--primary); +} + +/* 触屏没有 hover,常驻显示就是原来那副样子,所以也藏起来。 + 手机上跨标题跳转用右侧目录就够。 */ +@media (hover: none) { + .anchor { + display: none; + } +} + +/* 点 # 跳转时标题别被 sticky 导航栏盖住。 + 原来完全没有这个偏移,跳过去标题顶部紧贴视口上沿, + 正好藏在 69px 高的导航栏下面。 */ +:is(h1, h2, h3, h4, h5, h6)[id] { + scroll-margin-top: calc(var(--header-h, 69px) + 1.25rem); +} + /* ——— 首屏彩蛋:那句话出自王小波《万寿寺》 ——— * * 那句话本身不做任何变化(不加下划线、不变色、不可点),保持纯排版。