hexo-theme-stellar 1.44.0 → 2.0.0-rc.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.
Files changed (377) hide show
  1. package/CHANGELOG.md +63 -5
  2. package/CONTRIBUTING.md +29 -120
  3. package/README.md +66 -73
  4. package/README_EN.md +66 -73
  5. package/_config.yml +890 -743
  6. package/_data/icons.yml +43 -171
  7. package/_data/widgets.yml +1 -1
  8. package/assets/icon-v2-x512.webp +0 -0
  9. package/languages/en.yml +59 -2
  10. package/languages/zh-CN.yml +59 -2
  11. package/languages/zh-TW.yml +59 -2
  12. package/layout/404.ejs +7 -4
  13. package/layout/_partial/comments/artalk/layout.ejs +3 -2
  14. package/layout/_partial/comments/beaudar/layout.ejs +2 -2
  15. package/layout/_partial/comments/giscus/layout.ejs +2 -5
  16. package/layout/_partial/comments/layout.ejs +11 -40
  17. package/layout/_partial/comments/twikoo/layout.ejs +3 -2
  18. package/layout/_partial/comments/utterances/layout.ejs +2 -2
  19. package/layout/_partial/comments/waline/layout.ejs +3 -2
  20. package/layout/_partial/components/collection-item.ejs +30 -13
  21. package/layout/_partial/components/collection.ejs +2 -2
  22. package/layout/_partial/cover/index.ejs +5 -2
  23. package/layout/_partial/cover/wiki_cover.ejs +39 -28
  24. package/layout/_partial/dropdown.ejs +26 -14
  25. package/layout/_partial/head.ejs +96 -69
  26. package/layout/_partial/main/article/contributors.ejs +12 -9
  27. package/layout/_partial/main/article/post_footer.ejs +47 -0
  28. package/layout/_partial/main/article/post_read_next.ejs +22 -0
  29. package/layout/_partial/main/article/post_related.ejs +14 -0
  30. package/layout/_partial/main/article/post_tags.ejs +12 -0
  31. package/layout/_partial/main/article/share.ejs +55 -0
  32. package/layout/_partial/main/footer.ejs +11 -10
  33. package/layout/_partial/main/{navbar/nav_tabs_blog.ejs → listing_nav/blog.ejs} +21 -18
  34. package/layout/_partial/main/listing_nav/wiki.ejs +56 -0
  35. package/layout/_partial/main/navbar/article_banner.ejs +39 -35
  36. package/layout/_partial/main/navbar/breadcrumb/blog.ejs +15 -28
  37. package/layout/_partial/main/navbar/breadcrumb/note.ejs +12 -5
  38. package/layout/_partial/main/navbar/breadcrumb/wiki.ejs +15 -11
  39. package/layout/_partial/main/navbar/dateinfo.ejs +28 -12
  40. package/layout/_partial/main/navbar/ghinfo.ejs +5 -3
  41. package/layout/_partial/main/notebook/note_card.ejs +12 -17
  42. package/layout/_partial/main/notebook/note_tags.ejs +3 -4
  43. package/layout/_partial/main/notebook/notebook_card.ejs +11 -4
  44. package/layout/_partial/main/pin_slider.ejs +48 -43
  45. package/layout/_partial/main/post_list/archive_item.ejs +8 -4
  46. package/layout/_partial/main/post_list/latest_post_card.ejs +10 -4
  47. package/layout/_partial/main/post_list/post_card.ejs +39 -90
  48. package/layout/_partial/main/post_list/topic_card.ejs +7 -5
  49. package/layout/_partial/main/post_list/wiki_card.ejs +16 -13
  50. package/layout/_partial/menubtn.ejs +3 -3
  51. package/layout/_partial/primitives/item.ejs +8 -0
  52. package/layout/_partial/primitives/navigation.ejs +17 -0
  53. package/layout/_partial/primitives/region.ejs +16 -0
  54. package/layout/_partial/primitives/section.ejs +8 -0
  55. package/layout/_partial/primitives/shell.ejs +35 -0
  56. package/layout/_partial/regions/widgets.ejs +84 -0
  57. package/layout/_partial/scripts/defines.ejs +46 -76
  58. package/layout/_partial/scripts/runtime.ejs +55 -0
  59. package/layout/_partial/scripts.ejs +18 -42
  60. package/layout/_partial/search/dialog.ejs +33 -0
  61. package/layout/_partial/search/shortcut.ejs +1 -0
  62. package/layout/_partial/sidebar/brand.ejs +127 -0
  63. package/layout/_partial/sidebar/menu.ejs +59 -14
  64. package/layout/_partial/sidebar/search.ejs +58 -41
  65. package/layout/_partial/widgets/actions.ejs +25 -0
  66. package/layout/_partial/widgets/author.ejs +9 -5
  67. package/layout/_partial/widgets/components/linklist.ejs +1 -2
  68. package/layout/_partial/widgets/ghissues.ejs +7 -10
  69. package/layout/_partial/widgets/ghrepo.ejs +23 -16
  70. package/layout/_partial/widgets/ghuser.ejs +3 -3
  71. package/layout/_partial/widgets/recent.ejs +25 -21
  72. package/layout/_partial/widgets/related.ejs +25 -34
  73. package/layout/_partial/widgets/settings.ejs +17 -0
  74. package/layout/_partial/widgets/tagtree.ejs +18 -10
  75. package/layout/_partial/widgets/toc.ejs +17 -9
  76. package/layout/_partial/widgets/tree.ejs +12 -13
  77. package/layout/archive.ejs +20 -13
  78. package/layout/categories.ejs +4 -3
  79. package/layout/collection-preview.ejs +8 -9
  80. package/layout/index.ejs +32 -43
  81. package/layout/index_topic.ejs +6 -13
  82. package/layout/index_wiki.ejs +17 -34
  83. package/layout/layout.ejs +195 -65
  84. package/layout/notebooks.ejs +3 -3
  85. package/layout/notes.ejs +4 -4
  86. package/layout/page.ejs +95 -50
  87. package/layout/settings.ejs +109 -0
  88. package/layout/tags.ejs +4 -3
  89. package/{THIRD-PARTY-NOTICES.md → legal/THIRD-PARTY-NOTICES.md} +8 -4
  90. package/package.json +28 -6
  91. package/scripts/commands/stellar.js +55 -0
  92. package/scripts/events/index.js +15 -9
  93. package/scripts/events/lib/authors.js +9 -7
  94. package/scripts/events/lib/config-hot-reload.js +65 -0
  95. package/scripts/events/lib/config-schema.js +20 -0
  96. package/scripts/events/lib/config.js +22 -63
  97. package/scripts/events/lib/content-config.js +5 -0
  98. package/scripts/events/lib/doc_tree.js +99 -9
  99. package/scripts/events/lib/links.js +6 -3
  100. package/scripts/events/lib/notebooks.js +200 -8
  101. package/scripts/events/lib/utils.js +4 -4
  102. package/scripts/events/lib/version-check.js +57 -3
  103. package/scripts/filters/index.js +2 -27
  104. package/scripts/filters/lib/img_lazyload.js +4 -126
  105. package/scripts/filters/lib/img_onerror.js +14 -17
  106. package/scripts/filters/lib/page-view-model.js +152 -0
  107. package/scripts/generators/404.js +13 -8
  108. package/scripts/generators/author.js +16 -11
  109. package/scripts/generators/notebooks.js +68 -39
  110. package/scripts/generators/search.js +62 -67
  111. package/scripts/generators/settings.js +43 -0
  112. package/scripts/generators/stellar-icons.js +13 -5
  113. package/scripts/generators/topic.js +24 -13
  114. package/scripts/generators/wiki.js +49 -21
  115. package/scripts/helpers/ai_label.js +6 -12
  116. package/scripts/helpers/brand.js +17 -0
  117. package/scripts/helpers/browser_runtime_manifest.js +12 -0
  118. package/scripts/helpers/caption.js +8 -0
  119. package/scripts/helpers/category_color.js +13 -13
  120. package/scripts/helpers/collection.js +18 -0
  121. package/scripts/helpers/comments_model.js +14 -0
  122. package/scripts/helpers/extension_assets.js +8 -0
  123. package/scripts/helpers/json_ld.js +16 -7
  124. package/scripts/helpers/mdrender.js +3 -9
  125. package/scripts/helpers/menu.js +9 -0
  126. package/scripts/helpers/post_view_model.js +23 -0
  127. package/scripts/helpers/regions.js +37 -0
  128. package/scripts/helpers/scrollreveal.js +7 -7
  129. package/scripts/helpers/search_domain.js +34 -0
  130. package/scripts/helpers/service_provider.js +9 -0
  131. package/scripts/helpers/settings.js +16 -0
  132. package/scripts/helpers/share_action.js +8 -0
  133. package/scripts/helpers/stellar_config.js +12 -0
  134. package/scripts/helpers/stellar_data.js +8 -0
  135. package/scripts/helpers/stellar_info.js +21 -16
  136. package/scripts/helpers/stellar_inject.js +9 -0
  137. package/scripts/helpers/ui_classes.js +12 -0
  138. package/scripts/lib/ai_label.js +14 -11
  139. package/scripts/lib/brand.js +41 -0
  140. package/scripts/lib/browser-runtime.js +115 -0
  141. package/scripts/lib/caption.js +21 -0
  142. package/scripts/lib/collection-pipeline/adapters/notebook.js +62 -0
  143. package/scripts/lib/collection-pipeline/adapters/post.js +20 -0
  144. package/scripts/lib/collection-pipeline/adapters/topic.js +82 -0
  145. package/scripts/lib/collection-pipeline/adapters/wiki.js +20 -0
  146. package/scripts/lib/collection-pipeline/index.js +169 -0
  147. package/scripts/lib/collection-pipeline/registry.js +27 -0
  148. package/scripts/lib/collection-pipeline/shared.js +164 -0
  149. package/scripts/lib/comments.js +60 -0
  150. package/scripts/lib/config-inject.js +20 -0
  151. package/scripts/lib/config-schema.js +1095 -0
  152. package/scripts/lib/content-config.js +157 -0
  153. package/scripts/lib/content-defaults.js +56 -0
  154. package/scripts/lib/content-membership.js +233 -0
  155. package/scripts/lib/contribution-contract.js +165 -0
  156. package/scripts/lib/contribution-registry.js +429 -0
  157. package/scripts/lib/doc_tree.js +46 -44
  158. package/scripts/lib/doctor.js +296 -0
  159. package/scripts/lib/front-matter.js +29 -0
  160. package/scripts/lib/hero-effect-registry.js +262 -0
  161. package/scripts/lib/html-images.js +128 -0
  162. package/scripts/lib/internal-constants.js +152 -0
  163. package/scripts/lib/layout-config.js +62 -0
  164. package/scripts/lib/mdrender_html.js +36 -23
  165. package/scripts/lib/menu.js +36 -0
  166. package/scripts/lib/model-schema.js +90 -0
  167. package/scripts/lib/models/index.js +1542 -0
  168. package/scripts/lib/new-note.js +172 -0
  169. package/scripts/lib/page-view-model-registry.js +103 -0
  170. package/scripts/lib/regions.js +132 -0
  171. package/scripts/lib/runtime-data.js +23 -0
  172. package/scripts/lib/safe-path.js +44 -0
  173. package/scripts/lib/search-domain.js +59 -0
  174. package/scripts/lib/seo.js +10 -10
  175. package/scripts/lib/service-provider.js +12 -0
  176. package/scripts/lib/settings.js +16 -0
  177. package/scripts/lib/share-services.js +96 -0
  178. package/scripts/lib/source-config.js +27 -0
  179. package/scripts/lib/theme-metadata.js +22 -0
  180. package/scripts/lib/ui-capabilities.js +32 -0
  181. package/scripts/lib/widget-registry.js +150 -0
  182. package/scripts/lib/wiki_readme.js +15 -11
  183. package/scripts/schema/config-rules.js +290 -0
  184. package/scripts/schema/config-schema.js +163 -0
  185. package/scripts/schema/content-config-rules.js +264 -0
  186. package/scripts/schema/content-config-schema.js +321 -0
  187. package/scripts/schema/model-schema.js +971 -0
  188. package/scripts/schema/schema-utils.js +48 -0
  189. package/scripts/tags/index.js +2 -3
  190. package/scripts/tags/lib/albums.js +6 -6
  191. package/scripts/tags/lib/banner.js +10 -4
  192. package/scripts/tags/lib/box.js +1 -1
  193. package/scripts/tags/lib/button.js +1 -1
  194. package/scripts/tags/lib/chat.js +9 -31
  195. package/scripts/tags/lib/checkbox.js +6 -1
  196. package/scripts/tags/lib/copy.js +4 -1
  197. package/scripts/tags/lib/dropdown.js +6 -4
  198. package/scripts/tags/lib/emoji.js +5 -10
  199. package/scripts/tags/lib/friends.js +4 -4
  200. package/scripts/tags/lib/gallery.js +7 -7
  201. package/scripts/tags/lib/ghcard.js +5 -3
  202. package/scripts/tags/lib/gist.js +16 -0
  203. package/scripts/tags/lib/grid.js +4 -2
  204. package/scripts/tags/lib/hashtag.js +1 -1
  205. package/scripts/tags/lib/icon.js +1 -1
  206. package/scripts/tags/lib/image.js +1 -1
  207. package/scripts/tags/lib/link.js +6 -3
  208. package/scripts/tags/lib/mark.js +8 -3
  209. package/scripts/tags/lib/md.js +1 -1
  210. package/scripts/tags/lib/note.js +1 -1
  211. package/scripts/tags/lib/okr.js +18 -1
  212. package/scripts/tags/lib/posters.js +6 -6
  213. package/scripts/tags/lib/quot.js +15 -5
  214. package/scripts/tags/lib/rating.js +7 -4
  215. package/scripts/tags/lib/sites.js +8 -6
  216. package/scripts/tags/lib/tip.js +6 -6
  217. package/scripts/tags/lib/toc.js +2 -2
  218. package/scripts/tags/lib/vote.js +8 -5
  219. package/source/css/_appearances/_mixins.styl +258 -0
  220. package/source/css/_appearances/card.styl +30 -0
  221. package/source/css/_appearances/flat.styl +44 -0
  222. package/source/css/_appearances/glass.styl +138 -0
  223. package/source/css/_appearances/minimal.styl +325 -0
  224. package/source/css/_common/base.styl +5 -9
  225. package/source/css/_common/blockquote.styl +0 -1
  226. package/source/css/_common/button.styl +1 -1
  227. package/source/css/_common/device.styl +24 -42
  228. package/source/css/_common/dropdown.styl +20 -14
  229. package/source/css/_common/highlight.styl +5 -12
  230. package/source/css/_common/icon.styl +38 -0
  231. package/source/css/_common/input.styl +33 -0
  232. package/source/css/_common/loading.styl +2 -2
  233. package/source/css/_common/svg.styl +0 -8
  234. package/source/css/_components/collection.styl +21 -64
  235. package/source/css/_components/layout.styl +524 -84
  236. package/source/css/_components/list.styl +36 -22
  237. package/source/css/_components/main.styl +16 -19
  238. package/source/css/_components/md.styl +8 -13
  239. package/source/css/_components/pages/article-indent.styl +1 -1
  240. package/source/css/_components/pages/article-story.styl +7 -6
  241. package/source/css/_components/pages/article-tech.styl +5 -5
  242. package/source/css/_components/pages/collection-preview.styl +1 -5
  243. package/source/css/_components/pages/settings.styl +184 -0
  244. package/source/css/_components/partial/article-banner.styl +2 -2
  245. package/source/css/_components/partial/article-footer.styl +0 -32
  246. package/source/css/_components/partial/article-share.styl +48 -0
  247. package/source/css/_components/partial/article-tags.styl +1 -1
  248. package/source/css/_components/partial/cover.styl +13 -13
  249. package/source/css/_components/partial/listing-nav.styl +68 -0
  250. package/source/css/_components/partial/paginator.styl +1 -1
  251. package/source/css/_components/pin-slider.styl +3 -3
  252. package/source/css/_components/sidebar/brand.styl +135 -0
  253. package/source/css/_components/sidebar/footer.styl +14 -31
  254. package/source/css/_components/sidebar/nav-area.styl +4 -0
  255. package/source/css/_components/sidebar/search.styl +176 -26
  256. package/source/css/_components/sidebar/sidebar.styl +76 -110
  257. package/source/css/_components/tag-plugins/banner.styl +16 -28
  258. package/source/css/_components/tag-plugins/checkbox.styl +7 -22
  259. package/source/css/_components/tag-plugins/copy.styl +3 -5
  260. package/source/css/_components/tag-plugins/gallery.styl +2 -2
  261. package/source/css/_components/tag-plugins/grid.styl +3 -0
  262. package/source/css/_components/tag-plugins/image.styl +2 -1
  263. package/source/css/_components/tag-plugins/link.styl +0 -1
  264. package/source/css/_components/tag-plugins/mark.styl +19 -4
  265. package/source/css/_components/tag-plugins/mdrender.styl +2 -2
  266. package/source/css/_components/tag-plugins/navbar.styl +23 -17
  267. package/source/css/_components/tag-plugins/note.styl +2 -2
  268. package/source/css/_components/tag-plugins/okr.styl +1 -2
  269. package/source/css/_components/tag-plugins/quot.styl +5 -0
  270. package/source/css/_components/tag-plugins/rating.styl +3 -2
  271. package/source/css/_components/tag-plugins/timeline.styl +1 -1
  272. package/source/css/_components/tag-plugins/tip.styl +1 -1
  273. package/source/css/_components/tag-plugins/vote.styl +3 -2
  274. package/source/css/_components/widgets/ghrepo.styl +2 -1
  275. package/source/css/_components/widgets/ghuser.styl +4 -6
  276. package/source/css/_components/widgets/settings.styl +49 -0
  277. package/source/css/_components/widgets/timeline.styl +2 -2
  278. package/source/css/_components/widgets/toc.styl +12 -33
  279. package/source/css/_components/widgets/widgets.styl +14 -19
  280. package/source/css/_components/wiki-card.styl +2 -2
  281. package/source/css/_custom.styl +26 -32
  282. package/source/css/_defines/func.styl +47 -37
  283. package/source/css/_defines/theme_base.styl +8 -10
  284. package/source/css/_plugins/card-hover.styl +1 -1
  285. package/source/css/_plugins/copycode.styl +16 -19
  286. package/source/css/_plugins/index.styl +3 -8
  287. package/source/css/_plugins/lazyload.styl +1 -1
  288. package/source/css/main.styl +12 -0
  289. package/source/js/main.js +302 -59
  290. package/source/js/plugins/adaptive-text.js +33 -16
  291. package/source/js/plugins/card-hover.js +2 -24
  292. package/source/js/plugins/copycode.js +10 -16
  293. package/source/js/plugins/voice.js +51 -48
  294. package/source/js/profile.js +316 -0
  295. package/source/js/runtime/asset-loader.js +61 -0
  296. package/source/js/runtime/extension-registry.js +149 -0
  297. package/source/js/runtime/extensions/card-hover.js +5 -0
  298. package/source/js/runtime/extensions/color-scheme-switch.js +101 -0
  299. package/source/js/runtime/extensions/comments.js +200 -0
  300. package/source/js/runtime/extensions/deferred-icons.js +53 -0
  301. package/source/js/{plugins → runtime/extensions}/dropdown.js +69 -30
  302. package/source/js/runtime/extensions/feature.js +200 -0
  303. package/source/js/runtime/extensions/hero-effect.js +179 -0
  304. package/source/js/runtime/extensions/reveal.js +80 -0
  305. package/source/js/runtime/extensions/search.js +23 -0
  306. package/source/js/{services.js → runtime/extensions/services.js} +67 -54
  307. package/source/js/runtime/extensions/settings.js +235 -0
  308. package/source/js/runtime/hero-effects/ferrofluid.js +385 -0
  309. package/source/js/{plugins → runtime/hero-effects}/galaxy.js +53 -177
  310. package/source/js/runtime/hero-effects/light-rays.js +293 -0
  311. package/source/js/runtime/index.js +75 -0
  312. package/source/js/runtime/legacy-request-adapter.js +47 -0
  313. package/source/js/runtime/request-cache.js +219 -0
  314. package/source/js/search/algolia-search.js +76 -51
  315. package/source/js/search/highlight.js +1 -1
  316. package/source/js/search/local-search.js +96 -57
  317. package/source/js/search/shortcut.js +31 -20
  318. package/source/js/services/friends_and_posts.js +6 -1
  319. package/source/js/services/rating.js +14 -6
  320. package/source/js/services/siteinfo.js +3 -7
  321. package/source/js/services/timeline.js +1 -1
  322. package/source/js/services/vote.js +7 -5
  323. package/source/js/services/weibo.js +1 -1
  324. package/source/js/utils.js +19 -444
  325. package/.claude/skills/stellar-theme-dev/SKILL.md +0 -51
  326. package/_data/chat_users.yml +0 -0
  327. package/layout/_partial/comments/artalk/script.ejs +0 -76
  328. package/layout/_partial/comments/beaudar/script.ejs +0 -33
  329. package/layout/_partial/comments/giscus/script.ejs +0 -31
  330. package/layout/_partial/comments/script.ejs +0 -3
  331. package/layout/_partial/comments/twikoo/script.ejs +0 -25
  332. package/layout/_partial/comments/utterances/script.ejs +0 -33
  333. package/layout/_partial/comments/waline/script.ejs +0 -43
  334. package/layout/_partial/main/article/article_footer.ejs +0 -164
  335. package/layout/_partial/main/article/article_tags.ejs +0 -16
  336. package/layout/_partial/main/article/read_next.ejs +0 -63
  337. package/layout/_partial/main/article/related_posts.ejs +0 -16
  338. package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +0 -52
  339. package/layout/_partial/scripts/bootstrap.ejs +0 -37
  340. package/layout/_partial/scripts/lazyload.ejs +0 -89
  341. package/layout/_partial/sidebar/grad-def.ejs +0 -26
  342. package/layout/_partial/sidebar/index_leftbar.ejs +0 -191
  343. package/layout/_partial/sidebar/index_rightbar.ejs +0 -94
  344. package/layout/_partial/sidebar/logo.ejs +0 -99
  345. package/layout/_plugins/adaptive_text.ejs +0 -11
  346. package/layout/_plugins/card_hover.ejs +0 -32
  347. package/layout/_plugins/copycode.ejs +0 -17
  348. package/layout/_plugins/fancybox.ejs +0 -53
  349. package/layout/_plugins/heti.ejs +0 -21
  350. package/layout/_plugins/index.ejs +0 -50
  351. package/layout/_plugins/mathjax.ejs +0 -49
  352. package/layout/_plugins/mermaid.ejs +0 -33
  353. package/layout/_plugins/preload.ejs +0 -9
  354. package/layout/_plugins/scrollreveal.ejs +0 -102
  355. package/layout/_plugins/search/algolia_search.ejs +0 -12
  356. package/layout/_plugins/search/local_search.ejs +0 -9
  357. package/layout/_plugins/swiper.ejs +0 -27
  358. package/layout/_plugins/tianli_gpt.ejs +0 -14
  359. package/scripts/commands/new-note.js +0 -49
  360. package/scripts/events/lib/merge_posts.js +0 -48
  361. package/scripts/events/lib/topic_tree.js +0 -48
  362. package/scripts/helpers/related_posts.js +0 -51
  363. package/scripts/helpers/subtitle.js +0 -8
  364. package/scripts/lib/notebooks.js +0 -181
  365. package/scripts/lib/subtitle.js +0 -28
  366. package/scripts/tags/lib/about.js +0 -48
  367. package/source/css/_components/page-transition.styl +0 -22
  368. package/source/css/_components/partial/navbar.styl +0 -51
  369. package/source/css/_components/sidebar/logo.styl +0 -111
  370. package/source/css/_components/tag-plugins/about.styl +0 -56
  371. package/source/css/_components/widgets/tree.styl +0 -0
  372. package/source/css/_plugins/scrollreveal.styl +0 -6
  373. package/source/css/_plugins/tianli_gpt.styl +0 -213
  374. package/source/css/plugins/mermaid.styl +0 -137
  375. package/source/js/icons.js +0 -60
  376. package/source/js/services/fcircle.js +0 -35
  377. package/source/js/theme.js +0 -61
@@ -0,0 +1,971 @@
1
+ /* global hexo */
2
+ "use strict";
3
+
4
+ const { deepFreeze } = require("./schema-utils");
5
+
6
+ const PROFILES = Object.freeze(["notebook", "post", "topic", "wiki"]);
7
+
8
+ function literal(value) {
9
+ return { kind: "literal", value };
10
+ }
11
+
12
+ function derived(...from) {
13
+ return { kind: "derived", from };
14
+ }
15
+
16
+ function inherited(...from) {
17
+ return { kind: "inherited", from };
18
+ }
19
+
20
+ function computed(description) {
21
+ return { kind: "computed", description };
22
+ }
23
+
24
+ function omitted() {
25
+ return { kind: "omitted" };
26
+ }
27
+
28
+ function defaultExample(types) {
29
+ if (types.includes("string")) return "example";
30
+ if (types.includes("boolean")) return true;
31
+ if (types.includes("number")) return 1;
32
+ if (types.includes("array")) return [];
33
+ if (types.includes("object")) return {};
34
+ return null;
35
+ }
36
+
37
+ function createFieldFactory(scope, consumers) {
38
+ function field(type, options = {}) {
39
+ const types = Array.isArray(type) ? type : [type];
40
+ return {
41
+ type: types,
42
+ default: Object.prototype.hasOwnProperty.call(options, "default")
43
+ ? options.default
44
+ : options.required === true
45
+ ? computed("由构建期模型归一化")
46
+ : omitted(),
47
+ scope,
48
+ consumers: options.consumers || consumers,
49
+ example: Object.prototype.hasOwnProperty.call(options, "example")
50
+ ? options.example
51
+ : defaultExample(types),
52
+ required: options.required === true,
53
+ ...(options.reference ? { reference: options.reference } : {}),
54
+ ...(options.properties ? { properties: options.properties } : {}),
55
+ ...(options.items ? { items: options.items } : {}),
56
+ ...(options.additionalProperties ? { additionalProperties: true } : {})
57
+ };
58
+ }
59
+
60
+ function object(properties, options = {}) {
61
+ return field("object", {
62
+ default: Object.prototype.hasOwnProperty.call(options, "default")
63
+ ? options.default
64
+ : options.required === true ? literal({}) : omitted(),
65
+ example: Object.prototype.hasOwnProperty.call(options, "example") ? options.example : {},
66
+ properties,
67
+ required: options.required,
68
+ consumers: options.consumers,
69
+ reference: options.reference,
70
+ additionalProperties: options.additionalProperties
71
+ });
72
+ }
73
+
74
+ function array(items, options = {}) {
75
+ return field("array", {
76
+ default: Object.prototype.hasOwnProperty.call(options, "default")
77
+ ? options.default
78
+ : options.required === true ? literal([]) : omitted(),
79
+ example: Object.prototype.hasOwnProperty.call(options, "example") ? options.example : [],
80
+ items,
81
+ required: options.required
82
+ });
83
+ }
84
+
85
+ return { array, field, object };
86
+ }
87
+
88
+ function brandSchema(factory, options = {}) {
89
+ const { field, object } = factory;
90
+ const properties = {
91
+ ...(options.leftbar ? {
92
+ source: field("string", { default: computed("由 Site 或 Collection Brand 来源解析"), example: "site" }),
93
+ style: field("string", { default: literal("regular"), example: "regular" }),
94
+ backButton: field("boolean", { default: literal(true), example: true }),
95
+ search: field("boolean", { default: literal(true), example: true }),
96
+ backHref: field(["string", "null"], { default: computed("由 Collection 类型的索引路径生成"), example: "/wiki/" }),
97
+ backLabel: field("string", { default: computed("由 Collection 类型生成"), example: "btn.all_wiki" })
98
+ } : {}),
99
+ image: object({
100
+ src: field(["string", "null"], { example: "/images/avatar.webp" }),
101
+ variant: field("string", { example: "avatar" })
102
+ }, { example: { src: "/images/avatar.webp", variant: "avatar" } }),
103
+ name: field(["string", "null"], {
104
+ default: options.nameDefault || derived("site.title"),
105
+ example: "Stellar",
106
+ required: options.requiredName === true
107
+ }),
108
+ tagline: field(["string", "null"], {
109
+ default: options.taglineDefault || derived("site.subtitle"),
110
+ example: "每个人的独立博客",
111
+ required: options.requiredTagline === true
112
+ }),
113
+ href: field(["string", "null"], {
114
+ default: options.hrefDefault || omitted(),
115
+ example: "/",
116
+ required: options.requiredHref === true
117
+ })
118
+ };
119
+ return object(properties, {
120
+ default: options.default || (options.requiredName
121
+ ? computed("由 Region Brand 配置解析")
122
+ : omitted()),
123
+ example: {
124
+ name: "Stellar",
125
+ tagline: "每个人的独立博客",
126
+ href: "/"
127
+ }
128
+ });
129
+ }
130
+
131
+ function identitySchema(factory) {
132
+ const { field, object } = factory;
133
+ return object({
134
+ name: field("string", { default: literal(""), example: "Stellar", required: true }),
135
+ headline: field("string", { default: derived("collection.name"), example: "每个人的独立博客", required: true }),
136
+ tagline: field("string", { default: literal(""), example: "基于 Hexo 的全能型主题", required: true }),
137
+ description: field("string", { default: literal(""), example: "Stellar 项目文档", required: true }),
138
+ audience: field("string", { default: literal(""), example: "独立博主", required: true }),
139
+ icon: field("string", { default: literal(""), example: "/stellar.svg", required: true })
140
+ }, {
141
+ default: derived("collection.name", "collection.headline", "collection.icon"),
142
+ example: { name: "Stellar", headline: "每个人的独立博客", icon: "/stellar.svg" },
143
+ required: true
144
+ });
145
+ }
146
+
147
+ function sourceSchema(factory, options = {}) {
148
+ const { field, object } = factory;
149
+ return object({
150
+ ...(options.includeFile ? {
151
+ file: field("string", { default: derived("page.source"), example: "_posts/hello.md", required: true })
152
+ } : {}),
153
+ repository: field("string", { example: "xaoxuu/hexo-theme-stellar" }),
154
+ branch: field("string", { example: "v2" })
155
+ }, {
156
+ default: options.default || literal({}),
157
+ example: options.includeFile
158
+ ? { file: "_posts/hello.md", repository: "xaoxuu/hexo-theme-stellar", branch: "v2" }
159
+ : { repository: "xaoxuu/hexo-theme-stellar", branch: "v2" },
160
+ required: options.required === true
161
+ });
162
+ }
163
+
164
+ function navigationSchema(factory, extension = {}) {
165
+ const { field, object } = factory;
166
+ return object({
167
+ menu: field("string", { example: "post" }),
168
+ breadcrumb: field("boolean", { example: true }),
169
+ ...extension
170
+ }, {
171
+ default: inherited("profile.navigation", "collection.navigation", "page.navigation"),
172
+ example: { menu: "post", breadcrumb: true },
173
+ required: true
174
+ });
175
+ }
176
+
177
+ function regionConfigSchemas(factory) {
178
+ const { array, field, object } = factory;
179
+ const widget = field(["string", "object"], {
180
+ default: computed("由 widgets 数组逐项提供"),
181
+ example: "toc",
182
+ additionalProperties: true
183
+ });
184
+ const menuItem = field("object", { example: { id: "post", title: "博客", url: "/" }, additionalProperties: true });
185
+ const actionItem = field("object", { example: { type: "link", title: "GitHub", url: "https://github.com" }, additionalProperties: true });
186
+ const bar = (name, example) => object({
187
+ enabled: field(["boolean", "null"], { example: name !== "topbar" }),
188
+ brand: (() => {
189
+ const schema = brandSchema(factory);
190
+ schema.type = ["object", "boolean", "null"];
191
+ schema.example = { name: "Stellar", href: "/" };
192
+ return schema;
193
+ })(),
194
+ menu: array(menuItem, { example: [] }),
195
+ widgets: array(widget, { example })
196
+ }, {
197
+ default: inherited(`profile.${name}`, `collection.${name}`, `page.${name}`),
198
+ example: { widgets: example }
199
+ });
200
+ const leftbar = example => object({
201
+ enabled: field(["boolean", "null"], { example: true }),
202
+ brand: (() => {
203
+ const schema = brandSchema(factory, { leftbar: true });
204
+ schema.type = ["object", "boolean", "null"];
205
+ return schema;
206
+ })(),
207
+ menu: array(menuItem, { example: [] }),
208
+ footer: object({
209
+ actions: array(actionItem, { example: [] })
210
+ }, { example: { actions: [] } }),
211
+ widgets: array(widget, { example })
212
+ }, {
213
+ default: inherited("profile.leftbar", "collection.leftbar", "page.leftbar"),
214
+ example: { enabled: true, widgets: example }
215
+ });
216
+ return {
217
+ topbar: bar("topbar", ["spacer", "menu"]),
218
+ leftbar: leftbar(["recent"]),
219
+ rightbar: object({
220
+ enabled: field(["boolean", "null"], { example: true }),
221
+ widgets: array(widget, { example: ["toc"] })
222
+ }, {
223
+ default: inherited("profile.rightbar", "collection.rightbar", "page.rightbar"),
224
+ example: { enabled: true, widgets: ["toc"] }
225
+ })
226
+ };
227
+ }
228
+
229
+ function bannerSchema(factory) {
230
+ const { field, object } = factory;
231
+ return object({
232
+ enabled: field("boolean", { example: true }),
233
+ image: field("string", { example: "/banner.webp" }),
234
+ avatar: field("string", { example: "/avatar.webp" }),
235
+ headline: field("string", { example: "开始使用 Stellar" }),
236
+ tagline: field("string", { example: "十分钟搭好站点" })
237
+ }, { example: { enabled: true, image: "/banner.webp" } });
238
+ }
239
+
240
+ function heroSchema(factory, options = {}) {
241
+ const { array, field, object } = factory;
242
+ const command = object({
243
+ label: field("string", { example: "安装" }),
244
+ codes: field("string", { example: "npm install hexo-theme-stellar" })
245
+ }, { example: { label: "安装", codes: "npm install hexo-theme-stellar" } });
246
+ const action = object({
247
+ title: field("string", { example: "开始使用" }),
248
+ url: field("string", { example: "/wiki/stellar/" }),
249
+ icon: field("string", { example: "solar:rocket-bold-duotone" })
250
+ }, { example: { title: "开始使用", url: "/wiki/stellar/" } });
251
+ return object({
252
+ enabled: field("boolean", { example: true }),
253
+ background: object({
254
+ image: field("string", { example: "/hero.webp", consumers: options.pathConsumers }),
255
+ effect: object({
256
+ type: field("string", { example: "galaxy" }),
257
+ options: field("object", {
258
+ example: { starSpeed: 0.5 },
259
+ additionalProperties: true
260
+ }),
261
+ runtime: object({
262
+ pauseWhenHidden: field("boolean", { example: true }),
263
+ respectReducedMotion: field("boolean", { example: true })
264
+ }, { example: { pauseWhenHidden: true } })
265
+ }, { example: { type: "galaxy", options: { starSpeed: 0.5 } } })
266
+ }, {
267
+ example: { image: "/hero.webp" },
268
+ consumers: options.pathConsumers
269
+ }),
270
+ preview: object({
271
+ type: field("string", { example: "terminal" }),
272
+ src: field("string", { example: "/preview.webp" }),
273
+ alt: field("string", { example: "Stellar 预览" }),
274
+ commands: array(command, { example: [{ label: "安装", codes: "npm install hexo-theme-stellar" }] })
275
+ }, { example: { type: "image", src: "/preview.webp" } }),
276
+ actions: array(action, { example: [{ title: "开始使用", url: "/wiki/stellar/" }] })
277
+ }, {
278
+ default: literal({}),
279
+ example: { enabled: true, background: { image: "/hero.webp" } },
280
+ consumers: options.pathConsumers
281
+ });
282
+ }
283
+
284
+ function articleSchema(factory) {
285
+ const { field, object } = factory;
286
+ return object({
287
+ style: field("string", { default: inherited("hexo.stellar.config.article.style"), example: "tech" }),
288
+ paragraphIndent: field("string", { default: inherited("hexo.stellar.config.article.paragraphIndent"), example: "auto" }),
289
+ author: field("string", { example: "xaoxuu" }),
290
+ aiLabel: field(["string", "null"], { default: inherited("collection.article.aiLabel", "page.article.aiLabel"), example: "reviewed" })
291
+ }, { default: inherited("hexo.stellar.config.article", "collection.article", "page.article"), example: { style: "tech", paragraphIndent: "auto" } });
292
+ }
293
+
294
+ function footerSchema(factory, options = {}) {
295
+ const { array, field, object } = factory;
296
+ return object({
297
+ references: array(field("any", {
298
+ default: computed("由 references 数组逐项提供"),
299
+ example: { title: "Hexo", url: "https://hexo.io/" },
300
+ additionalProperties: true
301
+ }), { default: literal([]), example: [] }),
302
+ license: field(["boolean", "string", "null"], {
303
+ default: options.licenseDefault || inherited("hexo.stellar.config.article.footer.license"),
304
+ example: "CC BY-NC-SA 4.0"
305
+ }),
306
+ share: field(["boolean", "array", "null"], {
307
+ default: options.shareDefault || computed("普通博客继承 article.footer.share;Collection 默认关闭;Collection 与 Page 可用 true 或服务数组开启"),
308
+ example: true
309
+ }),
310
+ showTags: field(["boolean", "null"], { default: inherited("hexo.stellar.config.article.footer.showTags"), example: true })
311
+ }, { default: inherited("hexo.stellar.config.article.footer", "collection.footer", "page.footer"), example: { references: [], share: true } });
312
+ }
313
+
314
+ function commentsSchema(factory) {
315
+ const { field, object } = factory;
316
+ const properties = {
317
+ enabled: field("boolean", { default: computed("provider 非空时默认启用,可由 collection/page comments.enabled 覆盖"), example: true }),
318
+ title: field(["string", "null"], { default: inherited("hexo.stellar.config.comments.title"), example: "参与讨论" }),
319
+ id: field("string", { example: "post-hello" }),
320
+ provider: field(["string", "null"], { default: inherited("hexo.stellar.config.comments.provider"), example: "giscus" }),
321
+ options: field("object", { example: { "data-repo": "owner/site" }, additionalProperties: true })
322
+ };
323
+ return object(properties, {
324
+ default: inherited("hexo.stellar.config.comments", "collection.comments", "page.comments"),
325
+ example: { enabled: true, provider: "giscus", options: { "data-repo": "owner/site" } }
326
+ });
327
+ }
328
+
329
+ function renderRegionsSchema(factory) {
330
+ const { array, field } = factory;
331
+ return {
332
+ topbar: field("object", {
333
+ default: computed("由 Region 四层级联与 Widget presentation 能力解析"),
334
+ example: { widgets: [] },
335
+ required: true,
336
+ additionalProperties: true
337
+ }),
338
+ leftbar: field("object", {
339
+ default: computed("由 Region 四层级联与 Widget presentation 能力解析"),
340
+ example: { enabled: true, defaultState: "expanded", widgets: [] },
341
+ required: true,
342
+ additionalProperties: true
343
+ }),
344
+ rightbar: field("object", {
345
+ default: computed("由 Region 四层级联与 Widget presentation 能力解析"),
346
+ example: { widgets: [] },
347
+ required: true,
348
+ additionalProperties: true
349
+ }),
350
+ regionWarnings: array(field("object", { additionalProperties: true }), {
351
+ default: literal([]),
352
+ example: [],
353
+ required: true
354
+ })
355
+ };
356
+ }
357
+
358
+ function presentationSchema(factory, options = {}) {
359
+ const cascadeFactory = options.cascadeFactory || factory;
360
+ const properties = {
361
+ ...(options.includeHero ? { hero: heroSchema(factory, { pathConsumers: options.heroConsumers }) } : {}),
362
+ ...(options.includeBanner ? { banner: bannerSchema(factory) } : {}),
363
+ ...regionConfigSchemas(cascadeFactory),
364
+ article: articleSchema(cascadeFactory),
365
+ footer: footerSchema(cascadeFactory),
366
+ comments: commentsSchema(cascadeFactory)
367
+ };
368
+ for (const value of Object.values(properties)) value.required = true;
369
+ return cascadeFactory.object(properties, {
370
+ default: inherited("profile.presentation", "collection.presentation", "page.presentation"),
371
+ example: { article: { style: "tech", paragraphIndent: "auto" }, footer: { references: [] } },
372
+ required: true
373
+ });
374
+ }
375
+
376
+ function visibilitySchema(factory, options = {}) {
377
+ const { field, object } = factory;
378
+ return object({
379
+ listed: field("boolean", { default: options.listedDefault || literal(true), example: true, required: true }),
380
+ searchable: field("boolean", { default: options.searchableDefault || literal(true), example: true, required: true })
381
+ }, { default: literal({ listed: true, searchable: true }), example: { listed: true, searchable: true }, required: true });
382
+ }
383
+
384
+ function collectionSchema(profile) {
385
+ const scope = `CollectionModel:${profile}`;
386
+ const factory = createFieldFactory(scope, ["PageViewModel"]);
387
+ const cascadeFactory = createFieldFactory(scope, ["ContentItemModel", "PageViewModel"]);
388
+ const wikiHeroConsumers = ["buildWikiPageViewModel", "PageViewModel"];
389
+ const { array, field, object } = factory;
390
+ const required = value => {
391
+ value.required = true;
392
+ return value;
393
+ };
394
+
395
+ const routeProperties = {
396
+ baseDir: field("string", { default: derived("profile.path", "collection.route.path"), example: profile === "post" ? "blog" : `${profile}/example`, required: true })
397
+ };
398
+ if (profile === "wiki") {
399
+ routeProperties.homepage = field("string", { default: literal(""), example: "wiki/stellar", required: true });
400
+ }
401
+ if (profile === "topic") {
402
+ routeProperties.path = field("string", { default: derived("collection.route.path"), example: "topic/stellar-v2", required: true });
403
+ routeProperties.start = field("string", { default: literal(""), example: "topic/stellar-v2/start", required: true });
404
+ }
405
+
406
+ let navigationExtension = {};
407
+ if (profile === "wiki") {
408
+ const treeItem = object({
409
+ id: field("string", { default: derived("page._id", "page.path"), example: "wiki-start", required: true }),
410
+ title: field("string", { default: literal(""), example: "开始", required: true }),
411
+ path: field("string", { default: literal(""), example: "wiki/stellar", required: true }),
412
+ pageNumber: field(["number", "null"], { default: literal(null), example: 0, required: true }),
413
+ isHomepage: field("boolean", { default: literal(false), example: true, required: true })
414
+ }, { example: { id: "wiki-start", title: "开始", path: "wiki/stellar", pageNumber: 0, isHomepage: true } });
415
+ const treeSection = object({
416
+ title: field("string", { default: literal(""), example: "快速开始", required: true }),
417
+ items: array(treeItem, { example: [{ id: "wiki-start", title: "开始", path: "wiki/stellar", pageNumber: 0, isHomepage: true }], required: true })
418
+ }, { example: { title: "快速开始", items: [] } });
419
+ navigationExtension.tree = array(treeSection, { example: [{ title: "快速开始", items: [] }], required: true });
420
+ }
421
+ if (profile === "topic") {
422
+ const seriesItem = object({
423
+ id: field("string", { default: derived("page._id", "page.path"), example: "chapter-1", required: true }),
424
+ title: field("string", { default: literal(""), example: "Chapter 1", required: true }),
425
+ path: field("string", { default: literal(""), example: "posts/chapter-1", required: true }),
426
+ date: field(["string", "null"], { default: literal(null), example: "2026-08-22T00:00:00.000Z", required: true }),
427
+ current: field("boolean", { default: literal(false), example: true, required: true })
428
+ }, { example: { id: "chapter-1", title: "Chapter 1", path: "posts/chapter-1", date: "2026-08-22T00:00:00.000Z", current: true } });
429
+ navigationExtension.series = array(seriesItem, { example: [], required: true });
430
+ }
431
+ if (profile === "notebook") {
432
+ const tagItem = object({
433
+ id: field("string", { default: computed("由标签层级名称转为小写"), example: "tools/cli", required: true }),
434
+ name: field("string", { default: literal(""), example: "Tools/CLI", required: true }),
435
+ label: field("string", { default: literal(""), example: "CLI", required: true }),
436
+ path: field("string", { default: computed("由集合路由与标签 id 生成"), example: "notes/dev/tags/tools/cli", required: true }),
437
+ parentId: field(["string", "null"], { default: literal(null), example: "tools", required: true })
438
+ }, { example: { id: "tools/cli", name: "Tools/CLI", label: "CLI", path: "notes/dev/tags/tools/cli", parentId: "tools" } });
439
+ navigationExtension.tags = array(tagItem, { example: [], required: true });
440
+ }
441
+
442
+ const listingProperties = profile === "post"
443
+ ? {
444
+ pinStyle: field(["string", "null"], { default: inherited("hexo.stellar.config.article.listing.pinnedLayout"), example: "carousel", required: true }),
445
+ cardStyle: field(["string", "null"], { default: inherited("hexo.stellar.config.article.listing.cardLayout"), example: "hero", required: true }),
446
+ excerptLength: field(["number", "null"], { default: inherited("hexo.stellar.config.article.listing.excerptLength"), example: 128, required: true })
447
+ }
448
+ : {
449
+ priority: field("number", { default: literal(0), example: 0, required: true }),
450
+ order: field(["number", "null"], { default: profile === "topic" ? literal(null) : literal(0), example: 0, required: true }),
451
+ ...(profile === "topic" ? {
452
+ cardStyle: field(["string", "null"], { default: inherited("hexo.stellar.config.article.listing.cardLayout"), example: "hero", required: true })
453
+ } : {}),
454
+ excerptLength: field(["number", "null"], {
455
+ default: profile === "notebook" ? inherited("hexo.stellar.config.profiles.notebook.listing.excerptLength") : literal(null),
456
+ example: 128,
457
+ required: true
458
+ }),
459
+ perPage: field(["number", "null"], {
460
+ default: profile === "notebook" ? inherited("hexo.stellar.config.profiles.notebook.listing.perPage", "site.per_page") : literal(null),
461
+ example: 10,
462
+ required: true
463
+ }),
464
+ ...(profile === "wiki" ? {} : {
465
+ sort: object({
466
+ field: field("string", { default: profile === "topic" ? literal("date") : literal("updated"), example: "updated", required: true }),
467
+ direction: field("string", { default: literal("desc"), example: "desc", required: true })
468
+ }, { default: derived("collection.listing.sort", "hexo.stellar.config.profiles.notebook.listing.sort"), example: { field: "updated", direction: "desc" }, required: true })
469
+ })
470
+ };
471
+
472
+ const properties = {
473
+ id: field("string", { default: profile === "post" ? literal("post") : derived("collection.id"), example: profile === "post" ? "post" : "stellar", required: true }),
474
+ profile: field("string", { default: literal(profile), example: profile, required: true }),
475
+ identity: required(profile === "post"
476
+ ? brandSchema(factory, { requiredName: true, requiredTagline: true })
477
+ : identitySchema(factory)),
478
+ ...(profile === "post" ? {} : {
479
+ cover: field("string", { default: derived("collection.cover"), example: "/cover.webp", required: true })
480
+ }),
481
+ source: sourceSchema(cascadeFactory, { required: true }),
482
+ route: object(routeProperties, { default: derived("profile.route", "collection.route"), example: { baseDir: profile === "post" ? "blog" : `${profile}/stellar` }, required: true }),
483
+ navigation: navigationSchema(cascadeFactory, navigationExtension),
484
+ listing: object(listingProperties, { default: inherited("profile.listing", "collection.listing"), example: {}, required: true }),
485
+ presentation: presentationSchema(factory, {
486
+ includeHero: profile === "wiki",
487
+ includeBanner: true,
488
+ cascadeFactory,
489
+ heroConsumers: profile === "wiki" ? wikiHeroConsumers : undefined
490
+ }),
491
+ visibility: visibilitySchema(profile === "post" ? cascadeFactory : factory, {
492
+ listedDefault: profile === "wiki" || profile === "topic" ? derived("collection publish state") : literal(true)
493
+ })
494
+ };
495
+ return object(properties, { required: true, example: { id: profile === "post" ? "post" : "stellar", profile } });
496
+ }
497
+
498
+ function contentItemSchema() {
499
+ const factory = createFieldFactory("ContentItemModel", ["PageViewModel"]);
500
+ const { array, field, object } = factory;
501
+ const stringItem = field("string", { default: computed("由数组逐项归一化"), example: "Hexo" });
502
+ const properties = {
503
+ id: field("string", { default: derived("page._id", "page.source", "page.path"), example: "post-hello", required: true }),
504
+ title: field("string", { default: derived("page.title", "frontMatter.title"), example: "Hello Stellar", required: true }),
505
+ layout: field("string", { default: literal("post"), example: "post", required: true }),
506
+ content: field("string", { default: literal(""), example: "<p>Hello</p>", required: true }),
507
+ excerpt: field("string", { default: literal(""), example: "<p>Hello</p>", required: true }),
508
+ date: field(["string", "null"], { default: literal(null), example: "2026-08-22T00:00:00.000Z", required: true }),
509
+ updated: field(["string", "null"], { default: derived("page.updated", "page.date"), example: "2026-08-22T00:00:00.000Z", required: true }),
510
+ tags: array(stringItem, { default: literal([]), example: ["Hexo"], required: true }),
511
+ categories: array(stringItem, { default: literal([]), example: ["开发"], required: true }),
512
+ cover: field("string", { default: derived("frontMatter.cover"), example: "/cover.webp", required: true }),
513
+ tagline: field("string", { default: derived("frontMatter.tagline"), example: "开始阅读", required: true }),
514
+ source: sourceSchema(factory, { includeFile: true, default: inherited("collection.source", "page.source"), required: true }),
515
+ route: object({
516
+ path: field("string", { default: literal(""), example: "posts/hello", required: true }),
517
+ permalink: field("string", { default: literal(""), example: "https://example.com/posts/hello/", required: true })
518
+ }, { default: derived("page.path", "page.permalink"), example: { path: "posts/hello", permalink: "https://example.com/posts/hello/" }, required: true }),
519
+ navigation: navigationSchema(factory),
520
+ listing: object({
521
+ priority: field("number", { default: literal(0), example: 0, required: true })
522
+ }, { default: literal({ priority: 0 }), example: { priority: 0 }, required: true }),
523
+ presentation: presentationSchema(factory, { includeBanner: true }),
524
+ visibility: visibilitySchema(factory, {
525
+ listedDefault: inherited("collection.visibility.listed", "page.visibility.listed"),
526
+ searchableDefault: inherited("collection.visibility.searchable", "page.visibility.searchable")
527
+ })
528
+ };
529
+ return object(properties, { required: true, example: { id: "post-hello", title: "Hello Stellar", layout: "post" } });
530
+ }
531
+
532
+ function pageViewModelSchema(profile) {
533
+ const factory = createFieldFactory(`PageViewModel:${profile}`, ["page.viewModel"]);
534
+ const { array, field, object } = factory;
535
+ const properties = {
536
+ collection: object({}, {
537
+ default: computed("由当前 profile 的 CollectionModel 构建器生成"),
538
+ example: { id: "post", profile: "post" },
539
+ reference: "CollectionModel",
540
+ required: true
541
+ }),
542
+ item: object({}, {
543
+ default: computed("由 ContentItemModel 构建器生成"),
544
+ example: { id: "post-hello", title: "Hello Stellar", layout: "post" },
545
+ reference: "ContentItemModel",
546
+ required: true
547
+ })
548
+ };
549
+
550
+ if (profile === "post" || profile === "topic") {
551
+ const stringItem = field("string", {
552
+ default: computed("由构建期数组逐项归一化"),
553
+ example: "Stellar"
554
+ });
555
+ const breadcrumbItem = object({
556
+ name: field("string", { default: literal(""), example: "思考", required: true }),
557
+ path: field("string", { default: literal(""), example: "blog/categories/thinking", required: true })
558
+ }, { example: { name: "思考", path: "blog/categories/thinking" } });
559
+ const openGraph = field(["object", "null"], {
560
+ default: computed("由 hexo.stellar.config.openGraph 与页面 Open Graph 覆盖生成;禁用时为 null"),
561
+ example: {
562
+ args: { image: "/cover.webp", twitter_card: "summary_large_image" },
563
+ title: "Hello Stellar",
564
+ siteName: "Stellar",
565
+ twitterTitle: "Hello Stellar"
566
+ },
567
+ required: true,
568
+ properties: {
569
+ args: field("object", { example: { image: "/cover.webp" }, required: true, additionalProperties: true }),
570
+ title: field("string", { example: "Hello Stellar", required: true }),
571
+ siteName: field("string", { example: "Stellar", required: true }),
572
+ twitterTitle: field("string", { example: "Hello Stellar", required: true }),
573
+ publishedTime: field(["string", "null"], { example: "2026-08-22T00:00:00.000Z", required: true }),
574
+ modifiedTime: field(["string", "null"], { example: "2026-08-22T00:00:00.000Z", required: true }),
575
+ tags: array(stringItem, { default: literal([]), example: ["Hexo"], required: true })
576
+ }
577
+ });
578
+ const postLink = field(["object", "null"], {
579
+ default: computed("由 Hexo prev/next 关系规范化;不存在时为 null"),
580
+ example: { title: "上一篇", path: "blog/previous", date: "2026-08-21T00:00:00.000Z" },
581
+ required: true,
582
+ properties: {
583
+ title: field("string", { default: literal(""), example: "上一篇", required: true }),
584
+ path: field("string", { default: literal(""), example: "blog/previous", required: true }),
585
+ date: field(["string", "null"], { default: literal(null), example: "2026-08-21T00:00:00.000Z", required: true })
586
+ }
587
+ });
588
+ const tagLink = object({
589
+ name: field("string", { default: literal(""), example: "Hexo", required: true }),
590
+ path: field("string", { default: literal(""), example: "tags/hexo", required: true })
591
+ }, { example: { name: "Hexo", path: "tags/hexo" } });
592
+ const relatedItem = object({
593
+ title: field("string", { default: literal(""), example: "Related Post", required: true }),
594
+ path: field("string", { default: literal(""), example: "/blog/related/", required: true }),
595
+ excerpt: field("string", { default: literal(""), example: "Related excerpt", required: true })
596
+ }, { example: { title: "Related Post", path: "/blog/related/", excerpt: "Related excerpt" } });
597
+ const share = field(["object", "null"], {
598
+ default: computed("由最终 footer.share 与文章分享数据生成;禁用时为 null"),
599
+ example: { services: ["link"], permalink: "https://example.com/blog/hello/", title: "Hello - Stellar" },
600
+ required: true,
601
+ properties: {
602
+ services: array(stringItem, { default: literal([]), example: ["wechat", "link"], required: true }),
603
+ permalink: field("string", { default: inherited("item.route.permalink"), example: "https://example.com/blog/hello/", required: true }),
604
+ title: field("string", { default: computed("由文章标题与站点标题组合"), example: "Hello - Stellar", required: true }),
605
+ image: field("string", { default: inherited("item.cover"), example: "/cover.webp", required: true }),
606
+ summary: field("string", { default: computed("由 description/excerpt/content 截断"), example: "文章摘要", required: true })
607
+ }
608
+ });
609
+ const contributor = field(["object", "null"], {
610
+ default: computed("由选中的 services.contributors provider 按最长 source_prefix 匹配与源文件生成"),
611
+ example: { editUrl: "https://github.com/example/repo/blob/main/post.md", commitsUrl: "https://api.github.com/repos/example/repo/commits?path=post.md" },
612
+ required: true,
613
+ properties: {
614
+ editUrl: field("string", { default: literal(""), example: "https://github.com/example/repo/blob/main/post.md", required: true }),
615
+ commitsUrl: field("string", { default: literal(""), example: "https://api.github.com/repos/example/repo/commits?path=post.md", required: true })
616
+ }
617
+ });
618
+ properties.render = object({
619
+ document: object({
620
+ language: field("string", { default: derived("page.lang", "page.language", "site.language"), example: "zh-CN", required: true }),
621
+ headBeginInject: field("string", { default: literal(""), example: "", required: true }),
622
+ headEndInject: field("string", { default: literal(""), example: "", required: true }),
623
+ bodyBeginInject: field("string", { default: literal(""), example: "", required: true }),
624
+ bodyEndInject: field("string", { default: literal(""), example: "", required: true }),
625
+ preferredTheme: field("string", { default: derived("hexo.stellar.config.appearance.colorScheme"), example: "auto", required: true })
626
+ }, { required: true, example: { language: "zh-CN", headBeginInject: "", headEndInject: "", bodyBeginInject: "", bodyEndInject: "", preferredTheme: "auto" } }),
627
+ layout: object({
628
+ pageType: field("string", { default: literal("content"), example: "content", required: true }),
629
+ articleStyle: field(["string", "null"], { default: inherited("item.presentation.article.style"), example: "tech", required: true }),
630
+ indent: field("boolean", { default: computed("由 article.paragraphIndent 与 style 解析"), example: false, required: true }),
631
+ siteBackground: field("boolean", { default: derived("hexo.stellar.config.appearance.backgrounds.page.image"), example: false, required: true }),
632
+ blogPath: field("string", { default: derived("site.index_generator.path"), example: "blog", required: true }),
633
+ ...renderRegionsSchema(factory),
634
+ breadcrumbs: array(breadcrumbItem, { default: literal([]), example: [{ name: "思考", path: "blog/categories/thinking" }], required: true })
635
+ }, { required: true, example: { pageType: "content", articleStyle: "tech", indent: false, blogPath: "blog", breadcrumbs: [] } }),
636
+ seo: object({
637
+ title: field("string", { default: computed("由文章标题与站点标题组合"), example: "Hello Stellar - Stellar", required: true }),
638
+ description: field("string", { default: derived("page.description", "item.excerpt", "item.content"), example: "文章摘要", required: true }),
639
+ keywords: array(stringItem, { default: derived("page.keywords", "item.tags", "site.keywords"), example: ["Hexo", "Stellar"], required: true }),
640
+ robots: field(["string", "null"], { default: derived("IS_BACKUP", "page.robots"), example: "noindex, nofollow", required: true }),
641
+ canonical: field(["string", "null"], { default: derived("hexo.stellar.config.canonical.host", "item.route.path"), example: "https://example.com/blog/hello/", required: true }),
642
+ openGraph,
643
+ jsonLd: field("object", { default: computed("由 BlogPosting 结构化数据规则生成"), example: { "@type": "BlogPosting" }, required: true, additionalProperties: true })
644
+ }, { required: true, example: { title: "Hello Stellar - Stellar", description: "文章摘要", keywords: ["Hexo"], robots: null, canonical: null, openGraph: null, jsonLd: { "@type": "BlogPosting" } } }),
645
+ article: object({
646
+ heti: field("boolean", { default: derived("hexo.stellar.config.features.heti.enabled"), example: false, required: true }),
647
+ ...(profile === "topic" ? {
648
+ banner: field("object", {
649
+ default: inherited("item.presentation.banner"),
650
+ example: { image: "/topic.webp", headline: "Topic" },
651
+ required: true,
652
+ additionalProperties: true
653
+ })
654
+ } : {}),
655
+ tags: array(tagLink, { default: computed("由 Hexo 标签关系规范化;hexo.stellar.config.article.footer.showTags 禁用时为空"), example: [{ name: "Hexo", path: "tags/hexo" }], required: true }),
656
+ footer: object({
657
+ references: array(field("any", { additionalProperties: true }), { default: inherited("item.presentation.footer.references"), example: [], required: true }),
658
+ license: field("string", { default: computed("由最终许可协议与作者信息生成"), example: "CC BY 4.0", required: true }),
659
+ share,
660
+ contributor
661
+ }, { required: true, example: { references: [], license: "", share: null, contributor: null } }),
662
+ previous: postLink,
663
+ next: postLink,
664
+ related: object({
665
+ enabled: field("boolean", { default: derived("hexo.stellar.config.article.relatedPostsLimit > 0"), example: false, required: true }),
666
+ title: field("string", { default: literal(""), example: "Related Posts", required: true }),
667
+ maxCount: field("number", { default: literal(5), example: 5, required: true }),
668
+ items: array(relatedItem, { default: literal([]), example: [], required: true })
669
+ }, { required: true, example: { enabled: false, title: "", maxCount: 5, items: [] } }),
670
+ comments: object({
671
+ enabled: field("boolean", { default: computed("由最终 comments.enabled 与 service 生成"), example: true, required: true }),
672
+ title: field(["string", "null"], { default: inherited("item.presentation.comments.title"), example: "参与讨论", required: true }),
673
+ id: field("string", { default: inherited("item.presentation.comments.id"), example: "post-hello", required: true }),
674
+ service: field("string", { default: inherited("item.presentation.comments.service"), example: "giscus", required: true }),
675
+ options: field("object", { default: computed("由激活服务参数袋生成"), example: { "data-repo": "example/repo" }, required: true, additionalProperties: true }),
676
+ pageTitle: field("string", { default: inherited("item.title"), example: "Hello Stellar", required: true })
677
+ }, { required: true, example: { enabled: true, title: "参与讨论", id: "", service: "giscus", options: {}, pageTitle: "Hello Stellar" } })
678
+ }, { required: true, example: { heti: false, tags: [], footer: {}, previous: null, next: null, related: {}, comments: {} } }),
679
+ listing: object({
680
+ href: field("string", { default: derived("page.link", "item.route.path"), example: "blog/hello", required: true }),
681
+ title: field("string", { default: inherited("item.title"), example: "Hello Stellar", required: true }),
682
+ layout: field("string", { default: inherited("item.layout"), example: "post", required: true }),
683
+ date: field(["string", "null"], { default: inherited("item.date"), example: "2026-08-22T00:00:00.000Z", required: true }),
684
+ cover: field("string", { default: inherited("item.cover"), example: "/cover.webp", required: true }),
685
+ caption: field("string", { default: computed("由 tagline/description/excerpt/content 生成"), example: "文章说明", required: true }),
686
+ excerpt: field("string", { default: computed("由 excerpt/description/content 和列表长度生成"), example: "文章摘要", required: true }),
687
+ categories: array(stringItem, { default: inherited("item.categories"), example: ["开发"], required: true }),
688
+ categoryStyle: field("string", { default: derived("hexo.stellar.config.article.categoryColors"), example: "--text-p2:#f44336;--theme-block:#f4433620", required: true }),
689
+ tags: array(stringItem, { default: computed("hexo.stellar.config.article.listing.showTags 启用时最多五项"), example: ["Hexo"], required: true }),
690
+ authorId: field("string", { default: derived("item.presentation.article.author", "stellar.data.defaultAuthor.id"), example: "xaoxuu", required: true }),
691
+ priority: field("number", { default: inherited("item.listing.priority"), example: 1, required: true }),
692
+ listed: field("boolean", { default: inherited("item.visibility.listed"), example: true, required: true }),
693
+ cardStyle: field("string", { default: inherited("collection.listing.cardStyle"), example: "hero", required: true })
694
+ }, { required: true, example: { href: "blog/hello", title: "Hello Stellar", layout: "post", date: null, cover: "", caption: "", excerpt: "", categories: [], categoryStyle: "", tags: [], authorId: "", priority: 0, listed: true, cardStyle: "classic" } })
695
+ }, {
696
+ default: computed(`由 ${profile === "topic" ? "Topic" : "Post"} PageViewModel 构建器生成`),
697
+ example: { document: { language: "zh-CN", headBeginInject: "", headEndInject: "", bodyBeginInject: "", bodyEndInject: "" }, layout: { pageType: "content" }, seo: { title: "Hello Stellar - Stellar" } },
698
+ required: true
699
+ });
700
+ }
701
+
702
+ if (profile === "wiki") {
703
+ const stringItem = field("string", { default: computed("由构建期数组逐项归一化"), example: "Stellar" });
704
+ const wikiLink = field(["object", "null"], {
705
+ default: computed("由 Wiki navigation.tree 当前页码计算;不存在时为 null"),
706
+ example: { title: "安装", path: "wiki/stellar/install", date: null },
707
+ required: true,
708
+ properties: {
709
+ title: field("string", { default: literal(""), example: "安装", required: true }),
710
+ path: field("string", { default: literal(""), example: "wiki/stellar/install", required: true }),
711
+ date: field(["string", "null"], { default: literal(null), example: null, required: true })
712
+ }
713
+ });
714
+ const share = field(["object", "null"], {
715
+ default: computed("由最终 footer.share 生成;禁用时为 null"),
716
+ example: { services: ["link"], permalink: "https://example.com/wiki/stellar/", title: "Stellar - Example" },
717
+ required: true,
718
+ properties: {
719
+ services: array(stringItem, { default: literal([]), example: ["wechat", "link"], required: true }),
720
+ permalink: field("string", { default: inherited("item.route.permalink"), example: "https://example.com/wiki/stellar/", required: true }),
721
+ title: field("string", { default: computed("由页面标题与站点标题组合"), example: "Stellar - Example", required: true }),
722
+ image: field("string", { default: inherited("item.cover"), example: "/cover.webp", required: true }),
723
+ summary: field("string", { default: computed("由 description/excerpt/content 截断"), example: "Wiki 摘要", required: true })
724
+ }
725
+ });
726
+ const contributor = field(["object", "null"], {
727
+ default: computed("由 contributors 选中 provider 的 repositories 最长 source_prefix 匹配与源文件生成"),
728
+ example: { editUrl: "https://github.com/example/repo/blob/main/index.md", commitsUrl: "https://api.github.com/repos/example/repo/commits?path=index.md" },
729
+ required: true,
730
+ properties: {
731
+ editUrl: field("string", { default: literal(""), example: "https://github.com/example/repo/blob/main/index.md", required: true }),
732
+ commitsUrl: field("string", { default: literal(""), example: "https://api.github.com/repos/example/repo/commits?path=index.md", required: true })
733
+ }
734
+ });
735
+ const comments = object({
736
+ enabled: field("boolean", { default: computed("由最终 comments.enabled 与 service 生成"), example: true, required: true }),
737
+ title: field(["string", "null"], { default: inherited("item.presentation.comments.title"), example: "参与讨论", required: true }),
738
+ id: field("string", { default: inherited("item.presentation.comments.id"), example: "wiki-stellar", required: true }),
739
+ service: field("string", { default: inherited("item.presentation.comments.provider"), example: "giscus", required: true }),
740
+ options: field("object", { default: computed("由激活服务参数袋生成"), example: { "data-repo": "example/repo" }, required: true, additionalProperties: true }),
741
+ pageTitle: field("string", { default: inherited("item.title"), example: "开始", required: true })
742
+ }, { required: true, example: { enabled: true, title: "参与讨论", id: "", service: "giscus", options: {}, pageTitle: "开始" } });
743
+ const relatedItem = object({
744
+ href: field("string", { default: literal(""), example: "wiki/example", required: true }),
745
+ title: field("string", { default: literal(""), example: "Example", required: true }),
746
+ description: field("string", { default: literal(""), example: "Related Wiki", required: true })
747
+ }, { example: { href: "wiki/example", title: "Example", description: "Related Wiki" } });
748
+ const relatedGroup = object({
749
+ name: field("string", { default: literal(""), example: "博客主题", required: true }),
750
+ items: array(relatedItem, { default: literal([]), example: [], required: true })
751
+ }, { example: { name: "博客主题", items: [] } });
752
+ const breadcrumb = object({
753
+ name: field("string", { default: inherited("collection.identity.name"), example: "Stellar", required: true }),
754
+ path: field("string", { default: inherited("collection.route.homepage"), example: "wiki/stellar", required: true })
755
+ }, { example: { name: "Stellar", path: "wiki/stellar" } });
756
+ const openGraph = field(["object", "null"], {
757
+ default: computed("由 SEO 配置与页面 Open Graph 覆盖生成;禁用时为 null"),
758
+ example: { args: { image: "/cover.webp" }, title: "开始", siteName: "Example", twitterTitle: "开始", publishedTime: null, modifiedTime: null, tags: [] },
759
+ required: true,
760
+ additionalProperties: true
761
+ });
762
+
763
+ properties.render = object({
764
+ document: object({
765
+ language: field("string", { default: derived("page.lang", "page.language", "site.language"), example: "zh-CN", required: true }),
766
+ headBeginInject: field("string", { default: literal(""), example: "", required: true }),
767
+ headEndInject: field("string", { default: literal(""), example: "", required: true }),
768
+ bodyBeginInject: field("string", { default: literal(""), example: "", required: true }),
769
+ bodyEndInject: field("string", { default: literal(""), example: "", required: true }),
770
+ preferredTheme: field("string", { default: derived("hexo.stellar.config.appearance.colorScheme"), example: "auto", required: true })
771
+ }, { required: true, example: { language: "zh-CN", headBeginInject: "", headEndInject: "", bodyBeginInject: "", bodyEndInject: "", preferredTheme: "auto" } }),
772
+ layout: object({
773
+ pageType: field("string", { default: literal("content"), example: "content", required: true }),
774
+ articleStyle: field(["string", "null"], { default: inherited("item.presentation.article.style"), example: "tech", required: true }),
775
+ indent: field("boolean", { default: computed("由 article.paragraphIndent 与 style 解析"), example: false, required: true }),
776
+ siteBackground: field("boolean", { default: derived("hexo.stellar.config.appearance.backgrounds.page.image"), example: false, required: true }),
777
+ wikiIndexPath: field("string", { default: derived("hexo.stellar.config.profiles.wikiIndex.path"), example: "wiki", required: true }),
778
+ algoliaFilterPath: field("string", { default: computed("由页面路径生成 Algolia 搜索的两段目录范围"), example: "wiki/stellar/", required: true }),
779
+ ...renderRegionsSchema(factory),
780
+ breadcrumbs: array(breadcrumb, { default: literal([]), example: [{ name: "Stellar", path: "wiki/stellar" }], required: true })
781
+ }, { required: true, example: { pageType: "content", articleStyle: "tech", indent: false, wikiIndexPath: "wiki", showWikiHome: true, algoliaFilterPath: "wiki/stellar/", leftbar: {}, breadcrumbs: [] } }),
782
+ seo: object({
783
+ title: field("string", { default: computed("由 Wiki 名、页面标题和站点标题组合"), example: "Stellar:开始 - Example", required: true }),
784
+ description: field("string", { default: derived("page.description", "collection.identity.description", "item.excerpt", "item.content"), example: "Wiki 摘要", required: true }),
785
+ keywords: array(stringItem, { default: derived("page.keywords", "item.tags", "site.keywords"), example: ["Stellar"], required: true }),
786
+ robots: field(["string", "null"], { default: derived("IS_BACKUP", "page.robots"), example: null, required: true }),
787
+ canonical: field(["string", "null"], { default: derived("hexo.stellar.config.canonical.host", "item.route.path"), example: "https://example.com/wiki/stellar/", required: true }),
788
+ openGraph,
789
+ jsonLd: field("object", { default: computed("由 WebPage 结构化数据规则生成"), example: { "@type": "WebPage" }, required: true, additionalProperties: true })
790
+ }, { required: true, example: { title: "Stellar:开始 - Example", description: "Wiki 摘要", keywords: ["Stellar"], robots: null, canonical: null, openGraph: null, jsonLd: { "@type": "WebPage" } } }),
791
+ cover: object({
792
+ enabled: field("boolean", { default: computed("仅 Wiki 首页且 hero.enabled 时启用"), example: true, required: true }),
793
+ background: field("object", { default: inherited("collection.presentation.hero.background"), example: { effect: { type: "galaxy" } }, required: true, additionalProperties: true }),
794
+ preview: field("object", { default: inherited("collection.presentation.hero.preview"), example: { type: "terminal" }, required: true, additionalProperties: true }),
795
+ actions: array(field("object", { additionalProperties: true }), { default: literal([]), example: [], required: true }),
796
+ title: field("string", { default: inherited("collection.identity.headline"), example: "每个人的独立博客", required: true }),
797
+ description: field("string", { default: inherited("collection.identity.description"), example: "Stellar 文档", required: true }),
798
+ repository: field("string", { default: inherited("collection.source.repository"), example: "xaoxuu/hexo-theme-stellar", required: true }),
799
+ sourceUrl: field("string", { default: computed("由 repository 生成"), example: "https://github.com/xaoxuu/hexo-theme-stellar", required: true }),
800
+ releaseApi: field("string", { default: computed("由 GitHub service API 与 repository 生成"), example: "https://api.github.com/repos/xaoxuu/hexo-theme-stellar/tags", required: true }),
801
+ projectName: field("string", { default: inherited("collection.identity.name"), example: "Stellar", required: true }),
802
+ siteName: field("string", { default: derived("site.title"), example: "Example", required: true })
803
+ }, { required: true, example: { enabled: true, background: {}, preview: {}, actions: [], title: "Stellar", description: "", repository: "", sourceUrl: "", releaseApi: "", projectName: "Stellar", siteName: "Example" } }),
804
+ article: object({
805
+ heti: field("boolean", { default: derived("features.heti.enabled"), example: false, required: true }),
806
+ banner: field("object", { default: inherited("item.presentation.banner"), example: { headline: "开始" }, required: true, additionalProperties: true }),
807
+ updated: field(["string", "null"], { default: inherited("item.updated"), example: "2026-08-23T00:00:00.000Z", required: true }),
808
+ readmeHtml: field("string", { default: computed("Wiki 首页正文为空且配置 repository 时生成远程 README 占位"), example: "", required: true }),
809
+ footer: object({
810
+ references: array(field("any", { additionalProperties: true }), { default: inherited("item.presentation.footer.references"), example: [], required: true }),
811
+ license: field("string", { default: computed("由最终许可协议与作者信息生成"), example: "CC BY-NC-SA 4.0", required: true }),
812
+ share,
813
+ contributor
814
+ }, { required: true, example: { references: [], license: "", share: null, contributor: null } }),
815
+ previous: wikiLink,
816
+ next: wikiLink,
817
+ comments,
818
+ related: array(relatedGroup, { default: literal([]), example: [], required: true })
819
+ }, { required: true, example: { heti: false, banner: {}, updated: null, readmeHtml: "", footer: {}, previous: null, next: null, comments: {}, related: [] } }),
820
+ listing: object({
821
+ id: field("string", { default: inherited("collection.id"), example: "stellar", required: true }),
822
+ href: field("string", { default: inherited("collection.route.homepage"), example: "wiki/stellar", required: true }),
823
+ name: field("string", { default: inherited("collection.identity.name"), example: "Stellar", required: true }),
824
+ headline: field("string", { default: inherited("collection.identity.headline"), example: "每个人的独立博客", required: true }),
825
+ caption: field("string", { default: computed("由 collection tagline/description 生成"), example: "基于 Hexo 的全能型主题", required: true }),
826
+ description: field("string", { default: inherited("collection.identity.description"), example: "Stellar 文档", required: true }),
827
+ tags: array(stringItem, { default: derived("collection.tags"), example: ["博客主题"], required: true }),
828
+ audience: field("string", { default: inherited("collection.identity.audience"), example: "独立博主", required: true }),
829
+ icon: field("string", { default: inherited("collection.identity.icon"), example: "/stellar.svg", required: true }),
830
+ cover: field("string", { default: inherited("collection.cover"), example: "/cover.webp", required: true }),
831
+ repository: field("string", { default: inherited("collection.source.repository"), example: "xaoxuu/hexo-theme-stellar", required: true }),
832
+ repositoryApi: field("string", { default: computed("由 GitHub service API 与 repository 生成"), example: "https://api.github.com/repos/xaoxuu/hexo-theme-stellar", required: true }),
833
+ priority: field("number", { default: inherited("collection.listing.priority"), example: 1, required: true }),
834
+ order: field("number", { default: inherited("collection.listing.order"), example: 0, required: true }),
835
+ listed: field("boolean", { default: inherited("collection.visibility.listed"), example: true, required: true })
836
+ }, { required: true, example: { id: "stellar", href: "wiki/stellar", name: "Stellar", headline: "Stellar", caption: "", description: "", tags: [], audience: "", icon: "", cover: "", repository: "", repositoryApi: "", priority: 0, order: 0, listed: true } })
837
+ }, {
838
+ default: computed("由 Wiki PageViewModel 构建器生成"),
839
+ example: { document: { language: "zh-CN" }, layout: { pageType: "content" }, seo: { title: "Stellar - Example" } },
840
+ required: true
841
+ });
842
+ }
843
+
844
+ if (profile === "notebook") {
845
+ const stringItem = field("string", { default: computed("由构建期数组逐项归一化"), example: "tools" });
846
+ const breadcrumb = object({
847
+ name: field("string", { default: inherited("collection.identity.headline"), example: "Development Notes", required: true }),
848
+ path: field("string", { default: inherited("collection.route.baseDir"), example: "notes/dev", required: true })
849
+ }, { example: { name: "Development Notes", path: "notes/dev" } });
850
+ const tagLink = object({
851
+ name: field("string", { default: literal(""), example: "tools/cli", required: true }),
852
+ path: field("string", { default: computed("由 Notebook 标签导航映射"), example: "notes/dev/tags/tools/cli", required: true })
853
+ }, { example: { name: "tools/cli", path: "notes/dev/tags/tools/cli" } });
854
+ const tagTreeItem = object({
855
+ id: field("string", { default: literal(""), example: "tools/cli", required: true }),
856
+ name: field("string", { default: literal(""), example: "tools/cli", required: true }),
857
+ label: field("string", { default: literal(""), example: "cli", required: true }),
858
+ path: field("string", { default: inherited("collection.navigation.tags.path"), example: "notes/dev/tags/tools/cli", required: true }),
859
+ parentId: field(["string", "null"], { default: literal(null), example: "tools", required: true }),
860
+ children: array(stringItem, { default: literal([]), example: [], required: true })
861
+ }, { example: { id: "tools/cli", name: "tools/cli", label: "cli", path: "notes/dev/tags/tools/cli", parentId: "tools", children: [] } });
862
+ const comments = object({
863
+ enabled: field("boolean", { default: computed("由最终 comments.enabled 与 service 生成"), example: true, required: true }),
864
+ title: field(["string", "null"], { default: inherited("item.presentation.comments.title"), example: "参与讨论", required: true }),
865
+ id: field("string", { default: inherited("item.presentation.comments.id"), example: "note-node", required: true }),
866
+ service: field("string", { default: inherited("item.presentation.comments.provider"), example: "giscus", required: true }),
867
+ options: field("object", { default: computed("由激活服务参数袋生成"), example: {}, required: true, additionalProperties: true }),
868
+ pageTitle: field("string", { default: inherited("item.title"), example: "Node.js", required: true })
869
+ }, { required: true, example: { enabled: true, title: "参与讨论", id: "", service: "giscus", options: {}, pageTitle: "Node.js" } });
870
+ const share = field(["object", "null"], {
871
+ default: computed("由 Notebook Footer 分享配置生成;禁用时为 null"),
872
+ example: null,
873
+ required: true,
874
+ additionalProperties: true
875
+ });
876
+
877
+ properties.render = object({
878
+ document: object({
879
+ language: field("string", { default: derived("page.lang", "page.language", "site.language"), example: "zh-CN", required: true }),
880
+ headBeginInject: field("string", { default: literal(""), example: "", required: true }),
881
+ headEndInject: field("string", { default: literal(""), example: "", required: true }),
882
+ bodyBeginInject: field("string", { default: literal(""), example: "", required: true }),
883
+ bodyEndInject: field("string", { default: literal(""), example: "", required: true }),
884
+ preferredTheme: field("string", { default: derived("appearance.colorScheme"), example: "auto", required: true })
885
+ }, { required: true, example: { language: "zh-CN", headBeginInject: "", headEndInject: "", bodyBeginInject: "", bodyEndInject: "", preferredTheme: "auto" } }),
886
+ layout: object({
887
+ pageType: field("string", { default: literal("content"), example: "content", required: true }),
888
+ articleStyle: field(["string", "null"], { default: inherited("item.presentation.article.style"), example: "tech", required: true }),
889
+ indent: field("boolean", { default: computed("由 article.paragraphIndent 与 style 解析"), example: false, required: true }),
890
+ siteBackground: field("boolean", { default: derived("appearance.backgrounds.page.image"), example: false, required: true }),
891
+ notebookIndexPath: field("string", { default: derived("hexo.stellar.config.profiles.notebooks.path"), example: "notebooks", required: true }),
892
+ notebookPath: field("string", { default: inherited("collection.route.baseDir"), example: "notes/dev", required: true }),
893
+ algoliaFilterPath: field("string", { default: inherited("collection.route.baseDir"), example: "notes/dev", required: true }),
894
+ ...renderRegionsSchema(factory),
895
+ breadcrumbs: array(breadcrumb, { default: literal([]), example: [], required: true }),
896
+ tagTree: array(tagTreeItem, { default: literal([]), example: [], required: true }),
897
+ recentItems: array(field("object", { additionalProperties: true }), { default: literal([]), example: [], required: true })
898
+ }, { required: true, example: { pageType: "content", articleStyle: "tech", indent: false, notebookIndexPath: "notebooks", notebookPath: "notes/dev", algoliaFilterPath: "notes/dev", leftbar: {}, breadcrumbs: [], tagTree: [] } }),
899
+ seo: object({
900
+ title: field("string", { default: computed("由 Note 标题与站点标题组合"), example: "Node.js - Example", required: true }),
901
+ description: field("string", { default: derived("page.description", "item.excerpt", "item.content"), example: "Note 摘要", required: true }),
902
+ keywords: array(stringItem, { default: derived("page.keywords", "item.tags", "site.keywords"), example: ["Node.js"], required: true }),
903
+ robots: field(["string", "null"], { default: derived("IS_BACKUP", "page.robots"), example: null, required: true }),
904
+ canonical: field(["string", "null"], { default: derived("hexo.stellar.config.canonical.host", "item.route.path"), example: "https://example.com/notes/dev/node/", required: true }),
905
+ openGraph: field(["object", "null"], { default: computed("由 SEO 与页面 Open Graph 配置生成"), example: null, required: true, additionalProperties: true }),
906
+ jsonLd: field("object", { default: computed("由 WebPage 结构化数据规则生成"), example: { "@type": "WebPage" }, required: true, additionalProperties: true })
907
+ }, { required: true, example: { title: "Node.js - Example", description: "Note 摘要", keywords: [], robots: null, canonical: null, openGraph: null, jsonLd: { "@type": "WebPage" } } }),
908
+ article: object({
909
+ heti: field("boolean", { default: derived("features.heti.enabled"), example: false, required: true }),
910
+ banner: field("object", { default: inherited("item.presentation.banner"), example: {}, required: true, additionalProperties: true }),
911
+ created: field(["string", "null"], { default: inherited("item.date"), example: "2026-08-23T00:00:00.000Z", required: true }),
912
+ updated: field(["string", "null"], { default: inherited("item.updated"), example: "2026-08-23T00:00:00.000Z", required: true }),
913
+ tags: array(tagLink, { default: literal([]), example: [], required: true }),
914
+ footer: object({
915
+ references: array(field("any", { additionalProperties: true }), { default: inherited("item.presentation.footer.references"), example: [], required: true }),
916
+ license: field("string", { default: computed("由最终许可协议与作者信息生成"), example: "CC BY 4.0", required: true }),
917
+ share,
918
+ contributor: field(["object", "null"], { default: computed("由 contributors 选中 provider 的 repositories 最长 source_prefix 匹配与源文件生成"), example: null, required: true, additionalProperties: true })
919
+ }, { required: true, example: { references: [], license: "", share: null, contributor: null } }),
920
+ comments
921
+ }, { required: true, example: { heti: false, banner: {}, created: null, updated: null, tags: [], footer: {}, comments: {} } }),
922
+ listing: object({
923
+ id: field("string", { default: inherited("item.id"), example: "note-node", required: true }),
924
+ collectionId: field("string", { default: inherited("collection.id"), example: "dev", required: true }),
925
+ collectionName: field("string", { default: inherited("collection.identity.name"), example: "Development Notes", required: true }),
926
+ href: field("string", { default: derived("page.link", "item.route.path"), example: "notes/dev/node", required: true }),
927
+ title: field("string", { default: inherited("item.title"), example: "Node.js", required: true }),
928
+ cover: field("string", { default: inherited("item.cover"), example: "", required: true }),
929
+ excerpt: field("string", { default: computed("由 excerpt/description/content 与 Notebook 摘要长度生成"), example: "Note 摘要", required: true }),
930
+ tags: array(stringItem, { default: inherited("item.tags"), example: ["tools"], required: true }),
931
+ date: field(["string", "null"], { default: inherited("item.date"), example: null, required: true }),
932
+ updated: field(["string", "null"], { default: inherited("item.updated"), example: null, required: true }),
933
+ priority: field("number", { default: inherited("item.listing.priority"), example: 0, required: true }),
934
+ listed: field("boolean", { default: inherited("item.visibility.listed"), example: true, required: true })
935
+ }, { required: true, example: { id: "note-node", collectionId: "dev", collectionName: "Development Notes", href: "notes/dev/node", title: "Node.js", cover: "", excerpt: "", tags: [], date: null, updated: null, priority: 0, listed: true } })
936
+ }, {
937
+ default: computed("由 Notebook PageViewModel 构建器生成"),
938
+ example: { document: { language: "zh-CN" }, layout: { pageType: "content" }, seo: { title: "Node.js - Example" } },
939
+ required: true
940
+ });
941
+ }
942
+
943
+ return object(properties, {
944
+ required: true,
945
+ example: profile === "post" || profile === "wiki" || profile === "notebook"
946
+ ? { collection: { id: profile, profile }, item: { id: `${profile}-hello` }, render: { document: {}, layout: {}, seo: {} } }
947
+ : { collection: { id: profile, profile }, item: { id: `${profile}-hello` } }
948
+ });
949
+ }
950
+
951
+ const MODEL_SCHEMAS = deepFreeze({
952
+ CollectionModel: {
953
+ profiles: Object.fromEntries(PROFILES.map(profile => [profile, collectionSchema(profile)]))
954
+ },
955
+ ContentItemModel: {
956
+ schema: contentItemSchema()
957
+ },
958
+ PageViewModel: {
959
+ profiles: Object.fromEntries(PROFILES.map(profile => [profile, pageViewModelSchema(profile)]))
960
+ }
961
+ });
962
+
963
+ module.exports = {
964
+ MODEL_SCHEMAS,
965
+ PROFILES,
966
+ computed,
967
+ derived,
968
+ inherited,
969
+ literal,
970
+ omitted
971
+ };