跳转到内容

04 · 公开书签页

公开书签页 /bookmarks/ 目标是:加载快、可搜索、分区清晰、与 Starlight 主题一致,但不依赖 docs 布局。

src/pages/bookmarks/index.astro
---
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 一致性)
src/lib/bookmarks/page-data.ts
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
└── PublicBookmarkCard

filterBookmarkSections() 对 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 等)。

  1. 新建 src/pages/demo-bookmarks.astro,复制 bookmarks/index.astro 内容

  2. 新建极简 React 组件,只渲染 section 数量:

    import { readBookmarkSectionsFromPage } from "@/lib/bookmarks/page-data";
    export function Demo() {
    const sections = readBookmarkSectionsFromPage();
    return <p>{sections.length} 个分区</p>;
    }
  3. client:only="react" 挂载,访问 /demo-bookmarks/ 验证注水

  4. 确认 view-source 里能看到 JSON script 标签

公开页与管理端共享:

  • BookmarkSectionData 类型
  • filterBookmarkSections / clampSelectedSection 等工具
  • shadcn 风格 InputTabsCard

管理端在此基础上叠加编辑、拖拽、对话框——见 06 · 管理端 UI

  • Astro 负责 取数 + 静态壳;React 负责 交互
  • JSON script 标签是 islands 之间传递大量结构化数据的简单方案
  • 独立页面 + 共享主题,使书签模块既「脱钩」又「一体」