diff --git a/Cargo.lock b/Cargo.lock index ee614965ff..d149ac6ab8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -138,6 +149,9 @@ name = "arbitrary" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arg_enum_proc_macro" @@ -592,6 +606,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -751,6 +774,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.50" @@ -945,6 +978,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "content_disposition" version = "0.4.0" @@ -1242,6 +1281,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "deranged" version = "0.5.4" @@ -1262,6 +1307,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1310,6 +1366,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -2233,6 +2290,7 @@ dependencies = [ "dioxus-docs-examples", "dioxus-search", "dioxus-web", + "docsrs-search", "futures", "futures-util", "getrandom 0.2.16", @@ -2344,6 +2402,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docsrs-search" +version = "0.1.0" +dependencies = [ + "dioxus-search", + "reqwest", + "tempdir", + "walkdir", + "zip", +] + [[package]] name = "document-features" version = "0.2.11" @@ -2642,6 +2711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2729,6 +2799,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futf" version = "0.1.5" @@ -3377,6 +3453,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -3791,6 +3876,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "internment" version = "0.8.6" @@ -4014,6 +4108,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + [[package]] name = "libc" version = "0.2.177" @@ -4050,6 +4150,26 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "liblzma" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libredox" version = "0.1.10" @@ -4089,6 +4209,15 @@ dependencies = [ "x11", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -5021,6 +5150,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -5337,6 +5476,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -5638,6 +5783,19 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -5703,6 +5861,21 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -5830,6 +6003,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5879,6 +6061,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.12.24" @@ -5890,6 +6081,7 @@ dependencies = [ "cookie", "cookie_store", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", @@ -6892,6 +7084,16 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -8686,6 +8888,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] [[package]] name = "zerotrie" @@ -8720,6 +8936,79 @@ dependencies = [ "syn 2.0.107", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "liblzma", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 1b2adeccfe..79d80a4803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "packages/search/search", "packages/search/search-macro", "packages/search/search-shared", + "packages/docsrs-search", # Utilities "packages/notion-to-blog", diff --git a/packages/docsite/Cargo.toml b/packages/docsite/Cargo.toml index cbcb9037ac..1bbbac3830 100644 --- a/packages/docsite/Cargo.toml +++ b/packages/docsite/Cargo.toml @@ -63,6 +63,7 @@ automod = "1.0.13" stork-lib = { workspace = true, features = [ "build-v3", ], default-features = false } +docsrs-search = { version = "0.1.0", path = "../docsrs-search", optional = true } [features] default = [] @@ -79,6 +80,7 @@ server = [ "dioxus/ssr", "dioxus-docs-examples/server", "dep:askama_escape", + "dep:docsrs-search" ] production = [ "dioxus-docs-examples/production", diff --git a/packages/docsite/src/components.rs b/packages/docsite/src/components.rs index b35e68dfd2..b652fd79c1 100644 --- a/packages/docsite/src/components.rs +++ b/packages/docsite/src/components.rs @@ -16,7 +16,9 @@ pub mod notfound; pub use notfound::*; // pub mod playground; // pub use playground::*; +#[cfg(feature = "server")] pub mod search; +#[cfg(feature = "server")] pub use search::*; pub mod component_demo; pub use component_demo::*; diff --git a/packages/docsite/src/components/nav.rs b/packages/docsite/src/components/nav.rs index 357374de08..5c2e8372ce 100644 --- a/packages/docsite/src/components/nav.rs +++ b/packages/docsite/src/components/nav.rs @@ -174,7 +174,32 @@ fn LinkList() -> Element { } } -type Results = Result>, stork_lib::SearchError>; +struct MergedSearchIndex { + docsrs: dioxus_search::SearchIndex, + dioxus: dioxus_search::SearchIndex, +} + +impl MergedSearchIndex { + fn search( + &self, + query: &str, + ) -> Result>, stork_lib::SearchError> { + let docsrs_results = self.docsrs.search(query)?; + let dioxus_results = self.dioxus.search(query)?; + + // Take the first 5 results from dioxus and then append up to 5 results from docsrs + let merged_results = dioxus_results + .into_iter() + .take(5) + .map(|result| result.map(|route| route.to_string())) + .chain(docsrs_results.into_iter().take(5)) + .collect(); + + Ok(merged_results) + } +} + +type SearchItems = Result>, stork_lib::SearchError>; fn SearchModal() -> Element { let mut search_text = use_signal(String::new); @@ -198,28 +223,35 @@ fn SearchModal() -> Element { _ => "0_7", }; - let url = format!("{url_base}/index_searchable_{docs_index_version}.bin"); + let docsrs_url = format!("{url_base}/index_docsrs_{docs_index_version}.bin"); + let data = reqwest::get(docsrs_url).await.ok()?.bytes().await.ok()?; - let data = reqwest::get(url).await.ok()?.bytes().await.ok()?; + let (bytes, _) = + dioxus_search::yazi::decompress(&data, dioxus_search::yazi::Format::Zlib).ok()?; + + let docsrs: dioxus_search::SearchIndex = + dioxus_search::SearchIndex::from_bytes(format!("docsrs_{docs_index_version}"), bytes); + + let dioxus_url = format!("{url_base}/index_searchable_{docs_index_version}.bin"); + + let data = reqwest::get(dioxus_url).await.ok()?.bytes().await.ok()?; let (bytes, _) = dioxus_search::yazi::decompress(&data, dioxus_search::yazi::Format::Zlib).ok()?; - let index: dioxus_search::SearchIndex = + let dioxus: dioxus_search::SearchIndex = dioxus_search::SearchIndex::from_bytes("search", bytes); - Some(index) + Some(MergedSearchIndex { docsrs, dioxus }) }); let search = move || { let query = &search_text.read(); - let mut results = search_index + search_index .value() .as_ref() .and_then(|search| search.as_ref().map(|s| s.search(query))) - .unwrap_or_else(|| Ok(vec![])); - - results + .unwrap_or_else(|| Ok(vec![])) }; let mut results = use_signal(search); @@ -301,7 +333,7 @@ fn SearchModal() -> Element { } #[component] -fn SearchResults(results: Signal, search_text: Signal) -> Element { +fn SearchResults(results: Signal, search_text: Signal) -> Element { if let Err(err) = results.read().as_ref() { return rsx! { div { class: "text-red-500", "{err}" } @@ -310,59 +342,53 @@ fn SearchResults(results: Signal, search_text: Signal) -> Eleme let _results = results.read(); let results = _results.deref().as_ref().unwrap(); - let cur_route = use_route::(); - let results = results - .iter() - .filter(|route| match route.route { - Route::Docs03 { .. } => matches!(cur_route, Route::Docs03 { .. }), - Route::Docs04 { .. } => matches!(cur_route, Route::Docs04 { .. }), - Route::Docs05 { .. } => matches!(cur_route, Route::Docs05 { .. }), - Route::Docs06 { .. } => matches!(cur_route, Route::Docs06 { .. }), - _ => { - !matches!(cur_route, Route::Docs03 { .. }) - && !matches!(cur_route, Route::Docs04 { .. }) - && !matches!(cur_route, Route::Docs05 { .. }) - && !matches!(cur_route, Route::Docs06 { .. }) - } - }) - .collect::>(); - - use crate::docs::router_06::BookRoute; + // let cur_route = use_route::(); + // let results = results + // .iter() + // .filter(|route| match route.route { + // Route::Docs03 { .. } => matches!(cur_route, Route::Docs03 { .. }), + // Route::Docs04 { .. } => matches!(cur_route, Route::Docs04 { .. }), + // Route::Docs05 { .. } => matches!(cur_route, Route::Docs05 { .. }), + // Route::Docs06 { .. } => matches!(cur_route, Route::Docs06 { .. }), + // _ => { + // !matches!(cur_route, Route::Docs03 { .. }) + // && !matches!(cur_route, Route::Docs04 { .. }) + // && !matches!(cur_route, Route::Docs05 { .. }) + // && !matches!(cur_route, Route::Docs06 { .. }) + // } + // }) + // .collect::>(); + + use crate::docs::router_07::BookRoute; let default_searches = [ ( "Tutorial", - BookRoute::GuideIndex { + BookRoute::Index { section: Default::default(), }, ), ( "Web", - BookRoute::GuidesWebIndex { + BookRoute::GuidesPlatformsWeb { section: Default::default(), }, ), ( - "Desktop", - BookRoute::GuidesDesktopIndex { - section: Default::default(), - }, - ), - ( - "Mobile", - BookRoute::GuidesMobileIndex { + "Fullstack", + BookRoute::EssentialsFullstackIndex { section: Default::default(), }, ), ( - "Fullstack", - BookRoute::GuidesFullstackIndex { + "Desktop", + BookRoute::GuidesPlatformsDesktop { section: Default::default(), }, ), ( - "Typesafe Routing", - BookRoute::RouterReferenceIndex { + "Mobile", + BookRoute::GuidesPlatformsMobile { section: Default::default(), }, ), @@ -385,7 +411,7 @@ fn SearchResults(results: Signal, search_text: Signal) -> Eleme title: result.title.clone(), route: result.route.clone(), span { class: "mt-1", - for segment in result.excerpts.first().unwrap().text.iter() { + for segment in result.excerpts.iter().take(1).flat_map(|excerpt| excerpt.text.iter()) { if segment.highlighted { span { class: "text-blue-500", "{segment.text}" } } else { @@ -401,7 +427,8 @@ fn SearchResults(results: Signal, search_text: Signal) -> Eleme } #[component] -fn SearchResultItem(title: String, route: Route, children: Element) -> Element { +fn SearchResultItem(title: String, route: String, children: Element) -> Element { + let docs_rs_route = route.starts_with("https://docs.rs/"); rsx! { li { class: "w-full rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 ease-in-out", Link { @@ -412,8 +439,15 @@ fn SearchResultItem(title: String, route: Route, children: Element) -> Element { class: "flex flex-row items-center gap-x-2 p-2", div { class: "flex flex-col mt-1 mb-1", span { class: "flex flex-row items-center gap-x-1", - icons::DocumentIcon {} + if docs_rs_route { + icons::CratesIOIcon {} + } else { + icons::DocumentIcon {} + } h2 { class: "dark:text-white ml-1", "{title}" } + if docs_rs_route { + icons::ExternalLinkIcon2 {} + } } {children} } diff --git a/packages/docsite/src/components/search.rs b/packages/docsite/src/components/search.rs index 604460e62a..ddf48de7fe 100644 --- a/packages/docsite/src/components/search.rs +++ b/packages/docsite/src/components/search.rs @@ -1,29 +1,30 @@ pub fn generate_search_index() { - #[cfg(not(target_arch = "wasm32"))] - { - use crate::{static_dir, Route}; + use crate::{static_dir, Route}; - std::env::set_var("CARGO_MANIFEST_DIR", static_dir().join("assets")); - let version_filter: [(&str, fn(&Route) -> bool); 5] = [ - ("0_3", |route| matches!(route, Route::Docs03 { .. })), - ("0_4", |route| matches!(route, Route::Docs04 { .. })), - ("0_5", |route| matches!(route, Route::Docs05 { .. })), - ("0_6", |route| matches!(route, Route::Docs06 { .. })), - ("0_7", |route| matches!(route, Route::Docs07 { .. })), - ]; - for (version, filter) in version_filter { - dioxus_search::SearchIndex::::create( - format!("searchable_{version}"), - dioxus_search::BaseDirectoryMapping::new(static_dir()).map(|route| { - filter(&route).then(|| { - let route = route.to_string(); - println!("route: {route}"); - let (route, _) = route.split_once('#').unwrap_or((&route, "")); - let (route, _) = route.split_once('?').unwrap_or((&route, "")); - std::path::PathBuf::from(route).join("index.html") - }) - }), - ); - } + let out_dir = static_dir().join("assets"); + // Build the docsrs search index + docsrs_search::build_into(&out_dir); + + std::env::set_var("CARGO_MANIFEST_DIR", &out_dir); + let version_filter: &[(&str, fn(&Route) -> bool)] = &[ + ("0_3", |route| matches!(route, Route::Docs03 { .. })), + ("0_4", |route| matches!(route, Route::Docs04 { .. })), + ("0_5", |route| matches!(route, Route::Docs05 { .. })), + ("0_6", |route| matches!(route, Route::Docs06 { .. })), + ("0_7", |route| matches!(route, Route::Docs07 { .. })), + ]; + for (version, filter) in version_filter.iter().copied() { + dioxus_search::SearchIndex::::create( + format!("searchable_{version}"), + dioxus_search::BaseDirectoryMapping::new(static_dir()).map(|route| { + filter(&route).then(|| { + let route = route.to_string(); + println!("route: {route}"); + let (route, _) = route.split_once('#').unwrap_or((&route, "")); + let (route, _) = route.split_once('?').unwrap_or((&route, "")); + std::path::PathBuf::from(route).join("index.html") + }) + }), + ); } } diff --git a/packages/docsite/src/icons.rs b/packages/docsite/src/icons.rs index 564b5873ca..c6357a8a47 100644 --- a/packages/docsite/src/icons.rs +++ b/packages/docsite/src/icons.rs @@ -119,6 +119,22 @@ pub(crate) fn DocumentIcon() -> Element { } } +pub(crate) fn CratesIOIcon() -> Element { + rsx! { + svg { + "viewBox": "0 0 576 512", + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + path { + d: "M290.8 48.6l78.4 29.7L288 109.5 206.8 78.3l78.4-29.7c1.8-.7 3.8-.7 5.7 0zM136 92.5l0 112.2c-1.3 .4-2.6 .8-3.9 1.3l-96 36.4C14.4 250.6 0 271.5 0 294.7L0 413.9c0 22.2 13.1 42.3 33.5 51.3l96 42.2c14.4 6.3 30.7 6.3 45.1 0L288 457.5l113.5 49.9c14.4 6.3 30.7 6.3 45.1 0l96-42.2c20.3-8.9 33.5-29.1 33.5-51.3l0-119.1c0-23.3-14.4-44.1-36.1-52.4l-96-36.4c-1.3-.5-2.6-.9-3.9-1.3l0-112.2c0-23.3-14.4-44.1-36.1-52.4l-96-36.4c-12.8-4.8-26.9-4.8-39.7 0l-96 36.4C150.4 48.4 136 69.3 136 92.5zM392 210.6l-82.4 31.2 0-89.2L392 121l0 89.6zM154.8 250.9l78.4 29.7L152 311.7 70.8 280.6l78.4-29.7c1.8-.7 3.8-.7 5.7 0zm18.8 204.4l0-100.5L256 323.2l0 95.9-82.4 36.2zM421.2 250.9c1.8-.7 3.8-.7 5.7 0l78.4 29.7L424 311.7l-81.2-31.1 78.4-29.7zM523.2 421.2l-77.6 34.1 0-100.5L528 323.2l0 90.7c0 3.2-1.9 6-4.8 7.3z", + fill: "currentColor", + fill_rule: "nonzero", + } + } + } +} + pub(crate) fn ExternalLinkIcon2() -> Element { rsx! { svg { diff --git a/packages/docsrs-search/Cargo.toml b/packages/docsrs-search/Cargo.toml new file mode 100644 index 0000000000..2dd5986433 --- /dev/null +++ b/packages/docsrs-search/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "docsrs-search" +version = "0.1.0" +edition = "2024" + +[dependencies] +dioxus-search.workspace = true +reqwest = { workspace = true, features = ["blocking"] } +zip = "4.3.0" +walkdir = "2" +tempdir = "0.3.7" diff --git a/packages/docsrs-search/src/lib.rs b/packages/docsrs-search/src/lib.rs new file mode 100644 index 0000000000..f909babe19 --- /dev/null +++ b/packages/docsrs-search/src/lib.rs @@ -0,0 +1,163 @@ +use std::{collections::HashMap, ffi::OsStr}; + +use dioxus_search::*; + +pub fn build_into(output_directory: &impl AsRef) { + let versions = [ + "0.7.0-alpha.2", + "0.6.3", + "0.5.6", + "0.4.3", + "0.3.2", + "0.2.4", + "0.1.8", + ]; + for version in versions { + let flat_version = version.replace('.', "_").replace('-', "_"); + let major_version = flat_version + .split('_') + .take(2) + .collect::>() + .join("_"); + let temp_dir = + tempdir::TempDir::new(&major_version).expect("Failed to create temporary directory"); + let download_directory = temp_dir.path(); + let download_directory = download_directory.join(&major_version); + let url_prefix = format!("https://docs.rs/dioxus/{version}/dioxus/"); + + println!("Fetching dioxus documentation..."); + let data = reqwest::blocking::get(format!("https://docs.rs/crate/dioxus/{version}/download")) + .expect("Failed to fetch data") + .bytes() + .expect("Failed to read bytes"); + + println!("Extracting dioxus documentation..."); + + // extract the zip file + let mut archive = + zip::ZipArchive::new(std::io::Cursor::new(data)).expect("Failed to create ZipArchive"); + + // create a directory to extract the file + std::fs::create_dir_all(&download_directory).expect("Failed to create output directory"); + + println!("Extracting files to {:?}", download_directory); + + // extract the files + archive + .extract(&download_directory) + .expect("Failed to extract zip archive"); + println!("Extraction complete!"); + + let base_dir = download_directory.join("dioxus"); + let boring_paths = vec![ + "prelude/global_attributes", + "prelude/svg_attributes", + "prelude/attributes", + "prelude/props", + "prelude/keyboard_types", + "prelude/server_fn", + "events", + "all.html", + ]; + + fn extract_item_name(path: &std::path::Path) -> Option { + let file_stem = path.file_stem()?; + let file_stem_str = file_stem.to_string_lossy(); + let prefixes = [ + ("fn.", "fn"), + ("struct.", "struct"), + ("trait.", "trait"), + ("enum.", "enum"), + ("type.", "type"), + ("constant.", "const"), + ("macro.", "macro"), + ("attr.", "attr"), + ("derive.", "derive"), + ]; + + for (prefix, ty) in &prefixes { + if let Some(name) = file_stem_str.strip_prefix(prefix) { + if *ty == "fn" { + if name.starts_with("use_") { + return Some(format!("hook {}", name)); + } else if name + .chars() + .next() + .map_or(false, |c| c.is_ascii_uppercase()) + { + return Some(format!("component {}", name)); + } + } + return Some(format!("{} {}", ty, name)); + } + } + + None + } + + // Add all html files in the base directory and its subdirectories + let files = walkdir::WalkDir::new(&base_dir) + .into_iter() + .filter_map(|entry| { + let entry = entry.expect("Failed to read directory entry"); + let path = entry.path(); + if boring_paths + .iter() + .any(|bp| path.starts_with(&base_dir.join(bp))) + { + return None; // Skip files in the boring paths + } + if entry.file_type().is_file() + && path.extension().map_or(false, |ext| ext == "html") + { + println!("Processing file: {:?}", path); + let Some(title) = extract_item_name(path) else { + println!("Skipping file without a valid title: {:?}", path); + return None; // Skip files without a valid title + }; + let path = path + .strip_prefix(&base_dir) + .expect("Failed to strip base directory") + .to_string_lossy() + .to_string(); + Some(File { + path: path, + url: format!( + "{}{}", + url_prefix, + entry + .path() + .strip_prefix(&base_dir) + .unwrap() + .to_string_lossy() + ), + title: title.to_string(), + fields: HashMap::new(), + explicit_source: None, + }) + } else { + None + } + }) + .collect::>(); + + let config = Config { + input: InputConfig { + base_directory: base_dir.to_string_lossy().to_string(), + url_prefix: url_prefix.into(), + html_selector: Some( + ".top-doc .docblock, #implementations .docblock, .methods .docblock".into(), + ), + files, + break_on_file_error: false, + minimum_indexed_substring_length: 3, + minimum_index_ideographic_substring_length: 1, + }, + }; + write_index( + &config, + format!("docsrs_{}", major_version), + &output_directory.as_ref(), + ); + } +} diff --git a/packages/search/search-shared/src/lib.rs b/packages/search/search-shared/src/lib.rs index 4083659761..92baa525ac 100644 --- a/packages/search/search-shared/src/lib.rs +++ b/packages/search/search-shared/src/lib.rs @@ -1,8 +1,5 @@ use std::{ - collections::HashMap, - fmt::{Debug, Display}, - path::PathBuf, - str::FromStr, + collections::HashMap, ffi::OsStr, fmt::{Debug, Display}, path::PathBuf, str::FromStr }; use bytes::Bytes; @@ -36,6 +33,24 @@ impl Debug for SearchIndex { } } +pub fn write_index(asset_format: &Config, name: String, output_path: &impl AsRef) -> Bytes { + let toml = toml::to_string(&asset_format).unwrap(); + let bytes = build_index(&stork_lib::Config::try_from(&*toml).unwrap()) + .unwrap() + .bytes; + + stork_lib::register_index(&format!("index_{name}"), bytes.clone()).unwrap(); + + let output_dir = std::path::Path::new(output_path).join("dioxus_search"); + let output_path = output_dir.join(format!("index_{name}.bin")); + std::fs::create_dir_all(&output_dir).unwrap(); + let compressed = + yazi::compress(&bytes, yazi::Format::Zlib, yazi::CompressionLevel::Default).unwrap(); + std::fs::write(&output_path, compressed).unwrap(); + + bytes +} + impl SearchIndex where ::Err: Display, @@ -44,12 +59,8 @@ where let name = name.as_ref().to_string(); let asset_format = Config::from_route(mapping); - let toml = toml::to_string(&asset_format).unwrap(); - let bytes = build_index(&stork_lib::Config::try_from(&*toml).unwrap()) - .unwrap() - .bytes; - - stork_lib::register_index(&format!("index_{name}"), bytes.clone()).unwrap(); + let target_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); + let bytes = write_index(&asset_format, name.clone(), &target_dir); let myself = Self { index: bytes, @@ -58,21 +69,14 @@ where _marker: std::marker::PhantomData, }; - let target_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); - - let path = format!("{}/dioxus_search/index_{}.bin", target_dir, myself.name); - std::fs::create_dir_all(std::path::Path::new(&path).parent().unwrap()).unwrap(); - let compressed = yazi::compress( - &myself.index, - yazi::Format::Zlib, - yazi::CompressionLevel::Default, - ) - .unwrap(); - std::fs::write(path, compressed).unwrap(); - myself } +} +impl SearchIndex +where + ::Err: Display, +{ pub fn from_bytes>(name: impl AsRef, bytes: T) -> Self { let name = name.as_ref().to_string(); let bytes = bytes.into(); @@ -164,13 +168,27 @@ where } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SearchResult { +pub struct SearchResult { pub route: R, pub title: String, pub excerpts: Vec, pub score: usize, } +impl SearchResult { + pub fn map(self, f: F) -> SearchResult + where + F: Fn(R) -> T, + { + SearchResult { + route: f(self.route), + title: self.title, + excerpts: self.excerpts, + score: self.score, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Excerpt { pub text: Vec, @@ -246,7 +264,7 @@ impl Config { input: InputConfig { base_directory: base_directory.to_string_lossy().into(), url_prefix: "".into(), - html_selector: Some("#main".into()), + html_selector: Some(".markdown-body".into()), files, break_on_file_error: false, minimum_indexed_substring_length: 3, @@ -259,21 +277,21 @@ impl Config { #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct InputConfig { - base_directory: String, - url_prefix: String, - html_selector: Option, - files: Vec, - break_on_file_error: bool, - minimum_indexed_substring_length: u8, - minimum_index_ideographic_substring_length: u8, + pub base_directory: String, + pub url_prefix: String, + pub html_selector: Option, + pub files: Vec, + pub break_on_file_error: bool, + pub minimum_indexed_substring_length: u8, + pub minimum_index_ideographic_substring_length: u8, } #[derive(Deserialize, Serialize)] #[serde(deny_unknown_fields)] -struct File { - path: String, - url: String, - title: String, +pub struct File { + pub path: String, + pub url: String, + pub title: String, #[serde(flatten, default)] pub fields: HashMap, #[serde(flatten)]