跳转到内容

03 · 导航页与客户端渲染

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

---
// src/bookmarks/nav/entry.astro(integrations/bookmarks-admin injectRoute)
import { NavBookmarksPage } from '@/bookmarks/nav/NavBookmarksPage.tsx'
import { serializeBookmarkSectionsForPage } from '@/bookmarks/shared/data/page-data'
import { getBookmarkSections } from '@/bookmarks/shared/data/queries'
import themeInitScript from '@/theme/scripts/init.inline.js?raw'
import '@/bookmarks/nav/styles/bookmarks-page.css'
const sections = await getBookmarkSections()
const sectionsJson = serializeBookmarkSectionsForPage(sections)
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>书签导航 · wwlight</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" data-wwlight-favicon />
<script is:inline set:html={themeInitScript} />
</head>
<body>
<script type="application/json" id="bookmarks-sections-data" set:html={sectionsJson} />
<NavBookmarksPage client:only="react" />
</body>
</html>

与 Starlight 页面的差异:

  • 独立 <html>,无 sidebar / TOC
  • 构建时在服务端查 DB,数据通过 <script type="application/json"> 注入
  • React 用 client:only 完全客户端挂载(无需 SSR hydration 一致性)
src/bookmarks/shared/data/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) 首屏读取,避免二次请求。

NavBookmarksPage
└── ThemeProvider(@/theme,与文档站同一 storage 键)
└── NavBookmarks
├── BookmarkPageHeader + NavPageActions
│ └── ColorThemePicker / 返回博客 / 管理端(BookmarkSettingsIcon)
├── BookmarkStatsCards + 搜索框
└── Tabs(按 Section 切换)
└── NavSectionPanel
└── NavBookmarkCardGroup
└── NavBookmarkCard(.bookmark-card)
└── BackToTop

filterBookmarkSections() 对 title、description、url、card 名、section 名做大小写不敏感匹配;无结果的 section 整组隐藏。搜索时 clampSelectedSection 防止 Tab 索引越界。

head 内联 @/theme/scripts/init.inline.js?raw,在 CSS 前写入 data-theme / data-color-*,避免 FOUC。ThemeProvider@/theme/components/color-mode/Provider)mount 时 syncSiteThemeFromStorage(),并订阅 subscribeSiteThemeStorage——与 Starlight 顶栏定制器读写同一套 wwlight:color-mode 等键(见 主题系列)。

顶栏 NavPageActionsColorThemePickervariant=bookmarks),管理端入口用 BookmarkSettingsIcon 而非通用 Cog 图标。

样式链:

bookmarks-page.css
→ bookmarks-nav-app.css
→ bookmarks-theme-shared.css(Tailwind + shadcn-theme + @/theme/styles/index.css)
→ bookmarks-card.css(.bookmark-card 读 --card / --border)

卡片勿用 border-border/50 等 opacity 叠层,暗色下边框会几乎不可见。

  1. 新建 demo 页或复制 src/bookmarks/nav/entry.astro 内容

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

    import { readBookmarkSectionsFromPage } from "@/bookmarks/shared/data/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 等工具
  • .bookmark-cardshared/styles/bookmarks-card.css
  • shadcn 风格 InputTabsCard

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