feat(cms): 后台扩展五件——关于页 markdown 化/社交/头像/favicon/分轨首页
- T1 关于页=markdown 文档:about.yaml intro 扩为多段 markdown(zh/en),
AboutPage 用 @astrojs/markdown-remark 的 createMarkdownProcessor(进程
内单例复用)+ set:html 渲染,正文 p/ul/strong/a 对齐站点排版;名片与
技能卡结构不动
- T2 社交链接单一数据源:home.yaml socials[](label/url/icon)+
SocialLinks.astro(图标注册表白名单:github/rss/email/bilibili/x,
未登记 icon 回退纯文字),首页 zh/en 内联硬编码社交块删除
- T3 头像与 favicon 可上传:schema avatar: image()(Astro Image 出 60px
WebP,未设回退渐变 SVG);home.yaml favicon → BaseLayout link[rel=icon]
按扩展名给 type,留空回退 /favicon.svg
- T4 首页分轨:PostList perTrack → 阅读轨/技术轨两段各限 6,各自出口
(阅读→/books/ 技术→/archives/,en 同构);E2E 断言改为两段各≤6+各自
出口+阅读在前
- T5 后台列表日期:posts summary「{{pubDate}} · {{title}}」、moments
「{{fields.pubDate}} · {{fields.text}}」+sortable;photos 无 frontmatter
日期(JSON sidecar)保持分组 · 标题并在文档写明原因
- 顺手修 content-lint 漏报:中文文件名在 HTML 里为 %XX 编码,脚本直接
拼盘上路径导致 6 条"缺图"误报 → 加 decodeURI
- 清理:删除书封误传遗留的双重嵌套目录文件
车道:op-gitea(5/5,build+E2E 37/37+lint 47 页 0/0/0);收编复验:
build 46 页 + E2E 37/37 连跑(首跑两红为车道并发构建擦 dist 的瞬时态,
复跑绿)
验证: node v26.13.0 + astro 7.4.4 全量构建 46 页 exit 0;E2E 37/37;
content-lint 0/0/0;books details/命令面板 playwright 实测
Spec: none (后台能力扩展,车道级交付)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
227bc24797
commit
328ab68f18
16 files changed
+409
-146
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// 兜底纪律:文件缺 → 用默认文案;字段缺/空串 → 用默认文案(构建绝不因文案炸)
|
||||
// 注意:整文件被删会先在 file() loader 层报错(误删级),此处守的是字段级缺失/空值
|
||||
import { getEntry } from 'astro:content';
|
||||
import type { ImageMetadata } from 'astro';
|
||||
import type { Locale } from './locale';
|
||||
|
||||
export interface Bi {
|
||||
@@ -72,3 +73,61 @@ export async function openSourceCopy(locale: Locale): Promise<OpenSourceCopy> {
|
||||
pinned: pinned.length ? pinned : [...d.pinned],
|
||||
};
|
||||
}
|
||||
|
||||
// ---- 车道「后台能力扩展」:社交链接 / 头像 / 站点图标 / markdown 正文 ----
|
||||
|
||||
export interface SocialLink {
|
||||
label: string;
|
||||
url: string;
|
||||
/** 内置图标键:github / rss / email / bilibili / x;未知或空 → 纯文字按钮 */
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SOCIALS: SocialLink[] = [
|
||||
{ label: 'GitHub', url: 'https://github.com/wait4xx', icon: 'github' },
|
||||
{ label: 'RSS', url: '/rss.xml', icon: 'rss' },
|
||||
{ label: '邮件', url: 'mailto:admin@wait4x.online', icon: 'email' },
|
||||
];
|
||||
|
||||
/** 社交链接(顺序即展示顺序);yaml 空/缺省 → 默认三条(与历史硬编码一致) */
|
||||
export async function homeSocials(): Promise<SocialLink[]> {
|
||||
const entry = await getEntry('siteHome', 'home');
|
||||
const list = entry?.data.socials?.filter((s) => s.label?.trim() && s.url?.trim()) ?? [];
|
||||
return list.length ? list : DEFAULT_SOCIALS;
|
||||
}
|
||||
|
||||
/** 头像(Astro 图片管线的 ImageMetadata);未配置 → null,调用方回退渐变占位 */
|
||||
export async function homeAvatar(): Promise<ImageMetadata | null> {
|
||||
const entry = await getEntry('siteHome', 'home');
|
||||
return entry?.data.avatar ?? null;
|
||||
}
|
||||
|
||||
/** 站点图标路径:yaml 值非空即采信,否则 /favicon.svg;值不带前导 / 时自动补 */
|
||||
export async function siteFavicon(): Promise<{ href: string; type: string }> {
|
||||
const entry = await getEntry('siteHome', 'home');
|
||||
const raw = pick(entry?.data.favicon, '/favicon.svg');
|
||||
const href = raw.startsWith('/') || raw.startsWith('http') ? raw : `/${raw}`;
|
||||
const type = href.endsWith('.svg')
|
||||
? 'image/svg+xml'
|
||||
: href.endsWith('.ico')
|
||||
? 'image/x-icon'
|
||||
: href.endsWith('.png')
|
||||
? 'image/png'
|
||||
: '';
|
||||
return { href, type };
|
||||
}
|
||||
|
||||
/** markdown 字符串 → HTML(进程内单例处理器,构建期复用) */
|
||||
let mdProcessor: Promise<import('@astrojs/markdown-remark').MarkdownProcessor> | null = null;
|
||||
export async function renderMarkdown(md: string): Promise<string> {
|
||||
const { createMarkdownProcessor } = await import('@astrojs/markdown-remark');
|
||||
mdProcessor ??= createMarkdownProcessor();
|
||||
const { code } = await (await mdProcessor).render(md);
|
||||
return code;
|
||||
}
|
||||
|
||||
/** 关于页正文(markdown,未渲染);空 → 默认文案 */
|
||||
export async function aboutIntroMarkdown(locale: Locale): Promise<string> {
|
||||
const entry = await getEntry('siteAbout', 'about');
|
||||
return pickBi(entry?.data.intro, SITE_DEFAULTS.aboutIntro, locale);
|
||||
}
|
||||
Reference in new issue
Block a user