Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
80 changes: 26 additions & 54 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,90 +1,62 @@
name: Deploy Pages

# 触发条件,main分支被修改后
on:
push:
branches: [main]
# pull_request:
# branches: [main]

# 支持手动在工作流上触发
pull_request:
branches: [main]
workflow_dispatch:

# 设置时区
env:
TZ: Asia/Shanghai
NEXT_TELEMETRY_DISABLED: 1

# 权限设置
permissions:
# 允许读取仓库内容的权限。
contents: read
# 允许写入 GitHub Pages 的权限。
pages: write
# 允许写入 id-token 的权限。
id-token: write

# 并发控制配置
concurrency:
group: pages
cancel-in-progress: false
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

# 定义执行任务
jobs:
# 构建任务
build:

runs-on: ubuntu-latest

# node v24 运行
strategy:
matrix:
node-version: [24]

steps:
# 拉取代码
- name: Checkout
uses: actions/checkout@v3
- name: Checkout with complete page history
uses: actions/checkout@v4
with:
# 保留 Git 信息
fetch-depth: 0

# 设置使用 Node.js 版本
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- uses: pnpm/action-setup@v4
name: Install pnpm
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 11
version: 11.19.0
run_install: false

# 安装依赖
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile

# 构建项目
- name: Build blog project
run: |
echo ${{ github.workspace }}
pnpm build

# 上传 VitePress 构建产物,用于后续部署
# 不经 Jekyll 处理(dist/.nojekyll 已禁用),避免下划线开头的文件被丢弃
- name: Upload artifact
- name: Check types, search targets and static output
run: pnpm check
- name: Upload GitHub Pages artifact
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
path: ./docs/.vitepress/dist
path: ./out

# 部署任务
deploy:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4
16 changes: 11 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
node_modules
dist
cache
.temp
.DS_Store
node_modules/
dist/
cache/
.temp/
.DS_Store
.next/
.source/
out/
*.tsbuildinfo
next-env.d.ts
.env*.local
69 changes: 23 additions & 46 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,33 @@
# AGENTS.md

面向 AI agent 的仓库维护说明。人类投稿者请看站点[贡献指南](https://whudays.org/maintainer/contributing)。
武汉大学动漫协会(WHUDAYS)历史存档站。Fumadocs + Next.js + React;完整静态导出部署至 GitHub Pages,正式域名 `whudays.org`。

## 项目
## 硬性约束

武汉大学动漫协会(武大漫协 / WHUDAYS)社团历史存档站。[VitePress](https://vitepress.dev/) 静态站,GitHub Pages 部署,正式域名 `whudays.org`。
1. 站内链接用绝对路径,例如 `/group/ani-key/`;不要使用相对链接。构建的死链检查失败时不能提交。
2. 新增页面需同步 `lib/navigation.json` 对应分区侧栏;子分区按最长路径匹配,不能覆盖丢失。
3. 改动后运行 `pnpm check`,确保类型检查和完整构建通过。
4. Commit 不要带 AI 的 Co-Authors。Commit message 使用中文并说明原因。
5. 不能删除或改名历史附件、中文 URL、旧锚点或独立 HTML 存档。变更时必须先解释兼容方案。

- 内容源在 `docs/`,VitePress 配置在 `docs/.vitepress/config.js`
- 本地预览 `pnpm dev`,构建 `pnpm build`
## 内容与资源

## 硬性约束(最易违反,务必遵守)
- 内容在 `docs/<区域>/**/*.mdx`;区域为 activity、about、department、group、message-box、maintainer
- 图片、附件和独立 HTML 存档直接维护在根目录 `public/`,用根绝对路径引用;例如 `public/activity/2024/x.jpg` → `/activity/2024/x.jpg`
- 年份为学年,例如 2024 指 2024.6–2025.6,与干部任期一致
- 内容页面使用无 `.html` 后缀的地址;独立 HTML 存档保持原地址
- 标题锚点沿用旧规则;添加显式锚点使用 Fumadocs 支持的 `[#anchor]`

1. **站内链接用绝对路径**(`/group/...`),禁用相对路径。构建期有死链检查,失效链接会让 `pnpm build` 失败、阻断部署。
2. **新增页面必须同步侧边栏**:在 `docs/.vitepress/config.js` 的 `themeConfig.sidebar` 对应区域手动登记,否则页面不出现在导航。
3. **改动后跑 `pnpm build` 验证**能通过再提交。
4. **commit 不要带 AI 的 Co-Authors**——这是社区硬性规矩,带了会被拒。
5. commit message 用中文,描述清楚"为什么"。
## React / MDX

## 目录与资源约定
- `ChatMessage`、`MemberCard`、`TeamMembers`、`TeamPage`、`TeamPageTitle`、`TeamPageSection`、`Badge`、`Callout` 已全局注册
- 组件支持服务器渲染,不再依赖 Vue `ClientOnly`;保留的同名兼容组件会直接输出子内容
- JavaScript 变量用 `export const`;动态属性用 `avatar={avatarOf('名字')}`;不能使用 Vue `:prop`、`<script setup>`、`<template #...>`
- 统一人员数据在 `lib/people.js`;复用头像,暂无头像使用占位图;有 GitHub 的提交者需配置 github/name/email 别名
- 作者映射邮件只用于构建期匹配,不要把完整注册表或邮箱别名传入客户端组件
- `gitChangelog: false` 隐藏历史;历史仍追溯原 `.md` 文件和重命名。GitHub Actions 必须 `fetch-depth: 0`
- 贡献者参考 `docs/maintainer/contributing.mdx`

- **内容**:Markdown 放 `docs/<区域>/`(`activity` / `about` / `department` / `group` / `message-box` / `maintainer`)
- **资源**:图片等放 `docs/public/` 的**镜像目录**,页面用绝对路径引用(`public` = 站点根)。如 `docs/public/activity/2024/x.jpg` → 页面写 `/activity/2024/x.jpg`
- **年份 = 学年**:`2024` 指 2024 学年(2024.6–2025.6),与干部任期一致
## 验证

## 常见任务

新增内容时**优先模仿同区域的现有页面**,再按需登记侧边栏:

| 任务 | 参考现有文件 |
| --- | --- |
| 加活动页 | `docs/activity/2024/winter-festival.md` |
| 加成员卡 | `docs/activity/2025/welcome-party/credits.md` |
| 加留声箱 | `docs/message-box/quq/words.md` |
| 加部门/小群 | `docs/department/` 或 `docs/group/` 下任一 |

## 针对框架的修改与二开

### 1. 自定义组件

`docs/.vitepress/theme/` 注册了两个全局组件,**必须用 `<ClientOnly>` 包裹**(否则 SSR/hydration 报错):

- `ChatMessage`:对话气泡。仅 `docs/about/introduction.md` 中使用。props `avatar` / `nickname` / `message`(支持 HTML)/ `position`。例:
- `MemberCard`:成员卡。仅在 `docs/group/vocaloid-utau-fans` 中使用。props `name` / `avatar` / `description` / `link` / `:badges` / `:socials`

### 2. gitChangelog 插件

为插件添加了 patch(`patches/@nolebase__vitepress-plugin-git-changelog@2.17.1.patch`) 以显示贡献者名称

用法细节见站点[贡献指南](https://whudays.org/maintainer/contributing)。

### 3. 人员数据与贡献者映射

统一参考和修改 `docs/.vitepress/data/people.js`。
- **头像复用**:若某一成员的头像已存在,请直接复用,**不要新建条目**。暂无头像可缺省,会自动回退到占位图。
- **贡献者别名映射**:`people.js` 同时作为 `gitChangelog` 的作者数据源。如果成员是仓库提交者(有 GitHub),需在其数据对象中补充 `github` 字段,并根据 Git 历史配置 `mapByNameAliases` 和 `mapByEmailAliases`,插件才能将不同机器上的 commit 正确归拢到同一作者卡片下。
`pnpm dev` 开发;`pnpm build` 构建并检查;`pnpm preview` 在 4173 端口预览实际静态输出;`pnpm typecheck`、`pnpm check` 为单项和完整检查。PR 工作流只检查不部署,main 更新才部署。
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
# 武汉大学动漫协会社团网站

- 网址:[whudays.org](https://whudays.org/)
- GitHub 组织首页:[WHUDAYS](https://github.com/WHUDAYS)
- GitHub 组织:[WHUDAYS](https://github.com/WHUDAYS)

本站点为 `武大漫协 HQ 工作文档存档项目` 的**副产物**,定位为社团历史活动与部分资料的**保存库**。基于 [VitePress](https://vitepress.dev/) 搭建,将 Markdown 转换为静态网页。
本站是「武大漫协 HQ 工作文档存档项目」的副产物,保存社团历史活动与资料。基于 [Fumadocs](https://www.fumadocs.dev/)、Next.js 和 React,构建为无需服务器的静态网站,继续使用 GitHub Pages 与原域名。

## 参与建设
## 本地开发

欢迎投稿与协助维护!无论你是想直接提 PR,还是不会 Markdown 想委托维护者代发,完整说明都在:
需要 Node.js 24、pnpm 11。请完整克隆仓库(不要 shallow clone),以显示完整页面历史。

**➡️ [贡献指南](https://whudays.org/maintainer/contributing)**
```sh
pnpm install --frozen-lockfile
pnpm dev # http://localhost:3000
pnpm check # 类型、静态构建与路由/资源/搜索定位检查
pnpm preview # http://localhost:4173,预览 out/ 中的实际静态产物
```

里面涵盖了本地部署、目录与资源约定、页面撰写规范、自定义组件用法,以及提交与合并流程。
## 目录

- `docs/**/*.mdx`:内容;原 Markdown 文件名/路由保留,Vue 片段已迁移为 React/MDX
- `public/`:原有图片、PDF、视频、PDF.js 和独立历史页面,由 Next.js 直接读取,构建时原样输出到站点根目录
- `lib/navigation.json`:顶栏和按最长路径匹配的分区侧栏
- `lib/git-history.mjs`:构建期读取页面历史
- `lib/people.js`、`lib/icons.js`:统一人员、头像、贡献者别名与社交图标
- `components/content.tsx`:聊天气泡、成员卡、团队页等 React 组件
- `components/git-history-view.tsx`:页面完整 Git 历史、排序/展开、作者与共同作者
- `source.config.ts`、`lib/remark-legacy-headings.mjs`:Fumadocs MDX 及历史标题锚点兼容
- `scripts/`:仅保留发布产物检查;搜索由 Fumadocs 生成,站点地图由 Next.js 生成,本地静态预览使用 `serve`
- `out/`:GitHub Pages 静态输出,包含站点地图、CNAME 和原有独立 HTML 存档;文章使用无 `.html` 后缀的地址

## 投稿与维护

欢迎投稿和协助维护!详见[贡献指南](https://whudays.org/maintainer/contributing)。新增页面时请同步更新侧栏,站内链接继续使用绝对路径。`pnpm build` 会检查站内链接与归档资源;失败时不要部署。

图片和附件直接维护在根目录 `public/`。构建检查当前静态页面的站内链接、资源地址和锚点,不依赖旧站迁移快照。

## 许可

本站点内容采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)进行许可。
本站内容采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)许可。图片、文档等版权归原作者所有。
68 changes: 68 additions & 0 deletions app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { notFound } from 'next/navigation';
import type { Metadata } from 'next';
import { DocsLayout } from '@fumadocs/base-ui/layouts/spacious';
import { HomeLayout } from '@fumadocs/base-ui/layouts/home';
import { DocsPage, DocsBody, MarkdownCopyButton } from '@fumadocs/base-ui/layouts/spacious/page';
import { Heading } from '@fumadocs/base-ui/components/heading';
import { source, getCanonicalPath, getMarkdownPath } from '@/lib/source';
import { baseOptions } from '@/lib/layout.shared';
import { getSidebar, getPagination, sectionTabs } from '@/lib/navigation';
import { getMDXComponents } from '@/components/mdx';
import { TeamPageTitle } from '@/components/content';
import { PageViewOptions } from '@/components/page-view-options';
import { GitChangelog } from '@/components/git-changelog';
import { shouldShowChangelog } from '@/lib/changelog';
import { CanonicalUrl } from '@/components/canonical-url';
import { Home } from '@/components/home';
import { SiteFooter } from '@/components/site-footer';
import { site } from '@/lib/site';

type Props = { params: Promise<{ slug?: string[] }> };
export const dynamicParams = false;
export function generateStaticParams() { return source.getPages().map((page) => ({ slug: page.slugs.map(decodeURIComponent) })); }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const page = source.getPage(slug);
if (!page) notFound();
const path = getCanonicalPath(page.path);
return {
title: page.data.layout === 'home' ? { absolute: site.title } : page.data.title,
description: page.data.description ?? site.description,
alternates: { canonical: `${site.url}${path}` },
openGraph: { title: page.data.title, description: page.data.description ?? site.description, url: `${site.url}${path}`, images: ['/WHUDAYS.png'] },
};
}
export default async function Page({ params }: Props) {
const { slug } = await params;
const page = source.getPage(slug);
if (!page) notFound();
const data = page.data;
if (data.layout === 'home') return <><CanonicalUrl path="/" /><Home hero={data.hero} features={data.features} /></>;
const MDX = data.body;
const markdownUrl = getMarkdownPath(page.path);
const pageActions = <div className="not-prose my-6 flex flex-wrap items-center gap-2" aria-label="页面操作">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<PageViewOptions markdownUrl={markdownUrl}
githubUrl={`https://github.com/WHUDAYS/WHUDAYS.github.io/blob/main/docs/${page.path.split('/').map(encodeURIComponent).join('/')}`}
pageUrl={`${site.url}${getCanonicalPath(page.path)}`} />
</div>;
const content = <MDX components={getMDXComponents({
h1: (props) => <><Heading as="h1" {...props} />{pageActions}</>,
TeamPageTitle: (props) => <><TeamPageTitle {...props} />{pageActions}</>,
})} />;
if (data.layout === 'page') return <HomeLayout {...baseOptions()}><CanonicalUrl path={getCanonicalPath(page.path)} />
<main id="main-content" className="team-document"><div data-search-content>{content}<span data-search-content-end /></div></main><SiteFooter />
</HomeLayout>;
const outline = data.outline;
const toc = [...data.toc, ...(shouldShowChangelog(data) ? [{ title: '页面历史', url: '#页面历史', depth: 2 }] : [])].filter((entry) => entry.depth >= (Array.isArray(outline) ? outline[0] : 2) && entry.depth <= (Array.isArray(outline) ? outline[1] : typeof outline === 'number' ? outline : outline === 'deep' ? 6 : 2));
return <DocsLayout {...baseOptions()} links={[]} tree={getSidebar(page.url)} tabs={sectionTabs}>
<CanonicalUrl path={getCanonicalPath(page.path)} />
<DocsPage id="main-content" toc={toc} footer={{ items: getPagination(page.url) }} breadcrumb={{ enabled: false }} tableOfContent={{ enabled: outline !== false, style: 'clerk' }} tableOfContentPopover={{ enabled: outline !== false, style: 'clerk' }}>
<DocsBody><div data-search-content>{content}<span data-search-content-end /></div></DocsBody>
<GitChangelog sourcePath={`docs/${page.path}`} frontmatter={data} />
<a className="back-to-top" href="#main-content">回到顶部 ↑</a>
<SiteFooter />
</DocsPage>
</DocsLayout>;
}
49 changes: 49 additions & 0 deletions app/content.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Preserved page-specific styles from the VitePress content. */

/* Migrated from docs/group/ani-key/activity/participants.md. */
.ak-no-changelog{ display:none; }
.ak-no-changelog ~ h2,
.ak-no-changelog ~ div{ display:none !important; }

/* Migrated from docs/group/ani-key/index.md. */
/* 仅保留头像墙样式 */
.ak-hero__pics{
display:flex; justify-content:center; gap:2rem; flex-wrap:wrap;
margin:1.6rem 0 2rem;
}
.ak-hero__pics figure{ margin:0; text-align:center; }
.ak-hero__pics img{
width:118px; height:118px; object-fit:cover;
border:1px solid var(--vp-c-text-1);
padding:5px; background:var(--vp-c-bg);
outline:1px solid var(--vp-c-divider); outline-offset:3px;
transition:filter .35s ease; filter:grayscale(.35);
}
.ak-hero__pics img:hover{ filter:grayscale(0); }
.ak-hero__pics figcaption{
margin-top:.85rem; font-size:.74rem; color:var(--vp-c-text-2); font-style:italic;
}
@media (max-width:520px){
.ak-hero__pics{ gap:1.2rem; }
}

/* Migrated from docs/index.md. */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg,rgb(215, 86, 81) 0%, rgb(254, 52, 76) 100%);

--vp-home-hero-image-background-image: linear-gradient(-45deg,rgb(254, 52, 76) 50%, #47caff 50%);
--vp-home-hero-image-filter: blur(44px);
}

@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}

@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
Loading
Loading