clarity-theme 0.1.1 → 0.1.2
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 +53 -1
- package/README.zh-CN.md +50 -1
- package/nuxt.config.ts +18 -15
- package/package.json +13 -19
- package/{app → src}/app.config.ts +1 -1
- package/{app → src}/composables/useArticle.ts +2 -2
- package/{config → src/config}/app.ts +1 -1
- package/{config → src/config}/feeds.empty.ts +1 -1
- package/{config → src/config}/index.ts +1 -1
- package/{img → src/img}/index.ts +2 -2
- package/{modules → src/modules}/clarity-config/index.ts +12 -9
- package/src/modules/clarity-source-layout/index.ts +78 -0
- package/{server → src/server}/routes/subscriptions.opml.get.ts +1 -1
- package/{app → src}/types/article.ts +3 -3
- package/src/types/feed.ts +1 -0
- package/app/types/feed.ts +0 -1
- /package/{app → src}/app.vue +0 -0
- /package/{app → src}/assets/css/_variable.scss +0 -0
- /package/{app → src}/assets/css/animation.scss +0 -0
- /package/{app → src}/assets/css/article.scss +0 -0
- /package/{app → src}/assets/css/color.scss +0 -0
- /package/{app → src}/assets/css/font.scss +0 -0
- /package/{app → src}/assets/css/main.scss +0 -0
- /package/{app → src}/assets/css/reusable.scss +0 -0
- /package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
- /package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
- /package/{app → src}/components/blog/BlogAside.vue +0 -0
- /package/{app → src}/components/blog/BlogFooter.vue +0 -0
- /package/{app → src}/components/blog/BlogHeader.global.vue +0 -0
- /package/{app → src}/components/blog/BlogPanel.vue +0 -0
- /package/{app → src}/components/blog/BlogSidebar.vue +0 -0
- /package/{app → src}/components/blog/BlogWidget.vue +0 -0
- /package/{app → src}/components/blog/Mask.vue +0 -0
- /package/{app → src}/components/blog/SkipToContent.vue +0 -0
- /package/{app → src}/components/blog/ThemeToggle.vue +0 -0
- /package/{app → src}/components/content/Alert.vue +0 -0
- /package/{app → src}/components/content/Badge.vue +0 -0
- /package/{app → src}/components/content/Blur.vue +0 -0
- /package/{app → src}/components/content/CardList.vue +0 -0
- /package/{app → src}/components/content/Chat.vue +0 -0
- /package/{app → src}/components/content/Copy.vue +0 -0
- /package/{app → src}/components/content/EmojiClock.vue +0 -0
- /package/{app → src}/components/content/FeedCard.vue +0 -0
- /package/{app → src}/components/content/FeedGroup.vue +0 -0
- /package/{app → src}/components/content/Folding.vue +0 -0
- /package/{app → src}/components/content/Key.vue +0 -0
- /package/{app → src}/components/content/LinkBanner.vue +0 -0
- /package/{app → src}/components/content/LinkCard.vue +0 -0
- /package/{app → src}/components/content/MdTitle.vue +0 -0
- /package/{app → src}/components/content/Mermaid.vue +0 -0
- /package/{app → src}/components/content/MusicScore.vue +0 -0
- /package/{app → src}/components/content/Pic.vue +0 -0
- /package/{app → src}/components/content/Poetry.vue +0 -0
- /package/{app → src}/components/content/ProseA.vue +0 -0
- /package/{app → src}/components/content/ProseCode.vue +0 -0
- /package/{app → src}/components/content/ProsePre.vue +0 -0
- /package/{app → src}/components/content/ProseTable.vue +0 -0
- /package/{app → src}/components/content/Quote.vue +0 -0
- /package/{app → src}/components/content/Tab.vue +0 -0
- /package/{app → src}/components/content/Timeline.vue +0 -0
- /package/{app → src}/components/content/Tip.vue +0 -0
- /package/{app → src}/components/content/VideoEmbed.vue +0 -0
- /package/{app → src}/components/partial/Button.vue +0 -0
- /package/{app → src}/components/partial/DlGroup.vue +0 -0
- /package/{app → src}/components/partial/Dropdown.vue +0 -0
- /package/{app → src}/components/partial/Error.vue +0 -0
- /package/{app → src}/components/partial/Expand.vue +0 -0
- /package/{app → src}/components/partial/IconNavList.vue +0 -0
- /package/{app → src}/components/partial/Pagination.vue +0 -0
- /package/{app → src}/components/partial/RadioGroup.vue +0 -0
- /package/{app → src}/components/partial/Secret.vue +0 -0
- /package/{app → src}/components/partial/Slider.vue +0 -0
- /package/{app → src}/components/partial/Toggle.vue +0 -0
- /package/{app → src}/components/popover/Lightbox.vue +0 -0
- /package/{app → src}/components/popover/Search.vue +0 -0
- /package/{app → src}/components/popover/SearchItem.vue +0 -0
- /package/{app → src}/components/post/Archive.vue +0 -0
- /package/{app → src}/components/post/Article.vue +0 -0
- /package/{app → src}/components/post/Comment.vue +0 -0
- /package/{app → src}/components/post/Excerpt.vue +0 -0
- /package/{app → src}/components/post/OrderToggle.vue +0 -0
- /package/{app → src}/components/post/PostFooter.vue +0 -0
- /package/{app → src}/components/post/PostHeader.vue +0 -0
- /package/{app → src}/components/post/PostSurround.vue +0 -0
- /package/{app → src}/components/post/Slide.vue +0 -0
- /package/{app → src}/components/util/Date.vue +0 -0
- /package/{app → src}/components/util/HydrateSafe.vue +0 -0
- /package/{app → src}/components/util/Img.vue +0 -0
- /package/{app → src}/components/util/Link.vue +0 -0
- /package/{app → src}/components/widget/BlogLog.vue +0 -0
- /package/{app → src}/components/widget/BlogStats.vue +0 -0
- /package/{app → src}/components/widget/BlogTech.vue +0 -0
- /package/{app → src}/components/widget/Empty.vue +0 -0
- /package/{app → src}/components/widget/Toc.vue +0 -0
- /package/{app → src}/composables/useAvoid.ts +0 -0
- /package/{app → src}/composables/useCopy.ts +0 -0
- /package/{app → src}/composables/usePagination.ts +0 -0
- /package/{app → src}/composables/useShiki.ts +0 -0
- /package/{app → src}/composables/useToc.ts +0 -0
- /package/{app → src}/composables/useWidgets.ts +0 -0
- /package/{config → src/config}/content.d.mts +0 -0
- /package/{config → src/config}/content.mjs +0 -0
- /package/{config → src/config}/content.ts +0 -0
- /package/{config → src/config}/define.mjs +0 -0
- /package/{config → src/config}/define.ts +0 -0
- /package/{config → src/config}/feed.ts +0 -0
- /package/{config → src/config}/index.d.mts +0 -0
- /package/{config → src/config}/index.mjs +0 -0
- /package/{config → src/config}/public.ts +0 -0
- /package/{config → src/config}/schema.d.mts +0 -0
- /package/{config → src/config}/schema.mjs +0 -0
- /package/{config → src/config}/schema.ts +0 -0
- /package/{config → src/config}/server.ts +0 -0
- /package/{app → src}/error.vue +0 -0
- /package/{img → src/img}/index.d.mts +0 -0
- /package/{img → src/img}/index.mjs +0 -0
- /package/{app → src}/layouts/default.vue +0 -0
- /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
- /package/{app → src}/pages/[...slug].vue +0 -0
- /package/{app → src}/pages/archive.vue +0 -0
- /package/{app → src}/pages/index.vue +0 -0
- /package/{app → src}/pages/link.vue +0 -0
- /package/{app → src}/pages/preview.vue +0 -0
- /package/{app → src}/plugins/easter-egg.ts +0 -0
- /package/{app → src}/plugins/init.ts +0 -0
- /package/{app → src}/plugins/tippy.ts +0 -0
- /package/{public → src/public}/assets/atom.css +0 -0
- /package/{public → src/public}/assets/atom.xsl +0 -0
- /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +0 -0
- /package/{server → src/server}/api/stats.get.ts +0 -0
- /package/{server → src/server}/routes/atom.xml.get.ts +0 -0
- /package/{server → src/server}/utils/clarity.ts +0 -0
- /package/{shared → src/shared}/types/content.ts +0 -0
- /package/{shared → src/shared}/utils/clarity.ts +0 -0
- /package/{shared → src/shared}/utils/icon.ts +0 -0
- /package/{shared → src/shared}/utils/link.ts +0 -0
- /package/{shared → src/shared}/utils/str.ts +0 -0
- /package/{shared → src/shared}/utils/time.ts +0 -0
- /package/{app → src}/shiki.config.ts +0 -0
- /package/{app → src}/stores/layout.ts +0 -0
- /package/{app → src}/stores/search.ts +0 -0
- /package/{app → src}/types/index.ts +0 -0
- /package/{app → src}/types/modal.ts +0 -0
- /package/{app → src}/types/nav.ts +0 -0
- /package/{app → src}/utils/anim.ts +0 -0
- /package/{app → src}/utils/img.ts +0 -0
package/README.md
CHANGED
|
@@ -245,6 +245,57 @@ All documents are also available in 简体中文 as sibling `*.zh-CN.md` files.
|
|
|
245
245
|
| [Changelog](./CHANGELOG.md) | Consumer-facing release history |
|
|
246
246
|
| [Extraction history](./docs/history/2026-09-layer-extraction.md) | Historical phases and one-time differential validation |
|
|
247
247
|
|
|
248
|
+
## Repository Layout
|
|
249
|
+
|
|
250
|
+
The repository root is the engineering layer; all Theme runtime source lives
|
|
251
|
+
under `src/`:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
clarity-theme/
|
|
255
|
+
├─ src/ # Theme runtime source (Layer srcDir)
|
|
256
|
+
│ ├─ assets/ components/ composables/ layouts/ middleware/
|
|
257
|
+
│ ├─ pages/ plugins/ stores/ types/ utils/
|
|
258
|
+
│ ├─ app.config.ts app.vue error.vue shiki.config.ts
|
|
259
|
+
│ ├─ config/ # config API sources (npm ./config ./content ./schema)
|
|
260
|
+
│ ├─ img/ # img API sources (npm ./img)
|
|
261
|
+
│ ├─ modules/ # source-layout bootstrap + clarity-config modules
|
|
262
|
+
│ ├─ public/ server/ shared/ remark-plugins/
|
|
263
|
+
├─ skills/ # Agent workflow skills (not part of the npm package)
|
|
264
|
+
├─ docs/ # Human documentation
|
|
265
|
+
├─ playground/ # Workspace-linked development consumer
|
|
266
|
+
├─ scripts/ tests/ # Verification and tooling
|
|
267
|
+
└─ nuxt.config.ts, package.json, sync-manifest.json, …
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`nuxt.config.ts` stays at the package root. Its first module,
|
|
271
|
+
`src/modules/clarity-source-layout`, applies the `src/` directory metadata to
|
|
272
|
+
the Clarity layer only; static `srcDir`/`serverDir`/`dir.*` values are not
|
|
273
|
+
used because c12 would merge them into consumer root config. npm, Git-commit,
|
|
274
|
+
and local-directory installs therefore resolve the same layout without
|
|
275
|
+
overriding a consumer's own application directories.
|
|
276
|
+
|
|
277
|
+
The npm package, the Agent skill, and the docs are three separate concerns:
|
|
278
|
+
|
|
279
|
+
- **`clarity-theme` (npm package)** — the runtime Nuxt Layer installed with
|
|
280
|
+
`extends: ['clarity-theme']`. Its `files` field ships only `src/` plus the
|
|
281
|
+
root entry files; dev assets never enter the tarball.
|
|
282
|
+
- **`skills/migrate-blog-v3-to-clarity`** — the canonical Agent workflow for
|
|
283
|
+
migrating an existing blog-v3 project. It is versioned with the repository
|
|
284
|
+
but intentionally **not** bundled into the npm package.
|
|
285
|
+
- **`docs/`** — human-readable documentation mirrored in English and Chinese.
|
|
286
|
+
|
|
287
|
+
### Installing the migration skill
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npx skills add iicemeta/clarity-theme --list # discover available skills
|
|
291
|
+
npx skills add iicemeta/clarity-theme --skill migrate-blog-v3-to-clarity
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
For development, `npx skills add ./skills --list` discovers the local copy.
|
|
295
|
+
To migrate a blog-v3 site with an Agent, ask it to use the
|
|
296
|
+
`migrate-blog-v3-to-clarity` skill; the human walkthrough remains
|
|
297
|
+
[Migration](./docs/MIGRATION.md).
|
|
298
|
+
|
|
248
299
|
## Development
|
|
249
300
|
|
|
250
301
|
```bash
|
|
@@ -260,6 +311,7 @@ pnpm test:migration
|
|
|
260
311
|
pnpm test:contract
|
|
261
312
|
pnpm test:consumer
|
|
262
313
|
pnpm test:compatibility
|
|
314
|
+
pnpm pack --dry-run # inspect the npm tarball without writing it
|
|
263
315
|
```
|
|
264
316
|
|
|
265
317
|
CI derives Node and pnpm versions from package metadata. It runs lint/typecheck/verify/sync/migration/contract/peers on the fixed Node matrix, then playground generate, real consumer acceptance, and rendering compatibility on the primary Node version. A separate weekly workflow only detects and reports upstream drift.
|
|
@@ -281,7 +333,7 @@ The weekly workflow only detects and reports drift. It never applies, commits, o
|
|
|
281
333
|
|
|
282
334
|
## Release Status
|
|
283
335
|
|
|
284
|
-
Clarity Theme is a **v0.1.
|
|
336
|
+
Clarity Theme is a **v0.1.2 gated npm release**. The Layer/package boundary, validated configuration, Content factory, rendering pipeline, server outputs, playground, real-consumer acceptance, compatibility matrix, three-layer CI, upstream-sync baseline, release gate, and OIDC publish workflow are implemented. See the [release checklist](./docs/RELEASE-CHECKLIST.md) for the exact remaining manual publication steps.
|
|
285
337
|
|
|
286
338
|
The v0.1.0 correctness gates from [Roadmap](./docs/ROADMAP.md) are resolved: server/client configuration is split, feature-off routes return 404 at runtime, anti-mirror navigation is verified in a real browser, the no-op random-permalink field is removed, and multi-pattern stats are covered by a regression test. Remaining deferred items are recorded in the roadmap and release notes.
|
|
287
339
|
|
package/README.zh-CN.md
CHANGED
|
@@ -266,6 +266,54 @@ UI 分组、同路径组件覆盖、CSS 覆盖、服务端/路由自定义与 Sh
|
|
|
266
266
|
| [更新日志](./CHANGELOG.md) | 面向使用者的发布历史 |
|
|
267
267
|
| [抽取历史](./docs/history/2026-09-layer-extraction.zh-CN.md) | 历史阶段与一次性差分验证 |
|
|
268
268
|
|
|
269
|
+
## 仓库结构
|
|
270
|
+
|
|
271
|
+
仓库根目录是工程管理层;Theme 的全部运行时源码统一放在 `src/`:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
clarity-theme/
|
|
275
|
+
├─ src/ # Theme 运行时源码(Layer srcDir)
|
|
276
|
+
│ ├─ assets/ components/ composables/ layouts/ middleware/
|
|
277
|
+
│ ├─ pages/ plugins/ stores/ types/ utils/
|
|
278
|
+
│ ├─ app.config.ts app.vue error.vue shiki.config.ts
|
|
279
|
+
│ ├─ config/ # config API 源码(npm ./config ./content ./schema)
|
|
280
|
+
│ ├─ img/ # img API 源码(npm ./img)
|
|
281
|
+
│ ├─ modules/ # source-layout 引导模块 + clarity-config 模块
|
|
282
|
+
│ ├─ public/ server/ shared/ remark-plugins/
|
|
283
|
+
├─ skills/ # Agent 工作流 Skill(不进入 npm 包)
|
|
284
|
+
├─ docs/ # 人工文档
|
|
285
|
+
├─ playground/ # workspace 链接的开发用消费项目
|
|
286
|
+
├─ scripts/ tests/ # 验证与工具脚本
|
|
287
|
+
└─ nuxt.config.ts、package.json、sync-manifest.json、…
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`nuxt.config.ts` 保留在包根目录。它最先加载的
|
|
291
|
+
`src/modules/clarity-source-layout` 只为 Clarity 自身 layer 应用 `src/`
|
|
292
|
+
目录元数据;不静态声明 `srcDir`/`serverDir`/`dir.*`,是因为 c12 会把它们
|
|
293
|
+
合并进未显式覆盖这些键的 consumer root 配置。因此 npm 安装、Git commit
|
|
294
|
+
安装与本地目录安装解析到同一份布局,同时不会覆盖 consumer 自己的
|
|
295
|
+
应用目录。
|
|
296
|
+
|
|
297
|
+
npm 包、Agent Skill 与文档三者职责分离:
|
|
298
|
+
|
|
299
|
+
- **`clarity-theme`(npm 包)** — 运行时 Nuxt Layer,通过
|
|
300
|
+
`extends: ['clarity-theme']` 使用。`files` 只发布 `src/` 与根入口文件,
|
|
301
|
+
开发资产不会进入 tarball。
|
|
302
|
+
- **`skills/migrate-blog-v3-to-clarity`** — 迁移既有 blog-v3 项目的
|
|
303
|
+
Agent 工作流 Skill。它与仓库一起版本化,但**刻意不**打进 npm 包。
|
|
304
|
+
- **`docs/`** — 中英双语的人工文档。
|
|
305
|
+
|
|
306
|
+
### 安装迁移 Skill
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
npx skills add iicemeta/clarity-theme --list # 发现可用 Skill
|
|
310
|
+
npx skills add iicemeta/clarity-theme --skill migrate-blog-v3-to-clarity
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
开发环境可用 `npx skills add ./skills --list` 发现本地副本。让 Agent 迁移
|
|
314
|
+
blog-v3 站点时,请让它使用 `migrate-blog-v3-to-clarity` Skill;人工流程
|
|
315
|
+
见[迁移指南](./docs/MIGRATION.zh-CN.md)。
|
|
316
|
+
|
|
269
317
|
## 开发
|
|
270
318
|
|
|
271
319
|
```bash
|
|
@@ -281,6 +329,7 @@ pnpm test:migration
|
|
|
281
329
|
pnpm test:contract
|
|
282
330
|
pnpm test:consumer
|
|
283
331
|
pnpm test:compatibility
|
|
332
|
+
pnpm pack --dry-run # 不落盘检查 npm tarball
|
|
284
333
|
```
|
|
285
334
|
|
|
286
335
|
CI 从包元数据推导 Node 与 pnpm 版本。它先在固定的 Node 矩阵上运行 lint/typecheck/verify/sync/migration/contract/peers,再在主 Node 版本上运行 playground 生成、真实消费者验收与渲染兼容性。另有一个每周工作流只检测并报告上游漂移。
|
|
@@ -302,7 +351,7 @@ pnpm sync:verify # 重新运行主题纯度与基线检查
|
|
|
302
351
|
|
|
303
352
|
## 发布状态
|
|
304
353
|
|
|
305
|
-
Clarity Theme 是一个 **v0.1.
|
|
354
|
+
Clarity Theme 是一个 **v0.1.2 门禁 npm 发布版本**。Layer/包边界、已校验配置、Content 工厂、渲染管线、服务端输出、playground、真实消费者验收、兼容性矩阵、三层 CI、上游同步基线、发布门禁与 OIDC 发布工作流均已实现。剩余的人工发布步骤见[发布检查清单](./docs/RELEASE-CHECKLIST.zh-CN.md)。
|
|
306
355
|
|
|
307
356
|
[路线图](./docs/ROADMAP.zh-CN.md)中的 v0.1.0 正确性门禁已解决:服务端/客户端配置已拆分、功能关闭路由在运行时返回 404、反镜像导航已在真实浏览器中验证、no-op 随机固定链接字段已移除,多模式统计已增加回归测试。其余推迟事项记录在路线图与发布说明中。
|
|
308
357
|
|
package/nuxt.config.ts
CHANGED
|
@@ -12,13 +12,13 @@ const toThemePath = (path: string) => join(themeDir, path).replaceAll('\\', '/')
|
|
|
12
12
|
function pluginPath(path: string) {
|
|
13
13
|
// 插件必须是 .mjs:@nuxt/content 以 Node 原生方式加载,
|
|
14
14
|
// 不允许 node_modules 内的 TS 文件(TS 剥离限制)
|
|
15
|
-
return pathToFileURL(join(themeDir, 'remark-plugins', `${path}.mjs`)).href
|
|
15
|
+
return pathToFileURL(join(themeDir, 'src', 'remark-plugins', `${path}.mjs`)).href
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Clarity Theme Layer 配置
|
|
20
20
|
*
|
|
21
|
-
* 站点数据(SEO、favicon、scripts 等)由 modules/clarity-config
|
|
21
|
+
* 站点数据(SEO、favicon、scripts 等)由 src/modules/clarity-config
|
|
22
22
|
* 读取消费项目的 clarity.config.ts 后统一注入,此处仅包含 Theme 自身能力。
|
|
23
23
|
*/
|
|
24
24
|
export default defineNuxtConfig({
|
|
@@ -50,18 +50,18 @@ export default defineNuxtConfig({
|
|
|
50
50
|
compatibilityDate: '2024-08-03',
|
|
51
51
|
|
|
52
52
|
components: [
|
|
53
|
-
{ path: toThemePath('
|
|
54
|
-
toThemePath('
|
|
53
|
+
{ path: toThemePath('src/components/partial'), prefix: 'Z' },
|
|
54
|
+
toThemePath('src/components'),
|
|
55
55
|
],
|
|
56
56
|
|
|
57
57
|
// @keep-sorted
|
|
58
58
|
css: [
|
|
59
|
-
toThemePath('
|
|
60
|
-
toThemePath('
|
|
61
|
-
toThemePath('
|
|
62
|
-
toThemePath('
|
|
63
|
-
toThemePath('
|
|
64
|
-
toThemePath('
|
|
59
|
+
toThemePath('src/assets/css/animation.scss'),
|
|
60
|
+
toThemePath('src/assets/css/article.scss'),
|
|
61
|
+
toThemePath('src/assets/css/color.scss'),
|
|
62
|
+
toThemePath('src/assets/css/font.scss'),
|
|
63
|
+
toThemePath('src/assets/css/main.scss'),
|
|
64
|
+
toThemePath('src/assets/css/reusable.scss'),
|
|
65
65
|
],
|
|
66
66
|
|
|
67
67
|
// @keep-sorted
|
|
@@ -72,6 +72,9 @@ export default defineNuxtConfig({
|
|
|
72
72
|
|
|
73
73
|
// @keep-sorted
|
|
74
74
|
modules: [
|
|
75
|
+
// 必须最先运行:把 src/ 仅应用到 Clarity 自身 layer,避免静态
|
|
76
|
+
// srcDir / serverDir / dir.* 经 c12 合并泄漏到 consumer root 配置。
|
|
77
|
+
toThemePath('src/modules/clarity-source-layout'),
|
|
75
78
|
'@bikariya/image-viewer',
|
|
76
79
|
'@bikariya/modals',
|
|
77
80
|
'@bikariya/shiki',
|
|
@@ -84,7 +87,7 @@ export default defineNuxtConfig({
|
|
|
84
87
|
'@vueuse/nuxt',
|
|
85
88
|
// Layer 配置中的相对 module 路径会以消费项目为基准解析,必须使用绝对路径;
|
|
86
89
|
// 且 clarity-config 必须先于 nuxt-llms 运行:后者在 setup 时读取注入的 llms 站点配置
|
|
87
|
-
toThemePath('modules/clarity-config'),
|
|
90
|
+
toThemePath('src/modules/clarity-config'),
|
|
88
91
|
'nuxt-llms',
|
|
89
92
|
],
|
|
90
93
|
|
|
@@ -112,8 +115,8 @@ export default defineNuxtConfig({
|
|
|
112
115
|
nodeTsConfig: {
|
|
113
116
|
// @keep-sorted
|
|
114
117
|
include: [
|
|
115
|
-
'../config/**/*.ts',
|
|
116
|
-
'../remark-plugins/**/*.ts',
|
|
118
|
+
'../src/config/**/*.ts',
|
|
119
|
+
'../src/remark-plugins/**/*.ts',
|
|
117
120
|
],
|
|
118
121
|
},
|
|
119
122
|
},
|
|
@@ -122,7 +125,7 @@ export default defineNuxtConfig({
|
|
|
122
125
|
css: {
|
|
123
126
|
preprocessorOptions: {
|
|
124
127
|
scss: {
|
|
125
|
-
additionalData: `@use "${toThemePath('
|
|
128
|
+
additionalData: `@use "${toThemePath('src/assets/css/_variable.scss')}" as *;`,
|
|
126
129
|
},
|
|
127
130
|
},
|
|
128
131
|
},
|
|
@@ -189,7 +192,7 @@ ${homepage}
|
|
|
189
192
|
|
|
190
193
|
icon: {
|
|
191
194
|
customCollections: [
|
|
192
|
-
{ prefix: 'clarity', dir: toThemePath('
|
|
195
|
+
{ prefix: 'clarity', dir: toThemePath('src/assets/icons') },
|
|
193
196
|
],
|
|
194
197
|
clientBundle: {
|
|
195
198
|
scan: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-theme",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "Clarity Theme — 从 blog-v3 提取的可复用 Nuxt 4 Layer 博客主题",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
@@ -18,36 +18,29 @@
|
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./nuxt.config.ts",
|
|
20
20
|
"./config": {
|
|
21
|
-
"types": "./config/index.d.mts",
|
|
22
|
-
"default": "./config/index.mjs"
|
|
21
|
+
"types": "./src/config/index.d.mts",
|
|
22
|
+
"default": "./src/config/index.mjs"
|
|
23
23
|
},
|
|
24
24
|
"./content": {
|
|
25
|
-
"types": "./config/content.d.mts",
|
|
26
|
-
"default": "./config/content.mjs"
|
|
25
|
+
"types": "./src/config/content.d.mts",
|
|
26
|
+
"default": "./src/config/content.mjs"
|
|
27
27
|
},
|
|
28
28
|
"./img": {
|
|
29
|
-
"types": "./img/index.d.mts",
|
|
30
|
-
"default": "./img/index.mjs"
|
|
29
|
+
"types": "./src/img/index.d.mts",
|
|
30
|
+
"default": "./src/img/index.mjs"
|
|
31
31
|
},
|
|
32
32
|
"./schema": {
|
|
33
|
-
"types": "./config/schema.d.mts",
|
|
34
|
-
"default": "./config/schema.mjs"
|
|
33
|
+
"types": "./src/config/schema.d.mts",
|
|
34
|
+
"default": "./src/config/schema.mjs"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"main": "./nuxt.config.ts",
|
|
38
38
|
"files": [
|
|
39
39
|
"LICENSE",
|
|
40
40
|
"README.md",
|
|
41
|
-
"app",
|
|
42
|
-
"config",
|
|
43
|
-
"img",
|
|
44
|
-
"modules",
|
|
45
41
|
"nuxt.config.ts",
|
|
46
42
|
"package.json",
|
|
47
|
-
"
|
|
48
|
-
"remark-plugins",
|
|
49
|
-
"server",
|
|
50
|
-
"shared"
|
|
43
|
+
"src"
|
|
51
44
|
],
|
|
52
45
|
"engines": {
|
|
53
46
|
"node": "^22.19 || ^24.11 || >=26"
|
|
@@ -112,6 +105,7 @@
|
|
|
112
105
|
"@nuxt/hints": "^1.1.4",
|
|
113
106
|
"@stylistic/stylelint-plugin": "^5.2.1",
|
|
114
107
|
"@types/hast": "^3.0.5",
|
|
108
|
+
"@types/mdast": "^4.0.4",
|
|
115
109
|
"@zinkawaii/stylelint-config": "^0.5.2",
|
|
116
110
|
"cspell": "^10.2.2",
|
|
117
111
|
"eslint": "^10.10.0",
|
|
@@ -132,8 +126,8 @@
|
|
|
132
126
|
"dev": "pnpm --dir playground dev",
|
|
133
127
|
"build": "pnpm --dir playground build",
|
|
134
128
|
"generate": "pnpm --dir playground generate",
|
|
135
|
-
"lint": "eslint && stylelint \"
|
|
136
|
-
"lint:fix": "eslint --fix && stylelint \"
|
|
129
|
+
"lint": "eslint && stylelint \"src/**/*.{vue,scss}\" \"playground/app/**/*.vue\"",
|
|
130
|
+
"lint:fix": "eslint --fix && stylelint \"src/**/*.{vue,scss}\" \"playground/app/**/*.vue\" --fix",
|
|
137
131
|
"typecheck": "pnpm --dir playground typecheck",
|
|
138
132
|
"verify": "node scripts/verify-theme.mjs",
|
|
139
133
|
"test:sync": "node --test tests/sync-upstream.test.mjs",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MetaSlotsTree } from '
|
|
2
|
-
import type { ClarityContentRow } from '
|
|
1
|
+
import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
|
|
2
|
+
import type { ClarityContentRow } from '../shared/types/content'
|
|
3
3
|
import type { ArticleOrderType, ArticleProps } from '../types/article'
|
|
4
4
|
import { orderBy } from 'es-toolkit/array'
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Nav, NavItem } from '../
|
|
1
|
+
export type { Nav, NavItem } from '../types/nav'
|
|
2
2
|
export type { ClarityAppConfig, ClarityUiConfig, ClarityUiConfigInput, NavGroup } from './app'
|
|
3
3
|
export { defineClarityConfig } from './define'
|
|
4
4
|
export type { Arch, FeedEntry, FeedGroup } from './feed'
|
package/{img → src/img}/index.ts
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `clarity-theme/img` 类型真源。
|
|
3
3
|
*
|
|
4
|
-
* 类型实现复用 `
|
|
4
|
+
* 类型实现复用 `src/utils/img.ts`(Layer 内部与包导出共享同一份签名),
|
|
5
5
|
* 运行时实现在同目录 index.mjs:Node 原生 TS 剥离不允许
|
|
6
6
|
* node_modules 内的 TS 文件,因此包入口运行时必须是 .mjs。
|
|
7
7
|
*/
|
|
8
|
-
export * from '../
|
|
8
|
+
export * from '../utils/img'
|
|
@@ -21,7 +21,10 @@ import { toServerClarityConfig } from '../../config/server'
|
|
|
21
21
|
import handleMirror from './anti-mirror-client'
|
|
22
22
|
|
|
23
23
|
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
24
|
-
|
|
24
|
+
/** Theme 运行时源码根(src/):模块位于 src/modules/clarity-config */
|
|
25
|
+
const themeSrcDir = resolve(moduleDir, '../..')
|
|
26
|
+
/** Theme npm 包根(nuxt.config.ts / package.json 所在层) */
|
|
27
|
+
const themePkgDir = resolve(moduleDir, '../../..')
|
|
25
28
|
|
|
26
29
|
/** app/app.config.ts 的 clarity 键只允许 UI 覆盖(站点级字段属于 clarity.config.ts) */
|
|
27
30
|
const uiConfigKeys = new Set(['component', 'footer', 'header', 'link', 'nav', 'pagination', 'themes'])
|
|
@@ -42,7 +45,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
42
45
|
const rootDir = nuxt.options.rootDir
|
|
43
46
|
|
|
44
47
|
const configPath = resolve(rootDir, options.configFile ?? findConfigFile(rootDir))
|
|
45
|
-
const feedsPath = findFeedsFile(rootDir,
|
|
48
|
+
const feedsPath = findFeedsFile(rootDir, themeSrcDir)
|
|
46
49
|
|
|
47
50
|
// ---- 依赖注入层:Theme 内部不感知消费项目的文件布局 ----
|
|
48
51
|
Object.assign(nuxt.options.alias, {
|
|
@@ -51,14 +54,14 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
51
54
|
})
|
|
52
55
|
|
|
53
56
|
// @pinia/nuxt 不会自动扫描 Layer 的 stores 目录,需显式注册
|
|
54
|
-
addImportsDir(resolve(
|
|
57
|
+
addImportsDir(resolve(themeSrcDir, 'stores'))
|
|
55
58
|
|
|
56
59
|
// ---- AppConfig 类型化:消费者 defineAppConfig({ clarity: ... }) 获得完整类型提示 ----
|
|
57
60
|
// 注意:类型文件生成于 buildDir/types/ 下,相对路径必须以其为基准计算,
|
|
58
61
|
// 否则导入解析失败会因 skipLibCheck 静默退化为 any(审计发现 #9)。
|
|
59
62
|
const appConfigTypeDir = resolve(nuxt.options.buildDir, 'types')
|
|
60
|
-
const appConfigTypePath = relative(appConfigTypeDir, resolve(
|
|
61
|
-
const globalsTypePath = relative(appConfigTypeDir, resolve(
|
|
63
|
+
const appConfigTypePath = relative(appConfigTypeDir, resolve(themeSrcDir, 'config/app.ts')).replaceAll('\\', '/')
|
|
64
|
+
const globalsTypePath = relative(appConfigTypeDir, resolve(themeSrcDir, 'types/index.ts')).replaceAll('\\', '/')
|
|
62
65
|
addTypeTemplate({
|
|
63
66
|
filename: 'types/clarity-app-config.d.ts',
|
|
64
67
|
getContents: () => [
|
|
@@ -86,7 +89,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
86
89
|
// 需置于 '~' 之前以保证 Vite 别名前缀优先匹配
|
|
87
90
|
if (!existsSync(resolve(nuxt.options.srcDir, 'shiki.config.ts'))) {
|
|
88
91
|
nuxt.options.alias = {
|
|
89
|
-
'~/shiki.config': resolve(
|
|
92
|
+
'~/shiki.config': resolve(themeSrcDir, 'shiki.config.ts'),
|
|
90
93
|
...nuxt.options.alias,
|
|
91
94
|
}
|
|
92
95
|
}
|
|
@@ -176,7 +179,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
176
179
|
|
|
177
180
|
// ---- 构建信息(供 BlogTech 等 Widget 使用) ----
|
|
178
181
|
const consumerPkg = await loadJson(resolve(rootDir, 'package.json'), jiti)
|
|
179
|
-
const themePkg = await loadJson(resolve(
|
|
182
|
+
const themePkg = await loadJson(resolve(themePkgDir, 'package.json'), jiti)
|
|
180
183
|
nuxt.options.runtimeConfig.public.clarity = {
|
|
181
184
|
theme: 'Clarity',
|
|
182
185
|
themeVersion: String(themePkg?.version ?? ''),
|
|
@@ -308,13 +311,13 @@ function findConfigFile(rootDir: string) {
|
|
|
308
311
|
return 'clarity.config.ts'
|
|
309
312
|
}
|
|
310
313
|
|
|
311
|
-
function findFeedsFile(rootDir: string,
|
|
314
|
+
function findFeedsFile(rootDir: string, themeSrcDir: string) {
|
|
312
315
|
for (const name of ['feeds.ts', 'feeds.mjs', 'feeds.js']) {
|
|
313
316
|
if (existsSync(resolve(rootDir, name))) {
|
|
314
317
|
return { found: true, resolved: resolve(rootDir, name) }
|
|
315
318
|
}
|
|
316
319
|
}
|
|
317
|
-
return { found: false, resolved: resolve(
|
|
320
|
+
return { found: false, resolved: resolve(themeSrcDir, 'config/feeds.empty.ts') }
|
|
318
321
|
}
|
|
319
322
|
|
|
320
323
|
async function loadJson(path: string, jiti: ReturnType<typeof createJiti>) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Nuxt } from '@nuxt/schema'
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
|
|
5
|
+
|
|
6
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const themeSrcDir = resolve(moduleDir, '../..')
|
|
8
|
+
const themePkgDir = resolve(moduleDir, '../../..')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Clarity keeps all runtime source under `src/`. These values must only apply
|
|
12
|
+
* to the theme layer; declaring them statically in `nuxt.config.ts` would let
|
|
13
|
+
* c12 merge them into consumers which do not explicitly override them.
|
|
14
|
+
*/
|
|
15
|
+
export default defineNuxtModule({
|
|
16
|
+
meta: {
|
|
17
|
+
name: 'clarity-source-layout',
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
setup(_options, nuxt) {
|
|
21
|
+
const layer = findThemeLayer(nuxt)
|
|
22
|
+
if (!layer) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sourceDir = withTrailingSlash(themeSrcDir)
|
|
27
|
+
const directories = {
|
|
28
|
+
layouts: withTrailingSlash(resolve(themeSrcDir, 'layouts')),
|
|
29
|
+
middleware: withTrailingSlash(resolve(themeSrcDir, 'middleware')),
|
|
30
|
+
modules: withTrailingSlash(resolve(themeSrcDir, 'modules')),
|
|
31
|
+
pages: withTrailingSlash(resolve(themeSrcDir, 'pages')),
|
|
32
|
+
plugins: withTrailingSlash(resolve(themeSrcDir, 'plugins')),
|
|
33
|
+
public: withTrailingSlash(resolve(themeSrcDir, 'public')),
|
|
34
|
+
server: withTrailingSlash(resolve(themeSrcDir, 'server')),
|
|
35
|
+
shared: withTrailingSlash(resolve(themeSrcDir, 'shared')),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
layer.config.srcDir = sourceDir
|
|
39
|
+
layer.config.serverDir = directories.server
|
|
40
|
+
layer.config.dir = {
|
|
41
|
+
...layer.config.dir,
|
|
42
|
+
app: sourceDir,
|
|
43
|
+
modules: directories.modules,
|
|
44
|
+
public: directories.public,
|
|
45
|
+
shared: directories.shared,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// `getLayerDirectories()` caches a plain metadata object per layer before
|
|
49
|
+
// modules are installed. Nuxt exposes no invalidation API, so update the
|
|
50
|
+
// cached paths in place as well as `layer.config`.
|
|
51
|
+
const cached = getLayerDirectories(nuxt).find(directory => isSamePath(directory.root, themePkgDir))
|
|
52
|
+
if (cached) {
|
|
53
|
+
Object.assign(cached, {
|
|
54
|
+
app: sourceDir,
|
|
55
|
+
appLayouts: directories.layouts,
|
|
56
|
+
appMiddleware: directories.middleware,
|
|
57
|
+
appPages: directories.pages,
|
|
58
|
+
appPlugins: directories.plugins,
|
|
59
|
+
modules: directories.modules,
|
|
60
|
+
public: directories.public,
|
|
61
|
+
server: directories.server,
|
|
62
|
+
shared: directories.shared,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
function findThemeLayer(nuxt: Nuxt) {
|
|
69
|
+
return nuxt.options._layers.find(layer => isSamePath(layer.config.rootDir ?? layer.cwd, themePkgDir))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isSamePath(left: string, right: string) {
|
|
73
|
+
return left === right || relative(resolve(left), resolve(right)) === ''
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function withTrailingSlash(path: string) {
|
|
77
|
+
return `${path.replace(/[\\/]$/, '').replaceAll('\\', '/')}/`
|
|
78
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ClarityAppConfig } from '
|
|
2
|
-
import type { ArticleSchema } from '
|
|
3
|
-
import type { MetaSlotsTree } from '
|
|
1
|
+
import type { ClarityAppConfig } from '../config/app'
|
|
2
|
+
import type { ArticleSchema } from '../config/content'
|
|
3
|
+
import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
|
|
4
4
|
|
|
5
5
|
export type ArticleOrderType = keyof ClarityAppConfig['article']['order']
|
|
6
6
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { FeedEntry, FeedGroup } from '../config/feed'
|
package/app/types/feed.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type { FeedEntry, FeedGroup } from '../../config/feed'
|
/package/{app → src}/app.vue
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{app → src}/error.vue
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|