diff --git a/crates/app/src/public/html/profile/posts.lisp b/crates/app/src/public/html/profile/posts.lisp index 0c9d79a..06aca2f 100644 --- a/crates/app/src/public/html/profile/posts.lisp +++ b/crates/app/src/public/html/profile/posts.lisp @@ -44,9 +44,10 @@ ("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?user_id={{ profile.id }}&tag={{ tag }}&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?user_id={{ profile.id }}&tag={{ tag }}&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{% endblock %}") diff --git a/crates/app/src/public/html/profile/settings.lisp b/crates/app/src/public/html/profile/settings.lisp index 88c6d59..268dfef 100644 --- a/crates/app/src/public/html/profile/settings.lisp +++ b/crates/app/src/public/html/profile/settings.lisp @@ -1403,6 +1403,11 @@ \"Hides dislikes on all posts. Users will also no longer be able to dislike your posts.\", \"text\", ], + [ + [\"paged_timelines\", \"Make timelines paged instead of infinitely scrolled\"], + \"{{ profile.settings.paged_timelines }}\", + \"checkbox\", + ], [[], \"Fun\", \"title\"], [ [\"disable_gpa_fun\", \"Disable GPA\"], diff --git a/crates/app/src/public/html/stacks/feed.lisp b/crates/app/src/public/html/stacks/feed.lisp index 5698065..5002856 100644 --- a/crates/app/src/public/html/stacks/feed.lisp +++ b/crates/app/src/public/html/stacks/feed.lisp @@ -83,9 +83,10 @@ ("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?stack_id={{ stack.id }}&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?stack_id={{ stack.id }}&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{%- endif %}")))) diff --git a/crates/app/src/public/html/timelines/all.lisp b/crates/app/src/public/html/timelines/all.lisp index c38dd88..7cced78 100644 --- a/crates/app/src/public/html/timelines/all.lisp +++ b/crates/app/src/public/html/timelines/all.lisp @@ -33,9 +33,10 @@ ("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&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=AllPosts&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/following.lisp b/crates/app/src/public/html/timelines/following.lisp index b1759e4..ef23a55 100644 --- a/crates/app/src/public/html/timelines/following.lisp +++ b/crates/app/src/public/html/timelines/following.lisp @@ -11,9 +11,10 @@ ("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=FollowingPosts&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=FollowingPosts&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/home.lisp b/crates/app/src/public/html/timelines/home.lisp index e398615..5a5658b 100644 --- a/crates/app/src/public/html/timelines/home.lisp +++ b/crates/app/src/public/html/timelines/home.lisp @@ -31,9 +31,10 @@ (div ("ui_ident" "io_data_marker"))) (text "{%- endif %}")) +(text "{% set paged = user and user.settings.paged_timelines %}") (script (text "setTimeout(() => { - trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/popular.lisp b/crates/app/src/public/html/timelines/popular.lisp index 6d26f3d..d0223df 100644 --- a/crates/app/src/public/html/timelines/popular.lisp +++ b/crates/app/src/public/html/timelines/popular.lisp @@ -11,9 +11,10 @@ ("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=PopularPosts&page=\", Number.parseInt(\"{{ page }}\") - 1]); + trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=PopularPosts&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); });")) (text "{% endblock %}") diff --git a/crates/app/src/public/html/timelines/swiss_army.lisp b/crates/app/src/public/html/timelines/swiss_army.lisp index eb722c9..23243ce 100644 --- a/crates/app/src/public/html/timelines/swiss_army.lisp +++ b/crates/app/src/public/html/timelines/swiss_army.lisp @@ -30,3 +30,7 @@ (str (text "chats:label.go_back"))) (text "{%- endif %}")) (text "{%- endif %}") + +(text "{% if paginated -%}") +(text "{{ components::pagination(page=page, items=list|length) }}") +(text "{%- endif %}") diff --git a/crates/app/src/public/js/atto.js b/crates/app/src/public/js/atto.js index 6c30428..835f76e 100644 --- a/crates/app/src/public/js/atto.js +++ b/crates/app/src/public/js/atto.js @@ -1141,7 +1141,7 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} }, ); - self.define("io_data_load", (_, tmpl, page) => { + self.define("io_data_load", (_, tmpl, page, paginated_mode = false) => { self.IO_DATA_MARKER = document.querySelector( "[ui_ident=io_data_marker]", ); @@ -1164,7 +1164,16 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} self.IO_DATA_PAGE = page; self.IO_DATA_SEEN_IDS = []; - self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER); + if (!paginated_mode) { + self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER); + } else { + // immediately load first page + self.IO_DATA_TMPL = self.IO_DATA_TMPL.replace("&page=", ""); + self.IO_DATA_TMPL += `&paginated=true&page=`; + self.io_load_data(); + } + + self.IO_PAGINATED = paginated_mode; }); self.define("io_load_data", async () => { diff --git a/crates/app/src/routes/api/v1/auth/images.rs b/crates/app/src/routes/api/v1/auth/images.rs index e062be1..e177db7 100644 --- a/crates/app/src/routes/api/v1/auth/images.rs +++ b/crates/app/src/routes/api/v1/auth/images.rs @@ -213,7 +213,7 @@ pub async fn upload_avatar_request( if mime == "image/gif" { // gif image, don't encode if img.0.len() > MAXIMUM_GIF_FILE_SIZE { - return Json(Error::DataTooLong("gif".to_string()).into()); + return Json(Error::FileTooLarge.into()); } std::fs::write(&path, img.0).unwrap(); @@ -226,7 +226,7 @@ pub async fn upload_avatar_request( // check file size if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::DataTooLong("image".to_string()).into()); + return Json(Error::FileTooLarge.into()); } // upload image @@ -314,7 +314,7 @@ pub async fn upload_banner_request( if mime == "image/gif" { // gif image, don't encode if img.0.len() > MAXIMUM_GIF_FILE_SIZE { - return Json(Error::DataTooLong("gif".to_string()).into()); + return Json(Error::FileTooLarge.into()); } std::fs::write(&path, img.0).unwrap(); @@ -327,7 +327,7 @@ pub async fn upload_banner_request( // check file size if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::DataTooLong("image".to_string()).into()); + return Json(Error::FileTooLarge.into()); } // upload image diff --git a/crates/app/src/routes/api/v1/communities/images.rs b/crates/app/src/routes/api/v1/communities/images.rs index 464dede..3ddee00 100644 --- a/crates/app/src/routes/api/v1/communities/images.rs +++ b/crates/app/src/routes/api/v1/communities/images.rs @@ -136,7 +136,7 @@ pub async fn upload_avatar_request( // check file size if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::DataTooLong("image".to_string()).into()); + return Json(Error::FileTooLarge.into()); } // upload image @@ -191,7 +191,7 @@ pub async fn upload_banner_request( // check file size if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::DataTooLong("image".to_string()).into()); + return Json(Error::FileTooLarge.into()); } // upload image diff --git a/crates/app/src/routes/api/v1/communities/posts.rs b/crates/app/src/routes/api/v1/communities/posts.rs index 81a1fae..7bf4bf3 100644 --- a/crates/app/src/routes/api/v1/communities/posts.rs +++ b/crates/app/src/routes/api/v1/communities/posts.rs @@ -133,7 +133,7 @@ pub async fn create_request( // check sizes for img in &images { if img.len() > MAXIMUM_FILE_SIZE { - return Json(Error::DataTooLong("image".to_string()).into()); + return Json(Error::FileTooLarge.into()); } } diff --git a/crates/app/src/routes/pages/misc.rs b/crates/app/src/routes/pages/misc.rs index 3ff3f0d..8b76292 100644 --- a/crates/app/src/routes/pages/misc.rs +++ b/crates/app/src/routes/pages/misc.rs @@ -576,6 +576,8 @@ pub struct TimelineQuery { pub user_id: usize, #[serde(default)] pub tag: String, + #[serde(default)] + pub paginated: bool, } /// `/_swiss_army_timeline` @@ -697,6 +699,7 @@ pub async fn swiss_army_timeline_request( context.insert("list", &list); context.insert("page", &req.page); + context.insert("paginated", &req.paginated); Ok(Html( data.1 .render("timelines/swiss_army.html", &context) diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index a5714c3..bc8b13f 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -231,6 +231,9 @@ pub struct UserSettings { /// A list of strings the user has muted. #[serde(default)] pub muted: Vec, + /// If timelines are paged instead of infinitely scrolled. + #[serde(default)] + pub paged_timelines: bool, } fn mime_avif() -> String { diff --git a/crates/core/src/model/mod.rs b/crates/core/src/model/mod.rs index c50ea7c..62f26a3 100644 --- a/crates/core/src/model/mod.rs +++ b/crates/core/src/model/mod.rs @@ -41,6 +41,7 @@ pub enum Error { AlreadyAuthenticated, DataTooLong(String), DataTooShort(String), + FileTooLarge, UsernameInUse, TitleInUse, QuestionsDisabled, @@ -62,6 +63,7 @@ impl Display for Error { Self::AlreadyAuthenticated => "Already authenticated".to_string(), Self::DataTooLong(name) => format!("Given {name} is too long!"), Self::DataTooShort(name) => format!("Given {name} is too short!"), + Self::FileTooLarge => "Given file is too large".to_string(), Self::UsernameInUse => "Username in use".to_string(), Self::TitleInUse => "Title in use".to_string(), Self::QuestionsDisabled => "You are not allowed to ask questions there".to_string(),