From 5109ac65f44c17169f9b66ae1f85cef51e56e615 Mon Sep 17 00:00:00 2001 From: trisua <tri@swmff.org> Date: Thu, 10 Apr 2025 21:37:33 -0400 Subject: [PATCH] add: community search --- crates/app/src/assets.rs | 2 + crates/app/src/langs/en-US.toml | 4 ++ crates/app/src/public/css/style.css | 4 ++ .../app/src/public/html/communities/list.html | 26 ++++++++--- .../src/public/html/communities/search.html | 45 +++++++++++++++++++ crates/app/src/public/html/components.html | 12 +++-- .../app/src/public/html/timelines/home.html | 10 ++++- crates/app/src/routes/pages/communities.rs | 40 ++++++++++++++++- crates/app/src/routes/pages/mod.rs | 9 ++++ crates/core/src/database/communities.rs | 27 +++++++++++ 10 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 crates/app/src/public/html/communities/search.html diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 43c8fd1..1b348b3 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -52,6 +52,7 @@ pub const COMMUNITIES_FEED: &str = include_str!("./public/html/communities/feed. pub const COMMUNITIES_POST: &str = include_str!("./public/html/communities/post.html"); pub const COMMUNITIES_SETTINGS: &str = include_str!("./public/html/communities/settings.html"); pub const COMMUNITIES_MEMBERS: &str = include_str!("./public/html/communities/members.html"); +pub const COMMUNITIES_SEARCH: &str = include_str!("./public/html/communities/search.html"); pub const TIMELINES_HOME: &str = include_str!("./public/html/timelines/home.html"); pub const TIMELINES_FOLLOWING: &str = include_str!("./public/html/timelines/following.html"); @@ -180,6 +181,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"communities/post.html"(crate::assets::COMMUNITIES_POST) --config=config); write_template!(html_path->"communities/settings.html"(crate::assets::COMMUNITIES_SETTINGS) --config=config); write_template!(html_path->"communities/members.html"(crate::assets::COMMUNITIES_MEMBERS) --config=config); + write_template!(html_path->"communities/search.html"(crate::assets::COMMUNITIES_SEARCH) --config=config); write_template!(html_path->"timelines/home.html"(crate::assets::TIMELINES_HOME) -d "timelines" --config=config); write_template!(html_path->"timelines/following.html"(crate::assets::TIMELINES_FOLLOWING) --config=config); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index 839bf95..0713af7 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -80,6 +80,10 @@ version = "1.0.0" "communities:label.repost" = "Repost" "communities:label.quote_post" = "Quote post" "communities:label.expand_original" = "Expand original" +"communities:label.search" = "Search" +"communities:label.search_results" = "Search results" +"communities:label.query" = "Query" +"communities:label.join_new" = "Join new" "notifs:action.mark_as_read" = "Mark as read" "notifs:action.mark_as_unread" = "Mark as unread" diff --git a/crates/app/src/public/css/style.css b/crates/app/src/public/css/style.css index 442b1f4..bf366ef 100644 --- a/crates/app/src/public/css/style.css +++ b/crates/app/src/public/css/style.css @@ -157,6 +157,10 @@ p { margin-bottom: 0; } +.no_p_margin img { + display: block !important; +} + .name { max-width: 250px; overflow: hidden; diff --git a/crates/app/src/public/html/communities/list.html b/crates/app/src/public/html/communities/list.html index 8a6191b..31f0b66 100644 --- a/crates/app/src/public/html/communities/list.html +++ b/crates/app/src/public/html/communities/list.html @@ -33,9 +33,16 @@ {% endif %} <div class="card-nest w-full"> - <div class="card small flex items-center gap-2"> - {{ icon "award" }} - <span>{{ text "communities:label.my_communities" }}</span> + <div class="card small flex items-center justify-between gap-2"> + <div class="flex items-center gap-2"> + {{ icon "award" }} + <span>{{ text "communities:label.my_communities" }}</span> + </div> + + <a href="/communities/search" class="button quaternary small"> + {{ icon "search" }} + <span>{{ text "communities:label.join_new" }}</span> + </a> </div> <div class="card flex flex-col gap-2"> @@ -45,9 +52,16 @@ </div> <div class="card-nest w-full"> - <div class="card small flex items-center gap-2"> - {{ icon "trending-up" }} - <span>{{ text "communities:label.popular_communities" }}</span> + <div class="card small flex items-center justify-between gap-2"> + <div class="flex items-center gap-2"> + {{ icon "trending-up" }} + <span>{{ text "communities:label.popular_communities" }}</span> + </div> + + <a href="/communities/search" class="button quaternary small"> + {{ icon "search" }} + <span>{{ text "communities:label.search" }}</span> + </a> </div> <div class="card flex flex-col gap-2"> diff --git a/crates/app/src/public/html/communities/search.html b/crates/app/src/public/html/communities/search.html new file mode 100644 index 0000000..d54cb15 --- /dev/null +++ b/crates/app/src/public/html/communities/search.html @@ -0,0 +1,45 @@ +{% extends "root.html" %} {% block head %} +<title>Search communities - {{ config.name }}</title> +{% endblock %} {% block body %} {{ macros::nav(selected="communities") }} +<main class="flex flex-col gap-2"> + <div class="card-nest"> + <div class="card small flex items-center gap-2"> + {{ icon "search" }} + <span>{{ text "communities:label.search" }}</span> + </div> + + <form class="card flex flex-col gap-4"> + <div class="flex flex-col gap-1"> + <label for="text">{{ text "communities:label.query" }}</label> + <input + type="text" + name="text" + id="text" + placeholder="text" + required + maxlength="32" + value="{{ text }}" + /> + </div> + + <button class="primary">{{ text "dialog:action.continue" }}</button> + </form> + </div> + + <div class="card-nest"> + <div class="card small flex items-center gap-2"> + {{ icon "book-marked" }} + <span>{{ text "communities:label.search_results" }}</span> + </div> + + <!-- prettier-ignore --> + <div class="card flex flex-col gap-4"> + {% for item in list %} + {{ components::community_listing_card(community=item) }} + {% endfor %} + + {{ components::pagination(page=page, items=list|length, key="&text=", value=text) }} + </div> + </div> +</main> +{% endblock %} diff --git a/crates/app/src/public/html/components.html b/crates/app/src/public/html/components.html index 093fd5e..4c70d79 100644 --- a/crates/app/src/public/html/components.html +++ b/crates/app/src/public/html/components.html @@ -392,17 +392,23 @@ show_community and post.community != config.town_square %} </div> </div> </a> -{%- endmacro %} {% macro pagination(page=0, items=0) -%} +{%- endmacro %} {% macro pagination(page=0, items=0, key="", value="") -%} <div class="flex justify-between gap-2 w-full"> {% if page > 0 %} - <a class="button quaternary" href="?page={{ page - 1 }}"> + <a + class="button quaternary" + href="?page={{ page - 1 }}{{ key }}{{ value }}" + > {{ icon "arrow-left" }} <span>{{ text "general:link.previous" }}</span> </a> {% else %} <div></div> {% endif %} {% if items != 0 %} - <a class="button quaternary" href="?page={{ page + 1 }}"> + <a + class="button quaternary" + href="?page={{ page + 1 }}{{ key }}{{ value }}" + > <span>{{ text "general:link.next" }}</span> {{ icon "arrow-right"}} </a> diff --git a/crates/app/src/public/html/timelines/home.html b/crates/app/src/public/html/timelines/home.html index 2b89e6f..6a596a7 100644 --- a/crates/app/src/public/html/timelines/home.html +++ b/crates/app/src/public/html/timelines/home.html @@ -11,8 +11,14 @@ <b>✨ Welcome to <i>{{ config.name }}</i>!</b> </div> - <div class="card"> - Join some communities to populate your home timeline! + <div class="card no_p_margin"> + <p>Join some communities to populate your home timeline!</p> + <p> + You can get started by + <a href="/communities/search" + >searching for a community to join!</a + > + </p> </div> </div> {% else %} diff --git a/crates/app/src/routes/pages/communities.rs b/crates/app/src/routes/pages/communities.rs index 1276d8f..b9eabcb 100644 --- a/crates/app/src/routes/pages/communities.rs +++ b/crates/app/src/routes/pages/communities.rs @@ -1,4 +1,4 @@ -use super::{PaginatedQuery, render_error}; +use super::{render_error, PaginatedQuery, SearchedQuery}; use crate::{assets::initial_context, get_lang, get_user_from_token, sanitize::clean_context, State}; use axum::{ Extension, @@ -166,6 +166,44 @@ pub async fn list_request(jar: CookieJar, Extension(data): Extension<State>) -> )) } +/// `/communities/search` +pub async fn search_request( + jar: CookieJar, + Extension(data): Extension<State>, + Query(req): Query<SearchedQuery>, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &None).await, + )); + } + }; + + let communities = match data + .0 + .get_communities_searched(&req.text, 12, req.page) + .await + { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &Some(user)).await; + + context.insert("list", &communities); + context.insert("page", &req.page); + context.insert("text", &req.text); + + // return + Ok(Html( + data.1.render("communities/search.html", &context).unwrap(), + )) +} + pub fn community_context( context: &mut Context, community: &Community, diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 52eb6a1..43cac0e 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -45,6 +45,7 @@ pub fn routes() -> Router { .route("/@{username}/followers", get(profile::followers_request)) // communities .route("/communities", get(communities::list_request)) + .route("/communities/search", get(communities::search_request)) .route("/community/{title}", get(communities::feed_request)) .route( "/community/{title}/manage", @@ -74,3 +75,11 @@ pub struct PaginatedQuery { #[serde(default)] pub page: usize, } + +#[derive(Deserialize)] +pub struct SearchedQuery { + #[serde(default)] + pub text: String, + #[serde(default)] + pub page: usize, +} diff --git a/crates/core/src/database/communities.rs b/crates/core/src/database/communities.rs index 1d11c26..3d50d85 100644 --- a/crates/core/src/database/communities.rs +++ b/crates/core/src/database/communities.rs @@ -145,6 +145,33 @@ impl DataManager { Ok(res.unwrap()) } + /// Get all communities, filtering their title. + /// Communities are sorted by popularity first, creation date second. + pub async fn get_communities_searched( + &self, + query: &str, + batch: usize, + page: usize, + ) -> Result<Vec<Community>> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = query_rows!( + &conn, + "SELECT * FROM communities WHERE title LIKE $1 ORDER BY member_count DESC, created DESC LIMIT $2 OFFSET $3", + params![&format!("%{query}%"), &(batch as i64), &(page as i64)], + |x| { Self::get_community_from_row(x) } + ); + + if res.is_err() { + return Err(Error::GeneralNotFound("communities".to_string())); + } + + Ok(res.unwrap()) + } + /// Create a new community in the database. /// /// # Arguments