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.
- package/CHANGELOG.md +63 -5
- package/CONTRIBUTING.md +29 -120
- package/README.md +66 -73
- package/README_EN.md +66 -73
- package/_config.yml +890 -743
- package/_data/icons.yml +43 -171
- package/_data/widgets.yml +1 -1
- package/assets/icon-v2-x512.webp +0 -0
- package/languages/en.yml +59 -2
- package/languages/zh-CN.yml +59 -2
- package/languages/zh-TW.yml +59 -2
- package/layout/404.ejs +7 -4
- package/layout/_partial/comments/artalk/layout.ejs +3 -2
- package/layout/_partial/comments/beaudar/layout.ejs +2 -2
- package/layout/_partial/comments/giscus/layout.ejs +2 -5
- package/layout/_partial/comments/layout.ejs +11 -40
- package/layout/_partial/comments/twikoo/layout.ejs +3 -2
- package/layout/_partial/comments/utterances/layout.ejs +2 -2
- package/layout/_partial/comments/waline/layout.ejs +3 -2
- package/layout/_partial/components/collection-item.ejs +30 -13
- package/layout/_partial/components/collection.ejs +2 -2
- package/layout/_partial/cover/index.ejs +5 -2
- package/layout/_partial/cover/wiki_cover.ejs +39 -28
- package/layout/_partial/dropdown.ejs +26 -14
- package/layout/_partial/head.ejs +96 -69
- package/layout/_partial/main/article/contributors.ejs +12 -9
- package/layout/_partial/main/article/post_footer.ejs +47 -0
- package/layout/_partial/main/article/post_read_next.ejs +22 -0
- package/layout/_partial/main/article/post_related.ejs +14 -0
- package/layout/_partial/main/article/post_tags.ejs +12 -0
- package/layout/_partial/main/article/share.ejs +55 -0
- package/layout/_partial/main/footer.ejs +11 -10
- package/layout/_partial/main/{navbar/nav_tabs_blog.ejs → listing_nav/blog.ejs} +21 -18
- package/layout/_partial/main/listing_nav/wiki.ejs +56 -0
- package/layout/_partial/main/navbar/article_banner.ejs +39 -35
- package/layout/_partial/main/navbar/breadcrumb/blog.ejs +15 -28
- package/layout/_partial/main/navbar/breadcrumb/note.ejs +12 -5
- package/layout/_partial/main/navbar/breadcrumb/wiki.ejs +15 -11
- package/layout/_partial/main/navbar/dateinfo.ejs +28 -12
- package/layout/_partial/main/navbar/ghinfo.ejs +5 -3
- package/layout/_partial/main/notebook/note_card.ejs +12 -17
- package/layout/_partial/main/notebook/note_tags.ejs +3 -4
- package/layout/_partial/main/notebook/notebook_card.ejs +11 -4
- package/layout/_partial/main/pin_slider.ejs +48 -43
- package/layout/_partial/main/post_list/archive_item.ejs +8 -4
- package/layout/_partial/main/post_list/latest_post_card.ejs +10 -4
- package/layout/_partial/main/post_list/post_card.ejs +39 -90
- package/layout/_partial/main/post_list/topic_card.ejs +7 -5
- package/layout/_partial/main/post_list/wiki_card.ejs +16 -13
- package/layout/_partial/menubtn.ejs +3 -3
- package/layout/_partial/primitives/item.ejs +8 -0
- package/layout/_partial/primitives/navigation.ejs +17 -0
- package/layout/_partial/primitives/region.ejs +16 -0
- package/layout/_partial/primitives/section.ejs +8 -0
- package/layout/_partial/primitives/shell.ejs +35 -0
- package/layout/_partial/regions/widgets.ejs +84 -0
- package/layout/_partial/scripts/defines.ejs +46 -76
- package/layout/_partial/scripts/runtime.ejs +55 -0
- package/layout/_partial/scripts.ejs +18 -42
- package/layout/_partial/search/dialog.ejs +33 -0
- package/layout/_partial/search/shortcut.ejs +1 -0
- package/layout/_partial/sidebar/brand.ejs +127 -0
- package/layout/_partial/sidebar/menu.ejs +59 -14
- package/layout/_partial/sidebar/search.ejs +58 -41
- package/layout/_partial/widgets/actions.ejs +25 -0
- package/layout/_partial/widgets/author.ejs +9 -5
- package/layout/_partial/widgets/components/linklist.ejs +1 -2
- package/layout/_partial/widgets/ghissues.ejs +7 -10
- package/layout/_partial/widgets/ghrepo.ejs +23 -16
- package/layout/_partial/widgets/ghuser.ejs +3 -3
- package/layout/_partial/widgets/recent.ejs +25 -21
- package/layout/_partial/widgets/related.ejs +25 -34
- package/layout/_partial/widgets/settings.ejs +17 -0
- package/layout/_partial/widgets/tagtree.ejs +18 -10
- package/layout/_partial/widgets/toc.ejs +17 -9
- package/layout/_partial/widgets/tree.ejs +12 -13
- package/layout/archive.ejs +20 -13
- package/layout/categories.ejs +4 -3
- package/layout/collection-preview.ejs +8 -9
- package/layout/index.ejs +32 -43
- package/layout/index_topic.ejs +6 -13
- package/layout/index_wiki.ejs +17 -34
- package/layout/layout.ejs +195 -65
- package/layout/notebooks.ejs +3 -3
- package/layout/notes.ejs +4 -4
- package/layout/page.ejs +95 -50
- package/layout/settings.ejs +109 -0
- package/layout/tags.ejs +4 -3
- package/{THIRD-PARTY-NOTICES.md → legal/THIRD-PARTY-NOTICES.md} +8 -4
- package/package.json +28 -6
- package/scripts/commands/stellar.js +55 -0
- package/scripts/events/index.js +15 -9
- package/scripts/events/lib/authors.js +9 -7
- package/scripts/events/lib/config-hot-reload.js +65 -0
- package/scripts/events/lib/config-schema.js +20 -0
- package/scripts/events/lib/config.js +22 -63
- package/scripts/events/lib/content-config.js +5 -0
- package/scripts/events/lib/doc_tree.js +99 -9
- package/scripts/events/lib/links.js +6 -3
- package/scripts/events/lib/notebooks.js +200 -8
- package/scripts/events/lib/utils.js +4 -4
- package/scripts/events/lib/version-check.js +57 -3
- package/scripts/filters/index.js +2 -27
- package/scripts/filters/lib/img_lazyload.js +4 -126
- package/scripts/filters/lib/img_onerror.js +14 -17
- package/scripts/filters/lib/page-view-model.js +152 -0
- package/scripts/generators/404.js +13 -8
- package/scripts/generators/author.js +16 -11
- package/scripts/generators/notebooks.js +68 -39
- package/scripts/generators/search.js +62 -67
- package/scripts/generators/settings.js +43 -0
- package/scripts/generators/stellar-icons.js +13 -5
- package/scripts/generators/topic.js +24 -13
- package/scripts/generators/wiki.js +49 -21
- package/scripts/helpers/ai_label.js +6 -12
- package/scripts/helpers/brand.js +17 -0
- package/scripts/helpers/browser_runtime_manifest.js +12 -0
- package/scripts/helpers/caption.js +8 -0
- package/scripts/helpers/category_color.js +13 -13
- package/scripts/helpers/collection.js +18 -0
- package/scripts/helpers/comments_model.js +14 -0
- package/scripts/helpers/extension_assets.js +8 -0
- package/scripts/helpers/json_ld.js +16 -7
- package/scripts/helpers/mdrender.js +3 -9
- package/scripts/helpers/menu.js +9 -0
- package/scripts/helpers/post_view_model.js +23 -0
- package/scripts/helpers/regions.js +37 -0
- package/scripts/helpers/scrollreveal.js +7 -7
- package/scripts/helpers/search_domain.js +34 -0
- package/scripts/helpers/service_provider.js +9 -0
- package/scripts/helpers/settings.js +16 -0
- package/scripts/helpers/share_action.js +8 -0
- package/scripts/helpers/stellar_config.js +12 -0
- package/scripts/helpers/stellar_data.js +8 -0
- package/scripts/helpers/stellar_info.js +21 -16
- package/scripts/helpers/stellar_inject.js +9 -0
- package/scripts/helpers/ui_classes.js +12 -0
- package/scripts/lib/ai_label.js +14 -11
- package/scripts/lib/brand.js +41 -0
- package/scripts/lib/browser-runtime.js +115 -0
- package/scripts/lib/caption.js +21 -0
- package/scripts/lib/collection-pipeline/adapters/notebook.js +62 -0
- package/scripts/lib/collection-pipeline/adapters/post.js +20 -0
- package/scripts/lib/collection-pipeline/adapters/topic.js +82 -0
- package/scripts/lib/collection-pipeline/adapters/wiki.js +20 -0
- package/scripts/lib/collection-pipeline/index.js +169 -0
- package/scripts/lib/collection-pipeline/registry.js +27 -0
- package/scripts/lib/collection-pipeline/shared.js +164 -0
- package/scripts/lib/comments.js +60 -0
- package/scripts/lib/config-inject.js +20 -0
- package/scripts/lib/config-schema.js +1095 -0
- package/scripts/lib/content-config.js +157 -0
- package/scripts/lib/content-defaults.js +56 -0
- package/scripts/lib/content-membership.js +233 -0
- package/scripts/lib/contribution-contract.js +165 -0
- package/scripts/lib/contribution-registry.js +429 -0
- package/scripts/lib/doc_tree.js +46 -44
- package/scripts/lib/doctor.js +296 -0
- package/scripts/lib/front-matter.js +29 -0
- package/scripts/lib/hero-effect-registry.js +262 -0
- package/scripts/lib/html-images.js +128 -0
- package/scripts/lib/internal-constants.js +152 -0
- package/scripts/lib/layout-config.js +62 -0
- package/scripts/lib/mdrender_html.js +36 -23
- package/scripts/lib/menu.js +36 -0
- package/scripts/lib/model-schema.js +90 -0
- package/scripts/lib/models/index.js +1542 -0
- package/scripts/lib/new-note.js +172 -0
- package/scripts/lib/page-view-model-registry.js +103 -0
- package/scripts/lib/regions.js +132 -0
- package/scripts/lib/runtime-data.js +23 -0
- package/scripts/lib/safe-path.js +44 -0
- package/scripts/lib/search-domain.js +59 -0
- package/scripts/lib/seo.js +10 -10
- package/scripts/lib/service-provider.js +12 -0
- package/scripts/lib/settings.js +16 -0
- package/scripts/lib/share-services.js +96 -0
- package/scripts/lib/source-config.js +27 -0
- package/scripts/lib/theme-metadata.js +22 -0
- package/scripts/lib/ui-capabilities.js +32 -0
- package/scripts/lib/widget-registry.js +150 -0
- package/scripts/lib/wiki_readme.js +15 -11
- package/scripts/schema/config-rules.js +290 -0
- package/scripts/schema/config-schema.js +163 -0
- package/scripts/schema/content-config-rules.js +264 -0
- package/scripts/schema/content-config-schema.js +321 -0
- package/scripts/schema/model-schema.js +971 -0
- package/scripts/schema/schema-utils.js +48 -0
- package/scripts/tags/index.js +2 -3
- package/scripts/tags/lib/albums.js +6 -6
- package/scripts/tags/lib/banner.js +10 -4
- package/scripts/tags/lib/box.js +1 -1
- package/scripts/tags/lib/button.js +1 -1
- package/scripts/tags/lib/chat.js +9 -31
- package/scripts/tags/lib/checkbox.js +6 -1
- package/scripts/tags/lib/copy.js +4 -1
- package/scripts/tags/lib/dropdown.js +6 -4
- package/scripts/tags/lib/emoji.js +5 -10
- package/scripts/tags/lib/friends.js +4 -4
- package/scripts/tags/lib/gallery.js +7 -7
- package/scripts/tags/lib/ghcard.js +5 -3
- package/scripts/tags/lib/gist.js +16 -0
- package/scripts/tags/lib/grid.js +4 -2
- package/scripts/tags/lib/hashtag.js +1 -1
- package/scripts/tags/lib/icon.js +1 -1
- package/scripts/tags/lib/image.js +1 -1
- package/scripts/tags/lib/link.js +6 -3
- package/scripts/tags/lib/mark.js +8 -3
- package/scripts/tags/lib/md.js +1 -1
- package/scripts/tags/lib/note.js +1 -1
- package/scripts/tags/lib/okr.js +18 -1
- package/scripts/tags/lib/posters.js +6 -6
- package/scripts/tags/lib/quot.js +15 -5
- package/scripts/tags/lib/rating.js +7 -4
- package/scripts/tags/lib/sites.js +8 -6
- package/scripts/tags/lib/tip.js +6 -6
- package/scripts/tags/lib/toc.js +2 -2
- package/scripts/tags/lib/vote.js +8 -5
- package/source/css/_appearances/_mixins.styl +258 -0
- package/source/css/_appearances/card.styl +30 -0
- package/source/css/_appearances/flat.styl +44 -0
- package/source/css/_appearances/glass.styl +138 -0
- package/source/css/_appearances/minimal.styl +325 -0
- package/source/css/_common/base.styl +5 -9
- package/source/css/_common/blockquote.styl +0 -1
- package/source/css/_common/button.styl +1 -1
- package/source/css/_common/device.styl +24 -42
- package/source/css/_common/dropdown.styl +20 -14
- package/source/css/_common/highlight.styl +5 -12
- package/source/css/_common/icon.styl +38 -0
- package/source/css/_common/input.styl +33 -0
- package/source/css/_common/loading.styl +2 -2
- package/source/css/_common/svg.styl +0 -8
- package/source/css/_components/collection.styl +21 -64
- package/source/css/_components/layout.styl +524 -84
- package/source/css/_components/list.styl +36 -22
- package/source/css/_components/main.styl +16 -19
- package/source/css/_components/md.styl +8 -13
- package/source/css/_components/pages/article-indent.styl +1 -1
- package/source/css/_components/pages/article-story.styl +7 -6
- package/source/css/_components/pages/article-tech.styl +5 -5
- package/source/css/_components/pages/collection-preview.styl +1 -5
- package/source/css/_components/pages/settings.styl +184 -0
- package/source/css/_components/partial/article-banner.styl +2 -2
- package/source/css/_components/partial/article-footer.styl +0 -32
- package/source/css/_components/partial/article-share.styl +48 -0
- package/source/css/_components/partial/article-tags.styl +1 -1
- package/source/css/_components/partial/cover.styl +13 -13
- package/source/css/_components/partial/listing-nav.styl +68 -0
- package/source/css/_components/partial/paginator.styl +1 -1
- package/source/css/_components/pin-slider.styl +3 -3
- package/source/css/_components/sidebar/brand.styl +135 -0
- package/source/css/_components/sidebar/footer.styl +14 -31
- package/source/css/_components/sidebar/nav-area.styl +4 -0
- package/source/css/_components/sidebar/search.styl +176 -26
- package/source/css/_components/sidebar/sidebar.styl +76 -110
- package/source/css/_components/tag-plugins/banner.styl +16 -28
- package/source/css/_components/tag-plugins/checkbox.styl +7 -22
- package/source/css/_components/tag-plugins/copy.styl +3 -5
- package/source/css/_components/tag-plugins/gallery.styl +2 -2
- package/source/css/_components/tag-plugins/grid.styl +3 -0
- package/source/css/_components/tag-plugins/image.styl +2 -1
- package/source/css/_components/tag-plugins/link.styl +0 -1
- package/source/css/_components/tag-plugins/mark.styl +19 -4
- package/source/css/_components/tag-plugins/mdrender.styl +2 -2
- package/source/css/_components/tag-plugins/navbar.styl +23 -17
- package/source/css/_components/tag-plugins/note.styl +2 -2
- package/source/css/_components/tag-plugins/okr.styl +1 -2
- package/source/css/_components/tag-plugins/quot.styl +5 -0
- package/source/css/_components/tag-plugins/rating.styl +3 -2
- package/source/css/_components/tag-plugins/timeline.styl +1 -1
- package/source/css/_components/tag-plugins/tip.styl +1 -1
- package/source/css/_components/tag-plugins/vote.styl +3 -2
- package/source/css/_components/widgets/ghrepo.styl +2 -1
- package/source/css/_components/widgets/ghuser.styl +4 -6
- package/source/css/_components/widgets/settings.styl +49 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +12 -33
- package/source/css/_components/widgets/widgets.styl +14 -19
- package/source/css/_components/wiki-card.styl +2 -2
- package/source/css/_custom.styl +26 -32
- package/source/css/_defines/func.styl +47 -37
- package/source/css/_defines/theme_base.styl +8 -10
- package/source/css/_plugins/card-hover.styl +1 -1
- package/source/css/_plugins/copycode.styl +16 -19
- package/source/css/_plugins/index.styl +3 -8
- package/source/css/_plugins/lazyload.styl +1 -1
- package/source/css/main.styl +12 -0
- package/source/js/main.js +302 -59
- package/source/js/plugins/adaptive-text.js +33 -16
- package/source/js/plugins/card-hover.js +2 -24
- package/source/js/plugins/copycode.js +10 -16
- package/source/js/plugins/voice.js +51 -48
- package/source/js/profile.js +316 -0
- package/source/js/runtime/asset-loader.js +61 -0
- package/source/js/runtime/extension-registry.js +149 -0
- package/source/js/runtime/extensions/card-hover.js +5 -0
- package/source/js/runtime/extensions/color-scheme-switch.js +101 -0
- package/source/js/runtime/extensions/comments.js +200 -0
- package/source/js/runtime/extensions/deferred-icons.js +53 -0
- package/source/js/{plugins → runtime/extensions}/dropdown.js +69 -30
- package/source/js/runtime/extensions/feature.js +200 -0
- package/source/js/runtime/extensions/hero-effect.js +179 -0
- package/source/js/runtime/extensions/reveal.js +80 -0
- package/source/js/runtime/extensions/search.js +23 -0
- package/source/js/{services.js → runtime/extensions/services.js} +67 -54
- package/source/js/runtime/extensions/settings.js +235 -0
- package/source/js/runtime/hero-effects/ferrofluid.js +385 -0
- package/source/js/{plugins → runtime/hero-effects}/galaxy.js +53 -177
- package/source/js/runtime/hero-effects/light-rays.js +293 -0
- package/source/js/runtime/index.js +75 -0
- package/source/js/runtime/legacy-request-adapter.js +47 -0
- package/source/js/runtime/request-cache.js +219 -0
- package/source/js/search/algolia-search.js +76 -51
- package/source/js/search/highlight.js +1 -1
- package/source/js/search/local-search.js +96 -57
- package/source/js/search/shortcut.js +31 -20
- package/source/js/services/friends_and_posts.js +6 -1
- package/source/js/services/rating.js +14 -6
- package/source/js/services/siteinfo.js +3 -7
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/vote.js +7 -5
- package/source/js/services/weibo.js +1 -1
- package/source/js/utils.js +19 -444
- package/.claude/skills/stellar-theme-dev/SKILL.md +0 -51
- package/_data/chat_users.yml +0 -0
- package/layout/_partial/comments/artalk/script.ejs +0 -76
- package/layout/_partial/comments/beaudar/script.ejs +0 -33
- package/layout/_partial/comments/giscus/script.ejs +0 -31
- package/layout/_partial/comments/script.ejs +0 -3
- package/layout/_partial/comments/twikoo/script.ejs +0 -25
- package/layout/_partial/comments/utterances/script.ejs +0 -33
- package/layout/_partial/comments/waline/script.ejs +0 -43
- package/layout/_partial/main/article/article_footer.ejs +0 -164
- package/layout/_partial/main/article/article_tags.ejs +0 -16
- package/layout/_partial/main/article/read_next.ejs +0 -63
- package/layout/_partial/main/article/related_posts.ejs +0 -16
- package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +0 -52
- package/layout/_partial/scripts/bootstrap.ejs +0 -37
- package/layout/_partial/scripts/lazyload.ejs +0 -89
- package/layout/_partial/sidebar/grad-def.ejs +0 -26
- package/layout/_partial/sidebar/index_leftbar.ejs +0 -191
- package/layout/_partial/sidebar/index_rightbar.ejs +0 -94
- package/layout/_partial/sidebar/logo.ejs +0 -99
- package/layout/_plugins/adaptive_text.ejs +0 -11
- package/layout/_plugins/card_hover.ejs +0 -32
- package/layout/_plugins/copycode.ejs +0 -17
- package/layout/_plugins/fancybox.ejs +0 -53
- package/layout/_plugins/heti.ejs +0 -21
- package/layout/_plugins/index.ejs +0 -50
- package/layout/_plugins/mathjax.ejs +0 -49
- package/layout/_plugins/mermaid.ejs +0 -33
- package/layout/_plugins/preload.ejs +0 -9
- package/layout/_plugins/scrollreveal.ejs +0 -102
- package/layout/_plugins/search/algolia_search.ejs +0 -12
- package/layout/_plugins/search/local_search.ejs +0 -9
- package/layout/_plugins/swiper.ejs +0 -27
- package/layout/_plugins/tianli_gpt.ejs +0 -14
- package/scripts/commands/new-note.js +0 -49
- package/scripts/events/lib/merge_posts.js +0 -48
- package/scripts/events/lib/topic_tree.js +0 -48
- package/scripts/helpers/related_posts.js +0 -51
- package/scripts/helpers/subtitle.js +0 -8
- package/scripts/lib/notebooks.js +0 -181
- package/scripts/lib/subtitle.js +0 -28
- package/scripts/tags/lib/about.js +0 -48
- package/source/css/_components/page-transition.styl +0 -22
- package/source/css/_components/partial/navbar.styl +0 -51
- package/source/css/_components/sidebar/logo.styl +0 -111
- package/source/css/_components/tag-plugins/about.styl +0 -56
- package/source/css/_components/widgets/tree.styl +0 -0
- package/source/css/_plugins/scrollreveal.styl +0 -6
- package/source/css/_plugins/tianli_gpt.styl +0 -213
- package/source/css/plugins/mermaid.styl +0 -137
- package/source/js/icons.js +0 -60
- package/source/js/services/fcircle.js +0 -35
- package/source/js/theme.js +0 -61
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function installLegacyRequestAdapter(utils, client, policy) {
|
|
2
|
+
if (!utils || typeof utils !== 'object') {
|
|
3
|
+
throw new TypeError('[stellar runtime] utils is required for the data-service adapter');
|
|
4
|
+
}
|
|
5
|
+
if (!policy || !Number.isInteger(policy.retries) || !(policy.timeoutMs > 0)) {
|
|
6
|
+
throw new TypeError('[stellar runtime] request policy is required for the data-service adapter');
|
|
7
|
+
}
|
|
8
|
+
const loaded = new WeakSet();
|
|
9
|
+
|
|
10
|
+
function serviceId(element, options) {
|
|
11
|
+
if (typeof options?.service === 'string' && options.service.length > 0) return options.service;
|
|
12
|
+
const match = String(element?.className || '').match(/\bds-([\w-]+)\b/);
|
|
13
|
+
return match?.[1] || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const request = function request(element, url, callback, onFailure, options = {}) {
|
|
17
|
+
if (element && loaded.has(element)) return Promise.resolve(null);
|
|
18
|
+
const requestOptions = Object.assign({ retries: policy.retries, timeout: policy.timeoutMs }, options, {
|
|
19
|
+
service: serviceId(element, options),
|
|
20
|
+
onNetworkStart: () => utils.onLoading?.(element)
|
|
21
|
+
});
|
|
22
|
+
return client.request(url, requestOptions).then(async response => {
|
|
23
|
+
if (element) loaded.add(element);
|
|
24
|
+
utils.onLoadSuccess?.(element);
|
|
25
|
+
await callback(response);
|
|
26
|
+
return response;
|
|
27
|
+
}).catch(error => {
|
|
28
|
+
utils.onLoadFailure?.(element);
|
|
29
|
+
onFailure?.(error);
|
|
30
|
+
throw error;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const requestWithoutLoading = function requestWithoutLoading(url, options = {}, maxRetry = policy.retries, timeout = policy.timeoutMs) {
|
|
35
|
+
return client.request(url, Object.assign({}, options, {
|
|
36
|
+
retries: maxRetry,
|
|
37
|
+
timeout
|
|
38
|
+
}));
|
|
39
|
+
};
|
|
40
|
+
utils.request = request;
|
|
41
|
+
utils.requestWithoutLoading = requestWithoutLoading;
|
|
42
|
+
const bridge = globalThis.__stellarRequestBridge;
|
|
43
|
+
if (bridge && typeof bridge.resolve === 'function') {
|
|
44
|
+
bridge.resolve(Object.freeze({ request, requestWithoutLoading }));
|
|
45
|
+
delete bridge.resolve;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
export const REQUEST_CACHE_PREFIX = 'Stellar.request-cache.v2.';
|
|
2
|
+
|
|
3
|
+
function responseFrom(entry) {
|
|
4
|
+
return new Response(entry.text, {
|
|
5
|
+
status: 200,
|
|
6
|
+
statusText: 'OK',
|
|
7
|
+
headers: { 'Content-Type': entry.contentType || 'application/json' }
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function requestKey(url, options) {
|
|
12
|
+
return `${options?.method || 'GET'} ${url}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function utf8Bytes(value) {
|
|
16
|
+
if (typeof TextEncoder === 'function') return new TextEncoder().encode(value).byteLength;
|
|
17
|
+
return value.length;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createRequestClient(options = {}) {
|
|
21
|
+
const policy = options.policy;
|
|
22
|
+
if (!policy || !Number.isInteger(policy.retries) || !(policy.timeoutMs > 0) || !(policy.idleTimeoutMs > 0) || !(policy.maxCacheEntryBytes > 0)) {
|
|
23
|
+
throw new TypeError('[stellar runtime] request policy is required');
|
|
24
|
+
}
|
|
25
|
+
const fetchImpl = options.fetch || globalThis.fetch?.bind(globalThis);
|
|
26
|
+
let storage = options.storage;
|
|
27
|
+
if (!storage) {
|
|
28
|
+
try { storage = globalThis.localStorage; } catch (error) { void error; storage = null; }
|
|
29
|
+
}
|
|
30
|
+
const now = options.clock || Date.now;
|
|
31
|
+
const schedule = options.scheduler || (fn => {
|
|
32
|
+
if (typeof globalThis.requestIdleCallback === 'function') {
|
|
33
|
+
globalThis.requestIdleCallback(fn, { timeout: policy.idleTimeoutMs });
|
|
34
|
+
} else {
|
|
35
|
+
setTimeout(fn, 0);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const dispatch = typeof options.dispatch === 'function' ? options.dispatch : () => {};
|
|
39
|
+
const config = options.cache;
|
|
40
|
+
if (!config || typeof config.enabled !== 'boolean' || !(config.defaultTtl >= 0) || !config.ttl || !(config.maxEntries >= 0)) {
|
|
41
|
+
throw new TypeError('[stellar runtime] cache policy is required');
|
|
42
|
+
}
|
|
43
|
+
const pending = new Map();
|
|
44
|
+
let cacheEnabled = config.enabled === true;
|
|
45
|
+
|
|
46
|
+
if (typeof fetchImpl !== 'function') throw new TypeError('[stellar runtime] fetch is required');
|
|
47
|
+
|
|
48
|
+
function serviceTtl(service) {
|
|
49
|
+
const value = service && config.ttl?.[service] != null ? config.ttl[service] : config.defaultTtl;
|
|
50
|
+
return typeof value === 'number' && value > 0 ? value : 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function shouldCache(url, requestOptions) {
|
|
54
|
+
if (!cacheEnabled || requestOptions.cache === false || requestOptions.cache === 'no-store') return false;
|
|
55
|
+
if ((requestOptions.method || 'GET') !== 'GET') return false;
|
|
56
|
+
return !/[?&]t=\d{10,}/.test(url);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function read(url) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = storage?.getItem(REQUEST_CACHE_PREFIX + url);
|
|
62
|
+
if (!raw) return null;
|
|
63
|
+
const entry = JSON.parse(raw);
|
|
64
|
+
if (!entry || typeof entry.text !== 'string' || typeof entry.ts !== 'number' || typeof entry.ttl !== 'number') return null;
|
|
65
|
+
return entry;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
void error;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function keys() {
|
|
73
|
+
const result = [];
|
|
74
|
+
if (!storage) return result;
|
|
75
|
+
try {
|
|
76
|
+
for (let index = 0; index < storage.length; index++) {
|
|
77
|
+
const key = storage.key(index);
|
|
78
|
+
if (key?.startsWith(REQUEST_CACHE_PREFIX)) result.push(key);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) { void error; }
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function clearCache() {
|
|
85
|
+
if (!storage) return Object.freeze({ ok: false, partial: false, removed: 0, failed: 1 });
|
|
86
|
+
try { void storage.length; } catch (error) {
|
|
87
|
+
void error;
|
|
88
|
+
return Object.freeze({ ok: false, partial: false, removed: 0, failed: 1 });
|
|
89
|
+
}
|
|
90
|
+
const targets = keys();
|
|
91
|
+
let removed = 0;
|
|
92
|
+
let failed = 0;
|
|
93
|
+
targets.forEach(key => {
|
|
94
|
+
try {
|
|
95
|
+
storage.removeItem(key);
|
|
96
|
+
removed++;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
void error;
|
|
99
|
+
failed++;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return Object.freeze({ ok: failed === 0, partial: removed > 0 && failed > 0, removed, failed });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function evictOldest(exceptKey = '') {
|
|
106
|
+
const candidates = keys().filter(key => key !== exceptKey).map(key => {
|
|
107
|
+
let ts = 0;
|
|
108
|
+
try {
|
|
109
|
+
ts = JSON.parse(storage.getItem(key) || 'null')?.ts || 0;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
void error;
|
|
112
|
+
}
|
|
113
|
+
return { key, ts };
|
|
114
|
+
}).sort((left, right) => left.ts - right.ts);
|
|
115
|
+
if (candidates[0]) storage.removeItem(candidates[0].key);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function trim() {
|
|
119
|
+
const all = keys().map(key => {
|
|
120
|
+
let ts = 0;
|
|
121
|
+
try {
|
|
122
|
+
ts = JSON.parse(storage.getItem(key) || 'null')?.ts || 0;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
void error;
|
|
125
|
+
}
|
|
126
|
+
return { key, ts };
|
|
127
|
+
}).sort((left, right) => left.ts - right.ts);
|
|
128
|
+
while (all.length > config.maxEntries) storage.removeItem(all.shift().key);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function write(url, text, contentType, ttl) {
|
|
132
|
+
if (!storage || !(ttl > 0) || utf8Bytes(text) > policy.maxCacheEntryBytes) return;
|
|
133
|
+
const key = REQUEST_CACHE_PREFIX + url;
|
|
134
|
+
const value = JSON.stringify({ text, contentType, ts: now(), ttl });
|
|
135
|
+
try {
|
|
136
|
+
storage.setItem(key, value);
|
|
137
|
+
trim();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
void error;
|
|
140
|
+
try {
|
|
141
|
+
evictOldest(key);
|
|
142
|
+
storage.setItem(key, value);
|
|
143
|
+
trim();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
void error;
|
|
146
|
+
cacheEnabled = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isFresh(entry) {
|
|
152
|
+
return !!entry && entry.ttl > 0 && now() - entry.ts < entry.ttl * 1000;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sharedFetch(url, requestOptions, timeout) {
|
|
156
|
+
const key = requestKey(url, requestOptions);
|
|
157
|
+
if (pending.has(key)) return pending.get(key).then(response => response.clone());
|
|
158
|
+
dispatch('stellar:request-start', { key });
|
|
159
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
160
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeout) : null;
|
|
161
|
+
const fetchOptions = Object.assign({}, requestOptions);
|
|
162
|
+
delete fetchOptions.service;
|
|
163
|
+
delete fetchOptions.retries;
|
|
164
|
+
delete fetchOptions.timeout;
|
|
165
|
+
delete fetchOptions.onNetworkStart;
|
|
166
|
+
if (fetchOptions.cache === false) delete fetchOptions.cache;
|
|
167
|
+
const callerSignal = fetchOptions.signal;
|
|
168
|
+
let removeCallerAbort = null;
|
|
169
|
+
if (controller) {
|
|
170
|
+
const abortFromCaller = () => controller.abort(callerSignal?.reason);
|
|
171
|
+
if (callerSignal?.aborted) abortFromCaller();
|
|
172
|
+
else if (callerSignal?.addEventListener) {
|
|
173
|
+
callerSignal.addEventListener('abort', abortFromCaller, { once: true });
|
|
174
|
+
removeCallerAbort = () => callerSignal.removeEventListener('abort', abortFromCaller);
|
|
175
|
+
}
|
|
176
|
+
fetchOptions.signal = controller.signal;
|
|
177
|
+
}
|
|
178
|
+
const promise = Promise.resolve(fetchImpl(url, fetchOptions)).then(response => {
|
|
179
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
180
|
+
return response;
|
|
181
|
+
}).finally(() => {
|
|
182
|
+
if (timer !== null) clearTimeout(timer);
|
|
183
|
+
removeCallerAbort?.();
|
|
184
|
+
pending.delete(key);
|
|
185
|
+
dispatch('stellar:request-end', { key });
|
|
186
|
+
});
|
|
187
|
+
pending.set(key, promise);
|
|
188
|
+
return promise.then(response => response.clone());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function request(url, requestOptions = {}) {
|
|
192
|
+
const ttl = serviceTtl(requestOptions.service);
|
|
193
|
+
const cacheable = shouldCache(url, requestOptions) && ttl > 0;
|
|
194
|
+
const cached = cacheable ? read(url) : null;
|
|
195
|
+
if (isFresh(cached)) return responseFrom(cached);
|
|
196
|
+
if (typeof requestOptions.onNetworkStart === 'function') requestOptions.onNetworkStart();
|
|
197
|
+
|
|
198
|
+
const retries = Number.isInteger(requestOptions.retries) ? requestOptions.retries : policy.retries;
|
|
199
|
+
const timeout = typeof requestOptions.timeout === 'number' ? requestOptions.timeout : policy.timeoutMs;
|
|
200
|
+
let error;
|
|
201
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
202
|
+
try {
|
|
203
|
+
const response = await sharedFetch(url, requestOptions, timeout);
|
|
204
|
+
if (cacheable) {
|
|
205
|
+
const clone = response.clone();
|
|
206
|
+
const contentType = clone.headers.get('Content-Type') || 'application/json';
|
|
207
|
+
schedule(() => clone.text().then(text => write(url, text, contentType, ttl)).catch(() => {}));
|
|
208
|
+
}
|
|
209
|
+
return response;
|
|
210
|
+
} catch (caught) {
|
|
211
|
+
error = caught;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (cached) return responseFrom(cached);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return Object.freeze({ request, clearCache });
|
|
219
|
+
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
(function() {
|
|
2
|
+
var roots = new WeakMap();
|
|
3
|
+
function mount(root) {
|
|
4
|
+
root = root || document;
|
|
5
|
+
if (roots.has(root)) return roots.get(root);
|
|
6
|
+
var searchWrappers = Array.from(root.querySelectorAll('.search-wrapper'));
|
|
7
|
+
if (searchWrappers.length === 0) {
|
|
8
|
+
return function () {};
|
|
5
9
|
}
|
|
6
10
|
|
|
7
|
-
var resultArea = document.querySelector("#search-result");
|
|
8
|
-
var searchWrapper = document.querySelector("#search-wrapper");
|
|
9
11
|
var client = algoliasearch(window.searchConfig.appId, window.searchConfig.apiKey);
|
|
10
12
|
var index = client.initIndex(window.searchConfig.indexName);
|
|
13
|
+
var active = true;
|
|
14
|
+
var cleanups = [];
|
|
11
15
|
|
|
12
16
|
function getCardHoverApi() {
|
|
13
17
|
if (typeof stellar === 'undefined' || !stellar.cardHover) return null;
|
|
@@ -34,8 +38,8 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
34
38
|
return hits.filter(hit => regex.test(hit.url));
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
function displayResults(hits) {
|
|
38
|
-
var resultList =
|
|
41
|
+
function displayResults(ownerDocument, searchWrapper, resultArea, hits) {
|
|
42
|
+
var resultList = ownerDocument.createElement("ul");
|
|
39
43
|
resultList.classList.add("search-result-list", "ui-collection-adapter");
|
|
40
44
|
if (hits.length === 0) {
|
|
41
45
|
searchWrapper.classList.add('noresult');
|
|
@@ -44,16 +48,16 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
44
48
|
hits.forEach(function(hit) {
|
|
45
49
|
var contentSnippet = hit._snippetResult.content.value;
|
|
46
50
|
var title = hit.hierarchy.lvl1 || 'Untitled';
|
|
47
|
-
var item =
|
|
48
|
-
var titleSpan =
|
|
51
|
+
var item = ownerDocument.createElement("li");
|
|
52
|
+
var titleSpan = ownerDocument.createElement("span");
|
|
49
53
|
titleSpan.className = "search-result-title";
|
|
50
54
|
titleSpan.textContent = title;
|
|
51
55
|
|
|
52
|
-
var link =
|
|
53
|
-
link.className =
|
|
56
|
+
var link = ownerDocument.createElement("a");
|
|
57
|
+
link.className = ctx.ui.classes.interactiveSpotlight;
|
|
54
58
|
link.href = hit.url;
|
|
55
59
|
|
|
56
|
-
var content =
|
|
60
|
+
var content = ownerDocument.createElement("p");
|
|
57
61
|
content.className = "search-result-content";
|
|
58
62
|
content.innerHTML = contentSnippet;
|
|
59
63
|
|
|
@@ -68,46 +72,67 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
68
72
|
mountResultCards(resultList);
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
var
|
|
73
|
-
var
|
|
75
|
+
searchWrappers.forEach(function(searchWrapper) {
|
|
76
|
+
var inputArea = searchWrapper.querySelector('.search-input');
|
|
77
|
+
var resultArea = searchWrapper.querySelector('.search-result');
|
|
78
|
+
if (!inputArea || !resultArea) return;
|
|
79
|
+
var ownerDocument = inputArea.ownerDocument || document;
|
|
80
|
+
var onInput = function() {
|
|
81
|
+
var query = inputArea.value.trim();
|
|
82
|
+
var filterPath = inputArea.getAttribute('data-algolia-filter-path');
|
|
83
|
+
|
|
84
|
+
if (query.length <= 0) {
|
|
85
|
+
searchWrapper.setAttribute('searching', 'false');
|
|
86
|
+
unmountResultCards(resultArea);
|
|
87
|
+
resultArea.replaceChildren();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
74
90
|
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
searchWrapper.setAttribute('searching', 'true');
|
|
92
|
+
|
|
93
|
+
index.search(query, {
|
|
94
|
+
hitsPerPage: window.searchConfig.hitsPerPage,
|
|
95
|
+
attributesToHighlight: ['content'],
|
|
96
|
+
attributesToSnippet: ['content:30'],
|
|
97
|
+
highlightPreTag: '<span class="search-keyword">',
|
|
98
|
+
highlightPostTag: '</span>',
|
|
99
|
+
restrictSearchableAttributes: ['content']
|
|
100
|
+
}).then(function(responses) {
|
|
101
|
+
if (active) displayResults(ownerDocument, searchWrapper, resultArea, filterResults(responses.hits, filterPath));
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
var onKeydown = function(e) {
|
|
106
|
+
if (e.key == 'Enter') e.preventDefault();
|
|
107
|
+
};
|
|
108
|
+
inputArea.addEventListener("input", onInput);
|
|
109
|
+
inputArea.addEventListener("keydown", onKeydown);
|
|
110
|
+
|
|
111
|
+
var observer = new MutationObserver(function(mutationsList) {
|
|
112
|
+
if (mutationsList.length === 1) {
|
|
113
|
+
if (mutationsList[0].addedNodes.length) {
|
|
114
|
+
searchWrapper.classList.remove('noresult');
|
|
115
|
+
} else if (mutationsList[0].removedNodes.length) {
|
|
116
|
+
searchWrapper.classList.add('noresult');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
observer.observe(resultArea, { childList: true });
|
|
121
|
+
cleanups.push(function() {
|
|
122
|
+
inputArea.removeEventListener("input", onInput);
|
|
123
|
+
inputArea.removeEventListener("keydown", onKeydown);
|
|
124
|
+
observer.disconnect();
|
|
77
125
|
unmountResultCards(resultArea);
|
|
78
|
-
resultArea.replaceChildren();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
searchWrapper.setAttribute('searching', 'true');
|
|
83
|
-
|
|
84
|
-
index.search(query, {
|
|
85
|
-
hitsPerPage: window.searchConfig.hitsPerPage,
|
|
86
|
-
attributesToHighlight: ['content'],
|
|
87
|
-
attributesToSnippet: ['content:30'],
|
|
88
|
-
highlightPreTag: '<span class="search-keyword">',
|
|
89
|
-
highlightPostTag: '</span>',
|
|
90
|
-
restrictSearchableAttributes: ['content']
|
|
91
|
-
}).then(function(responses) {
|
|
92
|
-
displayResults(filterResults(responses.hits, filterPath));
|
|
93
126
|
});
|
|
94
127
|
});
|
|
95
128
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else if (mutationsList[0].removedNodes.length) {
|
|
107
|
-
searchWrapper.classList.add('noresult');
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
observer.observe(resultArea, { childList: true });
|
|
113
|
-
});
|
|
129
|
+
var cleanup = function() {
|
|
130
|
+
active = false;
|
|
131
|
+
cleanups.forEach(function(dispose) { dispose(); });
|
|
132
|
+
roots.delete(root);
|
|
133
|
+
};
|
|
134
|
+
roots.set(root, cleanup);
|
|
135
|
+
return cleanup;
|
|
136
|
+
}
|
|
137
|
+
window.stellarAlgoliaSearch = { mount: mount };
|
|
138
|
+
})();
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
function highlightKeyword() {
|
|
17
17
|
// 优先主栏正文(避免命中 wiki 封面等其它 md-text 容器)
|
|
18
|
-
var root = document.querySelector('.
|
|
18
|
+
var root = document.querySelector('.site-main .md-text') || document.querySelector('.md-text');
|
|
19
19
|
if (!root) return;
|
|
20
20
|
|
|
21
21
|
var re = new RegExp(escapeRegExp(keyword), 'gi');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var searchCache = null; // 当前可用的搜索数据(数组)
|
|
2
2
|
var searchCacheEntry = null; // 最近读取/写入的缓存条目 { ts, ttl, data }
|
|
3
|
-
var searchCacheKey = '
|
|
3
|
+
var searchCacheKey = 'search_cache_v5';
|
|
4
4
|
var searchFetchPromise = null; // 单飞:同一时刻只允许一个请求
|
|
5
5
|
|
|
6
6
|
var searchLazyLoad = true;
|
|
@@ -65,6 +65,18 @@ function loadCacheIntoMemory() {
|
|
|
65
65
|
return searchCacheEntry;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function clearSearchCache() {
|
|
69
|
+
searchCache = null;
|
|
70
|
+
searchCacheEntry = null;
|
|
71
|
+
searchFetchPromise = null;
|
|
72
|
+
try {
|
|
73
|
+
localStorage.removeItem(searchCacheKey);
|
|
74
|
+
return { ok: true, partial: false, removed: 1, failed: 0 };
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return { ok: false, partial: false, removed: 0, failed: 1 };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
// 是否需要发起请求:不缓存 / 无缓存 / 缓存过期
|
|
69
81
|
function needsFetch() {
|
|
70
82
|
if (searchCacheTtl <= 0) return true;
|
|
@@ -96,10 +108,12 @@ function fetchSearchData(path) {
|
|
|
96
108
|
return searchFetchPromise;
|
|
97
109
|
}
|
|
98
110
|
|
|
99
|
-
var searchFunc = function(path,
|
|
100
|
-
|
|
111
|
+
var searchFunc = function(path, wrapperId, searchId, contentId, root) {
|
|
112
|
+
root = root || document;
|
|
113
|
+
var $input = root.querySelector('#' + searchId);
|
|
101
114
|
if (!$input || $input._searchInitialized === true) return;
|
|
102
115
|
if ($input._searchInitialized === 'pending') return; // 数据加载中,等待完成后初始化
|
|
116
|
+
var ownerDocument = $input.ownerDocument || document;
|
|
103
117
|
|
|
104
118
|
function getAllCombinations(keywords) {
|
|
105
119
|
const result = [];
|
|
@@ -150,20 +164,20 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
150
164
|
|
|
151
165
|
// 构建单条搜索结果 DOM
|
|
152
166
|
function buildResultElement(dataTitle, sectionName, secText, secFirst, pairs, href) {
|
|
153
|
-
const li =
|
|
167
|
+
const li = ownerDocument.createElement('li');
|
|
154
168
|
|
|
155
169
|
// 文章标题位于链接上方,不参与跳转
|
|
156
|
-
const titleSpan =
|
|
170
|
+
const titleSpan = ownerDocument.createElement('span');
|
|
157
171
|
titleSpan.className = 'search-result-title';
|
|
158
172
|
titleSpan.textContent = dataTitle;
|
|
159
173
|
li.appendChild(titleSpan);
|
|
160
174
|
|
|
161
|
-
const a =
|
|
162
|
-
a.className =
|
|
175
|
+
const a = ownerDocument.createElement('a');
|
|
176
|
+
a.className = ctx.ui.classes.interactiveSpotlight;
|
|
163
177
|
a.href = href;
|
|
164
178
|
|
|
165
179
|
if (sectionName) {
|
|
166
|
-
const sectionSpan =
|
|
180
|
+
const sectionSpan = ownerDocument.createElement('span');
|
|
167
181
|
sectionSpan.className = 'search-result-section';
|
|
168
182
|
sectionSpan.textContent = sectionName;
|
|
169
183
|
a.appendChild(sectionSpan);
|
|
@@ -180,7 +194,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
180
194
|
return "<span class=\"search-keyword\">" + keyword + "</span>";
|
|
181
195
|
});
|
|
182
196
|
|
|
183
|
-
const para =
|
|
197
|
+
const para = ownerDocument.createElement('p');
|
|
184
198
|
para.className = 'search-result-content';
|
|
185
199
|
para.innerHTML = matchContent + '...';
|
|
186
200
|
a.appendChild(para);
|
|
@@ -195,10 +209,10 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
195
209
|
if ($input._searchInitialized === true) return; // 防止重复绑定
|
|
196
210
|
$input._searchInitialized = true;
|
|
197
211
|
|
|
198
|
-
var $resultContent =
|
|
199
|
-
var $wrapper =
|
|
212
|
+
var $resultContent = root.querySelector('#' + contentId);
|
|
213
|
+
var $wrapper = root.querySelector('#' + wrapperId);
|
|
200
214
|
|
|
201
|
-
|
|
215
|
+
var onInput = function() {
|
|
202
216
|
var rawValue = this.value.trim();
|
|
203
217
|
unmountResultCards($resultContent);
|
|
204
218
|
$resultContent.innerHTML = "";
|
|
@@ -211,10 +225,11 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
211
225
|
var pairs = buildKeywordPairs(this.value);
|
|
212
226
|
var resultList = [];
|
|
213
227
|
|
|
214
|
-
// 读取模块级 searchCache
|
|
228
|
+
// 读取模块级 searchCache:后台刷新后即时生效;搜索域由每次打开浮层的入口更新。
|
|
229
|
+
var activeDomain = this.getAttribute('data-domain') || '';
|
|
215
230
|
(searchCache || []).forEach(function(data) {
|
|
216
231
|
if (!data.content?.trim().length) return;
|
|
217
|
-
if (
|
|
232
|
+
if (activeDomain && !data.domains?.includes(activeDomain)) return;
|
|
218
233
|
|
|
219
234
|
var dataTitle = data.title?.trim() || 'Untitled';
|
|
220
235
|
var dataTitleLowerCase = dataTitle.toLowerCase();
|
|
@@ -296,7 +311,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
296
311
|
return b.rank - a.rank || a.offset - b.offset;
|
|
297
312
|
});
|
|
298
313
|
|
|
299
|
-
const ul =
|
|
314
|
+
const ul = ownerDocument.createElement('ul');
|
|
300
315
|
ul.className = 'search-result-list ui-collection-adapter';
|
|
301
316
|
resultList.forEach(function(item) {
|
|
302
317
|
ul.appendChild(item.element);
|
|
@@ -306,13 +321,21 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
306
321
|
$resultContent.appendChild(ul);
|
|
307
322
|
mountResultCards(ul);
|
|
308
323
|
}
|
|
309
|
-
}
|
|
324
|
+
};
|
|
310
325
|
|
|
311
|
-
|
|
326
|
+
var onKeydown = function(e) {
|
|
312
327
|
if (e.key == 'Enter') {
|
|
313
328
|
e.preventDefault();
|
|
314
329
|
}
|
|
315
|
-
}
|
|
330
|
+
};
|
|
331
|
+
$input.addEventListener("input", onInput);
|
|
332
|
+
$input.addEventListener("keydown", onKeydown);
|
|
333
|
+
$input._searchCleanup = function() {
|
|
334
|
+
$input.removeEventListener("input", onInput);
|
|
335
|
+
$input.removeEventListener("keydown", onKeydown);
|
|
336
|
+
$input._searchInitialized = undefined;
|
|
337
|
+
$input._searchCleanup = undefined;
|
|
338
|
+
};
|
|
316
339
|
|
|
317
340
|
// 同步当前状态:已有文字立即执行一次搜索,否则清除加载态
|
|
318
341
|
if ($input.value && $input.value.trim().length > 0) {
|
|
@@ -345,7 +368,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
345
368
|
if ($input._searchInitialized === 'pending') {
|
|
346
369
|
$input._searchInitialized = undefined; // 允许下次聚焦重试
|
|
347
370
|
}
|
|
348
|
-
var $wrapper =
|
|
371
|
+
var $wrapper = root.querySelector('#' + wrapperId);
|
|
349
372
|
if ($wrapper) $wrapper.setAttribute('searching', 'false');
|
|
350
373
|
});
|
|
351
374
|
};
|
|
@@ -359,43 +382,59 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
359
382
|
})();
|
|
360
383
|
|
|
361
384
|
// 聚焦触发:懒加载模式下首次聚焦搜索框才加载数据
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
385
|
+
var localSearchRoots = new WeakMap();
|
|
386
|
+
|
|
387
|
+
function mountLocalSearch(root) {
|
|
388
|
+
root = root || document;
|
|
389
|
+
if (localSearchRoots.has(root)) return localSearchRoots.get(root);
|
|
390
|
+
var wrappers = Array.from(root.querySelectorAll('.search-wrapper'));
|
|
391
|
+
var observers = [];
|
|
392
|
+
var onFocus = function(e) {
|
|
393
|
+
var input = e.target;
|
|
394
|
+
if (!input || !input.classList?.contains('search-input')) return;
|
|
395
|
+
var wrapper = input.closest('.search-wrapper');
|
|
396
|
+
if (!wrapper) return;
|
|
397
|
+
var resultArea = wrapper.querySelector('.search-result');
|
|
398
|
+
if (!resultArea) return;
|
|
399
|
+
var path = getSearchPath();
|
|
400
|
+
if (input._searchInitialized === true) {
|
|
401
|
+
if (needsFetch()) fetchSearchData(path).catch(function() {});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (searchLazyLoad && !searchCache && !loadCacheIntoMemory()) {
|
|
405
|
+
wrapper.setAttribute('searching', 'true');
|
|
406
|
+
}
|
|
407
|
+
searchFunc(path, wrapper.id, input.id, resultArea.id, root);
|
|
408
|
+
if (searchCache && needsFetch()) fetchSearchData(path).catch(function() {});
|
|
409
|
+
};
|
|
410
|
+
root.addEventListener("focusin", onFocus);
|
|
411
|
+
wrappers.forEach(function(wrapper) {
|
|
412
|
+
var resultArea = wrapper.querySelector('.search-result');
|
|
413
|
+
var input = wrapper.querySelector('.search-input');
|
|
414
|
+
if (!resultArea || !input) return;
|
|
415
|
+
var observer = new MutationObserver(function() {
|
|
416
|
+
var hasResults = resultArea.querySelector(".search-result-list li");
|
|
417
|
+
wrapper.classList.toggle('noresult', !hasResults);
|
|
418
|
+
});
|
|
419
|
+
observer.observe(resultArea, { childList: true, subtree: true });
|
|
420
|
+
observers.push(observer);
|
|
421
|
+
if (!searchLazyLoad && input._searchInitialized !== true) {
|
|
422
|
+
searchFunc(getSearchPath(), wrapper.id, input.id, resultArea.id, root);
|
|
372
423
|
}
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// 懒加载:无可用数据时显示加载态(复用 searching 绿色图标状态)
|
|
377
|
-
if (searchLazyLoad && !searchCache && !loadCacheIntoMemory()) {
|
|
378
|
-
var $wrapper = document.getElementById('search-wrapper');
|
|
379
|
-
if ($wrapper) $wrapper.setAttribute('searching', 'true');
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
searchFunc(path, filter, 'search-wrapper', 'search-input', 'search-result');
|
|
383
|
-
|
|
384
|
-
// 有数据但已过期(或 ttl=0)→ 后台刷新
|
|
385
|
-
if (searchCache && needsFetch()) {
|
|
386
|
-
fetchSearchData(path).catch(function() {});
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// 无结果/有结果状态兜底
|
|
391
|
-
(function() {
|
|
392
|
-
var resultArea = document.querySelector("div#search-result");
|
|
393
|
-
if (!resultArea) return;
|
|
394
|
-
|
|
395
|
-
var observer = new MutationObserver(function(mutationsList) {
|
|
396
|
-
var hasResults = resultArea.querySelector(".search-result-list li");
|
|
397
|
-
var wrapper = document.querySelector('.search-wrapper');
|
|
398
|
-
if (wrapper) wrapper.classList.toggle('noresult', !hasResults);
|
|
399
424
|
});
|
|
400
|
-
|
|
401
|
-
|
|
425
|
+
var cleanup = function() {
|
|
426
|
+
root.removeEventListener("focusin", onFocus);
|
|
427
|
+
observers.forEach(function(observer) { observer.disconnect(); });
|
|
428
|
+
wrappers.forEach(function(wrapper) {
|
|
429
|
+
var input = wrapper.querySelector('.search-input');
|
|
430
|
+
if (!input) return;
|
|
431
|
+
input._searchCleanup?.();
|
|
432
|
+
input._searchInitialized = undefined;
|
|
433
|
+
});
|
|
434
|
+
localSearchRoots.delete(root);
|
|
435
|
+
};
|
|
436
|
+
localSearchRoots.set(root, cleanup);
|
|
437
|
+
return cleanup;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
window.stellarLocalSearch = { mount: mountLocalSearch, clearCache: clearSearchCache };
|