1use std::cell::RefCell;
2
3use wasm_bindgen::JsCast;
4
5pub(crate) fn strip_slash_suffix(path: &str) -> &str {
6 path.strip_suffix('/').unwrap_or(path)
7}
8
9static BASE_URL_LOADED: std::sync::Once = std::sync::Once::new();
10thread_local! {
11 static BASE_URL: RefCell<Option<String>> = RefCell::new(None);
12}
13
14pub fn base_url() -> Option<String> {
17 BASE_URL_LOADED.call_once(|| {
18 BASE_URL.with(|val| {
19 *val.borrow_mut() = fetch_base_url();
20 })
21 });
22 BASE_URL.with(|it| it.borrow().as_ref().map(|it| it.to_string()))
23}
24
25pub fn fetch_base_url() -> Option<String> {
26 match gloo::utils::document().query_selector("base[href]") {
27 Ok(Some(base)) => {
28 let base = base.unchecked_into::<web_sys::HtmlBaseElement>().href();
29
30 let url = web_sys::Url::new(&base).unwrap();
31 let base = url.pathname();
32
33 let base = if base != "/" {
34 strip_slash_suffix(&base)
35 } else {
36 return None;
37 };
38
39 Some(base.to_string())
40 }
41 _ => None,
42 }
43}
44
45#[cfg(target_arch = "wasm32")]
46pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
47 gloo::utils::window()
48 .location()
49 .href()
50 .ok()
51 .and_then(|base| web_sys::Url::new_with_base(pathname, &base).ok())
52 .map(|url| {
53 url.set_search(query);
54 format!("{}{}", url.pathname(), url.search())
55 })
56}
57
58#[cfg(not(target_arch = "wasm32"))]
59pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
60 let query = query.trim();
61
62 if !query.is_empty() {
63 Some(format!("{pathname}?{query}"))
64 } else {
65 Some(pathname.to_owned())
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use gloo::utils::document;
72 use wasm_bindgen_test::wasm_bindgen_test as test;
73 use yew_router::prelude::*;
74 use yew_router::utils::*;
75
76 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
77
78 #[derive(Debug, Clone, Copy, PartialEq, Routable)]
79 enum Routes {
80 #[at("/")]
81 Home,
82 #[at("/no")]
83 No,
84 #[at("/404")]
85 NotFound,
86 }
87
88 #[test]
89 fn test_base_url() {
90 document().head().unwrap().set_inner_html(r#""#);
91
92 assert_eq!(fetch_base_url(), None);
93
94 document()
95 .head()
96 .unwrap()
97 .set_inner_html(r#"<base href="/base/">"#);
98 assert_eq!(fetch_base_url(), Some("/base".to_string()));
99
100 document()
101 .head()
102 .unwrap()
103 .set_inner_html(r#"<base href="/base">"#);
104 assert_eq!(fetch_base_url(), Some("/base".to_string()));
105 }
106
107 #[test]
108 fn test_compose_path() {
109 assert_eq!(compose_path("/home", ""), Some("/home".to_string()));
110 assert_eq!(
111 compose_path("/path/to", "foo=bar"),
112 Some("/path/to?foo=bar".to_string())
113 );
114 assert_eq!(
115 compose_path("/events", "from=2019&to=2021"),
116 Some("/events?from=2019&to=2021".to_string())
117 );
118 }
119}