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,172 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const yaml = require("js-yaml");
6
+
7
+ const { resolveInside } = require("./safe-path");
8
+ const {
9
+ parseCollectionConfig,
10
+ parsePageConfig,
11
+ validateCollectionProfileConfig,
12
+ validatePageProfileConfig
13
+ } = require("./content-config");
14
+ const { getProfileAdapter } = require("./collection-pipeline/registry");
15
+ const { parseFrontMatterYaml } = require("./front-matter");
16
+ const { deepFreeze } = require("../schema/schema-utils");
17
+
18
+ class NewNoteError extends Error {
19
+ constructor(message, details = {}) {
20
+ super(message);
21
+ this.name = "NewNoteError";
22
+ Object.assign(this, details);
23
+ }
24
+ }
25
+
26
+ class NewNoteConflictError extends NewNoteError {
27
+ constructor(target) {
28
+ super(`Stellar new note 拒绝覆盖已有文件:${target}`, { target });
29
+ this.name = "NewNoteConflictError";
30
+ }
31
+ }
32
+
33
+ function formatDate(value) {
34
+ const date = value instanceof Date ? value : new Date(value);
35
+ if (Number.isNaN(date.getTime())) throw new NewNoteError("generatedAt 必须是合法日期");
36
+ const pad = number => String(number).padStart(2, "0");
37
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
38
+ }
39
+
40
+ function safeSegment(value, label) {
41
+ const segment = typeof value === "string" ? value.trim() : "";
42
+ if (segment.length === 0) throw new NewNoteError(`${label} 必须是非空字符串`);
43
+ const hasControl = Array.from(segment).some(character => character.charCodeAt(0) < 32);
44
+ if (segment === "." || segment === ".." || /[<>:"/\\|?*]/.test(segment) || hasControl) {
45
+ throw new NewNoteError(`${label} 不能包含路径或文件系统保留字符`);
46
+ }
47
+ return segment;
48
+ }
49
+
50
+ function normalizeTags(value) {
51
+ const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
52
+ const tags = [];
53
+ const seen = new Set();
54
+ for (const item of values) {
55
+ const tag = typeof item === "string" ? item.trim() : "";
56
+ if (tag.length === 0 || seen.has(tag)) continue;
57
+ if (Array.from(tag).some(character => character.charCodeAt(0) < 32)) {
58
+ throw new NewNoteError("--tags 不能包含换行或空字符");
59
+ }
60
+ seen.add(tag);
61
+ tags.push(tag);
62
+ }
63
+ return Object.freeze(tags);
64
+ }
65
+
66
+ function notebookConfigFile(sourceDir, notebook) {
67
+ const candidates = ["yml", "yaml"].map(extension => (
68
+ path.join(sourceDir, "_data", "notebooks", `${notebook}.${extension}`)
69
+ ));
70
+ return candidates.find(file => fs.existsSync(file)) || candidates[0];
71
+ }
72
+
73
+ function renderFrontMatter({ title, tags, generatedAt }) {
74
+ const lines = [
75
+ "---",
76
+ `date: ${formatDate(generatedAt)}`,
77
+ `title: ${JSON.stringify(title)}`
78
+ ];
79
+ if (tags.length > 0) {
80
+ lines.push("tags:", ...tags.map(tag => ` - ${JSON.stringify(tag)}`));
81
+ }
82
+ lines.push("---", "", "");
83
+ return lines.join("\n");
84
+ }
85
+
86
+ function buildNewNotePlan(options = {}) {
87
+ const baseDir = path.resolve(options.baseDir || process.cwd());
88
+ const sourceDir = path.resolve(options.sourceDir || path.join(baseDir, "source"));
89
+ const notebook = safeSegment(options.notebook, "--notebook");
90
+ const title = safeSegment(options.title, "--title");
91
+ const tags = normalizeTags(options.tags);
92
+ const configFile = notebookConfigFile(sourceDir, notebook);
93
+ if (!fs.existsSync(configFile) || !fs.statSync(configFile).isFile()) {
94
+ throw new NewNoteError(`未知 Notebook:${notebook};缺少 ${path.relative(baseDir, configFile)}`);
95
+ }
96
+ let rawCollection;
97
+ try {
98
+ rawCollection = yaml.load(fs.readFileSync(configFile, "utf8"), { filename: configFile }) || {};
99
+ } catch (error) {
100
+ throw new NewNoteError(`${configFile}: Notebook YAML 无效(${error.message})`);
101
+ }
102
+ const collectionSource = path.relative(baseDir, configFile).replace(/\\/g, "/");
103
+ validateCollectionProfileConfig(
104
+ parseCollectionConfig(rawCollection, collectionSource),
105
+ collectionSource,
106
+ "notebook",
107
+ getProfileAdapter("notebook").config
108
+ );
109
+
110
+ const target = path.posix.join("source", "notebooks", notebook, `${title}.md`);
111
+ const output = resolveInside(baseDir, target, `stellar new note: ${target}`);
112
+ const content = renderFrontMatter({ title, tags, generatedAt: options.generatedAt ?? new Date() });
113
+ validatePageProfileConfig(
114
+ parsePageConfig(parseFrontMatterYaml(content, target) || {}, target),
115
+ target,
116
+ "notebook",
117
+ getProfileAdapter("notebook").config
118
+ );
119
+ if (fs.existsSync(output.absolute)) throw new NewNoteConflictError(target);
120
+ return deepFreeze({
121
+ notebook,
122
+ title,
123
+ tags,
124
+ baseDir,
125
+ target,
126
+ outputPath: output.absolute,
127
+ content
128
+ });
129
+ }
130
+
131
+ function writeNewNotePlan(plan) {
132
+ const output = resolveInside(plan.baseDir, plan.target, `write: ${plan.target}`);
133
+ if (output.absolute !== path.resolve(plan.outputPath)) {
134
+ throw new NewNoteError(`${plan.target}: outputPath 与计划根目录不一致`);
135
+ }
136
+ if (fs.existsSync(plan.outputPath)) throw new NewNoteConflictError(plan.target);
137
+ const createdDirectories = [];
138
+ let directory = path.dirname(plan.outputPath);
139
+ while (!fs.existsSync(directory)) {
140
+ createdDirectories.push(directory);
141
+ directory = path.dirname(directory);
142
+ }
143
+ try {
144
+ fs.mkdirSync(path.dirname(plan.outputPath), { recursive: true });
145
+ fs.writeFileSync(plan.outputPath, plan.content, { encoding: "utf8", flag: "wx" });
146
+ } catch (error) {
147
+ if (fs.existsSync(plan.outputPath)) fs.unlinkSync(plan.outputPath);
148
+ for (const created of createdDirectories) {
149
+ if (fs.existsSync(created) && fs.readdirSync(created).length === 0) fs.rmdirSync(created);
150
+ }
151
+ if (error instanceof NewNoteError) throw error;
152
+ throw new NewNoteError(`Stellar new note 写入失败,已回滚本次创建的文件(${error.message})`);
153
+ }
154
+ return plan;
155
+ }
156
+
157
+ function formatNewNotePlan(plan, options = {}) {
158
+ return [
159
+ options.dryRun ? "Stellar new note dry-run" : "Stellar new note plan",
160
+ ` notebook ${plan.notebook}`,
161
+ ` create ${plan.target}`
162
+ ].join("\n");
163
+ }
164
+
165
+ module.exports = {
166
+ NewNoteConflictError,
167
+ NewNoteError,
168
+ buildNewNotePlan,
169
+ formatNewNotePlan,
170
+ normalizeTags,
171
+ writeNewNotePlan
172
+ };
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+
3
+ const profileInputs = new Map();
4
+ const profileBases = new Map();
5
+ const relatedItems = new Map();
6
+ const pageConfigs = new Map();
7
+
8
+ function keysForPage(page) {
9
+ return [page?.source, page?.path, page?._id]
10
+ .filter(value => typeof value === "string" && value.length > 0);
11
+ }
12
+
13
+ function resetPageViewModelRegistry() {
14
+ profileInputs.clear();
15
+ profileBases.clear();
16
+ relatedItems.clear();
17
+ pageConfigs.clear();
18
+ }
19
+
20
+ function setValue(store, page, value) {
21
+ for (const key of keysForPage(page)) store.set(key, value);
22
+ }
23
+
24
+ function getValue(store, page) {
25
+ for (const key of keysForPage(page)) {
26
+ if (store.has(key)) return store.get(key);
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function setPageConfig(page, config) {
32
+ setValue(pageConfigs, page, config);
33
+ }
34
+
35
+ function getPageConfig(page) {
36
+ return getValue(pageConfigs, page);
37
+ }
38
+
39
+ function profileStore(stores, profile) {
40
+ if (!stores.has(profile)) stores.set(profile, new Map());
41
+ return stores.get(profile);
42
+ }
43
+
44
+ function setProfileViewModelInput(profile, page, input) {
45
+ setValue(profileStore(profileInputs, profile), page, input);
46
+ }
47
+
48
+ function getProfileViewModelInput(profile, page) {
49
+ return getValue(profileStore(profileInputs, profile), page);
50
+ }
51
+
52
+ function setProfileViewModelBase(profile, page, base) {
53
+ setValue(profileStore(profileBases, profile), page, base);
54
+ }
55
+
56
+ function getProfileViewModelBase(profile, page) {
57
+ return getValue(profileStore(profileBases, profile), page);
58
+ }
59
+
60
+ function getPostViewModelInput(page) {
61
+ return getProfileViewModelInput("post", page);
62
+ }
63
+
64
+ function getTopicViewModelInput(page) {
65
+ return getProfileViewModelInput("topic", page);
66
+ }
67
+
68
+ function getTopicViewModelBase(page) {
69
+ return getProfileViewModelBase("topic", page);
70
+ }
71
+
72
+ function getNotebookViewModelInput(page) {
73
+ return getProfileViewModelInput("notebook", page);
74
+ }
75
+
76
+ function getNotebookViewModelBase(page) {
77
+ return getProfileViewModelBase("notebook", page);
78
+ }
79
+
80
+ function setRelatedItems(page, items) {
81
+ setValue(relatedItems, page, Object.freeze(items.slice()));
82
+ }
83
+
84
+ function getRelatedItems(page) {
85
+ return getValue(relatedItems, page) || [];
86
+ }
87
+
88
+ module.exports = {
89
+ getProfileViewModelBase,
90
+ getProfileViewModelInput,
91
+ getPageConfig,
92
+ getNotebookViewModelBase,
93
+ getNotebookViewModelInput,
94
+ getPostViewModelInput,
95
+ getRelatedItems,
96
+ getTopicViewModelBase,
97
+ getTopicViewModelInput,
98
+ resetPageViewModelRegistry,
99
+ setPageConfig,
100
+ setProfileViewModelBase,
101
+ setProfileViewModelInput,
102
+ setRelatedItems
103
+ };
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ const { resolveRegionWidgets } = require("./widget-registry");
4
+
5
+ const REGION_IDS = Object.freeze(["topbar", "leftbar", "rightbar"]);
6
+
7
+ function clone(value) {
8
+ if (Array.isArray(value)) return value.map(clone);
9
+ if (value == null || typeof value !== "object") return value;
10
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, clone(child)]));
11
+ }
12
+
13
+ function freeze(value) {
14
+ if (value == null || typeof value !== "object" || Object.isFrozen(value)) return value;
15
+ Object.values(value).forEach(freeze);
16
+ return Object.freeze(value);
17
+ }
18
+
19
+ function regionLayer(value) {
20
+ if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
21
+ return value;
22
+ }
23
+
24
+ function cascadeRegion(layers, region) {
25
+ let widgets = [];
26
+ for (const layer of layers) {
27
+ const current = regionLayer(layer?.[region]);
28
+ if (!current) continue;
29
+ if (Array.isArray(current.widgets)) widgets = current.widgets.map(clone);
30
+ }
31
+ return widgets;
32
+ }
33
+
34
+ function mergeBrand(base, override, sources = null) {
35
+ if (override == null) return clone(base);
36
+ if (override === false) return false;
37
+ if (regionLayer(override) == null) return clone(base);
38
+ const requestedSource = typeof override.source === "string" ? override.source : null;
39
+ const sourceBrand = requestedSource && sources && Object.prototype.hasOwnProperty.call(sources, requestedSource)
40
+ ? sources[requestedSource]
41
+ : undefined;
42
+ const result = sourceBrand !== undefined
43
+ ? (regionLayer(sourceBrand) == null ? {} : clone(sourceBrand))
44
+ : (regionLayer(base) == null ? {} : clone(base));
45
+ for (const [key, value] of Object.entries(override)) {
46
+ if (key === "image" && regionLayer(value)) {
47
+ result.image = { ...(regionLayer(result.image) ? result.image : {}), ...clone(value) };
48
+ } else {
49
+ result[key] = clone(value);
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+
55
+ function resolveRegion(layers, region, options = {}) {
56
+ const result = {
57
+ enabled: region !== "topbar",
58
+ widgets: []
59
+ };
60
+ if (["topbar", "leftbar"].includes(region)) {
61
+ result.brand = false;
62
+ result.menu = [];
63
+ }
64
+ if (region === "leftbar") result.footer = { actions: [] };
65
+ for (const layer of layers) {
66
+ const current = regionLayer(layer?.[region]);
67
+ if (!current) continue;
68
+ if (typeof current.enabled === "boolean") result.enabled = current.enabled;
69
+ if (["topbar", "leftbar"].includes(region) && current.brand !== undefined) {
70
+ result.brand = mergeBrand(result.brand, current.brand, region === "leftbar" ? options.brandSources : null);
71
+ }
72
+ if (["topbar", "leftbar"].includes(region) && Array.isArray(current.menu)) {
73
+ result.menu = current.menu.map(clone);
74
+ }
75
+ if (region === "leftbar" && Array.isArray(current.footer?.actions)) {
76
+ result.footer.actions = current.footer.actions.map(clone);
77
+ }
78
+ if (Array.isArray(current.widgets)) result.widgets = current.widgets.map(clone);
79
+ }
80
+ return result;
81
+ }
82
+
83
+ function resolveLeftbar(layers, options = {}) {
84
+ return resolveRegion(layers, "leftbar", options);
85
+ }
86
+
87
+ function cascadeRegions(layers, options = {}) {
88
+ const regions = {};
89
+ for (const region of REGION_IDS) {
90
+ regions[region] = resolveRegion(layers, region, options);
91
+ }
92
+ return freeze(regions);
93
+ }
94
+
95
+ function resolveRegions(options = {}) {
96
+ const layers = Array.isArray(options.layers) ? options.layers : [];
97
+ const resolvedRegions = {};
98
+ const warnings = [];
99
+ for (const region of REGION_IDS) {
100
+ const state = resolveRegion(layers, region, options);
101
+ const widgets = state.enabled ? state.widgets : [];
102
+ const resolved = resolveRegionWidgets(widgets, options.catalog || {}, {
103
+ region,
104
+ profile: options.profile,
105
+ contentOnly: region === "leftbar"
106
+ });
107
+ resolvedRegions[region] = {
108
+ widgets: resolved.instances,
109
+ enabled: state.enabled,
110
+ ...(["topbar", "leftbar"].includes(region) ? {
111
+ brand: clone(state.brand),
112
+ menu: clone(state.menu)
113
+ } : {}),
114
+ ...(region === "leftbar" ? {
115
+ defaultState: options.defaultState || "expanded",
116
+ footer: clone(state.footer)
117
+ } : {})
118
+ };
119
+ warnings.push(...resolved.warnings);
120
+ }
121
+ return freeze({ ...resolvedRegions, warnings });
122
+ }
123
+
124
+ module.exports = {
125
+ REGION_IDS,
126
+ cascadeRegion,
127
+ cascadeRegions,
128
+ mergeBrand,
129
+ resolveLeftbar,
130
+ resolveRegion,
131
+ resolveRegions
132
+ };
@@ -0,0 +1,23 @@
1
+ /* global hexo */
2
+ "use strict";
3
+
4
+ function ensureRuntimeData(ctx) {
5
+ ctx.stellar ||= {};
6
+ ctx.stellar.data ||= {};
7
+ return ctx.stellar.data;
8
+ }
9
+
10
+ function runtimeDataAt(ctx, path) {
11
+ const parts = typeof path === "string" && path.length > 0 ? path.split(".") : [];
12
+ let value = ctx.stellar?.data;
13
+ for (const part of parts) {
14
+ if (value == null || !Object.prototype.hasOwnProperty.call(value, part)) return undefined;
15
+ value = value[part];
16
+ }
17
+ return value;
18
+ }
19
+
20
+ module.exports = {
21
+ ensureRuntimeData,
22
+ runtimeDataAt
23
+ };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ function safeRelativePath(value, source) {
7
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(`${source}: 路径必须是非空相对路径`);
8
+ const unixPath = value.replace(/\\/g, "/");
9
+ const normalized = path.posix.normalize(unixPath);
10
+ const hasDriveRoot = /^[A-Za-z]:/.test(unixPath);
11
+ if (path.isAbsolute(value) || path.posix.isAbsolute(unixPath) || hasDriveRoot || unixPath.split("/").includes("..") || /^\.\/?$/.test(normalized)) {
12
+ throw new TypeError(`${source}: 路径不能逃逸根目录(${value})`);
13
+ }
14
+ return normalized;
15
+ }
16
+
17
+ function resolveInside(root, relative, source) {
18
+ const safe = safeRelativePath(relative, source);
19
+ const resolvedRoot = path.resolve(root);
20
+ const resolved = path.resolve(resolvedRoot, safe);
21
+ if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path.sep}`)) {
22
+ throw new TypeError(`${source}: 路径不能逃逸根目录(${relative})`);
23
+ }
24
+ const physicalRoot = fs.realpathSync(resolvedRoot);
25
+ let ancestor = resolved;
26
+ while (!fs.existsSync(ancestor)) {
27
+ const parent = path.dirname(ancestor);
28
+ if (parent === ancestor) break;
29
+ ancestor = parent;
30
+ }
31
+ const physicalAncestor = fs.realpathSync(ancestor);
32
+ if (physicalAncestor !== physicalRoot && !physicalAncestor.startsWith(`${physicalRoot}${path.sep}`)) {
33
+ throw new TypeError(`${source}: 路径经过根目录外的符号链接(${relative})`);
34
+ }
35
+ if (fs.existsSync(resolved)) {
36
+ const physicalResolved = fs.realpathSync(resolved);
37
+ if (physicalResolved !== physicalRoot && !physicalResolved.startsWith(`${physicalRoot}${path.sep}`)) {
38
+ throw new TypeError(`${source}: 路径经过根目录外的符号链接(${relative})`);
39
+ }
40
+ }
41
+ return { relative: safe, absolute: resolved };
42
+ }
43
+
44
+ module.exports = { resolveInside, safeRelativePath };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ const COLLECTION_PROFILES = new Set(["wiki", "notebook", "topic"]);
4
+
5
+ function collectionDomain(profile, id) {
6
+ if (typeof profile !== "string" || profile.length === 0) return null;
7
+ if (typeof id !== "string" || id.length === 0) return null;
8
+ return `${profile}:${id}`;
9
+ }
10
+
11
+ function indexDomains(page, config) {
12
+ const domains = [];
13
+ if (page?.layout === "post") domains.push("blog");
14
+ const collection = config?.collection;
15
+ const domain = COLLECTION_PROFILES.has(collection?.profile)
16
+ ? collectionDomain(collection.profile, collection.id)
17
+ : null;
18
+ if (domain && !domains.includes(domain)) domains.push(domain);
19
+ return domains;
20
+ }
21
+
22
+ function scopeAllows(indexScope, contentType) {
23
+ return indexScope === "all" || indexScope === contentType;
24
+ }
25
+
26
+ function collectionScope(collection, indexScope) {
27
+ const profile = collection?.profile;
28
+ if (!COLLECTION_PROFILES.has(profile)) return null;
29
+ const domain = collectionDomain(profile, collection?.id);
30
+ if (!domain) return null;
31
+ const contentType = profile === "topic" ? "post" : "page";
32
+ if (!scopeAllows(indexScope, contentType)) return null;
33
+ const label = collection?.identity?.name || collection?.name || collection.id;
34
+ const options = profile === "topic" && scopeAllows(indexScope, "post")
35
+ ? ["blog", domain]
36
+ : [domain];
37
+ return { current: domain, label, options };
38
+ }
39
+
40
+ function resolveSearchScope(input = {}) {
41
+ const indexScope = ["all", "post", "page"].includes(input.indexScope)
42
+ ? input.indexScope
43
+ : "all";
44
+ const collection = input.viewModel?.collection || input.collection || null;
45
+ const scoped = collectionScope(collection, indexScope);
46
+ if (scoped) return scoped;
47
+ if (collection?.profile === "post" || input.blogAggregate === true) {
48
+ if (!scopeAllows(indexScope, "post")) return null;
49
+ return { current: "blog", label: null, options: ["blog"] };
50
+ }
51
+ return null;
52
+ }
53
+
54
+ module.exports = {
55
+ collectionDomain,
56
+ indexDomains,
57
+ resolveSearchScope,
58
+ scopeAllows
59
+ };
@@ -23,25 +23,25 @@ function firstContentImage(content) {
23
23
  }
24
24
 
25
25
  /**
26
- * 构建文章图片数组(顺序:cover → banner → photos → 正文首图 → 默认封面)。
26
+ * 构建文章图片数组(顺序:cover → banner.image → photos → 正文首图 → 默认封面)。
27
27
  * @param {Object} opts
28
- * @param {string} opts.cover
29
- * @param {string} opts.banner
28
+ * @param {string} opts.cardCover
29
+ * @param {string} opts.bannerImage
30
30
  * @param {Array} opts.photos
31
31
  * @param {string} opts.content
32
- * @param {string} opts.defaultCover 主题默认封面(theme.default.cover)
32
+ * @param {string} opts.defaultCover 主题默认封面(fallbacks.cover)
33
33
  * @returns {Array<string>}
34
34
  */
35
35
  function postImages(opts) {
36
- const cover = opts.cover || '';
37
- const banner = opts.banner || '';
36
+ const cardCover = opts.cardCover || '';
37
+ const bannerImage = opts.bannerImage || '';
38
38
  const photos = Array.isArray(opts.photos) ? opts.photos.slice() : [];
39
39
  const defaultCover = opts.defaultCover || '';
40
40
  const images = photos;
41
- if (cover) {
42
- images.unshift(cover);
43
- } else if (banner) {
44
- images.unshift(banner);
41
+ if (cardCover) {
42
+ images.unshift(cardCover);
43
+ } else if (bannerImage) {
44
+ images.unshift(bannerImage);
45
45
  }
46
46
  if (images.length === 0) {
47
47
  const first = firstContentImage(opts.content);
@@ -0,0 +1,12 @@
1
+ /* global hexo */
2
+
3
+ "use strict";
4
+
5
+ function resolveServiceProvider(service) {
6
+ const provider = service?.provider;
7
+ if (provider == null) return null;
8
+ if (typeof service !== "object" || Array.isArray(service)) return null;
9
+ return Object.prototype.hasOwnProperty.call(service, provider) ? service[provider] : null;
10
+ }
11
+
12
+ module.exports = { resolveServiceProvider };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ const TOKEN_PATTERN = /\{(?:hexo|theme)\.[a-z]+\}/g;
4
+
5
+ function replaceSettingsTokens(value, tokens) {
6
+ const source = typeof value === "string" ? value : "";
7
+ return source.replace(TOKEN_PATTERN, token => Object.prototype.hasOwnProperty.call(tokens, token) ? String(tokens[token]) : token);
8
+ }
9
+
10
+ function routeIdentity(value) {
11
+ let route = String(value || "").trim().replace(/^\/+|\/+$/g, "");
12
+ route = route.replace(/\/index\.html$/i, "");
13
+ return route || "<root>";
14
+ }
15
+
16
+ module.exports = { replaceSettingsTokens, routeIdentity };