yggdrasil/components/
footer.rs1use leptos::prelude::*;
7
8use crate::api::settings::get_site_settings;
9use crate::components::ui::{Icon, IconName};
10use crate::hooks::ssr_resource::{route_data, unit, PageJson};
11use crate::models::settings::SiteSettings;
12use crate::routes::Route;
13
14#[leptos::lazy]
16async fn fetch_footer_settings() -> PageJson<SiteSettings> {
17 PageJson::new(get_site_settings().await)
18}
19
20#[component]
25pub fn Footer(#[prop(default = "max-w-[880px]")] max_width: &'static str) -> impl IntoView {
26 let settings_res = route_data(|| (), unit(fetch_footer_settings));
30
31 view! {
32 <footer data-vt-shell="frontend-footer" class="site-footer">
33 <div class=format!("site-footer-inner {max_width}")>
34 <span class="tabular-nums">"© 2026 Yggdrasil"</span>
35 <div class="site-footer-links">
36 <a href="/feed.xml" rel="external">
38 "RSS"
39 </a>
40 <a href=Route::Changelog.to_string()>"更新日志"</a>
41 <a href=Route::WritingGuide.to_string()>"写作指南"</a>
42 <span>"由 Rust 与 Leptos 构建"</span>
43 <Suspense>
44 {move || Suspend::new(async move {
45 let github_url = settings_res
46 .await
47 .ok()
48 .map(|s| s.github_url)
49 .filter(|u| !u.is_empty());
50 github_url.map(|url| view! {
51 <a
52 href=url
53 target="_blank"
54 rel="external noopener noreferrer"
55 aria-label="GitHub"
56 title="GitHub"
57 >
58 <Icon name=IconName::Github size=16/>
59 </a>
60 })
61 })}
62 </Suspense>
63 <button type="button" on:click=move |_| scroll_to_top()>
64 "回到顶部"
65 <Icon name=IconName::ArrowUp size=13/>
66 </button>
67 </div>
68 </div>
69 </footer>
70 }
71}
72
73fn scroll_to_top() {
77 #[cfg(target_arch = "wasm32")]
78 {
79 if let Some(window) = web_sys::window() {
80 let options = web_sys::ScrollToOptions::new();
81 options.set_top(0.0);
82 options.set_behavior(web_sys::ScrollBehavior::Smooth);
83 window.scroll_to_with_scroll_to_options(&options);
84
85 if let Ok(history) = window.history() {
86 let _ = history.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(" "));
87 }
88 }
89 }
90}