clarity-theme 0.1.3 → 0.2.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 +15 -5
- package/README.zh-CN.md +15 -5
- package/nuxt.config.ts +19 -1
- package/package.json +14 -2
- package/src/app.config.ts +8 -104
- package/src/assets/icons/zhilu.svg +8 -0
- package/src/blog.config.ts +71 -0
- package/src/components/blog/BlogFooter.vue +3 -3
- package/src/components/blog/BlogHeader.global.vue +9 -9
- package/src/components/blog/BlogSidebar.vue +3 -3
- package/src/components/blog/ThemeToggle.vue +2 -2
- package/src/components/content/Alert.vue +2 -2
- package/src/components/content/FeedCard.vue +2 -2
- package/src/components/content/Mermaid.vue +1 -1
- package/src/components/content/ProseCode.vue +4 -32
- package/src/components/content/ProsePre.vue +2 -2
- package/src/components/content/ProseTable.vue +1 -1
- package/src/components/popover/Search.vue +3 -3
- package/src/components/post/Comment.vue +3 -15
- package/src/components/post/Excerpt.vue +3 -3
- package/src/components/post/OrderToggle.vue +3 -3
- package/src/components/post/PostFooter.vue +3 -4
- package/src/components/post/PostHeader.vue +4 -4
- package/src/components/post/PostSurround.vue +2 -2
- package/src/components/post/Slide.vue +2 -2
- package/src/components/util/Img.vue +1 -1
- package/src/components/widget/BlogLog.vue +9 -19
- package/src/components/widget/BlogStats.vue +4 -4
- package/src/components/widget/BlogTech.vue +16 -17
- package/src/components/widget/CommGroup.vue +43 -0
- package/src/composables/useArticle.ts +9 -9
- package/src/composables/usePagination.ts +2 -2
- package/src/composables/useWidgets.ts +2 -0
- package/src/config/app.ts +40 -11
- package/src/config/content.ts +6 -0
- package/src/config/feed.ts +2 -42
- package/src/config/index.ts +2 -2
- package/src/config/pnpm-workspace.ts +19 -0
- package/src/config/public.ts +3 -61
- package/src/config/ui.ts +111 -0
- package/src/modules/anti-mirror/index.ts +38 -0
- package/src/modules/{clarity-config/anti-mirror-client.ts → anti-mirror/runtime/client.ts} +3 -3
- package/src/modules/clarity-config/index.ts +354 -93
- package/src/modules/clarity-source-layout/index.ts +13 -0
- package/src/pages/archive.vue +4 -4
- package/src/pages/index.vue +4 -3
- package/src/pages/link.vue +5 -5
- package/src/pages/preview.vue +2 -2
- package/src/server/api/stats.get.ts +9 -19
- package/src/server/routes/atom.xml.get.ts +48 -50
- package/src/server/routes/subscriptions.opml.get.ts +14 -31
- package/src/server/utils/clarity.ts +1 -1
- package/src/shared/utils/time.ts +13 -18
- package/src/types/article.ts +2 -2
- package/src/types/feed.ts +35 -1
- package/src/types/upstream-compat.d.ts +17 -0
- package/src/types/yaml-modules.d.ts +26 -0
- package/src/shared/types/content.ts +0 -30
- package/src/shared/utils/clarity.ts +0 -34
- package/src/types/modal.ts +0 -16
- /package/src/assets/icons/{solar-rewind-back-bold-duotone.svg → SolarRewindBackBoldDuotone.svg} +0 -0
- /package/src/assets/icons/{solar-rewind-forward-bold-duotone.svg → SolarRewindForwardBoldDuotone.svg} +0 -0
package/README.md
CHANGED
|
@@ -21,6 +21,13 @@ Clarity replaces the traditional sync-fork workflow. Instead of merging thousand
|
|
|
21
21
|
- **Integrations stay yours**: Twikoo, head scripts, and anti-mirror are driven by consumer configuration.
|
|
22
22
|
- **Override without forking**: UI app-config overrides, same-path component overrides, custom Shiki themes, and consumer CSS.
|
|
23
23
|
|
|
24
|
+
## Core principles
|
|
25
|
+
|
|
26
|
+
- **Clarity is a Layer extraction of blog-v3**, not a reimplemented theme. Upstream UI, layout, and interaction are the source of truth by default and stay byte-identical except for explicit mechanical path rewrites recorded in `tests/upstream-parity.manifest.json`.
|
|
27
|
+
- **Upstream hard-coded behavior is preserved first.** Generalization, configuration coverage, and cleanup are future work, not this parity line.
|
|
28
|
+
- **Personal site data stays with the consumer**, following the boundary that upstream `init-project` resets.
|
|
29
|
+
- **Drift is a bug.** `pnpm test:upstream-parity` fails on any undeclared difference inside the sync surface.
|
|
30
|
+
|
|
24
31
|
## Choose your path
|
|
25
32
|
|
|
26
33
|
| Starting point | Documentation |
|
|
@@ -34,12 +41,15 @@ For an Agent-assisted migration, ask your agent to use the `migrate-blog-v3-to-c
|
|
|
34
41
|
## Quick start
|
|
35
42
|
|
|
36
43
|
```bash
|
|
37
|
-
pnpm create clarity-theme
|
|
44
|
+
pnpm create clarity-theme my-blog
|
|
38
45
|
cd my-blog
|
|
39
46
|
pnpm dev
|
|
40
47
|
```
|
|
41
48
|
|
|
42
|
-
The creator
|
|
49
|
+
The creator shows editable defaults for site identity and detects your system
|
|
50
|
+
timezone; generated projects include the `pnpm new-blog` authoring command.
|
|
51
|
+
|
|
52
|
+
The creator package is published separately as `create-clarity-theme` on the npm `latest` dist-tag. See [`create-clarity-theme/README.md`](./create-clarity-theme/README.md) for all options. The manual path is four files:
|
|
43
53
|
|
|
44
54
|
```ts
|
|
45
55
|
// nuxt.config.ts
|
|
@@ -95,9 +105,9 @@ All documents are also available in 简体中文 as sibling `*.zh-CN.md` files.
|
|
|
95
105
|
- **Getting started** — [new project](./docs/getting-started/new-project.md), [manual installation](./docs/getting-started/manual-installation.md), [migrating from blog-v3](./docs/getting-started/migration-from-blog-v3.md)
|
|
96
106
|
- **Guides** — [configuration](./docs/guides/configuration.md), [content](./docs/guides/content.md), [customization](./docs/guides/customization.md), [integrations](./docs/guides/integrations.md)
|
|
97
107
|
- **Reference** — [public API](./docs/reference/api.md), [routes and outputs](./docs/reference/routes-and-outputs.md), [compatibility matrix](./docs/reference/compatibility.md)
|
|
98
|
-
- **Concepts** — [architecture](./docs/concepts/architecture.md)
|
|
99
|
-
- **Maintainers** — [development](./docs/maintainers/development.md), [testing](./docs/maintainers/testing.md), [upstream sync](./docs/maintainers/upstream-sync.md), [patches](./docs/maintainers/patches.md), [publishing](./docs/maintainers/publishing.md), [release checklist](./docs/maintainers/release-checklist.md), [project status](./docs/maintainers/project-status.md), [roadmap](./docs/maintainers/roadmap.md), [documentation rules](./docs/maintainers/documentation.md)
|
|
100
|
-
- **Release history** — [CHANGELOG](./CHANGELOG.md); historical records live under [docs/history](./docs/history/)
|
|
108
|
+
- **Concepts** — [architecture](./docs/concepts/architecture.md) — the upstream → sync-manifest → `src/` → Layer modules → consumer pipeline, and the SOURCE / TRANSFORM / CLARITY-ONLY / LEGACY source classification
|
|
109
|
+
- **Maintainers** — [development](./docs/maintainers/development.md), [testing & parity](./docs/maintainers/testing.md), [upstream sync](./docs/maintainers/upstream-sync.md), [transform parity](./docs/maintainers/transform-parity.md), [legacy policy](./docs/maintainers/legacy-policy.md), [patches](./docs/maintainers/patches.md), [publishing](./docs/maintainers/publishing.md), [release checklist](./docs/maintainers/release-checklist.md), [project status](./docs/maintainers/project-status.md), [roadmap](./docs/maintainers/roadmap.md), [documentation rules](./docs/maintainers/documentation.md)
|
|
110
|
+
- **Release history** — [CHANGELOG](./CHANGELOG.md); historical records live under [docs/history](./docs/history/) (frozen narratives — not current specification)
|
|
101
111
|
|
|
102
112
|
## Development
|
|
103
113
|
|
package/README.zh-CN.md
CHANGED
|
@@ -21,6 +21,13 @@ Clarity 取代传统的 sync fork 工作流。你不需要把上游数千行代
|
|
|
21
21
|
- **集成仍归你所有**:Twikoo、head 脚本与反镜像均由消费方配置驱动
|
|
22
22
|
- **覆盖而无需 fork**:UI app config 覆盖、同路径组件覆盖、自定义 Shiki 主题与消费方 CSS
|
|
23
23
|
|
|
24
|
+
## 核心原则
|
|
25
|
+
|
|
26
|
+
- **Clarity 是 blog-v3 的 Layer 化提取**,不是重新实现的独立主题。上游 UI、布局与交互默认是 source of truth,除 `tests/upstream-parity.manifest.json` 中显式记录的机械路径改写外保持 byte-identical。
|
|
27
|
+
- **上游硬编码行为优先保留**:泛化、配置化与清理属于后续路线,不属于本次 parity。
|
|
28
|
+
- **个人站点数据归消费方管理**,边界以上游 `init-project` 重置范围为准。
|
|
29
|
+
- **漂移即缺陷**:同步面内任何未登记差异都会让 `pnpm test:upstream-parity` 失败。
|
|
30
|
+
|
|
24
31
|
## 选择你的路径
|
|
25
32
|
|
|
26
33
|
| 起点 | 文档 |
|
|
@@ -34,12 +41,15 @@ Clarity 取代传统的 sync fork 工作流。你不需要把上游数千行代
|
|
|
34
41
|
## 快速开始
|
|
35
42
|
|
|
36
43
|
```bash
|
|
37
|
-
pnpm create clarity-theme
|
|
44
|
+
pnpm create clarity-theme my-blog
|
|
38
45
|
cd my-blog
|
|
39
46
|
pnpm dev
|
|
40
47
|
```
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
创建器会显示可编辑的站点默认值并自动检测系统时区;生成的项目自带
|
|
50
|
+
`pnpm new-blog` 写作命令。
|
|
51
|
+
|
|
52
|
+
创建包以独立的 `create-clarity-theme` npm 包分发,稳定版位于 `latest` dist-tag。完整参数见 [`create-clarity-theme/README.md`](./create-clarity-theme/README.md)。手动接入只需四个文件:
|
|
43
53
|
|
|
44
54
|
```ts
|
|
45
55
|
// nuxt.config.ts
|
|
@@ -95,9 +105,9 @@ Clarity 属于应用运行时的一部分(Nuxt 构建与静态生成会直接
|
|
|
95
105
|
- **入门** — [创建新项目](./docs/getting-started/new-project.zh-CN.md)、[手动安装](./docs/getting-started/manual-installation.zh-CN.md)、[从 blog-v3 迁移](./docs/getting-started/migration-from-blog-v3.zh-CN.md)
|
|
96
106
|
- **指南** — [配置](./docs/guides/configuration.zh-CN.md)、[内容](./docs/guides/content.zh-CN.md)、[自定义](./docs/guides/customization.zh-CN.md)、[集成](./docs/guides/integrations.zh-CN.md)
|
|
97
107
|
- **参考** — [公共 API](./docs/reference/api.zh-CN.md)、[路由与输出](./docs/reference/routes-and-outputs.zh-CN.md)、[兼容性矩阵](./docs/reference/compatibility.zh-CN.md)
|
|
98
|
-
- **概念** — [架构](./docs/concepts/architecture.zh-CN.md)
|
|
99
|
-
- **维护者** — [开发](./docs/maintainers/development.zh-CN.md)、[
|
|
100
|
-
- **发布历史** — [CHANGELOG](./CHANGELOG.md);历史记录位于 [docs/history](./docs/history/)
|
|
108
|
+
- **概念** — [架构](./docs/concepts/architecture.zh-CN.md)——upstream → sync-manifest → `src/` → Layer 模块 → consumer 管线,以及 SOURCE / TRANSFORM / CLARITY-ONLY / LEGACY 源码分类
|
|
109
|
+
- **维护者** — [开发](./docs/maintainers/development.zh-CN.md)、[测试与 Parity](./docs/maintainers/testing.zh-CN.md)、[上游同步](./docs/maintainers/upstream-sync.zh-CN.md)、[Transform 面治理](./docs/maintainers/transform-parity.zh-CN.md)、[Legacy 政策](./docs/maintainers/legacy-policy.zh-CN.md)、[补丁](./docs/maintainers/patches.zh-CN.md)、[发布](./docs/maintainers/publishing.zh-CN.md)、[发布清单](./docs/maintainers/release-checklist.zh-CN.md)、[项目状态](./docs/maintainers/project-status.zh-CN.md)、[路线图](./docs/maintainers/roadmap.zh-CN.md)、[文档规则](./docs/maintainers/documentation.zh-CN.md)
|
|
110
|
+
- **发布历史** — [CHANGELOG](./CHANGELOG.md);历史记录位于 [docs/history](./docs/history/)(冻结叙事——不是现行规范)
|
|
101
111
|
|
|
102
112
|
## 开发
|
|
103
113
|
|
package/nuxt.config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { arch, env, version as nodeVersion, platform } from 'node:process'
|
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
4
|
import { name as ciName, CLOUDFLARE_PAGES, GITHUB_ACTIONS, NETLIFY } from 'ci-info'
|
|
5
5
|
import { Temporal } from 'temporal-polyfill'
|
|
6
|
+
import Yaml from 'unplugin-yaml/vite'
|
|
6
7
|
import { homepage, name as themeName, version as themeVersion } from './package.json'
|
|
7
8
|
|
|
8
9
|
/** Theme 根目录(Layer 本地路径基准,兼容 npm 包 / git 包 / 本地目录安装) */
|
|
@@ -37,6 +38,8 @@ export default defineNuxtConfig({
|
|
|
37
38
|
// "JetBrains Mono", 思源宋体 "Noto Serif SC"
|
|
38
39
|
{ rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' },
|
|
39
40
|
{ rel: 'stylesheet', href: 'https://fonts.googleapis.cn/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Noto+Serif+SC:wght@200..900&display=swap' },
|
|
41
|
+
// 抖音美好体 "DOUYINSANSBOLD-GB"
|
|
42
|
+
{ rel: 'stylesheet', href: 'https://fonts.bytedance.com/dfd/api/v1/css?family=DOUYINSANSBOLD-GB&display=swap' },
|
|
40
43
|
],
|
|
41
44
|
templateParams: {
|
|
42
45
|
separator: '|',
|
|
@@ -88,6 +91,9 @@ export default defineNuxtConfig({
|
|
|
88
91
|
// Layer 配置中的相对 module 路径会以消费项目为基准解析,必须使用绝对路径;
|
|
89
92
|
// 且 clarity-config 必须先于 nuxt-llms 运行:后者在 setup 时读取注入的 llms 站点配置
|
|
90
93
|
toThemePath('src/modules/clarity-config'),
|
|
94
|
+
// 上游通过 Nuxt 的 modules 目录自动加载;Layer 场景下显式注册,且必须在
|
|
95
|
+
// clarity-config 之后(其导入 clarity-config 生成的 blog.config 数据模块)。
|
|
96
|
+
toThemePath('src/modules/anti-mirror'),
|
|
91
97
|
'nuxt-llms',
|
|
92
98
|
],
|
|
93
99
|
|
|
@@ -112,6 +118,13 @@ export default defineNuxtConfig({
|
|
|
112
118
|
},
|
|
113
119
|
|
|
114
120
|
typescript: {
|
|
121
|
+
// Theme 的 server 目录位于 src/ 内(跟随上游布局),会命中 App tsconfig
|
|
122
|
+
// 对 srcDir 的整体 include,导致 server 路由在 App 上下文被检查
|
|
123
|
+
// (queryCollection 等 Nitro 自动导入类型错误)。此处从 App tsconfig 排除;
|
|
124
|
+
// server 代码仍由 tsconfig.server.json 正常覆盖。
|
|
125
|
+
tsConfig: {
|
|
126
|
+
exclude: [toThemePath('src/server')],
|
|
127
|
+
},
|
|
115
128
|
nodeTsConfig: {
|
|
116
129
|
// @keep-sorted
|
|
117
130
|
include: [
|
|
@@ -122,6 +135,10 @@ export default defineNuxtConfig({
|
|
|
122
135
|
},
|
|
123
136
|
|
|
124
137
|
vite: {
|
|
138
|
+
// 上游 BlogTech 通过 `~~/pnpm-workspace.yaml` 读取技术栈版本,
|
|
139
|
+
// 需要 YAML 导入支持(上游注册 unplugin-yaml/nuxt;这里直接注册 vite 插件,
|
|
140
|
+
// 避免其向消费项目 compilerOptions.types 注入 pnpm 严格布局下无法解析的条目)
|
|
141
|
+
plugins: [Yaml()],
|
|
125
142
|
css: {
|
|
126
143
|
preprocessorOptions: {
|
|
127
144
|
scss: {
|
|
@@ -192,7 +209,8 @@ ${homepage}
|
|
|
192
209
|
|
|
193
210
|
icon: {
|
|
194
211
|
customCollections: [
|
|
195
|
-
|
|
212
|
+
// 上游使用 `zi` 前缀(zhilu icons)与原始 PascalCase 文件名,保持一致
|
|
213
|
+
{ prefix: 'zi', dir: toThemePath('src/assets/icons') },
|
|
196
214
|
],
|
|
197
215
|
clientBundle: {
|
|
198
216
|
scan: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-theme",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Clarity Theme — 从 blog-v3 提取的可复用 Nuxt 4 Layer 博客主题",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"main": "./nuxt.config.ts",
|
|
38
38
|
"files": [
|
|
39
|
+
"!src/generated",
|
|
39
40
|
"LICENSE",
|
|
40
41
|
"README.md",
|
|
41
42
|
"nuxt.config.ts",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"@bikariya/modals": "^0.0.8",
|
|
55
56
|
"@bikariya/shiki": "^0.0.8",
|
|
56
57
|
"@iconify-json/catppuccin": "^1.2.17",
|
|
58
|
+
"@iconify-json/devicon": "^1.2.10",
|
|
57
59
|
"@iconify-json/line-md": "^1.2.16",
|
|
58
60
|
"@iconify-json/ri": "^1.2.10",
|
|
59
61
|
"@iconify-json/simple-icons": "^1.2.94",
|
|
@@ -95,8 +97,10 @@
|
|
|
95
97
|
"temporal-polyfill": "^1.0.4",
|
|
96
98
|
"ufo": "^1.6.0",
|
|
97
99
|
"unist-util-visit": "^5.1.0",
|
|
100
|
+
"unplugin-yaml": "^4.2.1",
|
|
98
101
|
"vfile": "^6.0.3",
|
|
99
102
|
"vue-tippy": "^6.7.1",
|
|
103
|
+
"yaml": "^2.9.1",
|
|
100
104
|
"zod": "^4.5.4"
|
|
101
105
|
},
|
|
102
106
|
"devDependencies": {
|
|
@@ -132,10 +136,17 @@
|
|
|
132
136
|
"docs:check": "node scripts/check-docs.mjs",
|
|
133
137
|
"verify": "node scripts/verify-theme.mjs",
|
|
134
138
|
"test:sync": "node --test tests/sync-upstream.test.mjs",
|
|
139
|
+
"test:docs-governance": "node --test tests/docs-governance.test.mjs",
|
|
140
|
+
"test:config": "node --test tests/config-schema.test.mjs",
|
|
141
|
+
"test:transform-parity": "node --test tests/transform-parity.test.mjs",
|
|
135
142
|
"test:migration": "node --test tests/migration-skill.test.mjs",
|
|
136
143
|
"test:contract": "node scripts/test-compatibility.mjs --contract-only",
|
|
137
144
|
"test:release": "pnpm verify && pnpm test:consumer && pnpm test:compatibility",
|
|
138
145
|
"test:consumer": "node scripts/test-consumer.mjs",
|
|
146
|
+
"test:file-generate": "node scripts/test-file-generate.mjs",
|
|
147
|
+
"test:runtime-parity": "node scripts/test-runtime-parity.mjs",
|
|
148
|
+
"test:visual-parity": "node scripts/test-visual-parity.mjs",
|
|
149
|
+
"test:parity": "pnpm test:upstream-parity && pnpm test:runtime-parity && pnpm test:visual-parity",
|
|
139
150
|
"test:compatibility": "node scripts/test-compatibility.mjs",
|
|
140
151
|
"test:create": "pnpm --dir create-clarity-theme test",
|
|
141
152
|
"test:create:e2e": "pnpm --dir create-clarity-theme test:e2e",
|
|
@@ -145,6 +156,7 @@
|
|
|
145
156
|
"sync:check": "node scripts/sync-upstream.mjs check",
|
|
146
157
|
"sync:diff": "node scripts/sync-upstream.mjs diff",
|
|
147
158
|
"sync:apply": "node scripts/sync-upstream.mjs apply",
|
|
148
|
-
"sync:verify": "node scripts/sync-upstream.mjs verify"
|
|
159
|
+
"sync:verify": "node scripts/sync-upstream.mjs verify",
|
|
160
|
+
"test:upstream-parity": "node scripts/test-upstream-parity.mjs"
|
|
149
161
|
}
|
|
150
162
|
}
|
package/src/app.config.ts
CHANGED
|
@@ -1,110 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Layer 级 app.config。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* UI 默认值由 src/modules/clarity-config 从 src/config/ui.ts 注入(inline 层),
|
|
5
|
+
* 此处仅提供需要字面量类型的键:pagination.sortOrder 若由模块注入,
|
|
6
|
+
* JSON 字面量会被宽化为 string,导致上游 useArticle 的
|
|
7
|
+
* ref<ArticleOrderType>(initialOrder) 类型不匹配。以 Layer cfg 提供
|
|
8
|
+
* 'date' 字面量(运行时与默认值一致,可被消费项目顶层键覆盖)。
|
|
8
9
|
*/
|
|
9
10
|
export default defineAppConfig({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
component: {
|
|
13
|
-
alert: {
|
|
14
|
-
/** 默认使用卡片风格还是扁平风格 */
|
|
15
|
-
defaultStyle: 'card' as 'card' | 'flat',
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
codeblock: {
|
|
19
|
-
/** 代码块触发折叠的行数 */
|
|
20
|
-
triggerRows: 32,
|
|
21
|
-
/** 代码块折叠后的行数 */
|
|
22
|
-
collapsedRows: 16,
|
|
23
|
-
/** 启用代码块缩进导航会关闭空格渲染 */
|
|
24
|
-
enableIndentGuide: true,
|
|
25
|
-
/** 代码块缩进导航竖线匹配空格数 */
|
|
26
|
-
indent: 4,
|
|
27
|
-
/** tab 渲染宽度 */
|
|
28
|
-
tabSize: 3,
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
/** 文章开头摘要 */
|
|
32
|
-
excerpt: {
|
|
33
|
-
animation: true,
|
|
34
|
-
caret: '_',
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
/** 精选文章 Slide */
|
|
38
|
-
slide: {
|
|
39
|
-
/** 适合封面图无字时启用 */
|
|
40
|
-
showTitle: true,
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
stats: {
|
|
44
|
-
/** 归档页面每年标题对应的年龄,无需展示时可留空 */
|
|
45
|
-
birthYear: undefined as number | undefined,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
// @keep-sorted
|
|
50
|
-
footer: {
|
|
51
|
-
/** 侧边栏底部图标导航 */
|
|
52
|
-
iconNav: [] as { icon: string, text: string, url: string }[],
|
|
53
|
-
/** 页脚站点地图 */
|
|
54
|
-
nav: [] as {
|
|
55
|
-
title: string
|
|
56
|
-
items: { icon: string, text: string, url: string }[]
|
|
57
|
-
}[],
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
/** 左侧栏顶部 Logo */
|
|
61
|
-
header: {
|
|
62
|
-
/** 展示标题文本,否则展示纯 Logo */
|
|
63
|
-
showTitle: true,
|
|
64
|
-
emojiTail: [] as string[],
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
/** 友链页面 */
|
|
68
|
-
link: {
|
|
69
|
-
/** 无订阅源展示静音图标 */
|
|
70
|
-
remindNoFeed: true,
|
|
71
|
-
/** 友链分组内随机排序 */
|
|
72
|
-
randomInGroup: true,
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
/** 左侧栏导航 */
|
|
76
|
-
nav: [
|
|
77
|
-
{
|
|
78
|
-
title: '',
|
|
79
|
-
items: [
|
|
80
|
-
{ icon: 'tabler:files', text: '文章', url: '/' },
|
|
81
|
-
{ icon: 'tabler:link', text: '友链', url: '/link' },
|
|
82
|
-
{ icon: 'tabler:archive', text: '归档', url: '/archive' },
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
|
|
87
|
-
pagination: {
|
|
88
|
-
perPage: 10,
|
|
89
|
-
/** 默认排序方式,需要是 article.order 中的键名 */
|
|
90
|
-
sortOrder: 'date',
|
|
91
|
-
/** 允许(普通/预览/归档)文章列表正序,开启后排序方式左侧图标可切换顺序 */
|
|
92
|
-
allowAscending: false,
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
themes: {
|
|
96
|
-
light: {
|
|
97
|
-
icon: 'tabler:sun',
|
|
98
|
-
tip: '浅色模式',
|
|
99
|
-
},
|
|
100
|
-
system: {
|
|
101
|
-
icon: 'tabler:device-desktop',
|
|
102
|
-
tip: '跟随系统',
|
|
103
|
-
},
|
|
104
|
-
dark: {
|
|
105
|
-
icon: 'tabler:moon',
|
|
106
|
-
tip: '深色模式',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
11
|
+
pagination: {
|
|
12
|
+
sortOrder: 'date' as const,
|
|
109
13
|
},
|
|
110
14
|
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 64" stroke="currentColor" height="1em" fill="none"
|
|
3
|
+
stroke-linecap="round" stroke-linejoin="round" stroke-width="8"
|
|
4
|
+
>
|
|
5
|
+
<!-- © Zhilu https://zhilu.site -->
|
|
6
|
+
<polyline points="4,4 36,4 5,45 37,45" />
|
|
7
|
+
<polyline points="43,19 12,60 44,60" />
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上游 `blog.config.ts` 兼容适配层(Layer 边界的唯一转换点)。
|
|
3
|
+
*
|
|
4
|
+
* blog-v3 的组件与 server 路由通过 `~~/blog.config` 直接读取站点配置;
|
|
5
|
+
* Clarity 的消费项目使用 `clarity.config.ts`(嵌套 schema),
|
|
6
|
+
* 因此由 src/modules/clarity-config 注入 `~~/blog.config` 别名指向本文件,
|
|
7
|
+
* 将 clarity.config.ts 机械映射回上游的扁平配置形状(含 myFeed 导出)。
|
|
8
|
+
* 上游同步文件因此可以保持 byte-identical,不再逐文件改写。
|
|
9
|
+
*/
|
|
10
|
+
import type { FeedEntry } from './types/feed'
|
|
11
|
+
import config from '#clarity/config'
|
|
12
|
+
|
|
13
|
+
const { site, article, feed, stats, integrations } = config
|
|
14
|
+
|
|
15
|
+
const blogConfig = {
|
|
16
|
+
title: site.title,
|
|
17
|
+
subtitle: site.subtitle ?? '',
|
|
18
|
+
description: site.description,
|
|
19
|
+
author: {
|
|
20
|
+
name: site.author.name,
|
|
21
|
+
avatar: site.author.avatar ?? '',
|
|
22
|
+
email: site.author.email ?? '',
|
|
23
|
+
homepage: site.author.homepage ?? '/',
|
|
24
|
+
},
|
|
25
|
+
copyright: site.copyright ?? { abbr: '', name: '', url: '' },
|
|
26
|
+
favicon: site.favicon,
|
|
27
|
+
language: site.language,
|
|
28
|
+
timeEstablished: site.established ?? '',
|
|
29
|
+
timeZone: site.timezone,
|
|
30
|
+
url: site.url,
|
|
31
|
+
defaultCategory: article.defaultCategory,
|
|
32
|
+
|
|
33
|
+
article: {
|
|
34
|
+
categories: article.categories,
|
|
35
|
+
types: article.types,
|
|
36
|
+
order: article.order,
|
|
37
|
+
/** 仅供上游 pnpm new 脚本使用,Layer 消费场景不涉及 */
|
|
38
|
+
useRandomPremalat: false,
|
|
39
|
+
hidePostPrefix: article.hidePostPrefix,
|
|
40
|
+
robotsNotIndex: article.robotsNotIndex,
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/** 博客 Atom 订阅源 */
|
|
44
|
+
feed,
|
|
45
|
+
|
|
46
|
+
/** 向 <head> 中添加脚本 */
|
|
47
|
+
scripts: integrations.scripts,
|
|
48
|
+
|
|
49
|
+
/** 文章统计配置 */
|
|
50
|
+
stats,
|
|
51
|
+
|
|
52
|
+
/** 自己部署的 Twikoo 服务 */
|
|
53
|
+
twikoo: integrations.twikoo ?? { envId: '', preload: '' },
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 用于生成 OPML 和友链页面配置(与上游 blog.config.ts 的导出保持一致) */
|
|
57
|
+
export const myFeed: FeedEntry = {
|
|
58
|
+
author: blogConfig.author.name,
|
|
59
|
+
sitenick: blogConfig.title,
|
|
60
|
+
title: blogConfig.title,
|
|
61
|
+
desc: blogConfig.subtitle || blogConfig.description,
|
|
62
|
+
link: blogConfig.url,
|
|
63
|
+
feed: new URL('/atom.xml', blogConfig.url).toString(),
|
|
64
|
+
icon: blogConfig.favicon,
|
|
65
|
+
avatar: blogConfig.author.avatar,
|
|
66
|
+
archs: ['Nuxt'],
|
|
67
|
+
date: blogConfig.timeEstablished,
|
|
68
|
+
comment: '这是我自己',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default blogConfig
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
2
|
+
const appConfig = useAppConfig()
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
6
|
<footer class="blog-footer">
|
|
7
7
|
<nav class="footer-nav">
|
|
8
|
-
<div v-for="(group, groupIndex) in
|
|
8
|
+
<div v-for="(group, groupIndex) in appConfig.footer.nav" :key="groupIndex">
|
|
9
9
|
<hgroup class="text-creative" v-text="group.title" />
|
|
10
10
|
<menu>
|
|
11
11
|
<li v-for="(item, itemIndex) in group.items" :key="itemIndex">
|
|
@@ -17,7 +17,7 @@ const clarity = useClarityConfig()
|
|
|
17
17
|
</menu>
|
|
18
18
|
</div>
|
|
19
19
|
</nav>
|
|
20
|
-
<p v-html="
|
|
20
|
+
<p v-html="appConfig.footer.copyright" />
|
|
21
21
|
</footer>
|
|
22
22
|
</template>
|
|
23
23
|
|
|
@@ -4,14 +4,14 @@ withDefaults(defineProps<{
|
|
|
4
4
|
}>(), {
|
|
5
5
|
tag: 'div',
|
|
6
6
|
})
|
|
7
|
-
const
|
|
7
|
+
const appConfig = useAppConfig()
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<template>
|
|
11
11
|
<UtilLink class="blog-header">
|
|
12
|
-
<div v-if="
|
|
12
|
+
<div v-if="appConfig.header.emojiTail" class="emoji-tail">
|
|
13
13
|
<span
|
|
14
|
-
v-for="(emoji, emojiIndex) in
|
|
14
|
+
v-for="(emoji, emojiIndex) in appConfig.header.emojiTail"
|
|
15
15
|
:key="emojiIndex"
|
|
16
16
|
class="split-char"
|
|
17
17
|
:style="getFixedDelay(emojiIndex * .6 - 3)"
|
|
@@ -20,16 +20,16 @@ const clarity = useClarityConfig()
|
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
22
|
<NuxtImg
|
|
23
|
-
:src="
|
|
23
|
+
:src="appConfig.header.logo"
|
|
24
24
|
class="blog-logo round-cobblestone"
|
|
25
|
-
:class="{ circle:
|
|
26
|
-
:alt="
|
|
25
|
+
:class="{ circle: appConfig.header.showTitle }"
|
|
26
|
+
:alt="appConfig.title"
|
|
27
27
|
/>
|
|
28
28
|
|
|
29
|
-
<div v-if="
|
|
29
|
+
<div v-if="appConfig.header.showTitle" class="blog-text">
|
|
30
30
|
<component :is="tag" class="header-title">
|
|
31
31
|
<span
|
|
32
|
-
v-for="(char, charIndex) in
|
|
32
|
+
v-for="(char, charIndex) in appConfig.title"
|
|
33
33
|
:key="charIndex"
|
|
34
34
|
class="split-char"
|
|
35
35
|
:style="getFixedDelay((charIndex + 1) * .1)"
|
|
@@ -38,7 +38,7 @@ const clarity = useClarityConfig()
|
|
|
38
38
|
</component>
|
|
39
39
|
|
|
40
40
|
<div class="header-subtitle">
|
|
41
|
-
{{
|
|
41
|
+
{{ appConfig.header.subtitle }}
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
44
|
</UtilLink>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
2
|
+
const appConfig = useAppConfig()
|
|
3
3
|
const layoutStore = useLayoutStore()
|
|
4
4
|
const searchStore = useSearchStore()
|
|
5
5
|
|
|
@@ -25,7 +25,7 @@ const debouncedSelection = refDebounced(text)
|
|
|
25
25
|
<Key class="keycut" code="K" cmd prevent @press="layoutStore.toggle('search')" />
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
-
<template v-for="(group, groupIndex) in
|
|
28
|
+
<template v-for="(group, groupIndex) in appConfig.nav" :key="groupIndex">
|
|
29
29
|
<h3 v-if="group.title">
|
|
30
30
|
{{ group.title }}
|
|
31
31
|
</h3>
|
|
@@ -44,7 +44,7 @@ const debouncedSelection = refDebounced(text)
|
|
|
44
44
|
|
|
45
45
|
<footer class="sidebar-footer">
|
|
46
46
|
<BlogThemeToggle />
|
|
47
|
-
<ZIconNavList :list="
|
|
47
|
+
<ZIconNavList :list="appConfig.footer.iconNav" />
|
|
48
48
|
</footer>
|
|
49
49
|
</aside>
|
|
50
50
|
</template>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
2
|
+
const appConfig = useAppConfig()
|
|
3
3
|
const colorMode = useColorMode()
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="theme-toggle">
|
|
8
8
|
<button
|
|
9
|
-
v-for="(themeData, themeName) in
|
|
9
|
+
v-for="(themeData, themeName) in appConfig.themes"
|
|
10
10
|
:key="themeName"
|
|
11
11
|
v-tip="themeData.tip"
|
|
12
12
|
:aria-label="themeData.tip"
|
|
@@ -11,8 +11,8 @@ const props = withDefaults(defineProps<{
|
|
|
11
11
|
type: 'tip',
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
const card = computed(() =>
|
|
14
|
+
const appConfig = useAppConfig()
|
|
15
|
+
const card = computed(() => appConfig.component.alert.defaultStyle === 'flat' ? props.card : !props.flat)
|
|
16
16
|
|
|
17
17
|
const typeMap = {
|
|
18
18
|
tip: {
|
|
@@ -6,7 +6,7 @@ import { Temporal } from 'temporal-polyfill'
|
|
|
6
6
|
|
|
7
7
|
const props = defineProps<FeedEntry>()
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const appConfig = useAppConfig()
|
|
10
10
|
const route = useRoute()
|
|
11
11
|
const isInspect = computed(() => import.meta.dev && route.query.inspect !== undefined)
|
|
12
12
|
|
|
@@ -50,7 +50,7 @@ function getInspectStyle(src: string): CSSProperties {
|
|
|
50
50
|
</ClientOnly>
|
|
51
51
|
|
|
52
52
|
<NuxtImg v-else class="round-cobblestone" :src="avatar" :alt="author" loading="lazy" />
|
|
53
|
-
<Icon v-if="
|
|
53
|
+
<Icon v-if="appConfig.link.remindNoFeed && !feed" class="no-feed" name="tabler:bell-off" />
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
56
|
<span class="author">{{ author }}</span>
|
|
@@ -1,37 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { VNode } from 'vue'
|
|
3
|
-
|
|
4
2
|
const props = defineProps<{
|
|
5
3
|
language?: string
|
|
6
|
-
|
|
7
|
-
* 代码原文。
|
|
8
|
-
* MDC 原生行为中 inline code 通过默认插槽(text VNode)传递,
|
|
9
|
-
* 上游 @nuxtjs/mdc patch(inline code props.code 传入原文)存在时走此 prop,
|
|
10
|
-
* fenced code(ProsePre 内部)也始终通过此 prop 传递。
|
|
11
|
-
*/
|
|
12
|
-
code?: string
|
|
4
|
+
code: string
|
|
13
5
|
copy?: boolean
|
|
14
6
|
}>()
|
|
15
7
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
/** 从插槽 VNode 中递归提取纯文本 */
|
|
19
|
-
function extractText(vnode: VNode): string {
|
|
20
|
-
if (typeof vnode.children === 'string')
|
|
21
|
-
return vnode.children
|
|
22
|
-
if (Array.isArray(vnode.children))
|
|
23
|
-
return (vnode.children as VNode[]).map(extractText).join('')
|
|
24
|
-
return ''
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** 无 patch 时以插槽文本作为原文,有 patch 时优先使用 code prop */
|
|
28
|
-
const rawCode = computed(() => {
|
|
29
|
-
if (props.code !== undefined)
|
|
30
|
-
return props.code
|
|
31
|
-
return (slots.default?.() ?? []).map(extractText).join('')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const { copy: copyCode, copied } = useCopy(rawCode.value)
|
|
8
|
+
const { copy: copyCode, copied } = useCopy(props.code)
|
|
35
9
|
const shiki = useShiki()
|
|
36
10
|
const codeElement = useTemplateRef('code')
|
|
37
11
|
const highlighted = ref(false)
|
|
@@ -39,7 +13,7 @@ const highlighted = ref(false)
|
|
|
39
13
|
onMounted(async () => {
|
|
40
14
|
if (!props.language)
|
|
41
15
|
return
|
|
42
|
-
await shiki.mountInline(codeElement.value!,
|
|
16
|
+
await shiki.mountInline(codeElement.value!, props.code, {
|
|
43
17
|
language: props.language,
|
|
44
18
|
transformerOptions: ['ignoreColorizedBrackets'],
|
|
45
19
|
})
|
|
@@ -49,9 +23,7 @@ onMounted(async () => {
|
|
|
49
23
|
|
|
50
24
|
<template>
|
|
51
25
|
<code ref="code" :class="{ copyable: copy }">
|
|
52
|
-
<template v-if="!language || !highlighted">
|
|
53
|
-
<slot>{{ rawCode }}</slot>
|
|
54
|
-
</template>
|
|
26
|
+
<template v-if="!language || !highlighted">{{ code }}</template>
|
|
55
27
|
<Icon v-if="copy" v-show="false" name="tabler:check" />
|
|
56
28
|
<button v-if="copy" type="button" class="copy-button" aria-label="复制" @click="copyCode()">
|
|
57
29
|
<Icon :name="copied ? 'tabler:check' : 'tabler:copy'" />
|
|
@@ -27,8 +27,8 @@ const meta = computed(() => props.meta.split(' ').reduce((acc: CodeblockMeta, it
|
|
|
27
27
|
return acc
|
|
28
28
|
}, {}))
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
const compConf = computed(() =>
|
|
30
|
+
const appConfig = useAppConfig()
|
|
31
|
+
const compConf = computed(() => appConfig.component.codeblock)
|
|
32
32
|
|
|
33
33
|
const rows = computed(() => props.code.split('\n').length - 1)
|
|
34
34
|
const collapsible = computed(() => !meta.value.expand && rows.value > compConf.value.triggerRows)
|