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,5 @@
1
+ export async function mount(root, context) {
2
+ await context.assets.script(context.extension.config.assets.js);
3
+ window.stellar?.cardHover?.mountAll?.(root);
4
+ return () => window.stellar?.cardHover?.unmountAll?.(root);
5
+ }
@@ -0,0 +1,101 @@
1
+ const COLOR_SCHEMES = new Set(['light', 'dark', 'auto']);
2
+ const STORAGE_KEY = 'Stellar.colorScheme';
3
+
4
+ function selectedColorScheme(documentRef) {
5
+ return documentRef.documentElement.getAttribute('data-theme') || 'auto';
6
+ }
7
+
8
+ export function resolvedColorScheme(documentRef, mediaQuery) {
9
+ const selected = selectedColorScheme(documentRef);
10
+ if (selected === 'light' || selected === 'dark') return selected;
11
+ return mediaQuery.matches ? 'dark' : 'light';
12
+ }
13
+
14
+ function readStoredColorScheme(storage) {
15
+ try {
16
+ const value = storage?.getItem(STORAGE_KEY);
17
+ return COLOR_SCHEMES.has(value) ? value : null;
18
+ } catch (error) {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function persistColorScheme(storage, value) {
24
+ try {
25
+ storage?.setItem(STORAGE_KEY, value);
26
+ } catch (error) {
27
+ void error;
28
+ }
29
+ }
30
+
31
+ export function mount(root, context) {
32
+ if (root?.nodeType !== 9 || !root.defaultView) {
33
+ throw new TypeError('[stellar color-scheme-switch] document root is required');
34
+ }
35
+ const documentRef = root;
36
+ const windowRef = root.defaultView;
37
+ const mediaQuery = windowRef.matchMedia('(prefers-color-scheme: dark)');
38
+ let storage = null;
39
+ try {
40
+ storage = windowRef.localStorage;
41
+ } catch (error) {
42
+ void error;
43
+ }
44
+ const messages = context.extension.config.messages || {};
45
+ const previousSetter = windowRef.setColorScheme;
46
+
47
+ function dispatch(mode) {
48
+ documentRef.dispatchEvent(new windowRef.CustomEvent('stellar:color-scheme-change', {
49
+ detail: Object.freeze({
50
+ mode,
51
+ resolvedMode: resolvedColorScheme(documentRef, mediaQuery)
52
+ })
53
+ }));
54
+ }
55
+
56
+ function apply(mode, options = {}) {
57
+ if (!COLOR_SCHEMES.has(mode)) {
58
+ throw new TypeError(`[stellar color-scheme-switch] unsupported mode ${String(mode)}`);
59
+ }
60
+ if (mode === 'auto') {
61
+ documentRef.documentElement.removeAttribute('data-theme');
62
+ } else {
63
+ documentRef.documentElement.setAttribute('data-theme', mode);
64
+ }
65
+ if (options.persist !== false) persistColorScheme(storage, mode);
66
+ dispatch(mode);
67
+ if (options.notify !== false) context.legacy.stellar?.toast?.(messages[mode]);
68
+ return mode;
69
+ }
70
+
71
+ const setter = mode => apply(mode);
72
+ windowRef.setColorScheme = setter;
73
+
74
+ const stored = readStoredColorScheme(storage);
75
+ if (stored) {
76
+ apply(stored, { persist: false, notify: false });
77
+ } else {
78
+ dispatch(selectedColorScheme(documentRef));
79
+ }
80
+
81
+ const handleSystemChange = () => {
82
+ if (selectedColorScheme(documentRef) === 'auto') dispatch('auto');
83
+ };
84
+ if (typeof mediaQuery.addEventListener === 'function') {
85
+ mediaQuery.addEventListener('change', handleSystemChange);
86
+ } else if (typeof mediaQuery.addListener === 'function') {
87
+ mediaQuery.addListener(handleSystemChange);
88
+ }
89
+
90
+ return () => {
91
+ if (typeof mediaQuery.removeEventListener === 'function') {
92
+ mediaQuery.removeEventListener('change', handleSystemChange);
93
+ } else if (typeof mediaQuery.removeListener === 'function') {
94
+ mediaQuery.removeListener(handleSystemChange);
95
+ }
96
+ if (windowRef.setColorScheme === setter) {
97
+ if (previousSetter === undefined) delete windowRef.setColorScheme;
98
+ else windowRef.setColorScheme = previousSetter;
99
+ }
100
+ };
101
+ }
@@ -0,0 +1,200 @@
1
+ function viewportLoad(element, callback, enabled = true, onError = () => {}) {
2
+ let active = true;
3
+ const run = () => Promise.resolve()
4
+ .then(() => callback(() => active))
5
+ .catch(error => {
6
+ if (active) onError(error);
7
+ });
8
+ if (!enabled || !('IntersectionObserver' in window)) {
9
+ run();
10
+ return () => { active = false; };
11
+ }
12
+ const observer = new IntersectionObserver(entries => {
13
+ if (entries.some(entry => entry.isIntersecting)) {
14
+ observer.disconnect();
15
+ run();
16
+ }
17
+ });
18
+ observer.observe(element);
19
+ return () => {
20
+ active = false;
21
+ observer.disconnect();
22
+ };
23
+ }
24
+
25
+ function copyAttributes(source, target) {
26
+ Array.from(source.attributes).forEach(attribute => {
27
+ if (!['class', 'id'].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);
28
+ });
29
+ }
30
+
31
+ function commentPath(element) {
32
+ return element.getAttribute('comment_id') ?? decodeURI(window.location.pathname);
33
+ }
34
+
35
+ function uploadImage(options, tokenHeader, tokenValue, fieldName, responseField) {
36
+ if (!options?.api) return null;
37
+ return file => {
38
+ const headers = new Headers({ Accept: 'application/json' });
39
+ if (tokenValue) headers.set(tokenHeader || 'Authorization', tokenValue);
40
+ const body = new FormData();
41
+ body.append(fieldName || 'file', file);
42
+ return fetch(options.api, { method: 'POST', body, headers })
43
+ .then(response => response.json())
44
+ .then(response => response[responseField]);
45
+ };
46
+ }
47
+
48
+ async function mountArtalk(root, context, config) {
49
+ const element = root.querySelector('#artalk_container');
50
+ if (!element) return;
51
+ const targeted = /[?&]atk_comment=\d+/.test(window.location.search) || /#atk-comment-\d+/.test(window.location.hash);
52
+ let instance = null;
53
+ let historyTimer = null;
54
+ const cleanupViewport = viewportLoad(element, async isActive => {
55
+ await Promise.all([
56
+ context.assets.style(config.assets.localCss),
57
+ context.assets.style(config.assets.css),
58
+ context.assets.script(config.assets.js)
59
+ ]);
60
+ if (!isActive()) return;
61
+ const match = window.location.search.match(/[?&]atk_comment=(\d+)/);
62
+ if (match && !/#atk-comment-\d+/.test(window.location.hash)) {
63
+ const params = new URLSearchParams(window.location.search);
64
+ const notifyKey = params.get('atk_notify_key');
65
+ const query = notifyKey ? `?atk_notify_key=${encodeURIComponent(notifyKey)}` : '';
66
+ history.replaceState(null, '', `${window.location.pathname}${query}#atk-comment-${match[1]}`);
67
+ }
68
+ const options = Object.assign({}, config.options, {
69
+ el: '#artalk_container',
70
+ pageKey: commentPath(element),
71
+ pageTitle: config.pageTitle
72
+ });
73
+ const uploader = uploadImage(config.options.imageUploader, 'Authorization', config.options.imageUploader?.token, 'file', config.options.imageUploader?.resp);
74
+ if (uploader) options.imgUploader = uploader;
75
+ instance = window.Artalk.init(options);
76
+ if (targeted && window.location.search.includes('atk_')) {
77
+ instance.on('list-loaded', () => {
78
+ if (historyTimer !== null) clearTimeout(historyTimer);
79
+ historyTimer = setTimeout(() => {
80
+ historyTimer = null;
81
+ history.replaceState(null, '', window.location.pathname + window.location.hash);
82
+ }, 0);
83
+ });
84
+ }
85
+ }, !targeted, error => context.reportError(error));
86
+ return () => {
87
+ cleanupViewport();
88
+ if (historyTimer !== null) clearTimeout(historyTimer);
89
+ historyTimer = null;
90
+ instance?.destroy?.();
91
+ };
92
+ }
93
+
94
+ function mountEmbed(root, provider, src, context) {
95
+ const element = root.querySelector(`#comments #${provider}`);
96
+ if (!element) return;
97
+ let script = null;
98
+ let cancelLoad = null;
99
+ const cleanupViewport = viewportLoad(element, isActive => new Promise((resolve, reject) => {
100
+ element.replaceChildren();
101
+ script = element.ownerDocument.createElement('script');
102
+ script.src = src;
103
+ script.async = true;
104
+ copyAttributes(element, script);
105
+ let settled = false;
106
+ const finish = callback => {
107
+ if (settled) return;
108
+ settled = true;
109
+ script?.removeEventListener('load', onLoad);
110
+ script?.removeEventListener('error', onError);
111
+ cancelLoad = null;
112
+ callback();
113
+ };
114
+ const onLoad = () => finish(resolve);
115
+ const onError = () => finish(() => reject(new Error(`${provider} comment embed failed to load`)));
116
+ cancelLoad = () => finish(resolve);
117
+ script.addEventListener('load', onLoad, { once: true });
118
+ script.addEventListener('error', onError, { once: true });
119
+ if (!isActive()) {
120
+ cancelLoad();
121
+ return;
122
+ }
123
+ element.appendChild(script);
124
+ }), true, error => context.reportError(error));
125
+ return () => {
126
+ cleanupViewport();
127
+ cancelLoad?.();
128
+ script?.remove();
129
+ };
130
+ }
131
+
132
+ function mountTwikoo(root, context, config) {
133
+ const element = root.querySelector('#twikoo_container');
134
+ if (!element) return;
135
+ let instance = null;
136
+ const cleanupViewport = viewportLoad(element, async isActive => {
137
+ await context.assets.style(config.assets.localCss);
138
+ await context.assets.script(config.assets.js);
139
+ if (!isActive()) return;
140
+ const created = await window.twikoo.init(Object.assign({}, config.options, {
141
+ el: '#twikoo_container',
142
+ path: commentPath(element)
143
+ }));
144
+ if (!isActive()) created?.destroy?.();
145
+ else instance = created;
146
+ }, true, error => context.reportError(error));
147
+ return () => {
148
+ cleanupViewport();
149
+ instance?.destroy?.();
150
+ };
151
+ }
152
+
153
+ function mountWaline(root, context, config) {
154
+ const element = root.querySelector('#waline_container');
155
+ if (!element) return;
156
+ let instance = null;
157
+ const cleanupViewport = viewportLoad(element, async isActive => {
158
+ await Promise.all([
159
+ context.assets.style(config.assets.localCss),
160
+ context.assets.style(config.assets.css),
161
+ context.assets.style(config.assets.metaCss)
162
+ ]);
163
+ const module = await import(config.assets.js);
164
+ if (!isActive()) return;
165
+ const options = Object.assign({}, config.options, {
166
+ el: '#waline_container',
167
+ path: commentPath(element)
168
+ });
169
+ const uploader = uploadImage(
170
+ config.options.imageUploader,
171
+ config.options.imageUploader?.tokenName,
172
+ config.options.imageUploader?.token,
173
+ config.options.imageUploader?.fileName,
174
+ config.options.imageUploader?.resp
175
+ );
176
+ if (uploader) options.imageUploader = uploader;
177
+ instance = module.init(options);
178
+ }, true, error => context.reportError(error));
179
+ return () => {
180
+ cleanupViewport();
181
+ instance?.destroy?.();
182
+ };
183
+ }
184
+
185
+ export async function mount(root, context) {
186
+ const config = context.extension.config;
187
+ switch (config.provider) {
188
+ case 'artalk': return mountArtalk(root, context, config);
189
+ case 'beaudar':
190
+ await context.assets.style(config.assets.localCss);
191
+ return mountEmbed(root, 'beaudar', config.assets.js, context);
192
+ case 'giscus': return mountEmbed(root, 'giscus', config.assets.js, context);
193
+ case 'utterances':
194
+ await context.assets.style(config.assets.localCss);
195
+ return mountEmbed(root, 'utterances', config.assets.js, context);
196
+ case 'twikoo': return mountTwikoo(root, context, config);
197
+ case 'waline': return mountWaline(root, context, config);
198
+ default: throw new TypeError(`unknown comment provider ${config.provider}`);
199
+ }
200
+ }
@@ -0,0 +1,53 @@
1
+ /** 非首屏图标异步加载。 */
2
+
3
+ const RUNTIME_QUERY = new URL(import.meta.url).search;
4
+
5
+ function queryNodes(root) {
6
+ const nodes = [];
7
+ if (typeof root?.matches === 'function' && root.matches('svg.icon[data-icon]')) nodes.push(root);
8
+ if (typeof root?.querySelectorAll === 'function') nodes.push(...root.querySelectorAll('svg.icon[data-icon]'));
9
+ return nodes;
10
+ }
11
+
12
+ export function mount(root, context) {
13
+ const groups = {};
14
+ const controller = new AbortController();
15
+ let active = true;
16
+ queryNodes(root).forEach(node => {
17
+ const key = node.getAttribute('data-icon');
18
+ const index = key.indexOf(':');
19
+ if (index < 1) return;
20
+ const namespace = key.slice(0, index);
21
+ if (!groups[namespace]) groups[namespace] = [];
22
+ groups[namespace].push(node);
23
+ });
24
+
25
+ Object.keys(groups).forEach(namespace => {
26
+ const path = `/js/icons/${namespace}.json`;
27
+ const url = `${context.assets.resolve(path)}${RUNTIME_QUERY}`;
28
+ fetch(url, { signal: controller.signal })
29
+ .then(response => {
30
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
31
+ return response.json();
32
+ })
33
+ .then(data => {
34
+ if (!active) return;
35
+ const icons = data?.[namespace];
36
+ groups[namespace].forEach(node => {
37
+ if (!active || !node.isConnected) return;
38
+ const key = node.getAttribute('data-icon');
39
+ const svg = icons?.[key];
40
+ if (typeof svg === 'string' && svg.length > 0) node.outerHTML = svg;
41
+ else console.warn(`[stellar] icon not found: ${key}`);
42
+ });
43
+ })
44
+ .catch(error => {
45
+ if (error?.name !== 'AbortError') console.warn(`[stellar] icons load failed: ${namespace}`, error);
46
+ });
47
+ });
48
+
49
+ return () => {
50
+ active = false;
51
+ controller.abort();
52
+ };
53
+ }
@@ -1,5 +1,4 @@
1
1
  // 通用 dropdown 浮层:把打开的菜单挂到 body 下,避免被任意祖先容器裁剪。
2
- (() => {
3
2
  const selector = 'details.dropdown'
4
3
  const gap = 8
5
4
  const viewportPadding = 8
@@ -7,6 +6,7 @@
7
6
  let active = null
8
7
  let frame = null
9
8
  let menuId = 0
9
+ let mounts = 0
10
10
 
11
11
  function getLayer() {
12
12
  if (layer && document.documentElement.contains(layer)) {
@@ -303,12 +303,18 @@
303
303
  })
304
304
  }
305
305
 
306
- function bind(dropdown) {
306
+ function listen(state, target, type, handler, options) {
307
+ target.addEventListener(type, handler, options)
308
+ state.cleanups.push(() => target.removeEventListener(type, handler, options))
309
+ }
310
+
311
+ function bind(dropdown, state) {
307
312
  if (dropdown.__stellarDropdownBound) {
308
313
  return
309
314
  }
310
315
  dropdown.__stellarDropdownBound = true
311
- dropdown.addEventListener('mouseenter', () => {
316
+ state.bound.add(dropdown)
317
+ listen(state, dropdown, 'mouseenter', () => {
312
318
  dropdown.__stellarDropdownHovering = true
313
319
  if (active && active.dropdown === dropdown) {
314
320
  active.hoverMode = true
@@ -319,13 +325,13 @@
319
325
  })
320
326
  const trigger = getDirectChild(dropdown, '.dropdown-trigger')
321
327
  if (trigger) {
322
- trigger.addEventListener('click', event => {
328
+ listen(state, trigger, 'click', event => {
323
329
  event.preventDefault()
324
330
  if (!active || active.dropdown !== dropdown) {
325
331
  openDropdown(dropdown)
326
332
  }
327
333
  })
328
- trigger.addEventListener('keydown', event => {
334
+ listen(state, trigger, 'keydown', event => {
329
335
  if (event.key === 'Enter' || event.key === ' ') {
330
336
  event.preventDefault()
331
337
  if (active && active.dropdown === dropdown) {
@@ -336,10 +342,18 @@
336
342
  }
337
343
  })
338
344
  }
339
- dropdown.addEventListener('mouseleave', () => {
345
+ const menu = getDirectChild(dropdown, '.dropdown-menu')
346
+ if (menu) {
347
+ listen(state, menu, 'click', event => {
348
+ if (event.target.closest('.dropdown-item') && active?.dropdown === dropdown) {
349
+ closeState(active, false)
350
+ }
351
+ })
352
+ }
353
+ listen(state, dropdown, 'mouseleave', () => {
340
354
  dropdown.__stellarDropdownHovering = false
341
355
  })
342
- dropdown.addEventListener('toggle', () => {
356
+ listen(state, dropdown, 'toggle', () => {
343
357
  if (dropdown.open) {
344
358
  if (!active || active.dropdown !== dropdown) {
345
359
  hideMenuBeforeOpen(dropdown)
@@ -357,24 +371,27 @@
357
371
  dropdown.__stellarDropdownHovering = false
358
372
  }
359
373
 
360
- function bindTree(root) {
374
+ function bindTree(root, state) {
361
375
  if (root.nodeType !== 1) {
362
376
  return
363
377
  }
364
378
  if (root.matches(selector)) {
365
- bind(root)
379
+ bind(root, state)
366
380
  }
367
- root.querySelectorAll(selector).forEach(bind)
381
+ root.querySelectorAll(selector).forEach(dropdown => bind(dropdown, state))
368
382
  }
369
383
 
370
- function init() {
371
- if (!document.body || !window.MutationObserver) {
372
- return
373
- }
374
- bindTree(document.body)
384
+ export function mount(root) {
385
+ const scope = root && root.nodeType === 9 ? root.body : root
386
+ if (!scope || scope.nodeType !== 1 || !window.MutationObserver) {
387
+ return () => {}
388
+ }
389
+ const state = { bound: new Set(), cleanups: [], observer: null, cleaned: false }
390
+ mounts += 1
391
+ bindTree(scope, state)
375
392
  const observer = new MutationObserver(records => {
376
393
  records.forEach(record => {
377
- record.addedNodes.forEach(bindTree)
394
+ record.addedNodes.forEach(node => bindTree(node, state))
378
395
  record.removedNodes.forEach(node => {
379
396
  if (active && node.nodeType === 1 && (node === active.dropdown || node.contains(active.dropdown))) {
380
397
  closeState(active, false)
@@ -382,9 +399,10 @@
382
399
  })
383
400
  })
384
401
  })
385
- observer.observe(document.body, { childList: true, subtree: true })
402
+ observer.observe(scope, { childList: true, subtree: true })
403
+ state.observer = observer
386
404
 
387
- document.addEventListener('click', event => {
405
+ listen(state, document, 'click', event => {
388
406
  if (!active) {
389
407
  return
390
408
  }
@@ -394,14 +412,14 @@
394
412
  closeState(active, false)
395
413
  }, true)
396
414
 
397
- document.addEventListener('mousemove', event => {
415
+ listen(state, document, 'mousemove', event => {
398
416
  if (!active || !active.hoverMode || isPointerInsideState(active, event)) {
399
417
  return
400
418
  }
401
419
  closeState(active, false)
402
420
  }, true)
403
421
 
404
- document.addEventListener('keydown', event => {
422
+ listen(state, document, 'keydown', event => {
405
423
  if (!active) {
406
424
  return
407
425
  }
@@ -419,17 +437,38 @@
419
437
  }
420
438
  })
421
439
 
422
- window.addEventListener('resize', schedulePosition)
423
- window.addEventListener('scroll', schedulePosition, true)
440
+ listen(state, window, 'resize', schedulePosition)
441
+ listen(state, window, 'scroll', schedulePosition, true)
424
442
  if (window.visualViewport) {
425
- window.visualViewport.addEventListener('resize', schedulePosition)
426
- window.visualViewport.addEventListener('scroll', schedulePosition)
443
+ listen(state, window.visualViewport, 'resize', schedulePosition)
444
+ listen(state, window.visualViewport, 'scroll', schedulePosition)
427
445
  }
428
- }
429
446
 
430
- if (document.readyState === 'loading') {
431
- document.addEventListener('DOMContentLoaded', init, { once: true })
432
- } else {
433
- init()
447
+ return function cleanup() {
448
+ if (state.cleaned) {
449
+ return
450
+ }
451
+ state.cleaned = true
452
+ state.observer.disconnect()
453
+ if (active && state.bound.has(active.dropdown)) {
454
+ closeState(active, false)
455
+ }
456
+ if (frame !== null) {
457
+ window.cancelAnimationFrame(frame)
458
+ frame = null
459
+ }
460
+ for (let index = state.cleanups.length - 1; index >= 0; index -= 1) {
461
+ state.cleanups[index]()
462
+ }
463
+ state.bound.forEach(dropdown => {
464
+ delete dropdown.__stellarDropdownBound
465
+ delete dropdown.__stellarDropdownHovering
466
+ })
467
+ state.bound.clear()
468
+ mounts = Math.max(0, mounts - 1)
469
+ if (mounts === 0 && layer) {
470
+ layer.remove()
471
+ layer = null
472
+ }
473
+ }
434
474
  }
435
- })()