@windcoder/astro-theme-orchid 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +311 -2
- package/package.json +68 -3
- package/src/components/AlbumCard.astro +73 -0
- package/src/components/Analytics.astro +22 -0
- package/src/components/BaseLayout.astro +188 -0
- package/src/components/Calendar.astro +379 -0
- package/src/components/CategoryBar.astro +74 -0
- package/src/components/Comment.astro +48 -0
- package/src/components/EncryptedContent.astro +42 -0
- package/src/components/Footer.astro +31 -0
- package/src/components/Header.astro +153 -0
- package/src/components/License.astro +29 -0
- package/src/components/Live2DWidget.astro +262 -0
- package/src/components/MusicPlayer.astro +940 -0
- package/src/components/OutdatedCard.astro +53 -0
- package/src/components/Pagination.astro +94 -0
- package/src/components/PhotoCard.astro +22 -0
- package/src/components/PostCard.astro +89 -0
- package/src/components/PostNav.astro +40 -0
- package/src/components/RecommendedPost.astro +166 -0
- package/src/components/SakuraEffect.astro +152 -0
- package/src/components/Sidebar.astro +334 -0
- package/src/components/Toc.astro +27 -0
- package/src/components/analytics/GoogleAnalytics.astro +22 -0
- package/src/components/analytics/La51Analytics.astro +52 -0
- package/src/components/analytics/MicrosoftClarity.astro +18 -0
- package/src/components/analytics/UmamiAnalytics.astro +86 -0
- package/src/components/comments/Artalk.astro +51 -0
- package/src/components/comments/Disqus.astro +43 -0
- package/src/components/comments/Giscus.astro +50 -0
- package/src/components/comments/Twikoo.astro +65 -0
- package/src/components/comments/Waline.astro +36 -0
- package/src/i18n/en.ts +164 -0
- package/src/i18n/index.ts +18 -0
- package/src/i18n/resolve.ts +49 -0
- package/src/i18n/zh-CN.ts +184 -0
- package/src/icons.ts +71 -0
- package/src/index.ts +406 -0
- package/src/plugins/plantuml-encoder.ts +95 -0
- package/src/plugins/rehype-diagram.ts +218 -0
- package/src/plugins/rehype-email.ts +75 -0
- package/src/plugins/rehype-github-card.ts +114 -0
- package/src/plugins/rehype.ts +284 -0
- package/src/plugins/remark-diagram.ts +78 -0
- package/src/plugins/remark-directives.ts +100 -0
- package/src/plugins/remark-excerpt.ts +29 -0
- package/src/plugins/remark-image-grid.ts +97 -0
- package/src/plugins/remark-reading-time.ts +38 -0
- package/src/plugins/scripts/diagram-panzoom.js +388 -0
- package/src/plugins/scripts/plantuml-theme-switch.js +90 -0
- package/src/routes/404.astro +14 -0
- package/src/routes/_hydrate.ts +34 -0
- package/src/routes/about.astro +25 -0
- package/src/routes/api/allPostMeta.json.ts +26 -0
- package/src/routes/archive.astro +81 -0
- package/src/routes/categories/[category].astro +37 -0
- package/src/routes/categories/index.astro +29 -0
- package/src/routes/friends.astro +83 -0
- package/src/routes/gallery/[album].astro +97 -0
- package/src/routes/gallery/index.astro +100 -0
- package/src/routes/guestbook.astro +37 -0
- package/src/routes/index.astro +47 -0
- package/src/routes/og/[...slug].ts +281 -0
- package/src/routes/page/[page].astro +53 -0
- package/src/routes/posts/[...slug].astro +337 -0
- package/src/routes/robots.txt.ts +18 -0
- package/src/routes/rss.xml.ts +37 -0
- package/src/routes/series/[name].astro +63 -0
- package/src/routes/series/index.astro +64 -0
- package/src/routes/sponsor.astro +84 -0
- package/src/routes/tags/[tag].astro +40 -0
- package/src/routes/tags/index.astro +29 -0
- package/src/schema.ts +1212 -0
- package/src/scripts/app.ts +1041 -0
- package/src/scripts/share-poster.ts +468 -0
- package/src/styles/global.css +4830 -0
- package/src/utils/crypto.ts +46 -0
- package/src/utils/gallery.ts +76 -0
- package/src/utils/lqip.ts +18 -0
- package/src/utils/related.ts +101 -0
- package/src/utils.ts +181 -0
- package/src/vite.ts +105 -0
package/README.md
CHANGED
|
@@ -1,3 +1,312 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @windcoder/astro-theme-orchid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
清新简约的 Astro 博客主题,以 **Astro Integration** 形式提供:站点只做三件事——声明域名、接入主题、传入配置;页面、样式引擎、路由全部由主题注入。支持版本化升级与组件覆盖(`src/overrides/` 同名替换,升级永不覆盖站点定制)。
|
|
4
|
+
|
|
5
|
+
## 特性一览
|
|
6
|
+
|
|
7
|
+
- **页面**:首页(featured/cover/compact/text 四种卡片混排)、文章页(TOC/相关推荐/分享海报/打赏入口)、归档(按年折叠)、分类、标签、专题、画廊(含加密相册)、友链、留言板、打赏、关于
|
|
8
|
+
- **写作增强**:LaTeX(KaTeX + mhchem)、Mermaid 双主题、PlantUML、Callouts、GitHub 卡片、图语法(figure/九宫格)、Expressive Code 代码块(双主题/行号/折叠/复制/语言徽标)、邮箱防爬
|
|
9
|
+
- **互动系统**:评论五套(Artalk / Twikoo / Waline / Giscus / Disqus)、文章加密、过时提醒、公告、广告位
|
|
10
|
+
- **个性化**:深浅模式、hue 动态调色、樱花飘落、背景壁纸、音乐播放器(跨页不断播)、Live2D 看板娘、i18n(zh-CN / en)
|
|
11
|
+
- **工程化**:配置启动即校验(Zod)、OG 分享图、LQIP 占位、字体子集化、站点统计四件套(GA / Clarity / Umami / 51.la)、RSS / sitemap
|
|
12
|
+
|
|
13
|
+
## 快速上手
|
|
14
|
+
|
|
15
|
+
### 1. 安装
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm add @windcoder/astro-theme-orchid
|
|
19
|
+
# peerDependencies: astro ^7.0.0
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. 接入主题
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// astro.config.ts
|
|
26
|
+
import orchidTheme from "@windcoder/astro-theme-orchid";
|
|
27
|
+
import { defineConfig } from "astro/config";
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
site: "https://blog.example.com",
|
|
31
|
+
integrations: [orchidTheme({ title: "我的博客" /* 其余配置见下文 */ })],
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
配置在 dev/build 启动即校验(Zod schema),错误会直接报出来。所有配置项都有默认值,只有 `title` 必填。
|
|
36
|
+
|
|
37
|
+
### 3. 内容集合
|
|
38
|
+
|
|
39
|
+
Astro 要求 collections 定义在站点项目内,站点通过 `definePostSchema` 在兼容主题字段的基础上扩展自己的 frontmatter:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// src/content.config.ts
|
|
43
|
+
import { defineCollection, z } from "astro:content";
|
|
44
|
+
import { definePostSchema } from "@windcoder/astro-theme-orchid/schema";
|
|
45
|
+
|
|
46
|
+
const posts = defineCollection({
|
|
47
|
+
type: "content",
|
|
48
|
+
schema: definePostSchema({ /* 站点自定义字段 */ }),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const collections = { posts };
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### 可选:启用 MDX
|
|
55
|
+
|
|
56
|
+
MDX 是站点能力选择:是否写 `.mdx` 文章由站点决定。主题渲染管线与其天然兼容——
|
|
57
|
+
`@astrojs/mdx` 默认继承站点 `markdown` 配置,主题注册的自定义 unified processor
|
|
58
|
+
(KaTeX / Mermaid / PlantUML / directives / Expressive Code)原样作用于 `.mdx`,无需任何额外接线。
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
pnpm add @astrojs/mdx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// astro.config.ts
|
|
66
|
+
import mdx from "@astrojs/mdx";
|
|
67
|
+
|
|
68
|
+
export default defineConfig({
|
|
69
|
+
integrations: [mdx(), orchidTheme(themeConfig)],
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
集合 loader 同步扩展为 `"**/*.{md,mdx}"`。正文可直接 `import` 并渲染 Astro 组件
|
|
74
|
+
(组件是站点直接依赖,如 `astro-icon` 需在站点 `package.json` 声明)。
|
|
75
|
+
|
|
76
|
+
> 已知上游坑(§4.67):`hast-util-to-estree@3.1.3` 依赖的 `style-to-js@1.0.0`
|
|
77
|
+
> 纯 CJS(`exports.default` 形态),ESM 默认导入得到对象而非函数,MDX 编译带
|
|
78
|
+
> style 属性的节点(EC/katex 输出)报 `styleToJs is not a function`。在
|
|
79
|
+
> `pnpm-workspace.yaml` 的 `overrides` 加 `style-to-js: ^1.1.16` 即可
|
|
80
|
+
> (1.1.x 起修正 `module.exports` 互操作)。
|
|
81
|
+
|
|
82
|
+
### 4. 站点结构约定
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
site/
|
|
86
|
+
├── astro.config.ts # 接入主题(见上)
|
|
87
|
+
├── config/ # 配置按模块拆分(推荐做法,也可直接内联)
|
|
88
|
+
├── src/content/posts/ # 文章(.md;启用 MDX 后 .mdx 一并收录)
|
|
89
|
+
├── src/content/spec/ # 独立页内容:about.md / friends.md / guestbook.md
|
|
90
|
+
├── src/overrides/ # 组件覆盖(路径镜像主题,见「定制与覆盖」)
|
|
91
|
+
├── scripts/new-post.mjs # 写作辅助:pnpm new-post <标题>[.md|.mdx](demo 站)
|
|
92
|
+
└── public/ # 静态资源(音乐/壁纸/画廊/模型等)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
独立页 `friends.md` / `guestbook.md` 的 frontmatter 只需 `title`;页面数据(友链列表、打赏码等)来自主题配置(见下文 `friends` / `sponsor`)。
|
|
96
|
+
|
|
97
|
+
## 配置参考
|
|
98
|
+
|
|
99
|
+
> **同步约定**:主题新增或修改配置项时,本表同步更新(同一次提交内)。字段语义以 [`src/schema.ts`](./src/schema.ts) 注释为准。
|
|
100
|
+
|
|
101
|
+
### 基础信息
|
|
102
|
+
|
|
103
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `title` | `string` | (必填) | 站点名 |
|
|
106
|
+
| `description` | `string` | `""` | 站点描述(首页与 meta description 默认值) |
|
|
107
|
+
| `keywords` | `string[]` | `[]` | 站点级 SEO keywords;文章页由 frontmatter `tags` 覆盖,留空不输出 meta |
|
|
108
|
+
| `logo` | `string` | `""` | 导航栏 Logo 图片 URL(public 路径或外链),留空只渲染文字标题 |
|
|
109
|
+
| `author` | `string` | `""` | 作者名 |
|
|
110
|
+
| `accentColor` | `string` | `"#4a9d8f"` | 强调色(6 位 hex)。构建期转 HSL,全部派生色由公式计算 |
|
|
111
|
+
| `lang` | `string` | `"zh-CN"` | 站点语言(`<html lang>`),同时驱动 UI 文案(支持 `zh-CN` / `en`) |
|
|
112
|
+
| `pageWidth` | `number` | `1120` | 主容器页宽(800-1920 px),驱动主壳与文章页双列布局 |
|
|
113
|
+
| `timezone` | `string` | — | 构建期日期时区(IANA 名,如 `"Asia/Shanghai"`),留空跟随构建环境 |
|
|
114
|
+
| `since` | `string` | — | 建站日期 `YYYY-MM-DD`(站点信息卡「已运行」),不配置不显示 |
|
|
115
|
+
| `colorMode` | `"light" \| "dark" \| "system"` | `"system"` | 默认深浅模式(仅初值,用户切换后以 localStorage 为准) |
|
|
116
|
+
| `favicon` | `{src, type?, sizes?, theme?}[]` | `[]` | 数组非空时替换默认 favicon;`theme: "light"/"dark"` 按系统深浅切换 |
|
|
117
|
+
| `footer.customHtml` | `string` | `""` | 页脚自定义 HTML(备案号等),渲染在页脚最底部 |
|
|
118
|
+
|
|
119
|
+
### 导航与背景
|
|
120
|
+
|
|
121
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `nav` | `{label, href?, external?, children?}[]` | `[]` | 顶部导航。`children` 非空时父项为下拉入口;`external: true` 新窗口打开 |
|
|
124
|
+
| `banner.enabled` | `boolean` | `true` | 首页 banner 总开关(hero 文案 + 背景图合并单配置,§4.42);`false` 时两者都不渲染 |
|
|
125
|
+
| `banner.src` | `string \| string[]` | — | banner 图源。数组时构建期随机选一张(零 JS);不配置则无图(quote 有值时 hero 文案渲染为列表上方独立区块) |
|
|
126
|
+
| `banner.height` | `number` | `32` | banner 高度(10-90 vh) |
|
|
127
|
+
| `banner.dimOpacity` | `number` | `0.2` | 黑蒙层不透明度(0-1) |
|
|
128
|
+
| `banner.position` | `string` | `"center"` | 图片 object-position |
|
|
129
|
+
| `banner.homeOnly` | `boolean` | `true` | `true` 仅首页显示 |
|
|
130
|
+
| `banner.quote` / `banner.sub` | `string` | `""` | hero 主句/副句;quote 支持 `<em>` 强调色,留空不渲染。有 banner 图时叠加显示其上,否则为列表上方独立区块 |
|
|
131
|
+
| `huePanel.enabled` | `boolean` | `true` | 导航栏 hue 调色面板(预设色板 + 滑杆,运行时覆写 `--accent-h`,持久化 localStorage) |
|
|
132
|
+
|
|
133
|
+
### 文章列表与侧栏
|
|
134
|
+
|
|
135
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| `postList.style` | `"cover" \| "compact"` | `"cover"` | 默认卡片形态;frontmatter `cardStyle` 可按篇覆盖、可混排 |
|
|
138
|
+
| `postList.showCover` | `boolean` | `true` | 列表封面开关(§4.55);`false` 时列表卡不渲染封面(整体降级纯文字形态) |
|
|
139
|
+
| `postList.pageSize` | `number` | `10` | 每页文章数 |
|
|
140
|
+
| `postList.fallbackCover` | `string` | — | 无图文章兜底封面;不配置则降级纯文字卡 |
|
|
141
|
+
| `postList.randomCover.apis` | `string[]` | `[]` | 随机封面图源(URL 支持 `{slug}` 占位符);frontmatter `image: "api"` 的文章从此取图,客户端失败自动重试降级 |
|
|
142
|
+
| `postList.defaultLayout` | `"list" \| "grid"` | `"list"` | 默认列表形态(§4.55):访客未手动切换过时的初始形态;移动端小屏不生效(恒为单列上下布局) |
|
|
143
|
+
| `postList.allowSwitch` | `boolean` | `false` | 导航栏右上的列表/网格切换按钮(§4.55 移至 Header,仅首页/列表页显示;访客选择持久化 localStorage) |
|
|
144
|
+
| `postList.gridColumnWidth` | `number` | `320` | 网格模式最小列宽(220-600 px) |
|
|
145
|
+
| `categoryBar.enabled` | `boolean` | `false` | 分类导航栏(首页列表与文章页正文顶部) |
|
|
146
|
+
| `toc.enabled` | `boolean` | `true` | 文章页目录 |
|
|
147
|
+
| `archive.fold` | `boolean` | `false` | 归档页按年折叠(最新一年默认展开) |
|
|
148
|
+
| `outdated.enabled` / `outdated.threshold` | `boolean` / `number` | `true` / `30` | 文章过时提醒与阈值(天,按最后更新时间计算) |
|
|
149
|
+
| `sidebar.onPost` | `boolean` | `false` | 文章详情页是否启用侧栏 |
|
|
150
|
+
| `sidebar.widgets` | `{type, enabled?, showOnPost?, postOnly?, hideOnMobile?, position?, specificConfig?}[]` | 公告/分类/标签/友链/日历/TOC/站点信息/广告 | widget 顺序即渲染顺序,公共配置(开关/显隐/位置)配在数组项上,专属参数统一收进 `specificConfig` 入口(§4.52)。类型:`announcement` `categories` `tags` `friends` `calendar` `toc` `siteInfo` `advertisement` `profile`;`showOnPost: false` 仅列表页显示,`postOnly: true` 仅文章页显示,`hideOnMobile: true` 移动端隐藏,`position: "sticky"` ≥768px 跟随滚动 |
|
|
151
|
+
| `sidebar.widgets` tags 项 `specificConfig.count` | `number` | `12` | 展示的热门标签数量(0 = 全部展示),超出收进「…更多」入口 |
|
|
152
|
+
| `sidebar.widgets` friends 项 `specificConfig.count` / `specificConfig.random` | `number` / `boolean` | `3` / `false` | 友链展示数量;`random: true` 构建期随机取样 |
|
|
153
|
+
| `sidebar.widgets` calendar 项 `specificConfig.postTooltip` | `boolean` | `false` | `true` 时日历下方文章列表条目悬停显示全文 title |
|
|
154
|
+
| `announcement` | `{title, content, link{text,url,external}, closable}` | content 空 | 侧栏公告卡;`content` 留空整卡不渲染 |
|
|
155
|
+
| `advertisement` | `{enabled, title, image, imageLink, imageAlt, content, link{text,url,external}, closable, displayCount, expireDate?}` | `enabled: false` | 侧栏广告位;`displayCount` 按 localStorage 计数限次(-1 不限),`expireDate` 过期不渲染 |
|
|
156
|
+
| `profile` | `{avatar, name, bio, url, links[{name,url,icon,showName}]}` | 全空 | 侧栏个人资料卡(`sidebar.widgets` 含 `profile` 时渲染);`icon` 填内置图标名或内联 SVG,见「社交图标」 |
|
|
157
|
+
|
|
158
|
+
#### 社交图标
|
|
159
|
+
|
|
160
|
+
`profile.links[].icon` 三种写法:**内置简名**(推荐,构建期映射到 Iconify 标准集合内联渲染,无运行时请求与 JS 开销)、**Iconify 全名**(`"集:名"`,如 `"simple-icons:bilibili"`;站点补装任意 `@iconify-json/*` 集合包即可使用,无需改主题)或**内联 SVG 字符串**(以 `<` 开头,向后兼容)。名字 trim + 转小写后解析,未命中跳过图标只渲染文字,不报错。品牌图标 `fill: currentColor`、线性图标 `stroke: currentColor`,颜色随文字继承。
|
|
161
|
+
|
|
162
|
+
基于 [astro-icon](https://docs.astro.build/en/guides/integrations-guide/icon/) + Iconify 生态(§4.65 标准化方案),内置简名 20 枚:
|
|
163
|
+
|
|
164
|
+
- 线性描边(lucide,ISC):`github` `email` `rss`
|
|
165
|
+
- 品牌填充(simple-icons,CC0):`qq` `wechat`(别名 `weixin`) `weibo` `bilibili` `xiaohongshu` `x`(别名 `twitter`) `telegram` `youtube` `instagram` `linkedin` `discord` `steam` `zhihu` `gitee` `juejin` `csdn` `douyin`(simple-icons 未收录抖音官方形,暂以内联 SVG 兜底)
|
|
166
|
+
|
|
167
|
+
图文示例见 demo 站「主题使用指南」专题的《社交图标配置》一文。
|
|
168
|
+
|
|
169
|
+
### 写作增强
|
|
170
|
+
|
|
171
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
172
|
+
|---|---|---|---|
|
|
173
|
+
| `codeTheme.light` / `codeTheme.dark` | `string` | `"github-light"` / `"github-dark"` | Expressive Code 双主题(Shiki 主题名),按站点深浅模式切换 |
|
|
174
|
+
| `codeTheme.fold` | `{enabled, lineThreshold, previewLines, defaultCollapsed}` | `false` / `15` / `8` / `true` | 长代码自动折叠(pluginCollapsible):行数超过 `lineThreshold` 折叠为 `previewLines` 行预览;meta 标 `collapse` 的块恒可手动折叠;math/mermaid/plantuml 块不参与 |
|
|
175
|
+
| `codeTheme.lineNumbers` | `boolean` | `true` | 代码块显示行号(EC pluginLineNumbers,shell 会话块自动关闭) |
|
|
176
|
+
| `codeTheme.languageBadge` | `boolean` | `false` | 代码块右上角显示语言徽标(EC pluginLanguageBadge) |
|
|
177
|
+
| `codeTheme.wrap` | `boolean` | `false` | 长行自动换行(false 时横向滚动) |
|
|
178
|
+
| `latex.enabled` | `boolean` | `false` | `$...$` / `$$...$$` 数学公式(KaTeX,含 mhchem 化学宏) |
|
|
179
|
+
| `diagram.mermaid` | `{enabled, lightTheme, darkTheme}` | `false` / `"github-light"` / `"github-dark"` | Mermaid 构建期双主题静态渲染(` ```mermaid ` 代码块) |
|
|
180
|
+
| `diagram.plantuml` | `{enabled, server, lightTheme, darkTheme}` | `false` / 官方服务 / `""` / `""` | PlantUML(` ```plantuml ` 代码块),图片由浏览器访问渲染服务加载 |
|
|
181
|
+
| `emailProtection` | `{enabled, method}` | `false` / `"base64"` | mailto 链接构建期编码防爬(`base64` / `rot13`),点击时还原 |
|
|
182
|
+
| `og.enabled` | `boolean` | `false` | 构建期生成每篇文章 OG 分享图(1200x630);Google Fonts 拉取失败降级占位图,不中断构建 |
|
|
183
|
+
| `sharePoster.enabled` | `boolean` | `true` | 文章页「生成分享海报」按钮(纯浏览器 canvas,按需加载) |
|
|
184
|
+
| `relatedPosts` | `{enabled, count, randomCount}` | `true` / `5` / `5` | 文章页相关/随机推荐(相关栏构建期评分;加密文章不进两个池) |
|
|
185
|
+
| `license` | `{enabled, name, url, prefix, suffix}` | `true` / `"CC BY-NC-SA 4.0"` / CC 链接 / `""` / `""` | 版权声明;frontmatter `licenseName/licenseUrl/license` 可按篇覆盖 |
|
|
186
|
+
|
|
187
|
+
### 独立页与数据
|
|
188
|
+
|
|
189
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| `pages` | `{about, friends, guestbook, sponsor, gallery, series}` | 全 `true` | 独立页路由开关,`false` 则路由完全不生成 |
|
|
192
|
+
| `gallery` | `{title, description, albums[], columnWidth}` | 「画廊」/ `[]` / `240` | 相册数据纯配置驱动。`albums[]`: `{id, name, description?, date?, location?, tags?, cover?, password?, passwordHint?}`;照片来源 `public/gallery/<id>/` 目录扫描 + 同目录 `urls.txt` 远程列表;`password` 非空即加密相册(构建期加密) |
|
|
193
|
+
| `friends` | `{title, description, sort, apply, list[]}` | 「友链」/ weight 排序 | `list[]`: `{title, site, image?, desc?, tags?, weight?, enabled?, featured?}`;同时驱动友链页与侧栏 widget;头像留空渲染字头像 |
|
|
194
|
+
| `friends.apply` | `{title, description, link, label}` | title 空 | 申请友链卡片,title 留空整卡不渲染 |
|
|
195
|
+
| `sponsor` | `{title, description, usage, showSponsorsList, showButtonInPost, methods[], sponsors[]}` | 「打赏」 | `methods[]`: `{name, icon?, image?, link?, description?, enabled?}`(收款码与跳转链接至少配一个);`sponsors[]`: `{name, avatar?, amount?, date?}` 打赏者名单 |
|
|
196
|
+
| `comment` | `{enabled, provider, title, artalk, twikoo, waline, giscus, disqus}` | `false` / `"none"` / 「评论」 | 五套评论按 `provider` 分发;`enabled: false` 或 `provider: "none"` 页面不渲染评论区。各套子配置:`artalk{server, locale, visitorCount}`、`twikoo{envId, jsUrl, cssUrl}`、`waline{serverURL, lang, visitorCount}`、`giscus{repo, repoId, category, categoryId, mapping, strict, reactionsEnabled, emitMetadata, inputPosition, lang, loading}`、`disqus{shortname}`;对应字段留空即不启用该套 |
|
|
197
|
+
| `analytics` | `{googleAnalyticsId, microsoftClarityId, umami, la51}` | 全空 | 四选多,ID 留空即不注入。`umami{websiteId, scriptUrl, replaysScriptUrl, trackOutboundLinks, collectWebVitals, replays{...}}`;`la51{id, sdkUrl, ck, autoTrack, hashMode, screenRecord}` |
|
|
198
|
+
|
|
199
|
+
### 悬浮组件与特效
|
|
200
|
+
|
|
201
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
202
|
+
|---|---|---|---|
|
|
203
|
+
| `sakura` | `{enabled, density}` | `false` / `24` | 樱花飘落(全站 canvas 粒子,零贴图);`prefers-reduced-motion` 自动停用 |
|
|
204
|
+
| `music` | `{enabled, position, volume, playMode, showLyrics, playlist[]}` | `false` / `"left"` / `0.7` / `"list"` / `true` / `[]` | 音乐播放器(local 模式,`transition:persist` 跨页不断播)。`playlist[]`: `{name, artist?, url, cover?, lrc?}`;`lrc` 支持 `.lrc` 文件路径或内联歌词文本;`playMode`: `list`/`one`/`random`;音量访客调整后记忆。`enabled` 且歌单非空才渲染 |
|
|
205
|
+
| `fonts` | `{name, cssVariable, provider, weights?, styles?, subsets?, fallbacks?, display?, preload?, role?, options?}[]` | `[]` | 字体(Astro Fonts API 标准方案,§4.66):声明式接入,构建期产出 @font-face 与变量,`<head>` 渲染 `<Font>` 组件;`role` 映射主题三角色变量。详见下方「fonts 详细配置」 |
|
|
206
|
+
| `live2d` | 见下 | `enabled: false` | Live2D 看板娘(基于 [l2d-widget](https://l2d-widget.hacxy.cn)),`transition:persist` 跨页常驻 |
|
|
207
|
+
|
|
208
|
+
#### live2d 详细配置
|
|
209
|
+
|
|
210
|
+
| 子项 | 类型 | 默认值 | 说明 |
|
|
211
|
+
|---|---|---|---|
|
|
212
|
+
| `enabled` | `boolean` | `false` | 总开关 |
|
|
213
|
+
| `model` | `{path, volume?, scale?, x?, y?}` 或其数组 | — | 模型配置,数组可多模型切换(默认菜单自动出现「切换模型」)。`path` 为模型 json 的 public 路径或外链;`volume` 动作音量 0-1(默认 0 静音);`scale` 缩放;`x`/`y` 偏移 -2~2 |
|
|
214
|
+
| `position` | `"bottom-left" \| "bottom-right"` | `"bottom-left"` | 显示位置。与音乐播放器(左下)/ 返回顶部(右下)同角时会重叠,注意 `zIndex` 层级取舍 |
|
|
215
|
+
| `size` | `number` 或 `{width, height}` | `280` | 画布尺寸(px),数字为正方形边长 |
|
|
216
|
+
| `primaryColor` | `string` | `""` | 菜单/气泡等 UI 主题色,留空用库默认 |
|
|
217
|
+
| `transitionDuration` / `transitionType` | `number` / `"slide" \| "fade"` | — | 入场/退场动画时长(ms)与类型 |
|
|
218
|
+
| `menus.items` | `{icon?, label, action, url?}[]` | 主页/回到顶部/休眠(+切换模型) | 自定义菜单(覆盖默认)。`action` 内置动作:`home` `scrollToTop` `sleep` `switchModel`,或 `"link"` + `url` 跳转外链 |
|
|
219
|
+
| `menus.align` | `"left" \| "right"` | `"right"` | 菜单对齐 |
|
|
220
|
+
| `tips` | `{enable, welcomeMessage[], messages[], duration, interval, offset{x,y}, typing?}` | — | 提示气泡:欢迎语与循环提示文案(站点个性化内容,自行配置);`duration` 显示时长 / `interval` 轮换间隔(ms);不配置用库默认,`enable: false` 关闭 |
|
|
221
|
+
| `responsive` | `{hideOnMobile, mobileBreakpoint}` | `true` / `768` | 移动端按断点隐藏 |
|
|
222
|
+
| `zIndex` | `number` | `240` | 容器层级(返回顶部 250 / 音乐播放器 255,默认放其下避免按钮被挡) |
|
|
223
|
+
|
|
224
|
+
#### fonts 详细配置
|
|
225
|
+
|
|
226
|
+
| 子项 | 类型 | 默认值 | 说明 |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| `name` | `string` | (必填) | 字体族名(provider 侧标识,如 `"Inter"`;`local` 为自定义名) |
|
|
229
|
+
| `cssVariable` | `string` | (必填) | CSS 变量名(`--` 开头,如 `"--font-inter"`),Font 组件据此产出变量 |
|
|
230
|
+
| `provider` | `"google" \| "fontsource" \| "local" \| "bunny" \| "fontshare" \| "npm"` | (必填) | 字体来源。`google`/`fontsource`/`bunny`/`fontshare` 构建期联网拉取;`local` 配合 `options.variants` 声明本地文件;`npm` 从已装字体包读取 |
|
|
231
|
+
| `weights` | `number[]` | `[400]` | 字重档位(Astro 只下载列出的字重) |
|
|
232
|
+
| `styles` | `("normal" \| "italic" \| "oblique")[]` | `["normal", "italic"]` | 字形 |
|
|
233
|
+
| `subsets` | `string[]` | `["latin"]` | 语言子集 |
|
|
234
|
+
| `fallbacks` | `string[]` | `["sans-serif"]` | 回退字体族(Astro 自动产出 size-adjust 优化 fallback @font-face,减少布局抖动) |
|
|
235
|
+
| `display` | `"auto" \| "swap" \| "block" \| "metadata" \| "fallback"` | Astro 默认 | font-display 行为 |
|
|
236
|
+
| `preload` | `boolean` | `false` | `<link rel=preload>` 首屏预载字体文件(正文字体建议开启) |
|
|
237
|
+
| `role` | `"body" \| "display" \| "mono"` | — | 角色映射(主题层字段):`<head>` 产出 `html:root` 高优先级规则,把 `--font-body` / `--font-display` / `--font-mono` 重定向为 `var(<cssVariable>), <原角色回退栈>`——字体未覆盖的字形(如 Inter 只有 latin,中文)自然回落原栈 |
|
|
238
|
+
| `options.variants` | `{src, weight?, style?}[]` | — | `provider: "local"` 专属:本地字体文件声明(相对站点项目根) |
|
|
239
|
+
|
|
240
|
+
示例(demo 站即此配置):
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
// site/config/appearanceConfig.ts
|
|
244
|
+
export const fonts: ThemeConfig["fonts"] = [
|
|
245
|
+
{
|
|
246
|
+
name: "Inter",
|
|
247
|
+
cssVariable: "--font-inter",
|
|
248
|
+
provider: "fontsource",
|
|
249
|
+
weights: [400, 700],
|
|
250
|
+
preload: true,
|
|
251
|
+
role: "body",
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
与字体子集化管线(§4.5)的边界:`site/subset-fonts.json` 面向 `public/` 下自托管字体文件(构建后子集化 + URL 重写);`fonts` 走 Astro Fonts API,产物在 `dist/_astro/fonts/`、由 `<Font>` 直接产出引用。两条管线互不相交,可并存。
|
|
257
|
+
|
|
258
|
+
## 文章 frontmatter
|
|
259
|
+
|
|
260
|
+
`definePostSchema` 全量字段(均可选字段见默认值):
|
|
261
|
+
|
|
262
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
263
|
+
|---|---|---|---|
|
|
264
|
+
| `title` | `string` | (必填) | 标题 |
|
|
265
|
+
| `description` | `string` | `""` | 摘要描述(构建期另有自动摘要插件兜底) |
|
|
266
|
+
| `published` / `updated` | `date` | 必填 / — | 发布/更新时间 |
|
|
267
|
+
| `draft` / `pinned` | `boolean` | `false` | 草稿 / 置顶 |
|
|
268
|
+
| `image` | `string` | — | 封面图;`"api"` 走 `postList.randomCover.apis` 随机图 |
|
|
269
|
+
| `tags` / `category` | `string[]` / `string` | `[]` / — | 标签 / 分类 |
|
|
270
|
+
| `password` / `passwordHint` | `string` | — | 非空即加密文章(构建期加密,密码校验在客户端) |
|
|
271
|
+
| `cardStyle` | `"cover" \| "compact" \| "text"` | — | 列表卡片形态按篇覆盖 |
|
|
272
|
+
| `series` / `seriesOrder` | `string` / `number` | — | 专题名与专题内顺序(聚合到 `/series/<name>/`) |
|
|
273
|
+
| `lang` / `author` | `string` | — | 语言 / 作者(空跟随站点) |
|
|
274
|
+
| `sourceLink` | `string` | — | 原文链接(转载) |
|
|
275
|
+
| `licenseName` / `licenseUrl` / `license` | `string` / `string` / `boolean` | — / — / `true` | 文章级版权覆盖,`license: false` 按篇关闭声明 |
|
|
276
|
+
| `comment` | `boolean` | `true` | 本篇是否展示评论 |
|
|
277
|
+
|
|
278
|
+
## 定制与覆盖
|
|
279
|
+
|
|
280
|
+
- **组件覆盖**:站点 `src/overrides/` 下镜像主题路径放同名文件即可整体替换,例如
|
|
281
|
+
`src/overrides/components/PostCard.astro` 替换文章卡片。主题内部统一经
|
|
282
|
+
`virtual:theme/components/X.astro` 引用组件,解析时优先命中 overrides,找不到回退包内默认实现;
|
|
283
|
+
overrides 内再 import 同名组件时强制解析到包内默认实现(保留包装扩展的能力)。
|
|
284
|
+
- **样式**:主题全局样式经 `@windcoder/astro-theme-orchid/styles/global.css` 注入;强调色派生色全部走 CSS 变量(`--accent-h/s/l`),可通过覆盖变量或 overrides 组件实现深度定制。
|
|
285
|
+
- **页脚注入**:`footer.customHtml` 支持注入备案号、自定义脚本/样式等任意 HTML。
|
|
286
|
+
|
|
287
|
+
## 文档同步约定
|
|
288
|
+
|
|
289
|
+
- 新增/修改**配置项**:同一次提交内更新本 README「配置参考」对应行,并在 `docs/design.md` 记录功能节(§ 编号)。
|
|
290
|
+
- 新增**frontmatter 字段**:同步更新「文章 frontmatter」表。
|
|
291
|
+
- 行为变更不涉及配置面时,至少在 `docs/design.md` 留痕。
|
|
292
|
+
|
|
293
|
+
## 本仓库开发
|
|
294
|
+
|
|
295
|
+
```text
|
|
296
|
+
packages/theme 主题包(exports 直指 src,无构建步骤)
|
|
297
|
+
site 中性 demo 站点(config/ 按模块拆分配置,是配置参考的最佳示例)
|
|
298
|
+
docs/design.md 设计与实现记录(§ 编号推进,含踩坑与验证口径)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
pnpm install
|
|
303
|
+
pnpm --filter site dev # 本地开发
|
|
304
|
+
pnpm --filter site build # 构建
|
|
305
|
+
pnpm --filter site new-post "文章标题" # 生成 frontmatter 骨架到 site/src/content/posts/(.mdx 后缀可显式指定)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
> 注意:改动主题渲染管线(rehype/remark 插件)后构建前需清除 content layer 渲染缓存:`rm -rf site/node_modules/.astro`。
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windcoder/astro-theme-orchid",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "清新简约的 Astro 博客主题,以 Integration 形式提供,支持版本化升级与组件覆盖",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"astro",
|
|
8
|
+
"astro-integration",
|
|
9
|
+
"blog",
|
|
10
|
+
"theme",
|
|
11
|
+
"tailwindcss"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/iwinder/qy-astro-theme-orchid.git",
|
|
17
|
+
"directory": "packages/theme"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/iwinder/qy-astro-theme-orchid/tree/main/packages/theme#readme",
|
|
20
|
+
"bugs": "https://github.com/iwinder/qy-astro-theme-orchid/issues",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./src/index.ts",
|
|
27
|
+
"./schema": "./src/schema.ts",
|
|
28
|
+
"./routes/*": "./src/routes/*",
|
|
29
|
+
"./components/*": "./src/components/*",
|
|
30
|
+
"./plugins/*": "./src/plugins/*",
|
|
31
|
+
"./styles/*": "./src/styles/*"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@iconify-json/lucide": "^1.2.0",
|
|
38
|
+
"@iconify-json/simple-icons": "^1.2.0",
|
|
39
|
+
"astro": "^7.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@astrojs/markdown-remark": "^7.3.2",
|
|
43
|
+
"@astrojs/rss": "^4.0.19",
|
|
44
|
+
"@astrojs/sitemap": "^3.7.3",
|
|
45
|
+
"@expressive-code/plugin-collapsible-sections": "^0.44.2",
|
|
46
|
+
"@expressive-code/plugin-line-numbers": "^0.44.2",
|
|
47
|
+
"@tailwindcss/vite": "^4.3.2",
|
|
48
|
+
"astro-icon": "^1.2.0",
|
|
49
|
+
"beautiful-mermaid": "^1.1.3",
|
|
50
|
+
"expressive-code-collapsible": "^0.1.0",
|
|
51
|
+
"expressive-code-language-badge": "^1.1.0",
|
|
52
|
+
"hastscript": "^9.0.1",
|
|
53
|
+
"katex": "^0.19.0",
|
|
54
|
+
"l2d-widget": "^0.1.2",
|
|
55
|
+
"pako": "^3.0.2",
|
|
56
|
+
"photoswipe": "^5.4.4",
|
|
57
|
+
"qrcode": "^1.5.4",
|
|
58
|
+
"rehype-expressive-code": "^0.44.2",
|
|
59
|
+
"rehype-katex": "^7.0.1",
|
|
60
|
+
"remark-directive": "^4.0.0",
|
|
61
|
+
"remark-math": "^6.0.0",
|
|
62
|
+
"satori": "^0.47.1",
|
|
63
|
+
"sharp": "^0.35.5",
|
|
64
|
+
"tailwindcss": "^4.3.2"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/pako": "^2.0.3",
|
|
68
|
+
"@types/qrcode": "^1.5.6",
|
|
69
|
+
"subset-font": "^2.5.0"
|
|
70
|
+
}
|
|
6
71
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { t } from "../i18n/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* 相册卡片(M3b-b8):画廊列表页网格单元,样式对齐设计原型 §9。
|
|
5
|
+
* data-tags(标签数组)与 data-search(名称/描述/地点/标签全文小写)供 gallery-filter 筛选。
|
|
6
|
+
*/
|
|
7
|
+
import { getLqipStyle } from "../utils/lqip.js";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
cover: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
date?: string;
|
|
15
|
+
location?: string;
|
|
16
|
+
tags?: string[];
|
|
17
|
+
photoCount: number;
|
|
18
|
+
encrypted?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
id,
|
|
23
|
+
name,
|
|
24
|
+
cover,
|
|
25
|
+
description = "",
|
|
26
|
+
date = "",
|
|
27
|
+
location = "",
|
|
28
|
+
tags = [],
|
|
29
|
+
photoCount,
|
|
30
|
+
encrypted = false,
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
|
|
33
|
+
const searchText = [name, description, location, ...tags]
|
|
34
|
+
.join(" ")
|
|
35
|
+
.toLowerCase();
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
<a class="album-card card" href={`/gallery/${id}/`} data-tags={tags.join(',')} data-search={searchText} aria-label={encrypted ? `${name}(${t('encryptedAlbum')})` : name}>
|
|
39
|
+
<div class="album-cover">
|
|
40
|
+
{cover ? (
|
|
41
|
+
<>
|
|
42
|
+
{/* LQIP 占位渐变(b9.5):外链封面查不到数据时降级纯色 */}
|
|
43
|
+
<div class="qy-lqip" style={getLqipStyle(cover)} aria-hidden="true"></div>
|
|
44
|
+
<img src={cover} alt={name} loading="lazy" decoding="async" data-lqip />
|
|
45
|
+
</>
|
|
46
|
+
) : (
|
|
47
|
+
<span class="album-cover-fallback">
|
|
48
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="m21 15-4.2-4.2a1.4 1.4 0 0 0-2 0L7 18.6"/></svg>
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
{encrypted && (
|
|
52
|
+
<span class="lock-badge">
|
|
53
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
|
54
|
+
{t('encryptedAlbum')}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
<div class="album-info">
|
|
59
|
+
<span class="album-name">{name}</span>
|
|
60
|
+
<span class="album-count">{t('galleryPhotos', { n: photoCount })}</span>
|
|
61
|
+
</div>
|
|
62
|
+
{(date || location) && (
|
|
63
|
+
<div class="album-sub">
|
|
64
|
+
{date && <span>{date}</span>}
|
|
65
|
+
{location && <span>{location}</span>}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
{tags.length > 0 && (
|
|
69
|
+
<div class="album-tags">
|
|
70
|
+
{tags.slice(0, 4).map((tag) => <span class="chip">{tag}</span>)}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</a>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* 站点统计聚合挂载点(P1-16):BaseLayout body 尾部统一挂载。
|
|
4
|
+
* 各统计 ID 留空即完全不注入对应脚本;全部留空本组件零输出。
|
|
5
|
+
*/
|
|
6
|
+
import config from "virtual:theme/config";
|
|
7
|
+
import GoogleAnalytics from "./analytics/GoogleAnalytics.astro";
|
|
8
|
+
import La51Analytics from "./analytics/La51Analytics.astro";
|
|
9
|
+
import MicrosoftClarity from "./analytics/MicrosoftClarity.astro";
|
|
10
|
+
import UmamiAnalytics from "./analytics/UmamiAnalytics.astro";
|
|
11
|
+
|
|
12
|
+
const a = config.analytics;
|
|
13
|
+
const showGA = a.googleAnalyticsId.trim() !== "";
|
|
14
|
+
const showClarity = a.microsoftClarityId.trim() !== "";
|
|
15
|
+
const showUmami = a.umami.websiteId.trim() !== "";
|
|
16
|
+
const show51la = a.la51.id.trim() !== "";
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
{showGA && <GoogleAnalytics analyticsId={a.googleAnalyticsId} />}
|
|
20
|
+
{showClarity && <MicrosoftClarity clarityId={a.microsoftClarityId} />}
|
|
21
|
+
{showUmami && <UmamiAnalytics {...a.umami} />}
|
|
22
|
+
{show51la && <La51Analytics analyticsId={a.la51.id} sdkUrl={a.la51.sdkUrl} ck={a.la51.ck} autoTrack={a.la51.autoTrack} hashMode={a.la51.hashMode} screenRecord={a.la51.screenRecord} />}
|