From d2a905b67fa8f6996735b86aae0f48c41eeacbe1 Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Mon, 3 Apr 2023 23:53:23 -0400 Subject: [PATCH] A couple things :3 --- Cargo.lock | 4 +- Cargo.toml | 2 +- css/index.scss | 69 ++++++++++++++++++ scripts/History.js | 2 +- src/cached_wattpad.rs | 2 +- src/main.rs | 12 ++-- src/routes/about.rs | 48 +++++++++++++ src/routes/history.rs | 10 +-- src/routes/index.rs | 21 ++++++ src/routes/mod.rs | 2 + src/routes/search.rs | 163 ++++++++++++++++++++++++++++++++++++++++++ src/routes/story.rs | 10 ++- 12 files changed, 321 insertions(+), 24 deletions(-) create mode 100644 src/routes/about.rs create mode 100644 src/routes/search.rs diff --git a/Cargo.lock b/Cargo.lock index ab124ac..00b2267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,9 +2020,9 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wattpad" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d03f3af6f6244f85e6dd6a3981677de18daf8b8e1d8fd1d35642d47fc2a4e3f" +checksum = "64a19887ffed259ee2c57d877f9ae7112ff4b1ec28eee2442df985335ab62155" dependencies = [ "anyhow", "regex", diff --git a/Cargo.toml b/Cargo.toml index 0e0207a..40b8615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ serde_json = "1.0.94" tokio = { version = "1.26.0", features = ["full"] } tracing = "0.1.37" tracing-subscriber = "0.3.16" -wattpad = "0.2.5" +wattpad = "0.3.0" diff --git a/css/index.scss b/css/index.scss index 4503e28..daac019 100644 --- a/css/index.scss +++ b/css/index.scss @@ -127,3 +127,72 @@ a:hover { display: flex; justify-content: center; } + +.search-form { + display: flex; + flex-direction: column; + justify-content: center; + row-gap: 0.25em; + div { + display: flex; + flex-direction: row; + div { + display: flex; + align-items: center; + column-gap: 0.5em; + + div { + display: flex; + align-items: flex-start; + justify-content: flex-start; + column-gap: 0; + } + } + } +} + +.story-list { + margin-top: 0.5em; + display: flex; + flex-direction: column; + row-gap: 0.75em; + width: 75%; + align-items: flex-start; + @media (max-width: 900px) { + align-items: center; + } + + .listed-story { + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + column-gap: 0.5em; + @media (max-width: 900px) { + flex-direction: column; + } + + div { + display: flex; + flex-direction: column; + align-self: flex-start; + + @media (max-width: 900px) { + margin-top: 0.5em; + } + } + } +} + +.favorites-container { + display: flex; + flex-direction: row; + row-gap: 5em; + justify-content: space-evenly; + + @media (max-width: 900px) { + flex-direction: column; + column-gap: 1em; + justify-content: center; + } +} diff --git a/scripts/History.js b/scripts/History.js index 95e4c55..01e4d53 100644 --- a/scripts/History.js +++ b/scripts/History.js @@ -14,7 +14,7 @@ async function historyLoad() { ).json(); const searchEle = document.createElement("a"); - searchEle.className = "story"; + searchEle.className = "listed-story"; searchEle.href = "/story?id=" + story.id + diff --git a/src/cached_wattpad.rs b/src/cached_wattpad.rs index 86bf288..8c167fa 100644 --- a/src/cached_wattpad.rs +++ b/src/cached_wattpad.rs @@ -3,7 +3,7 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Local}; use lazy_static::lazy_static; use std::{collections::HashMap, sync::Mutex}; -use wattpad::{Part, Story}; +use wattpad::{Story}; lazy_static! { static ref STORY_CACHE: Mutex)>> = diff --git a/src/main.rs b/src/main.rs index ff0d928..4968981 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,12 @@ mod components; mod routes; mod web_api; -use routes::{history, index, not_found, story}; +use routes::*; -use axum::{extract::Extension, routing::get, Router}; +use axum::{routing::get, Router}; use lazy_static::lazy_static; -use maud::{html, Markup}; -use std::{net::SocketAddr, sync::Arc}; + +use std::net::SocketAddr; use tokio::runtime::Runtime; use wattpad::Wattpad; use web_api::get_story; @@ -23,11 +23,13 @@ fn main() { tracing_subscriber::fmt::init(); lazy_static::initialize(&WATTPAD); - let addr: SocketAddr = "127.0.0.1:3000".parse().unwrap(); + let addr: SocketAddr = "0.0.0.0:3000".parse().unwrap(); let app = Router::new() .route("/", get(index::render)) .route("/story", get(story::render)) + .route("/search", get(search::render)) .route("/history", get(history::render)) + .route("/about", get(about::render)) .route("/api/getStory", get(get_story)) .fallback(not_found::render); diff --git a/src/routes/about.rs b/src/routes/about.rs new file mode 100644 index 0000000..76ed046 --- /dev/null +++ b/src/routes/about.rs @@ -0,0 +1,48 @@ +use crate::components::header::{Header, HeaderLink}; + +use maud::{html, Markup, PreEscaped, DOCTYPE}; + +pub async fn render() -> Markup { + html! { + (DOCTYPE) + title {"About | Voltpad"}; + meta name="viewport" content="width=device-width, initial-scale=1.0"; + style {(PreEscaped( + grass::include!("./css/index.scss") + ))} + ( + Header(vec![ + HeaderLink { + name: "Home".to_string(), + path: "/".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "Search".to_string(), + path: "/search".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "History".to_string(), + path: "/history".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "About".to_string(), + path: "/about".to_string(), + prefix: None, + postfix: None + }, + ], "/about".to_string()) + ) + div .index-content { + h1 { "About Us" } + p { + "Voltpad is a custom-made Wattpad frontend, designed to avoid the problems of the official website and app while giving users (slightly more) privacy and a better overall experience." + } + } + } +} diff --git a/src/routes/history.rs b/src/routes/history.rs index 77e3000..50a0b13 100644 --- a/src/routes/history.rs +++ b/src/routes/history.rs @@ -1,17 +1,11 @@ -use std::sync::Arc; - -use crate::cached_wattpad::*; use crate::components::header::{Header, HeaderLink}; -use crate::WATTPAD; -use axum::extract::{Extension, Query, State as AxumState}; + use maud::{html, Markup, PreEscaped, DOCTYPE}; -use regex::Regex; -use serde::Deserialize; pub async fn render() -> Markup { html! { (DOCTYPE) - title {"Home | Voltpad"}; + title {"History | Voltpad"}; meta name="viewport" content="width=device-width, initial-scale=1.0"; style {(PreEscaped( grass::include!("./css/index.scss") diff --git a/src/routes/index.rs b/src/routes/index.rs index f007245..ea65df7 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -42,6 +42,27 @@ pub async fn render() -> Markup { div .index-content { h1 { "Voltpad" } div { "A blazingly fast (but for real this time) Wattpad frontend" } + noscript { + p { + b { "Warning: " } + "Some features will not work without JS enabled, including:" + ul { + li { + "Saving/favoriting stories & searches" + } + li { + "Story history" + } + li { + "'Go back to previous page' on story pages" + } + } + } + } + div .favorites-container { + div .favorite-searches #favorite-searches; + div .favorite-stories #favorite-stories; + } } } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f202a11..7f20c6b 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,4 +1,6 @@ +pub mod about; pub mod history; pub mod index; pub mod not_found; +pub mod search; pub mod story; diff --git a/src/routes/search.rs b/src/routes/search.rs new file mode 100644 index 0000000..c0a53eb --- /dev/null +++ b/src/routes/search.rs @@ -0,0 +1,163 @@ +use axum::extract::Query; +use maud::{html, Markup, PreEscaped, DOCTYPE}; +use serde::Deserialize; + +use crate::{ + components::header::{Header, HeaderLink}, + WATTPAD, +}; + +use wattpad::{SearchSort, SearchType}; + +#[derive(Deserialize)] +pub struct SearchParams { + query: Option, + #[serde(rename = "type")] + search_type: Option, + #[serde(rename = "sort")] + search_sort: Option, + #[serde(rename = "page")] + page_number: Option, +} + +/* if let Some(query) = params.query { + let search_type = params.search_type.unwrap_or("text".to_string()); + let search_sort = params.search_sort.unwrap_or("hot".to_string()); +} else { + +} */ + +pub async fn render(Query(params): Query) -> Markup { + let search_type = params.search_type.unwrap_or("text".to_string()); + let search_sort = params.search_sort.unwrap_or("hot".to_string()); + html! { + (DOCTYPE) + title {"Search | Voltpad"}; + meta name="viewport" content="width=device-width, initial-scale=1.0"; + style {(PreEscaped( + grass::include!("./css/index.scss") + ))} + ( + Header(vec![ + HeaderLink { + name: "Home".to_string(), + path: "/".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "Search".to_string(), + path: "/search".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "History".to_string(), + path: "/history".to_string(), + prefix: None, + postfix: None + }, + HeaderLink { + name: "About".to_string(), + path: "/about".to_string(), + prefix: None, + postfix: None + }, + ], "/search".to_string()) + ) + div .index-content { + h1 { "Search" } + form method="GET" .search-form { + div { + input + #query + type="text" + name="query" + autocomplete="off" + placeholder="Search Query" + value=[params.query.clone()]; + div { + div { + input + #text + type="radio" + name="type" + value="text" + checked=[(search_type == "text").then_some(80)]; // IS 80 ENOUGH PROOF FOR YOU? + label for="text" { "Text search" } + } + div { + input + #tag + type="radio" + name="type" + value="tag" + checked=[(search_type == "tag").then_some(80)]; + label for="tag" { "Tag search" } + } + div { + select #sort name="sort" { + option + value="hot" + selected=[(search_sort == "hot").then_some(80)] { "Hot" }; + option + value="new" + selected=[(search_sort == "new").then_some(80)] { "New" }; + } + } + } + } + input class="submit-search" type="submit" value="Submit"; + } + + @if let Some(query) = params.query.clone() { + ({ + let page_number = params.page_number.unwrap_or(0); + + let enumified_search_type = match search_type.as_str() { + "text" => SearchType::Text, + "title" => SearchType::Title, + "tag" => SearchType::Tag, + _ => SearchType::Text + }; + let enumified_search_sort = match search_sort.as_str() { + "hot" => SearchSort::Hot, + "new" => SearchSort::New, + _ => SearchSort::Hot + }; + + let search = WATTPAD.search(&query, enumified_search_type, enumified_search_sort, 30) + .await + .expect("Failed to create search"); + + let results = search.page(page_number) + .await + .expect("Failed to get search results"); + + html! { + div .story-list { + @for result in results.stories.clone() { + a .listed-story href=(format!("/story?id={}", result.id)) { + img src=(result.cover); + div { + h3 { (result.title) } + p { (PreEscaped(result.description.replace('\n', "
"))) } + } + } + } + div .story-nav { + @if page_number > 0 { + a href=(format!("/search?query={}&type={}&sort={}&page={}", query, search_type, search_sort, page_number-1)) { "<" } + } + " | " + @if results.stories.clone().len() == 30 { + a href=(format!("/search?query={}&type={}&sort={}&page={}", query, search_type, search_sort, page_number+1)) { ">" } + } + } + } + } + }) + } + } + } +} diff --git a/src/routes/story.rs b/src/routes/story.rs index 6b89869..a7adca3 100644 --- a/src/routes/story.rs +++ b/src/routes/story.rs @@ -1,9 +1,7 @@ -use std::sync::Arc; - use crate::cached_wattpad::*; use crate::components::header::{Header, HeaderLink}; -use crate::WATTPAD; -use axum::extract::{Extension, Query, State as AxumState}; + +use axum::extract::Query; use maud::{html, Markup, PreEscaped, DOCTYPE}; use regex::Regex; use serde::Deserialize; @@ -101,14 +99,14 @@ pub async fn render(Query(params): Query) -> Markup { (story.title) } p { - (PreEscaped(story.description.replace("\n", "
"))) + (PreEscaped(story.description.replace('\n', "
"))) } p { "Original URL: " a href=(story.url) { (story.url) } br; "Author: " - a href=(format!("/user?name={}", story._user.fullname)) {(story._user.name)} + a href=(format!("https://www.wattpad.com/user/{}", story._user.fullname)) {(story._user.name)} br; "© " (story.copyright) }