04 · 公开书签页
公开书签页 /bookmarks/ 目标是:加载快、可搜索、分区清晰、与 Starlight 主题一致,但不依赖 docs 布局。
---import { PublicBookmarksPage } from '../../components/bookmarks/public/PublicBookmarksPage.tsx'import { getBookmarkSections } from '../../lib/bookmarks/queries'import { serializeBookmarkSectionsForPage } from '../../lib/bookmarks/page-data'
const sections = await getBookmarkSections()const sectionsJson = serializeBookmarkSectionsForPage(sections)---
<!doctype html><html lang="zh-CN"> <head> <title>书签导航 · wwlight</title> <script is:inline set:html={themeInitScript} /> </head> <body> <script type="application/json" id="bookmarks-sections-data" set:html={sectionsJson} /> <PublicBookmarksPage client:only="react" /> </body></html>与 Starlight 页面的差异:
- 独立
<html>,无 sidebar / TOC - 构建时在服务端查 DB,数据通过
<script type="application/json">注入 - React 用
client:only完全客户端挂载(无需 SSR hydration 一致性)
JSON 注水与 XSS 防护
Section titled “JSON 注水与 XSS 防护”export function serializeBookmarkSectionsForPage(sections): string { return JSON.stringify(sections) .replace(/<\//g, "\\u003c/") .replace(/<!--/g, "\\u003c!--");}
export function readBookmarkSectionsFromPage(): BookmarkSectionData[] { const el = document.getElementById("bookmarks-sections-data"); return JSON.parse(el.textContent);}转义 < 防止书签标题/描述里出现 </script> 打断 HTML 解析。React 端用 useState(readBookmarkSectionsFromPage) 首屏读取,避免二次请求。
PublicBookmarksPage└── ThemeProvider(与 Starlight 共用 localStorage key) └── BookmarksPublic ├── 标题 + 统计 + PublicPageActions(返回文档 / 管理端链接) ├── 搜索框 └── Tabs(按 Section 切换) └── PublicSectionPanel └── PublicBookmarkCardGroup └── PublicBookmarkCardfilterBookmarkSections() 对 title、description、url、card 名、section 名做大小写不敏感匹配;无结果的 section 整组隐藏。搜索时 clampSelectedSection 防止 Tab 索引越界。
ThemeProvider 使用 storageKey="starlight-theme",与文档站读写同一 localStorage。页面 head 内联 theme-init.inline.js,在 CSS 加载前设置 data-theme,避免闪白。
样式在 src/styles/bookmarks-page.css,复用 shadcn 语义色变量(--background、--muted-foreground 等)。
搭一个最小公开页
Section titled “搭一个最小公开页”-
新建
src/pages/demo-bookmarks.astro,复制bookmarks/index.astro内容 -
新建极简 React 组件,只渲染 section 数量:
import { readBookmarkSectionsFromPage } from "@/lib/bookmarks/page-data";export function Demo() {const sections = readBookmarkSectionsFromPage();return <p>{sections.length} 个分区</p>;} -
client:only="react"挂载,访问/demo-bookmarks/验证注水 -
确认 view-source 里能看到 JSON script 标签
与管理端 UI 的复用
Section titled “与管理端 UI 的复用”公开页与管理端共享:
BookmarkSectionData类型filterBookmarkSections/clampSelectedSection等工具- shadcn 风格
Input、Tabs、Card
管理端在此基础上叠加编辑、拖拽、对话框——见 06 · 管理端 UI。
- Astro 负责 取数 + 静态壳;React 负责 交互
- JSON script 标签是 islands 之间传递大量结构化数据的简单方案
- 独立页面 + 共享主题,使书签模块既「脱钩」又「一体」