Skip to main content

yggdrasil/components/post/
post_nav_links.rs

1//! 文章上一篇/下一篇导航组件
2//!
3//! 在文章详情页底部提供相邻文章的快速跳转。
4
5use dioxus::prelude::*;
6use dioxus::router::components::Link;
7
8use crate::models::post::PostNav;
9use crate::router::Route;
10
11/// 文章相邻导航组件。
12///
13/// Props:
14/// - `prev`:上一篇文章摘要
15/// - `next`:下一篇文章摘要
16/// - `full_reload`:链接走整页加载而非客户端路由(默认 false)。仅当本组件
17///   渲染在 admin 布局内(如 `/admin/preview`)时传 true:跨 layout 分支的
18///   客户端导航会触发 dioxus 0.7.10 的 suspense 卸载双重回收 bug(详见
19///   `src/pages/admin/preview.rs` 模块文档),整页加载可完全规避。
20/// - `sample`:图鉴使用不可导航的样例锚点,默认 false。
21///
22/// 左右两侧分别渲染上一篇/下一篇链接;窄容器中纵向排列。
23/// 无相邻文章时不渲染对应链接,两侧都缺失时不渲染导航。
24#[component]
25pub fn PostNavLinks(
26    prev: Option<PostNav>,
27    next: Option<PostNav>,
28    #[props(default = false)] full_reload: bool,
29    #[props(default = false)] sample: bool,
30) -> Element {
31    if prev.is_none() && next.is_none() {
32        return rsx! {};
33    }
34
35    // NavigationTarget::External 生成原生 <a href>(Link 不接管 preventDefault),
36    // 浏览器整页加载;内部路由变体保持客户端导航。
37    let prev_to = prev.as_ref().map(|p| {
38        super::nav_target(
39            Route::PostDetail {
40                slug: p.slug.clone(),
41            },
42            full_reload,
43        )
44    });
45    let next_to = next.as_ref().map(|p| {
46        super::nav_target(
47            Route::PostDetail {
48                slug: p.slug.clone(),
49            },
50            full_reload,
51        )
52    });
53    rsx! {
54        nav { class: "paginav", aria_label: "相邻文章",
55            if let (Some(prev_post), Some(prev_to)) = (prev, prev_to) {
56                if sample {
57                    a { class: "prev", href: "#", onclick: move |event| event.prevent_default(),
58                        span { class: "title",
59                            span { class: "post-nav-arrow", aria_hidden: "true", "←" }
60                            "上一篇"
61                        }
62                        span { class: "post-title-nav", "{prev_post.title}" }
63                    }
64                } else {
65                    Link {
66                        class: "prev",
67                        to: prev_to,
68                        span { class: "title",
69                            span { class: "post-nav-arrow", aria_hidden: "true", "←" }
70                            "上一篇"
71                        }
72                        span { class: "post-title-nav", "{prev_post.title}" }
73                    }
74                }
75            }
76
77            if let (Some(next_post), Some(next_to)) = (next, next_to) {
78                if sample {
79                    a { class: "next", href: "#", onclick: move |event| event.prevent_default(),
80                        span { class: "title",
81                            "下一篇"
82                            span { class: "post-nav-arrow", aria_hidden: "true", "→" }
83                        }
84                        span { class: "post-title-nav", "{next_post.title}" }
85                    }
86                } else {
87                    Link {
88                        class: "next",
89                        to: next_to,
90                        span { class: "title",
91                            "下一篇"
92                            span { class: "post-nav-arrow", aria_hidden: "true", "→" }
93                        }
94                        span { class: "post-title-nav", "{next_post.title}" }
95                    }
96                }
97            }
98        }
99    }
100}