feat(opensource): 开源页改原生档案卡,个人叙述迁往关于页(5h T5/T6)

用户原话:开源页开篇是从 GitHub README 直接移植的,「部分内容没有居中,有些图片
也没有正常显示,可以自行进行美化,无需完全一致……有些可以直接移植到关于页面」。

病根:README 是 GitHub 专用 markdown——`<div align="center">` 靠 HTML 属性居中
(本站按普通块渲染,故"没居中"),且依赖 7 类外链图(readme-typing-svg /
shields.io / github-readme-stats / streak-stats / activity-graph / snake /
komarev),慢网或被墙时全空白(故"图片不显示")。

T5 开源页重构:
- 新增 geek/GithubProfile.astro:头像 / 名字@handle / 定位叙述 / 社交图标 /
  统计四格(followers、公开仓、总星标)/ 语言占比灰阶条(仅第一名 accent,
  其余 tokens 灰阶)——**零外链图、零 iframe、零第三方徽章**
- 语言聚合复用 loadProjects 的结果;followers 与公开仓数走 GitHub REST
- 任一取数失败只隐藏该项(catch + warn),绝不炸构建
- opensource.astro / en:档案卡承接原 README 位置 → 热力图 → 项目墙;
  README 渲染降级为兼容通道(intro_from=off 时不执行),yaml 注释写明
  7 类外链图与 align 失效原因并标「不推荐」
- **修既有隐患**:GitHub API 请求原先不带 token,匿名 60 次/小时在密集构建下
  被打满 → 项目墙退化成兜底 JSON(今天一度只剩 1 条)。现对 api.github.com
  自动附 Bearer(token 读宿主 0600 文件,不入仓),墙恢复 16 条、总星标 0→66、
  语言占比变真值

T6 关于页承接:
- about.yaml blocks 扩为 prose / tracks(兴趣版图)/ prose(关于我)/
  linklist(找到我:本站·邮件·GitHub·自建 gitea·RSS)/ prose(本站如何构建),
  双语齐备;AboutPage 零改动(blocks 早已驱动)
- 原则落地:关于页讲人,开源页讲开源,两边不再重复

验证: 构建 51 页 exit 0;content-lint 51 页 0/0/0;config-check 全过;
      E2E **46/46 PASS**(37 原有 + 本包新增 9 项:置顶顺序与徽标、文章页计数、
      模板彩色与折叠、瞬间评论区与只读计数、关于页块驱动);
      dist/opensource 七类第三方外链零命中、第三方 img 数 0;数据实值:
      语言占比 Python/TypeScript 各 36%、Shell/Rust/JS 各 9%

Spec: 2026-11-02_blog-5h-content-social (T5/T6)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-02 20:35:56 +08:00
1 parent 63c9c8ad4a
commit 3e377adc7f
8 files changed
+415 -21

No files matched your search

+53 -1
View File
@@ -1,3 +1,5 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
// 项目墙数据(极客层 P0):构建时双源拉取 + 仓内缓存兜底
// 双源 = 自家 gitea(数据自控叙事)+ GitHub(社区 star 真值);置顶清单人工维护
export interface RepoCard {
@@ -28,8 +30,18 @@ export const GITHUB_API = 'https://api.github.com/users/wait4xx/repos?per_page=1
export const PINNED_DEFAULT: string[] = ['blog'];
async function fetchJson(url: string): Promise<RawRepo[]> {
const headers: Record<string, string> = { 'User-Agent': 'wait4x-blog-build' };
// GitHub 无鉴权仅 60 次/小时(多次构建就耗尽)→ 有 token 就带上(5000/小时)
if (url.includes('api.github.com')) {
try {
const token = readFileSync(join(process.env.HOME ?? '', 'docker_apps/blog/github-token.txt'), 'utf8').trim();
if (token) headers.Authorization = `Bearer ${token}`;
} catch {
/* token 缺位则匿名(可能被限流,走兜底) */
}
}
const res = await fetch(url, {
headers: { Accept: 'application/json', 'User-Agent': 'wait4x-blog-build' },
headers: { ...headers, Accept: 'application/json' },
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) throw new Error(`${url} -> ${res.status}`);
@@ -158,3 +170,43 @@ function arrange(
return b.stars - a.stars;
});
}
/** GitHub 账号统计(followers / public repos):token 缺位或请求失败一律返回 null(调用方隐藏数字) */
export interface GithubUserStats {
followers: number;
publicRepos: number;
}
export async function githubUserStats(
login = 'wait4xx',
): Promise<GithubUserStats | null> {
const { readFileSync } = await import('node:fs');
const { join } = await import('node:path');
let token: string | null = null;
try {
token = readFileSync(join(process.env.HOME ?? '', 'docker_apps/blog/github-token.txt'), 'utf8').trim() || null;
} catch {
token = null;
}
try {
const res = await fetch(`https://api.github.com/users/${login}`, {
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
Accept: 'application/vnd.github+json',
'User-Agent': 'wait4x-blog-build',
},
});
if (!res.ok) {
console.warn(`[profile] GitHub 用户统计获取失败 HTTP ${res.status},相关数字已隐藏`);
return null;
}
const data = (await res.json()) as { followers?: number; public_repos?: number };
return {
followers: Number(data.followers ?? 0),
publicRepos: Number(data.public_repos ?? 0),
};
} catch (err) {
console.warn(`[profile] GitHub 用户统计请求异常(${(err as Error).message}),相关数字已隐藏`);
return null;
}
}