diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index e70e78b..1949204 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -118,6 +118,7 @@ version = "1.0.0" "communities:label.posts" = "Posts" "communities:label.forum_posts" = "Forum posts" "communities:label.topics" = "Topics" +"communities:label.topic" = "Topic" "communities:label.questions" = "Questions" "communities:label.not_allowed_to_read" = "You're not allowed to view this community's posts" "communities:label.might_need_to_join" = "You might need to join this community in order to interact with it!" diff --git a/crates/app/src/public/html/communities/create_post.lisp b/crates/app/src/public/html/communities/create_post.lisp index b2eae97..6f83326 100644 --- a/crates/app/src/public/html/communities/create_post.lisp +++ b/crates/app/src/public/html/communities/create_post.lisp @@ -109,6 +109,23 @@ ("class" "card flex flex_col gap_2") ("id" "create_form") ("onsubmit" "create_post_from_form(event)") + (text "{% if show_topics -%}") + (div + ("class" "flex flex_col gap_1") + (label + ("for" "topic") + (str (text "communities:label.topic"))) + (select + ("id" "topic") + ("name" "topic") + (text "{% for id, topic in topics %}") + (option + ("value" "{{ id }}") + ("selected" "{% if selected_topic|int == id|int -%}true{% else %}false{%- endif %}") + (text "{{ topic.title }}")) + (text "{% endfor %}"))) + (text "{%- endif %}") + (div ("class" "flex flex_col gap_1 hidden") ("id" "title_field") @@ -168,8 +185,7 @@ (text "{{ text \"communities:action.create\" }}")))))) (text "{% if not quoting -%}") (script - (text "globalThis.SEARCH_PARAMS = new URLSearchParams(window.location.search); - async function create_post_from_form(e) { + (text "async function create_post_from_form(e) { e.preventDefault(); await trigger(\"atto::debounce\", [\"posts::create\"]); @@ -210,7 +226,7 @@ content: e.target.content.value, community: !is_selected_stack ? selected_community : \"0\", stack: is_selected_stack ? selected_community : \"0\", - topic: !is_selected_stack ? SEARCH_PARAMS.get(\"topic\") || \"0\" : \"0\", + topic: e.target.topic.selectedOptions[0].value, poll: poll_data[1], title: e.target.title.value, }), diff --git a/crates/app/src/public/html/communities/topic.lisp b/crates/app/src/public/html/communities/topic.lisp index 08ed014..dba3c24 100644 --- a/crates/app/src/public/html/communities/topic.lisp +++ b/crates/app/src/public/html/communities/topic.lisp @@ -11,7 +11,7 @@ ("class" "flex gap_2") (text "{% if can_post -%}") (a - ("href" "/communities/intents/post?community={{ community.id }}&topic={{ topic_id }}&sig=true") + ("href" "/communities/intents/post?community={{ community.id }}&topic={{ topic_id }}&sig=true&topics=true") ("class" "button small lowered") ("data-turbo" "false") (icon (text "plus")) diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index d25fec0..505901a 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -574,6 +574,7 @@ (div ("class" "flex flex_col gap_2") ("style" "flex: 1 0 auto") + (b ("class" "no_p_margin") (text "{{ post.title|markdown|safe }}")) (span ("class" "no_p_margin") (text "{{ post.content|markdown|safe }}")) (text "{{ self::post_media(upload_ids=post.uploads) }}") (text "{% if poll -%} {{ self::poll(post=post, poll=poll) }} {%- endif %}")) diff --git a/crates/app/src/public/html/timelines/all_forum_posts.lisp b/crates/app/src/public/html/timelines/all_forum_posts.lisp index 62ba0cf..7a171fb 100644 --- a/crates/app/src/public/html/timelines/all_forum_posts.lisp +++ b/crates/app/src/public/html/timelines/all_forum_posts.lisp @@ -7,6 +7,16 @@ (text "{{ macros::timelines_nav(selected=\"all\", posts=\"/all\", questions=\"/all/questions\", secondary_selected=\"forum_posts\", forum_posts=\"/all/forum_posts\") }}") (div ("class" "card w_full flex flex_col gap_2") + (text "{% if config.town_square_forum != 0 -%}") + (a + ("href" "/communities/intents/post?community={{ config.town_square_forum }}&topic={{ config.town_square_forum_topic }}&sig=true&topics=true") + ("class" "button small lowered") + ("data-turbo" "false") + (icon (text "plus")) + (span + (str (text "general:action.post")))) + (text "{%- endif %}") + (div ("class" "w_full") ("style" "overflow: auto") diff --git a/crates/app/src/routes/api/v1/communities/communities.rs b/crates/app/src/routes/api/v1/communities/communities.rs index 066c8a5..afa9481 100644 --- a/crates/app/src/routes/api/v1/communities/communities.rs +++ b/crates/app/src/routes/api/v1/communities/communities.rs @@ -119,7 +119,7 @@ pub async fn update_context_request( jar: CookieJar, Extension(data): Extension, Path(id): Path, - Json(req): Json, + Json(mut req): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManage) { @@ -127,6 +127,16 @@ pub async fn update_context_request( None => return Json(Error::NotAllowed.into()), }; + let community = match data.get_community_by_id_no_void(id).await { + Ok(x) => x, + Err(e) => return Json(e.into()), + }; + + if community.is_forge || community.is_forum { + req.context.enable_titles = true; + req.context.require_titles = true; + } + // check lengths if req.context.display_name.len() > 32 { return Json(Error::DataTooLong("display name".to_string()).into()); diff --git a/crates/app/src/routes/pages/communities.rs b/crates/app/src/routes/pages/communities.rs index 246667d..2f0d03c 100644 --- a/crates/app/src/routes/pages/communities.rs +++ b/crates/app/src/routes/pages/communities.rs @@ -266,8 +266,12 @@ pub struct CreatePostProps { pub from_draft: usize, #[serde(default)] pub quote: usize, + #[serde(default)] + pub topic: usize, #[serde(default, alias = "sig")] pub use_signature: bool, + #[serde(default, alias = "topics")] + pub show_topics: bool, } /// `/communities/intents/post` @@ -350,6 +354,20 @@ pub async fn create_post_request( None }; + // fetch topics + let topics = if props.show_topics { + if props.community != 0 { + match data.0.get_community_by_id_no_void(props.community).await { + Ok(x) => x.topics, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + } + } else { + HashMap::new() + } + } else { + HashMap::new() + }; + // ... let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; @@ -361,7 +379,10 @@ pub async fn create_post_request( context.insert("communities", &communities); context.insert("selected_stack", &props.stack); context.insert("selected_community", &props.community); + context.insert("selected_topic", &props.topic); context.insert("use_signature", &props.use_signature); + context.insert("topics", &topics); + context.insert("show_topics", &props.show_topics); // return Ok(Html( diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index aa845f6..4924714 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -308,6 +308,13 @@ pub struct Config { /// This community **must** have open write access. #[serde(default)] pub town_square: usize, + /// The ID of the town square forum community. + #[serde(default)] + pub town_square_forum: usize, + /// The ID of the topic within the town square forum community that users are prompted + /// to post in by default. This should be some sort of "general" topic. + #[serde(default)] + pub town_square_forum_topic: usize, #[serde(default)] pub connections: ConnectionsConfig, /// The path to the HTML footer file. The contents of this file are embedded @@ -430,6 +437,8 @@ impl Default for Config { policies: default_policies(), turnstile: default_turnstile(), town_square: 0, + town_square_forum: 0, + town_square_forum_topic: 0, connections: default_connections(), html_footer_path: String::new(), stripe: None,