clarity-theme 0.1.3 → 0.1.4

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.
Files changed (61) hide show
  1. package/README.md +12 -2
  2. package/README.zh-CN.md +12 -2
  3. package/nuxt.config.ts +17 -1
  4. package/package.json +6 -2
  5. package/src/app.config.ts +8 -104
  6. package/src/assets/icons/zhilu.svg +8 -0
  7. package/src/blog.config.ts +71 -0
  8. package/src/components/blog/BlogFooter.vue +3 -3
  9. package/src/components/blog/BlogHeader.global.vue +9 -9
  10. package/src/components/blog/BlogSidebar.vue +3 -3
  11. package/src/components/blog/ThemeToggle.vue +2 -2
  12. package/src/components/content/Alert.vue +2 -2
  13. package/src/components/content/FeedCard.vue +2 -2
  14. package/src/components/content/Mermaid.vue +1 -1
  15. package/src/components/content/ProseCode.vue +4 -32
  16. package/src/components/content/ProsePre.vue +2 -2
  17. package/src/components/content/ProseTable.vue +1 -1
  18. package/src/components/popover/Search.vue +3 -3
  19. package/src/components/post/Comment.vue +3 -15
  20. package/src/components/post/Excerpt.vue +3 -3
  21. package/src/components/post/OrderToggle.vue +3 -3
  22. package/src/components/post/PostFooter.vue +3 -4
  23. package/src/components/post/PostHeader.vue +4 -4
  24. package/src/components/post/PostSurround.vue +2 -2
  25. package/src/components/post/Slide.vue +2 -2
  26. package/src/components/util/Img.vue +1 -1
  27. package/src/components/widget/BlogLog.vue +9 -19
  28. package/src/components/widget/BlogStats.vue +4 -4
  29. package/src/components/widget/BlogTech.vue +16 -17
  30. package/src/components/widget/CommGroup.vue +43 -0
  31. package/src/composables/useArticle.ts +9 -9
  32. package/src/composables/usePagination.ts +2 -2
  33. package/src/composables/useWidgets.ts +2 -0
  34. package/src/config/app.ts +50 -4
  35. package/src/config/content.ts +6 -0
  36. package/src/config/feed.ts +2 -42
  37. package/src/config/pnpm-workspace.ts +19 -0
  38. package/src/config/ui.ts +111 -0
  39. package/src/generated/blog.config.mjs +3 -0
  40. package/src/generated/package-json.mjs +4 -0
  41. package/src/generated/pnpm-workspace.mjs +1 -0
  42. package/src/modules/anti-mirror/index.ts +33 -0
  43. package/src/modules/{clarity-config/anti-mirror-client.ts → anti-mirror/runtime/client.ts} +3 -3
  44. package/src/modules/clarity-config/index.ts +335 -77
  45. package/src/modules/clarity-source-layout/index.ts +13 -0
  46. package/src/pages/archive.vue +4 -4
  47. package/src/pages/index.vue +4 -3
  48. package/src/pages/link.vue +5 -5
  49. package/src/pages/preview.vue +2 -2
  50. package/src/server/api/stats.get.ts +9 -19
  51. package/src/server/routes/atom.xml.get.ts +48 -50
  52. package/src/server/routes/subscriptions.opml.get.ts +14 -31
  53. package/src/shared/utils/time.ts +13 -18
  54. package/src/types/article.ts +2 -2
  55. package/src/types/feed.ts +35 -1
  56. package/src/types/upstream-compat.d.ts +17 -0
  57. package/src/types/yaml-modules.d.ts +26 -0
  58. package/src/shared/types/content.ts +0 -30
  59. package/src/types/modal.ts +0 -16
  60. /package/src/assets/icons/{solar-rewind-back-bold-duotone.svg → SolarRewindBackBoldDuotone.svg} +0 -0
  61. /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@beta my-blog
44
+ pnpm create clarity-theme my-blog
38
45
  cd my-blog
39
46
  pnpm dev
40
47
  ```
41
48
 
42
- The creator package is published separately as `create-clarity-theme`; the `@beta` dist-tag is used while its initial prerelease is the published channel. See [`create-clarity-theme/README.md`](./create-clarity-theme/README.md) for all options. The manual path is four files:
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
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@beta my-blog
44
+ pnpm create clarity-theme my-blog
38
45
  cd my-blog
39
46
  pnpm dev
40
47
  ```
41
48
 
42
- 创建包以独立的 `create-clarity-theme` npm 包分发;当前初始预发布位于 `@beta` dist-tag。完整参数见 [`create-clarity-theme/README.md`](./create-clarity-theme/README.md)。手动接入只需四个文件:
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
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 包 / 本地目录安装) */
@@ -88,6 +89,9 @@ export default defineNuxtConfig({
88
89
  // Layer 配置中的相对 module 路径会以消费项目为基准解析,必须使用绝对路径;
89
90
  // 且 clarity-config 必须先于 nuxt-llms 运行:后者在 setup 时读取注入的 llms 站点配置
90
91
  toThemePath('src/modules/clarity-config'),
92
+ // 上游通过 Nuxt 的 modules 目录自动加载;Layer 场景下显式注册,且必须在
93
+ // clarity-config 之后(其导入 clarity-config 生成的 blog.config 数据模块)。
94
+ toThemePath('src/modules/anti-mirror'),
91
95
  'nuxt-llms',
92
96
  ],
93
97
 
@@ -112,6 +116,13 @@ export default defineNuxtConfig({
112
116
  },
113
117
 
114
118
  typescript: {
119
+ // Theme 的 server 目录位于 src/ 内(跟随上游布局),会命中 App tsconfig
120
+ // 对 srcDir 的整体 include,导致 server 路由在 App 上下文被检查
121
+ // (queryCollection 等 Nitro 自动导入类型错误)。此处从 App tsconfig 排除;
122
+ // server 代码仍由 tsconfig.server.json 正常覆盖。
123
+ tsConfig: {
124
+ exclude: [toThemePath('src/server')],
125
+ },
115
126
  nodeTsConfig: {
116
127
  // @keep-sorted
117
128
  include: [
@@ -122,6 +133,10 @@ export default defineNuxtConfig({
122
133
  },
123
134
 
124
135
  vite: {
136
+ // 上游 BlogTech 通过 `~~/pnpm-workspace.yaml` 读取技术栈版本,
137
+ // 需要 YAML 导入支持(上游注册 unplugin-yaml/nuxt;这里直接注册 vite 插件,
138
+ // 避免其向消费项目 compilerOptions.types 注入 pnpm 严格布局下无法解析的条目)
139
+ plugins: [Yaml()],
125
140
  css: {
126
141
  preprocessorOptions: {
127
142
  scss: {
@@ -192,7 +207,8 @@ ${homepage}
192
207
 
193
208
  icon: {
194
209
  customCollections: [
195
- { prefix: 'clarity', dir: toThemePath('src/assets/icons') },
210
+ // 上游使用 `zi` 前缀(zhilu icons)与原始 PascalCase 文件名,保持一致
211
+ { prefix: 'zi', dir: toThemePath('src/assets/icons') },
196
212
  ],
197
213
  clientBundle: {
198
214
  scan: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clarity-theme",
3
3
  "type": "module",
4
- "version": "0.1.3",
4
+ "version": "0.1.4",
5
5
  "description": "Clarity Theme — 从 blog-v3 提取的可复用 Nuxt 4 Layer 博客主题",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -54,6 +54,7 @@
54
54
  "@bikariya/modals": "^0.0.8",
55
55
  "@bikariya/shiki": "^0.0.8",
56
56
  "@iconify-json/catppuccin": "^1.2.17",
57
+ "@iconify-json/devicon": "^1.2.10",
57
58
  "@iconify-json/line-md": "^1.2.16",
58
59
  "@iconify-json/ri": "^1.2.10",
59
60
  "@iconify-json/simple-icons": "^1.2.94",
@@ -95,8 +96,10 @@
95
96
  "temporal-polyfill": "^1.0.4",
96
97
  "ufo": "^1.6.0",
97
98
  "unist-util-visit": "^5.1.0",
99
+ "unplugin-yaml": "^4.2.1",
98
100
  "vfile": "^6.0.3",
99
101
  "vue-tippy": "^6.7.1",
102
+ "yaml": "^2.9.1",
100
103
  "zod": "^4.5.4"
101
104
  },
102
105
  "devDependencies": {
@@ -145,6 +148,7 @@
145
148
  "sync:check": "node scripts/sync-upstream.mjs check",
146
149
  "sync:diff": "node scripts/sync-upstream.mjs diff",
147
150
  "sync:apply": "node scripts/sync-upstream.mjs apply",
148
- "sync:verify": "node scripts/sync-upstream.mjs verify"
151
+ "sync:verify": "node scripts/sync-upstream.mjs verify",
152
+ "test:upstream-parity": "node scripts/test-upstream-parity.mjs"
149
153
  }
150
154
  }
package/src/app.config.ts CHANGED
@@ -1,110 +1,14 @@
1
1
  /**
2
- * Clarity Theme UI 默认配置。
2
+ * Layer 级 app.config。
3
3
  *
4
- * 站点数据(标题、作者、文章分类等)来自根目录 clarity.config.ts,
5
- * 由 src/modules/clarity-config 注入到 appConfig.clarity 中。
6
- *
7
- * 消费项目可在自己的 app/app.config.ts 中按需覆盖任意键。
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
- clarity: {
11
- // @keep-sorted
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 clarity = useClarityConfig()
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 clarity.footer.nav" :key="groupIndex">
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="clarity.footer.copyright" />
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 clarity = useClarityConfig()
7
+ const appConfig = useAppConfig()
8
8
  </script>
9
9
 
10
10
  <template>
11
11
  <UtilLink class="blog-header">
12
- <div v-if="clarity.header.emojiTail" class="emoji-tail">
12
+ <div v-if="appConfig.header.emojiTail" class="emoji-tail">
13
13
  <span
14
- v-for="(emoji, emojiIndex) in clarity.header.emojiTail"
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="clarity.header.logo"
23
+ :src="appConfig.header.logo"
24
24
  class="blog-logo round-cobblestone"
25
- :class="{ circle: clarity.header.showTitle }"
26
- :alt="clarity.site.title"
25
+ :class="{ circle: appConfig.header.showTitle }"
26
+ :alt="appConfig.title"
27
27
  />
28
28
 
29
- <div v-if="clarity.header.showTitle" class="blog-text">
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 clarity.site.title"
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
- {{ clarity.header.subtitle }}
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 clarity = useClarityConfig()
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 clarity.nav" :key="groupIndex">
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="clarity.footer.iconNav" />
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 clarity = useClarityConfig()
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 clarity.themes"
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 clarity = useClarityConfig()
15
- const card = computed(() => clarity.component.alert.defaultStyle === 'flat' ? props.card : !props.flat)
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 clarity = useClarityConfig()
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="clarity.link.remindNoFeed && !feed" class="no-feed" name="tabler:bell-off" />
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>
@@ -126,8 +126,8 @@ const diagram = computedAsync<{ svg?: string, width?: number, error?: string }>(
126
126
  }
127
127
 
128
128
  pre {
129
- overflow-wrap: anywhere;
130
129
  white-space: pre-wrap;
130
+ overflow-wrap: anywhere;
131
131
  }
132
132
  }
133
133
  </style>
@@ -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 slots = useSlots()
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!, rawCode.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 clarity = useClarityConfig()
31
- const compConf = computed(() => clarity.component.codeblock)
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)
@@ -44,8 +44,8 @@ const [scroll, toggleScroll] = useToggle(true)
44
44
 
45
45
  > thead {
46
46
  position: sticky;
47
- top: 0;
48
47
  z-index: 1; // ProseA 图标会透到表头上方
48
+ top: 0;
49
49
  }
50
50
 
51
51
  th {
@@ -1,13 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type { ModalEmits, ModalProps } from '../../types/modal'
2
+ import type { ModalEmits, ModalProps } from '#modals'
3
3
  import MiniSearch from 'minisearch'
4
4
 
5
5
  const props = defineProps<ModalProps>()
6
6
 
7
7
  defineEmits<ModalEmits>()
8
8
 
9
- const clarity = useClarityConfig()
10
- const segmenter = Intl.Segmenter && new Intl.Segmenter(clarity.site.language, { granularity: 'word' })
9
+ const appConfig = useAppConfig()
10
+ const segmenter = Intl.Segmenter && new Intl.Segmenter(appConfig.language, { granularity: 'word' })
11
11
 
12
12
  // await useAsyncData() 会阻塞渲染
13
13
  const { data, status } = await useLazyAsyncData(
@@ -1,8 +1,7 @@
1
1
  <script setup lang="tsx">
2
2
  import type { TippyComponent } from 'vue-tippy'
3
3
 
4
- const clarity = useClarityConfig()
5
- const enabled = computed(() => !!clarity.integrations.twikoo?.envId)
4
+ const appConfig = useAppConfig()
6
5
 
7
6
  const commentEl = useTemplateRef('comment')
8
7
  const popoverEl = useTemplateRef<TippyComponent>('popover')
@@ -53,11 +52,8 @@ function confirmOpen() {
53
52
  }
54
53
 
55
54
  onMounted(() => {
56
- if (!enabled.value)
57
- return
58
-
59
55
  window.twikoo?.init?.({
60
- envId: clarity.integrations.twikoo?.envId ?? '',
56
+ envId: appConfig.twikoo?.envId,
61
57
  // twikoo 会把挂载后的元素变为 #twikoo
62
58
  el: '#twikoo',
63
59
  })
@@ -108,12 +104,9 @@ onMounted(() => {
108
104
  </template>
109
105
  </Tooltip>
110
106
 
111
- <div v-if="enabled" id="twikoo">
107
+ <div id="twikoo">
112
108
  <p>评论加载中...</p>
113
109
  </div>
114
- <p v-else class="no-comment">
115
- 本文暂未开启评论
116
- </p>
117
110
  </section>
118
111
  </template>
119
112
 
@@ -215,11 +208,6 @@ onMounted(() => {
215
208
  }
216
209
  }
217
210
 
218
- .no-comment {
219
- margin: 2em 0;
220
- color: var(--c-text-3);
221
- }
222
-
223
211
  :deep(:where(.tk-preview-container,.tk-content)) {
224
212
  pre {
225
213
  overflow: auto;