diff options
| -rw-r--r-- | locale/en.ini | 2 | ||||
| -rw-r--r-- | server/src/routes/search.rs | 45 | ||||
| -rw-r--r-- | ui/src/components/mod.rs | 1 | ||||
| -rw-r--r-- | ui/src/components/search.rs | 31 | ||||
| -rw-r--r-- | ui/src/old/search.rs | 38 |
5 files changed, 56 insertions, 61 deletions
diff --git a/locale/en.ini b/locale/en.ini index 9b0f13a..bf0e19c 100644 --- a/locale/en.ini +++ b/locale/en.ini @@ -36,7 +36,7 @@ home.bin.watch_again=Watch again home.bin.daily_random_music=Discover Music search=Search -search.placeholder=Search Term +search.placeholder=Search Terms search.results.title=Results search.results.stats=Found {count} results in {dur}. diff --git a/server/src/routes/search.rs b/server/src/routes/search.rs index 8726b8e..3254394 100644 --- a/server/src/routes/search.rs +++ b/server/src/routes/search.rs @@ -13,7 +13,7 @@ use jellycommon::{ *, }; use jellydb::{Filter, Query, Sort}; -use jellyui::components::items::Items; +use jellyui::components::search::Search; use log::info; use rocket::{get, response::content::RawHtml}; @@ -21,30 +21,31 @@ use rocket::{get, response::content::RawHtml}; pub async fn r_search(ri: RequestInfo<'_>, q: Option<&str>) -> MyResult<RawHtml<String>> { ri.require_user()?; - let q = q.unwrap(); - let mut items = Vec::new(); - let t = Instant::now(); - ri.state.database.transaction(&mut |txn| { - let rows = txn - .query(Query { - filter: Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), - sort: Sort::TextSearch(Path(vec![NO_TITLE.0]), q.to_owned()), - ..Default::default() - })? - .take(64) - .collect::<Result<Vec<_>, _>>()?; + if let Some(q) = q { + let t = Instant::now(); + ri.state.database.transaction(&mut |txn| { + let rows = txn + .query(Query { + filter: Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), + sort: Sort::TextSearch(Path(vec![NO_TITLE.0]), q.to_owned()), + ..Default::default() + })? + .take(64) + .collect::<Result<Vec<_>, _>>()?; - items.clear(); - for (r, _is) in rows { - let node = txn.get(r)?.unwrap(); - items.push(node); - } - Ok(()) - })?; - info!("search {q:?} took {:?}", t.elapsed()); + items.clear(); + for (r, _is) in rows { + let node = txn.get(r)?.unwrap(); + items.push(node); + } + Ok(()) + })?; + info!("search {q:?} took {:?}", t.elapsed()); + } - Ok(ri.respond_ui(&Items { + Ok(ri.respond_ui(&Search { + query: q.unwrap_or_default(), items: &items .iter() .map(|node| Nku { diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index 3dd43a4..0161e7b 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -15,3 +15,4 @@ pub mod node_page; pub mod props; pub mod stats; pub mod user; +pub mod search; diff --git a/ui/src/components/search.rs b/ui/src/components/search.rs new file mode 100644 index 0000000..3d320cf --- /dev/null +++ b/ui/src/components/search.rs @@ -0,0 +1,31 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{RenderInfo, components::node_card::NodeCard, page}; +use jellycommon::{Nku, routes::u_items_cont}; +use jellyui_locale::tr; + +page!(Search<'_>, |x| tr(x.ri.lang, "search")); + +markup::define! { + Search<'a>(ri: &'a RenderInfo<'a>, query: &'a str, items: &'a [Nku<'a>], cont: Option<String>) { + .search { + h1 { @tr(ri.lang, "search") } + form[action="", method="GET"] { + input[type="text", name="q", placeholder=tr(ri.lang, "search.placeholder"), value=&query]; + input[type="submit", value="Search"]; + } + } + @if !items.is_empty() { + ul.nl.grid { @for nku in *items { + li { @NodeCard { ri, nku } } + }} + @if let Some(cont) = cont { + a.next_page[href=u_items_cont(cont)] { button { "Show more" } } + } + } + } +} diff --git a/ui/src/old/search.rs b/ui/src/old/search.rs deleted file mode 100644 index 0eb34b9..0000000 --- a/ui/src/old/search.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* - This file is part of jellything (https://codeberg.org/metamuffin/jellything) - which is licensed under the GNU Affero General Public License (version 3); see /COPYING. - Copyright (C) 2026 metamuffin <metamuffin.org> -*/ - -use crate::{Page, locale::tr, node_card::NodeCard, scaffold::RenderInfo}; -use markup::DynRender; - -impl Page for SearchPage<'_> { - fn title(&self) -> String { - tr(self.ri.lang, "search.title").to_string() - } - fn class(&self) -> Option<&'static str> { - Some("search") - } - fn to_render(&self) -> DynRender<'_> { - markup::new!(@self) - } -} - -markup::define! { - SearchPage<'a>(ri: &'a RenderInfo<'a>, r: Option<ApiSearchResponse>, query: &'a Option<String>) { - h1 { @tr(ri.lang, "search.title") } - form[action="", method="GET"] { - input[type="text", name="query", placeholder=tr(ri.lang, "search.placeholder"), value=&query]; - input[type="submit", value="Search"]; - } - @if let Some(r) = &r { - h2 { @tr(ri.lang, "search.results.title") } - p.stats { @tr(ri.lang, "search.results.stats").replace("{count}", &r.count.to_string()).replace("{dur}", &format!("{:?}", r.duration)) } - ul.children {@for nodeu in r.results.iter() { - li { @NodeCard { ri, nodeu } } - }} - // TODO pagination - } - } -} |