diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 3ab9d32..6f9addf 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -103,6 +103,8 @@ pub const TIMELINES_ALL_QUESTIONS: &str = include_str!("./public/html/timelines/all_questions.lisp"); pub const TIMELINES_SEARCH: &str = include_str!("./public/html/timelines/search.lisp"); pub const TIMELINES_SWISS_ARMY: &str = include_str!("./public/html/timelines/swiss_army.lisp"); +pub const TIMELINES_ALL_FORUM_POSTS: &str = + include_str!("./public/html/timelines/all_forum_posts.lisp"); pub const MOD_AUDIT_LOG: &str = include_str!("./public/html/mod/audit_log.lisp"); pub const MOD_REPORTS: &str = include_str!("./public/html/mod/reports.lisp"); @@ -336,6 +338,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"timelines/all_questions.html"(crate::assets::TIMELINES_ALL_QUESTIONS) --config=config --lisp plugins); write_template!(html_path->"timelines/search.html"(crate::assets::TIMELINES_SEARCH) --config=config --lisp plugins); write_template!(html_path->"timelines/swiss_army.html"(crate::assets::TIMELINES_SWISS_ARMY) --config=config --lisp plugins); + write_template!(html_path->"timelines/all_forum_posts.html"(crate::assets::TIMELINES_ALL_FORUM_POSTS) --config=config --lisp plugins); write_template!(html_path->"mod/audit_log.html"(crate::assets::MOD_AUDIT_LOG) -d "mod" --config=config --lisp plugins); write_template!(html_path->"mod/reports.html"(crate::assets::MOD_REPORTS) --config=config --lisp plugins); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index fdd6ffc..e70e78b 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -116,6 +116,7 @@ version = "1.0.0" "communities:label.content" = "Content" "communities:label.title" = "Title" "communities:label.posts" = "Posts" +"communities:label.forum_posts" = "Forum posts" "communities:label.topics" = "Topics" "communities:label.questions" = "Questions" "communities:label.not_allowed_to_read" = "You're not allowed to view this community's posts" diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index 55d3bf1..d25fec0 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -2636,8 +2636,18 @@ (text "{%- endif %}") (text "{%- endmacro %}") -(text "{% macro topic_post_display(post, owner, is_pinned=false) -%}") +(text "{% macro topic_post_display(post, owner, is_pinned=false, community=false) -%}") (tr + (text "{% if community %}") + (td + (a + ("href" "/community/{{ community.title }}/topic/{{ post.topic }}") + ("class" "flex gap_1 items_center") + (text "{{ self::community_avatar(id=post.community, community=community) }}") + (span + (text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}")))) + (text "{%- endif %}") + (td ("class" "flex gap_1") (a diff --git a/crates/app/src/public/html/macros.lisp b/crates/app/src/public/html/macros.lisp index 2a49ecf..6efc6eb 100644 --- a/crates/app/src/public/html/macros.lisp +++ b/crates/app/src/public/html/macros.lisp @@ -165,7 +165,7 @@ (text "{%- endif %}"))) (text "{%- endmacro %}") -(text "{% macro timelines_nav(selected=\"\", posts=\"\", questions=\"\", secondary_selected=\"posts\") -%}") +(text "{% macro timelines_nav(selected=\"\", posts=\"\", questions=\"\", secondary_selected=\"posts\", forum_posts=\"\") -%}") (div ("class" "mobile_nav mobile") ; primary nav @@ -184,7 +184,7 @@ (text "{% if posts and questions -%}") ; secondary nav - (text "{{ macros::timelines_secondary_nav(posts=posts, questions=questions, selected=secondary_selected) }}") + (text "{{ macros::timelines_secondary_nav(posts=posts, questions=questions, selected=secondary_selected, forum_posts=forum_posts) }}") (text "{%- endif %}")) (div @@ -194,7 +194,7 @@ ; secondary nav desktop only (text "{% if posts and questions -%}") - (text "{{ macros::timelines_secondary_nav(posts=posts, questions=questions, selected=secondary_selected) }}") + (text "{{ macros::timelines_secondary_nav(posts=posts, questions=questions, selected=secondary_selected, forum_posts=forum_posts) }}") (text "{%- endif %}")) (text "{%- endmacro %}") @@ -252,7 +252,7 @@ (text "{%- endif %}"))) (text "{%- endmacro %}") -(text "{% macro timelines_secondary_nav(posts=\"\", questions=\"\", selected=\"posts\") -%} {% if user -%}") +(text "{% macro timelines_secondary_nav(posts=\"\", questions=\"\", selected=\"posts\", forum_posts=\"\") -%} {% if user -%}") (div ("class" "pillmenu w_full") (a @@ -261,6 +261,14 @@ (icon (text "newspaper")) (span (str (text "communities:label.posts")))) + (text "{% if forum_posts|length > 0 -%}") + (a + ("href" "{{ forum_posts }}") + ("class" "{% if selected == 'forum_posts' -%}active{%- endif %}") + (icon (text "list-tree")) + (span (str (text "communities:label.forum_posts")))) + (text "{%- endif %}") + (a ("href" "{{ questions }}") ("class" "{% if selected == 'questions' -%}active{%- endif %}") diff --git a/crates/app/src/public/html/timelines/all.lisp b/crates/app/src/public/html/timelines/all.lisp index 4609e25..b3235b9 100644 --- a/crates/app/src/public/html/timelines/all.lisp +++ b/crates/app/src/public/html/timelines/all.lisp @@ -1,11 +1,10 @@ (text "{% extends \"root.html\" %} {% block head %}") (title (text "Latest posts - {{ config.name }}")) - (text "{% endblock %} {% block body %} {{ macros::nav() }}") (main ("class" "flex flex_col gap_2") - (text "{{ macros::timelines_nav(selected=\"all\", posts=\"/all\", questions=\"/all/questions\") }} {% if not user -%}") + (text "{{ macros::timelines_nav(selected=\"all\", posts=\"/all\", questions=\"/all/questions\", forum_posts=\"/all/forum_posts\") }} {% if not user -%}") (div ("class" "card_nest") (div @@ -32,11 +31,9 @@ ("class" "card w_full flex flex_col gap_2") ("ui_ident" "io_data_load") (div ("ui_ident" "io_data_marker")))) - (text "{% set paged = user and user.settings.paged_timelines %}") (script (text "setTimeout(() => { trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=AllPosts&before=$1$&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) - (text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/all_forum_posts.lisp b/crates/app/src/public/html/timelines/all_forum_posts.lisp new file mode 100644 index 0000000..62ba0cf --- /dev/null +++ b/crates/app/src/public/html/timelines/all_forum_posts.lisp @@ -0,0 +1,24 @@ +(text "{% extends \"root.html\" %} {% block head %}") +(title + (text "Latest forum posts - {{ config.name }}")) +(text "{% endblock %} {% block body %} {{ macros::nav() }}") +(main + ("class" "flex flex_col gap_2") + (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") + (div + ("class" "w_full") + ("style" "overflow: auto") + (table + ("class" "w_full") + (thead + (th (text "In")) + (th (text "Title")) + (th (text "Replies")) + (th (text "Score")) + (th (text "Created"))) + (tbody + (text "{% for post in feed %} {{ components::topic_post_display(post=post[0], owner=post[1], community=post[2]) }} {% endfor %}")))) + (text "{{ components::pagination(page=page, items=feed|length) }}"))) +(text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/all_questions.lisp b/crates/app/src/public/html/timelines/all_questions.lisp index 70243ee..00626de 100644 --- a/crates/app/src/public/html/timelines/all_questions.lisp +++ b/crates/app/src/public/html/timelines/all_questions.lisp @@ -1,13 +1,11 @@ (text "{% extends \"root.html\" %} {% block head %}") (title (text "Latest questions - {{ config.name }}")) - (text "{% endblock %} {% block body %} {{ macros::nav() }}") (main ("class" "flex flex_col gap_2") - (text "{{ macros::timelines_nav(selected=\"all\", posts=\"/all\", questions=\"/all/questions\", secondary_selected=\"questions\") }}") + (text "{{ macros::timelines_nav(selected=\"all\", posts=\"/all\", questions=\"/all/questions\", secondary_selected=\"questions\", forum_posts=\"/all/forum_posts\") }}") (div ("class" "card w_full flex flex_col gap_2") (text "{% for question in list %} {{ components::global_question(question=question, can_manage_questions=false, secondary=true) }} {% endfor %} {{ components::pagination(page=page, items=list|length) }}"))) - (text "{% endblock %}") diff --git a/crates/app/src/routes/pages/misc.rs b/crates/app/src/routes/pages/misc.rs index 7ee2f72..a4d6ad5 100644 --- a/crates/app/src/routes/pages/misc.rs +++ b/crates/app/src/routes/pages/misc.rs @@ -47,8 +47,8 @@ pub async fn index_request( // i'm only changing this for stripe let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &None).await; - context.insert("page", &req.page); + Html(data.1.render("timelines/all.html", &context).unwrap()) }; } @@ -79,6 +79,7 @@ pub async fn index_request( context.insert("list", &list); context.insert("page", &req.page); + Html(data.1.render("timelines/home.html", &context).unwrap()) } @@ -93,8 +94,8 @@ pub async fn popular_request( let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &user).await; - context.insert("page", &req.page); + Html(data.1.render("timelines/popular.html", &context).unwrap()) } @@ -116,8 +117,8 @@ pub async fn following_request( let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; - context.insert("page", &req.page); + Ok(Html( data.1.render("timelines/following.html", &context).unwrap(), )) @@ -134,8 +135,8 @@ pub async fn all_request( let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &user).await; - context.insert("page", &req.page); + Html(data.1.render("timelines/all.html", &context).unwrap()) } @@ -172,6 +173,7 @@ pub async fn index_questions_request( context.insert("list", &list); context.insert("page", &req.page); + Html( data.1 .render("timelines/home_questions.html", &context) @@ -212,6 +214,7 @@ pub async fn popular_questions_request( context.insert("list", &list); context.insert("page", &req.page); + Html( data.1 .render("timelines/popular_questions.html", &context) @@ -254,6 +257,7 @@ pub async fn following_questions_request( context.insert("list", &list); context.insert("page", &req.page); + Ok(Html( data.1 .render("timelines/following_questions.html", &context) @@ -271,7 +275,6 @@ pub async fn all_questions_request( let user = get_user_from_token!(jar, data.0); let ignore_users = crate::ignore_users_gen!(user, data); - let list = match data.0.get_latest_global_questions(12, req.page).await { Ok(l) => match data.0.fill_questions(l, &ignore_users).await { Ok(l) => l, @@ -285,6 +288,7 @@ pub async fn all_questions_request( context.insert("list", &list); context.insert("page", &req.page); + Html( data.1 .render("timelines/all_questions.html", &context) @@ -292,6 +296,50 @@ pub async fn all_questions_request( ) } +/// `/all/forum_posts` +pub async fn all_forum_posts_request( + jar: CookieJar, + Extension(data): Extension, + Query(req): Query, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Html(render_error(Error::NotAllowed, &jar, &data, &None).await); + } + }; + + let ignore_users = crate::ignore_users_gen!(user!, data); + let list = match data + .0 + .get_latest_forum_posts(48, req.page, &Some(user.clone()), req.before) + .await + { + Ok(l) => match data + .0 + .fill_posts_with_community(l, user.id, &ignore_users, &Some(user.clone())) + .await + { + Ok(l) => l, + Err(e) => return Html(render_error(e, &jar, &data, &Some(user)).await), + }, + Err(e) => return Html(render_error(e, &jar, &data, &Some(user)).await), + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; + + context.insert("page", &req.page); + context.insert("feed", &list); + + Html( + data.1 + .render("timelines/all_forum_posts.html", &context) + .unwrap(), + ) +} + #[derive(Deserialize)] pub struct NotificationsProps { #[serde(default)] diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index ec993fc..b077d1c 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -41,6 +41,8 @@ pub fn routes() -> Router { get(misc::following_questions_request), ) .route("/all/questions", get(misc::all_questions_request)) + // forum post timelines + .route("/all/forum_posts", get(misc::all_forum_posts_request)) // misc .route("/notifs", get(misc::notifications_request)) .route("/requests", get(misc::requests_request)) diff --git a/crates/core/src/database/posts.rs b/crates/core/src/database/posts.rs index bd326f9..1e856dd 100644 --- a/crates/core/src/database/posts.rs +++ b/crates/core/src/database/posts.rs @@ -1606,6 +1606,57 @@ impl DataManager { Ok(res.unwrap()) } + /// Get forum posts from all communities, sorted by creation. + /// + /// # Arguments + /// * `batch` - the limit of posts in each page + /// * `page` - the page number + pub async fn get_latest_forum_posts( + &self, + batch: usize, + page: usize, + as_user: &Option, + before_time: usize, + ) -> Result> { + // check if we should hide nsfw posts + let mut hide_nsfw: bool = true; + + if let Some(ua) = as_user { + hide_nsfw = !ua.settings.show_nsfw; + } + + // ... + let conn = match self.0.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = query_rows!( + &conn, + &format!( + "SELECT * FROM posts WHERE replying_to = 0{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND NOT topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2", + if before_time > 0 { + format!(" AND created < {before_time}") + } else { + String::new() + }, + if hide_nsfw { + " AND NOT context LIKE '%\"is_nsfw\":true%'" + } else { + "" + } + ), + &[&(batch as i64), &((page * batch) as i64)], + |x| { Self::get_post_from_row(x) } + ); + + if res.is_err() { + return Err(Error::GeneralNotFound("post".to_string())); + } + + Ok(res.unwrap()) + } + /// Get posts from all communities the given user is in. /// /// # Arguments