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,200 @@
1
+ function queryAll(root, selector) {
2
+ return Array.from(root.querySelectorAll(selector));
3
+ }
4
+
5
+ async function mountLazyLoading(root, context, config) {
6
+ if (root.nodeType !== 9) {
7
+ throw new TypeError('[stellar runtime] lazy-loading compatibility adapter requires a document root');
8
+ }
9
+ const ownerDocument = root.ownerDocument || root;
10
+ let instance = null;
11
+ const wrapLazyloadImages = container => {
12
+ const target = typeof container === 'string' ? root.querySelector(container) : container;
13
+ if (!target) return;
14
+ queryAll(target, 'img').forEach(image => {
15
+ if (image.classList.contains('lazy')) return;
16
+ const src = image.getAttribute('src');
17
+ if (!src) return;
18
+ const wrapper = ownerDocument.createElement('div');
19
+ wrapper.className = 'lazy-box';
20
+ const lazyImage = image.cloneNode();
21
+ lazyImage.removeAttribute('src');
22
+ lazyImage.setAttribute('data-src', src);
23
+ lazyImage.classList.add('lazy');
24
+ const icon = ownerDocument.createElement('div');
25
+ icon.className = 'lazy-icon';
26
+ wrapper.append(lazyImage, icon);
27
+ image.replaceWith(wrapper);
28
+ });
29
+ instance?.update?.();
30
+ };
31
+ window.wrapLazyloadImages = wrapLazyloadImages;
32
+ const onInitialized = event => {
33
+ instance = event.detail.instance;
34
+ window.lazyLoadInstance = instance;
35
+ };
36
+ const lazyLoadOptions = {
37
+ elements_selector: '.lazy',
38
+ callback_loaded(element) {
39
+ element.classList.add('loaded');
40
+ const wrapper = element.closest('.lazy-box') || element.parentElement;
41
+ wrapper?.querySelector('.lazy-icon')?.remove();
42
+ }
43
+ };
44
+ window.lazyLoadOptions = lazyLoadOptions;
45
+ window.addEventListener('LazyLoad::Initialized', onInitialized);
46
+ const observer = new MutationObserver(mutations => {
47
+ const found = mutations.some(mutation => Array.from(mutation.addedNodes).some(node =>
48
+ node.nodeType === 1 && (node.matches?.('.lazy') || node.querySelector?.('.lazy'))
49
+ ));
50
+ if (found) instance?.update?.();
51
+ });
52
+ observer.observe(root.documentElement || root, { childList: true, subtree: true });
53
+ const cleanup = () => {
54
+ observer.disconnect();
55
+ window.removeEventListener('LazyLoad::Initialized', onInitialized);
56
+ if (window.wrapLazyloadImages === wrapLazyloadImages) delete window.wrapLazyloadImages;
57
+ if (window.lazyLoadOptions === lazyLoadOptions) delete window.lazyLoadOptions;
58
+ instance?.destroy?.();
59
+ if (window.lazyLoadInstance === instance) delete window.lazyLoadInstance;
60
+ instance = null;
61
+ };
62
+ try {
63
+ await context.assets.script(config.asset);
64
+ instance?.update?.();
65
+ return cleanup;
66
+ } catch (error) {
67
+ cleanup();
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ async function mountLightbox(root, context, config) {
73
+ let selector = '[data-fancybox]:not(.error), .with-fancybox .atk-content img:not([atk-emoticon]):not([class*="emo"]), .with-fancybox .tk-content img:not([atk-emoticon]):not([class*="emo"]), .with-fancybox .wl-content img:not([atk-emoticon]):not([class*="emo"])';
74
+ if (config.selector) selector += `, ${config.selector}`;
75
+ await Promise.all([
76
+ context.assets.style(config.assets.localCss),
77
+ context.assets.style(config.assets.css),
78
+ context.assets.script(config.assets.js)
79
+ ]);
80
+ const options = {
81
+ hideScrollbar: false,
82
+ Thumbs: { autoStart: false },
83
+ caption: (_instance, slide) => slide.triggerEl.alt || slide.triggerEl.dataset.caption || null
84
+ };
85
+ const isDocumentRoot = root.nodeType === 9;
86
+ if (isDocumentRoot) {
87
+ window.Fancybox.bind(selector, options);
88
+ } else {
89
+ window.Fancybox.bind(root, selector, options);
90
+ }
91
+ return () => {
92
+ if (isDocumentRoot) window.Fancybox?.unbind?.(selector);
93
+ else window.Fancybox?.unbind?.(root, selector);
94
+ };
95
+ }
96
+
97
+ async function mountMathJax(root, context, config) {
98
+ window.MathJax = {
99
+ tex: {
100
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
101
+ processEscapes: true,
102
+ skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
103
+ },
104
+ startup: {
105
+ ready() {
106
+ window.MathJax.startup.defaultReady();
107
+ window.MathJax.typesetPromise().then(() => {
108
+ queryAll(root, 'mjx-container').forEach(element => element.parentNode?.classList.add('has-jax'));
109
+ });
110
+ }
111
+ }
112
+ };
113
+ await context.assets.script(config.asset);
114
+ return () => {};
115
+ }
116
+
117
+ async function mountDiagrams(root, context, config) {
118
+ await context.assets.script(config.assets.js);
119
+ const theme = config.colorScheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches
120
+ ? 'dark'
121
+ : config.theme;
122
+ window.mermaid.initialize({
123
+ startOnLoad: false,
124
+ theme,
125
+ logLevel: 3,
126
+ flowchart: { useMaxWidth: false, htmlLabels: true, curve: 'linear' },
127
+ gantt: { axisFormat: '%Y/%m/%d' },
128
+ sequence: { actorMargin: 50 }
129
+ });
130
+ await window.mermaid.run({ nodes: queryAll(root, '.mermaid') });
131
+ return () => {};
132
+ }
133
+
134
+ async function mountCodeCopy(root, context, config) {
135
+ const messages = config.messages || {};
136
+ const policy = context.manifest.policy.features;
137
+ context.legacy.ctx.copycode = {
138
+ label: messages.label || '',
139
+ copied: messages.copied || '',
140
+ denied: messages.denied || '',
141
+ unsupported: messages.unsupported || '',
142
+ toast_ms: policy.codeCopyToastMs
143
+ };
144
+ await context.assets.script(config.assets.js);
145
+ const elements = queryAll(root, '.code');
146
+ window.createCopyButtons?.(elements);
147
+ return () => elements.forEach(element => element.querySelector('.copy-btn')?.remove());
148
+ }
149
+
150
+ async function mountAdaptiveText(root, context, config) {
151
+ await context.assets.script(config.assets.colorJs);
152
+ await context.assets.script(config.assets.js);
153
+ return window.stellarAdaptiveText?.mount?.(queryAll(root, '[data-text-adaptive]')) || (() => {});
154
+ }
155
+
156
+ async function mountHeti(root, context, config) {
157
+ await context.assets.style(config.assets.css);
158
+ await context.assets.script(config.assets.js);
159
+ const heti = new window.Heti('.heti');
160
+ queryAll(root, heti.rootSelector).forEach(element => heti.spacingElement(element));
161
+ return () => {};
162
+ }
163
+
164
+ async function mountSwiper(root, context, config) {
165
+ await Promise.all([
166
+ context.assets.style(config.assets.localCss),
167
+ context.assets.style(config.assets.css),
168
+ context.assets.script(config.assets.js)
169
+ ]);
170
+ const element = root.querySelector('#swiper-api');
171
+ const instance = new window.Swiper(element, {
172
+ slidesPerView: 'auto',
173
+ spaceBetween: 8,
174
+ centeredSlides: true,
175
+ effect: element?.getAttribute('effect') || '',
176
+ rewind: true,
177
+ pagination: { el: '.swiper-pagination', clickable: true },
178
+ navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }
179
+ });
180
+ return () => instance.destroy?.(true, true);
181
+ }
182
+
183
+ export async function mount(root, context) {
184
+ const config = context.extension.config;
185
+ switch (config.feature) {
186
+ case 'lazy-loading': return mountLazyLoading(root, context, config);
187
+ case 'link-prefetch':
188
+ window.FPConfig = { delay: 0, ignoreKeywords: [], maxRPS: 5, hoverDelay: 25 };
189
+ await context.assets.script(config.asset);
190
+ return () => {};
191
+ case 'lightbox': return mountLightbox(root, context, config);
192
+ case 'mathjax': return mountMathJax(root, context, config);
193
+ case 'diagrams': return mountDiagrams(root, context, config);
194
+ case 'code-copy': return mountCodeCopy(root, context, config);
195
+ case 'adaptive-text': return mountAdaptiveText(root, context, config);
196
+ case 'heti': return mountHeti(root, context, config);
197
+ case 'swiper': return mountSwiper(root, context, config);
198
+ default: throw new TypeError(`unknown built-in feature ${config.feature}`);
199
+ }
200
+ }
@@ -0,0 +1,179 @@
1
+ const EFFECT_SELECTOR = 'canvas[data-hero-effect]';
2
+ const MODULE_QUERY = new URL(import.meta.url).search;
3
+
4
+ function objectValue(value) {
5
+ return value != null && typeof value === 'object' && !Array.isArray(value) ? value : {};
6
+ }
7
+
8
+ function readObject(canvas, attribute) {
9
+ const raw = canvas.getAttribute(attribute);
10
+ if (!raw) return {};
11
+ try {
12
+ return objectValue(JSON.parse(raw));
13
+ } catch (error) {
14
+ void error;
15
+ return {};
16
+ }
17
+ }
18
+
19
+ function runtimePolicy(canvas) {
20
+ const runtime = readObject(canvas, 'data-effect-runtime');
21
+ return Object.freeze({
22
+ pauseWhenHidden: runtime.pauseWhenHidden !== false,
23
+ respectReducedMotion: runtime.respectReducedMotion !== false
24
+ });
25
+ }
26
+
27
+ function effectCanvases(root) {
28
+ const canvases = [];
29
+ if (root?.matches?.(EFFECT_SELECTOR)) canvases.push(root);
30
+ root?.querySelectorAll?.(EFFECT_SELECTOR).forEach(canvas => canvases.push(canvas));
31
+ return canvases;
32
+ }
33
+
34
+ function versionedModule(context, module) {
35
+ const resolved = context.assets.resolve(module);
36
+ if (!resolved) throw new TypeError('[stellar hero effect] renderer module is required');
37
+ if (!MODULE_QUERY) return resolved;
38
+ return `${resolved}${resolved.includes('?') ? '&' : '?'}${MODULE_QUERY.slice(1)}`;
39
+ }
40
+
41
+ function reducedMotion(windowRef) {
42
+ return windowRef.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;
43
+ }
44
+
45
+ function createLifecycle(canvas, renderer, runtime) {
46
+ const background = canvas.parentElement;
47
+ const interactionTarget = canvas.closest('.wiki-hero') || background;
48
+ const documentRef = canvas.ownerDocument;
49
+ const windowRef = documentRef?.defaultView || window;
50
+ if (!background || !interactionTarget || !documentRef) return null;
51
+
52
+ const pointer = { x: 0.5, y: 0.5, active: false };
53
+ let animationFrame = null;
54
+ let inViewport = true;
55
+ let destroyed = false;
56
+
57
+ function shouldRender() {
58
+ return !destroyed && (!runtime.pauseWhenHidden || (inViewport && !documentRef.hidden));
59
+ }
60
+
61
+ function frame(time) {
62
+ animationFrame = null;
63
+ if (!shouldRender()) return;
64
+ renderer.render(time, pointer);
65
+ animationFrame = windowRef.requestAnimationFrame(frame);
66
+ }
67
+
68
+ function start() {
69
+ if (shouldRender() && animationFrame === null) {
70
+ animationFrame = windowRef.requestAnimationFrame(frame);
71
+ }
72
+ }
73
+
74
+ function stop() {
75
+ if (animationFrame === null) return;
76
+ windowRef.cancelAnimationFrame(animationFrame);
77
+ animationFrame = null;
78
+ }
79
+
80
+ function resize() {
81
+ renderer.resize(background.getBoundingClientRect(), windowRef.devicePixelRatio || 1);
82
+ }
83
+
84
+ function onMouseMove(event) {
85
+ const rect = background.getBoundingClientRect();
86
+ if (rect.width <= 0 || rect.height <= 0) return;
87
+ pointer.x = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
88
+ pointer.y = Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height));
89
+ pointer.active = true;
90
+ }
91
+
92
+ function onMouseLeave() {
93
+ pointer.active = false;
94
+ }
95
+
96
+ function onVisibilityChange() {
97
+ if (documentRef.hidden) stop();
98
+ else start();
99
+ }
100
+
101
+ if (renderer.pointer === true) {
102
+ interactionTarget.addEventListener('mousemove', onMouseMove);
103
+ interactionTarget.addEventListener('mouseleave', onMouseLeave);
104
+ }
105
+ if (runtime.pauseWhenHidden) documentRef.addEventListener('visibilitychange', onVisibilityChange);
106
+
107
+ let resizeObserver = null;
108
+ if (windowRef.ResizeObserver) {
109
+ resizeObserver = new windowRef.ResizeObserver(resize);
110
+ resizeObserver.observe(background);
111
+ } else {
112
+ windowRef.addEventListener('resize', resize);
113
+ }
114
+
115
+ let viewportObserver = null;
116
+ if (runtime.pauseWhenHidden && windowRef.IntersectionObserver) {
117
+ viewportObserver = new windowRef.IntersectionObserver(entries => {
118
+ inViewport = entries[0]?.isIntersecting === true;
119
+ if (inViewport) start();
120
+ else stop();
121
+ }, { threshold: 0.01 });
122
+ viewportObserver.observe(background);
123
+ }
124
+
125
+ resize();
126
+ start();
127
+
128
+ return function cleanup() {
129
+ if (destroyed) return;
130
+ destroyed = true;
131
+ stop();
132
+ if (renderer.pointer === true) {
133
+ interactionTarget.removeEventListener('mousemove', onMouseMove);
134
+ interactionTarget.removeEventListener('mouseleave', onMouseLeave);
135
+ }
136
+ if (runtime.pauseWhenHidden) documentRef.removeEventListener('visibilitychange', onVisibilityChange);
137
+ windowRef.removeEventListener('resize', resize);
138
+ resizeObserver?.disconnect();
139
+ viewportObserver?.disconnect();
140
+ renderer.destroy();
141
+ };
142
+ }
143
+
144
+ async function mountCanvas(canvas, context) {
145
+ const type = canvas.getAttribute('data-hero-effect') || '';
146
+ const definition = objectValue(context.extension.config.effects)[type];
147
+ if (!definition || typeof definition.module !== 'string') {
148
+ throw new TypeError(`[stellar hero effect] unregistered effect ${type || '<missing>'}`);
149
+ }
150
+ const runtime = runtimePolicy(canvas);
151
+ const windowRef = canvas.ownerDocument?.defaultView || window;
152
+ if (runtime.respectReducedMotion && reducedMotion(windowRef)) return null;
153
+ const module = await import(versionedModule(context, definition.module));
154
+ if (typeof module.createRenderer !== 'function') {
155
+ throw new TypeError(`[stellar hero effect] ${type} must export createRenderer(canvas, options, defaults)`);
156
+ }
157
+ const renderer = module.createRenderer(canvas, readObject(canvas, 'data-effect-options'), objectValue(definition.defaults));
158
+ if (!renderer) return null;
159
+ if (typeof renderer.resize !== 'function' || typeof renderer.render !== 'function' || typeof renderer.destroy !== 'function') {
160
+ renderer.destroy?.();
161
+ throw new TypeError(`[stellar hero effect] ${type} returned an invalid renderer`);
162
+ }
163
+ return createLifecycle(canvas, renderer, runtime);
164
+ }
165
+
166
+ export async function mount(root, context) {
167
+ const cleanups = [];
168
+ await Promise.all(effectCanvases(root).map(async canvas => {
169
+ try {
170
+ const cleanup = await mountCanvas(canvas, context);
171
+ if (typeof cleanup === 'function') cleanups.push(cleanup);
172
+ } catch (error) {
173
+ context.reportError(error);
174
+ }
175
+ }));
176
+ return () => {
177
+ for (let index = cleanups.length - 1; index >= 0; index--) cleanups[index]();
178
+ };
179
+ }
@@ -0,0 +1,80 @@
1
+ function queryAll(root, selector) {
2
+ return Array.from(root.querySelectorAll(selector));
3
+ }
4
+
5
+ const DISTANCE = '8px';
6
+ const DURATION_MS = 1000;
7
+ const INTERVAL_MS = 100;
8
+ const SCALE = 1;
9
+
10
+ function rootWindow(root) {
11
+ if (root?.defaultView) return root.defaultView;
12
+ if (root?.ownerDocument?.defaultView) return root.ownerDocument.defaultView;
13
+ return null;
14
+ }
15
+
16
+ function prefersReducedMotion(windowRef) {
17
+ if (typeof windowRef?.matchMedia !== 'function') return false;
18
+ try {
19
+ return windowRef.matchMedia('(prefers-reduced-motion: reduce)').matches;
20
+ } catch (error) {
21
+ void error;
22
+ return false;
23
+ }
24
+ }
25
+
26
+ export function mount(root) {
27
+ const elements = queryAll(root, '.slide-up');
28
+ const windowRef = rootWindow(root);
29
+ if (
30
+ elements.length === 0 ||
31
+ typeof windowRef?.IntersectionObserver !== 'function' ||
32
+ prefersReducedMotion(windowRef)
33
+ ) return () => {};
34
+
35
+ const animations = new Set();
36
+ const pendingInitialObservation = new WeakSet(elements);
37
+ let observer = null;
38
+
39
+ try {
40
+ observer = new windowRef.IntersectionObserver(entries => {
41
+ let sequenceIndex = 0;
42
+ entries.forEach(entry => {
43
+ if (pendingInitialObservation.has(entry.target)) {
44
+ pendingInitialObservation.delete(entry.target);
45
+ if (entry.isIntersecting) observer.unobserve(entry.target);
46
+ return;
47
+ }
48
+ if (!entry.isIntersecting) return;
49
+ observer.unobserve(entry.target);
50
+ if (typeof entry.target.animate !== 'function') return;
51
+ try {
52
+ const animation = entry.target.animate([
53
+ { opacity: 0, transform: `translateY(${DISTANCE}) scale(${SCALE})` },
54
+ { opacity: 1, transform: 'translateY(0) scale(1)' }
55
+ ], {
56
+ delay: sequenceIndex * INTERVAL_MS,
57
+ duration: DURATION_MS,
58
+ easing: 'ease-out',
59
+ fill: 'backwards'
60
+ });
61
+ animations.add(animation);
62
+ sequenceIndex += 1;
63
+ } catch (error) {
64
+ void error;
65
+ }
66
+ });
67
+ });
68
+ elements.forEach(element => observer.observe(element));
69
+ } catch (error) {
70
+ void error;
71
+ observer?.disconnect();
72
+ return () => {};
73
+ }
74
+
75
+ return () => {
76
+ observer.disconnect();
77
+ animations.forEach(animation => animation.cancel());
78
+ animations.clear();
79
+ };
80
+ }
@@ -0,0 +1,23 @@
1
+ export async function mount(root, context) {
2
+ const config = context.extension.config;
3
+ const assets = context.assets;
4
+ if (config.provider === 'algolia') {
5
+ window.searchConfig = Object.assign({}, config.options, {
6
+ js: config.assets.client,
7
+ hitsPerPage: 100
8
+ });
9
+ await assets.script(config.assets.client);
10
+ await assets.script(config.assets.provider);
11
+ } else if (config.provider === 'local') {
12
+ await assets.script(config.assets.provider);
13
+ }
14
+ await assets.script(config.assets.shortcut);
15
+ const providerCleanup = config.provider === 'algolia'
16
+ ? window.stellarAlgoliaSearch?.mount?.(root)
17
+ : window.stellarLocalSearch?.mount?.(root);
18
+ const shortcutCleanup = window.stellarSearchShortcut?.mount?.(root);
19
+ return () => {
20
+ shortcutCleanup?.();
21
+ providerCleanup?.();
22
+ };
23
+ }
@@ -1,77 +1,74 @@
1
- const initServices = () => {
1
+ export async function mount(root, context) {
2
+ if (root.nodeType !== 9) {
3
+ throw new TypeError('[stellar runtime] legacy data-service adapter requires a document root');
4
+ }
5
+ const assets = context.assets;
6
+ const config = context.extension.config;
7
+ const services = Object.assign({}, config.services, {
8
+ siteinfo: Object.assign({}, config.services.siteinfo, { api: config.siteInfoEndpoint })
9
+ });
10
+ const deps = { marked: config.marked };
11
+ const loads = [];
12
+ const voiceCleanups = [];
2
13
  // 用于存储需要清理的资源
3
- let timers = [];
14
+ let intervals = [];
15
+ let timeouts = [];
4
16
 
5
- for (let id of Object.keys(ctx.services)) {
6
- const js = ctx.services[id].js;
17
+ for (let id of Object.keys(services)) {
18
+ const js = services[id].js;
7
19
  if (id == 'siteinfo') {
8
- const cardlinks = document.querySelectorAll('a.link-card[cardlink]');
9
- const siteCards = document.querySelectorAll('.ds-sites, .site-card .card-link[data-siteinfo-api]');
20
+ const cardlinks = root.querySelectorAll('a.link-card[cardlink]');
21
+ const siteCards = root.querySelectorAll('.ds-sites, .site-card .card-link[data-siteinfo-api]');
10
22
  if (cardlinks?.length > 0 || siteCards?.length > 0) {
11
- utils.js(js, { defer: true }).then(function () {
23
+ loads.push(assets.script(js).then(function () {
12
24
  if (cardlinks?.length > 0) {
13
25
  setCardLink(cardlinks);
14
26
  }
15
- });
27
+ }));
16
28
  }
17
29
  } else if (id == 'ghinfo') {
18
- const els = document.querySelectorAll('.ds-ghinfo');
30
+ const els = root.querySelectorAll('.ds-ghinfo');
19
31
  if (els.length > 0) {
20
- utils.js(js, { defer: true });
32
+ loads.push(assets.script(js));
21
33
  }
22
34
  } else if (id == 'voice') {
23
- const voiceAudios = document.querySelectorAll('.voice>audio');
35
+ const voiceAudios = root.querySelectorAll('.voice>audio');
24
36
  if (voiceAudios?.length > 0) {
25
- utils.js(js, { defer: true }).then(function () {
26
- createVoiceDom(voiceAudios);
27
- });
37
+ loads.push(assets.script(js).then(function () {
38
+ const voiceCleanup = createVoiceDom(voiceAudios);
39
+ if (typeof voiceCleanup === 'function') voiceCleanups.push(voiceCleanup);
40
+ }));
28
41
  }
29
42
  } else if (id == 'video') {
30
- const videos = document.querySelectorAll('.video>video');
43
+ const videos = root.querySelectorAll('.video>video');
31
44
  if (videos?.length > 0) {
32
- utils.js(js, { defer: true }).then(function () {
45
+ loads.push(assets.script(js).then(function () {
33
46
  videoEvents(videos);
34
- });
47
+ }));
35
48
  }
36
49
  } else if (id == 'download-file') {
37
- const files = document.querySelectorAll('.chat-file');
50
+ const files = root.querySelectorAll('.chat-file');
38
51
  if (files?.length > 0) {
39
- utils.js(js, { defer: true }).then(function () {
52
+ loads.push(assets.script(js).then(function () {
40
53
  downloadFileEvent(files);
41
- });
54
+ }));
42
55
  }
43
56
  } else {
44
- const els = document.getElementsByClassName(`ds-${id}`);
57
+ const els = root.getElementsByClassName(`ds-${id}`);
45
58
  if (els?.length > 0) {
46
59
  if (id == 'timeline' || id == 'memos' || id == 'marked' || id == 'mdrender') {
47
- utils.js(deps.marked).then(function () {
48
- utils.js(js, { defer: true });
49
- });
60
+ loads.push(assets.script(deps.marked).then(function () {
61
+ return assets.script(js);
62
+ }));
50
63
  } else {
51
- utils.js(js, { defer: true });
64
+ loads.push(assets.script(js));
52
65
  }
53
66
  }
54
67
  }
55
68
  }
56
69
 
57
- // search
58
- if (ctx.search && typeof searchFunc === 'function') {
59
- const searchCfg = ctx.search.local_search;
60
- const lazyLoad = searchCfg ? searchCfg.lazy_load !== false : true;
61
- // 懒加载模式下由 local-search.js 的聚焦事件负责初始化,此处跳过
62
- if (!lazyLoad) {
63
- const inputArea = document.querySelector("input#search-input");
64
- if (inputArea && !inputArea._searchInitialized) {
65
- const path = ctx.search.path.startsWith('/') ? ctx.root + ctx.search.path.substring(1) : ctx.root + ctx.search.path;
66
- const filter = inputArea.getAttribute('data-filter') || '';
67
- searchFunc(path, filter, 'search-wrapper', 'search-input', 'search-result');
68
- // searchFunc 内部管理 _searchInitialized(pending → true),此处不再覆盖
69
- }
70
- }
71
- }
72
-
73
70
  // chat iphone time
74
- let phoneTimes = document.querySelectorAll('.chat .status-bar .time');
71
+ let phoneTimes = root.querySelectorAll('.chat .status-bar .time');
75
72
  let firstAdjustInterval = null;
76
73
  let mainInterval = null;
77
74
 
@@ -80,7 +77,7 @@ const initServices = () => {
80
77
  const date = new Date();
81
78
  const sec = date.getSeconds();
82
79
  firstAdjustInterval = setInterval(firstAdjustTime, 1000 * (60 - sec));
83
- timers.push(firstAdjustInterval);
80
+ intervals.push(firstAdjustInterval);
84
81
 
85
82
  function firstAdjustTime() {
86
83
  NowTime();
@@ -89,7 +86,7 @@ const initServices = () => {
89
86
  firstAdjustInterval = null;
90
87
  }
91
88
  mainInterval = setInterval(NowTime, 1000 * 60);
92
- timers.push(mainInterval);
89
+ intervals.push(mainInterval);
93
90
  }
94
91
 
95
92
  function NowTime() {
@@ -115,19 +112,22 @@ const initServices = () => {
115
112
  const chat_quote_obverser = new IntersectionObserver((entries, observer) => {
116
113
  entries.filter((entry) => { return entry.isIntersecting }).sort((a, b) => a.intersectionRect.y !== b.intersectionRect.y ? a.intersectionRect.y - b.intersectionRect.y : a.intersectionRect.x - b.intersectionRect.x).forEach((entry, index) => {
117
114
  observer.unobserve(entry.target);
118
- setTimeout(() => {
115
+ const blinkStart = setTimeout(() => {
119
116
  entry.target.classList.add('quote-blink');
120
- setTimeout(() => {
117
+ const blinkEnd = setTimeout(() => {
121
118
  entry.target.classList.remove('quote-blink');
122
119
  }, 1000);
120
+ timeouts.push(blinkEnd);
123
121
  }, Math.max(100, 16) * (index + 1));
122
+ timeouts.push(blinkStart);
124
123
  });
125
124
  });
126
125
 
127
- var chatQuotes = document.querySelectorAll(".chat .talk .quote");
126
+ var chatQuotes = root.querySelectorAll(".chat .talk .quote");
128
127
  chatQuotes.forEach((quote) => {
129
128
  const handler = function () {
130
- var chatCellDom = document.getElementById("quote-" + quote.getAttribute("quotedCellTag"));
129
+ var candidate = root.getElementById("quote-" + quote.getAttribute("quotedCellTag"));
130
+ var chatCellDom = candidate && root.documentElement.contains(candidate) ? candidate : null;
131
131
  if (chatCellDom) {
132
132
  var chatDiv = chatCellDom.parentElement;
133
133
  var mid = chatDiv.clientHeight / 2;
@@ -151,12 +151,16 @@ const initServices = () => {
151
151
  });
152
152
 
153
153
  // 返回清理函数,用于清理定时器和观察器
154
- return () => {
154
+ const cleanup = () => {
155
155
  // 清理所有定时器(包括可能未完成的 firstAdjustInterval)
156
- timers.forEach(timer => {
156
+ intervals.forEach(timer => {
157
157
  if (timer) clearInterval(timer);
158
158
  });
159
- timers = [];
159
+ intervals = [];
160
+ timeouts.forEach(timer => {
161
+ if (timer) clearTimeout(timer);
162
+ });
163
+ timeouts = [];
160
164
  if (firstAdjustInterval) {
161
165
  clearInterval(firstAdjustInterval);
162
166
  firstAdjustInterval = null;
@@ -176,7 +180,16 @@ const initServices = () => {
176
180
  quote.removeEventListener('click', handler);
177
181
  });
178
182
  quoteClickHandlers.clear();
183
+ for (let index = voiceCleanups.length - 1; index >= 0; index -= 1) {
184
+ voiceCleanups[index]();
185
+ }
186
+ voiceCleanups.length = 0;
179
187
  };
180
- };
181
-
182
- stellar.initPlugin(initServices, 'services');
188
+ try {
189
+ await Promise.all(loads);
190
+ } catch (error) {
191
+ cleanup();
192
+ throw error;
193
+ }
194
+ return cleanup;
195
+ }