A couple things :3

master
Drake 1 year ago
parent b3cf350d3f
commit d2a905b67f

4
Cargo.lock generated

@ -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",

@ -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"

@ -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;
}
}

@ -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 +

@ -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<HashMap<String, (Story, DateTime<Local>)>> =

@ -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);

@ -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."
}
}
}
}

@ -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")

@ -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;
}
}
}
}

@ -1,4 +1,6 @@
pub mod about;
pub mod history;
pub mod index;
pub mod not_found;
pub mod search;
pub mod story;

@ -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<String>,
#[serde(rename = "type")]
search_type: Option<String>,
#[serde(rename = "sort")]
search_sort: Option<String>,
#[serde(rename = "page")]
page_number: Option<i64>,
}
/* 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<SearchParams>) -> 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', "<br/>"))) }
}
}
}
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)) { ">" }
}
}
}
}
})
}
}
}
}

@ -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<StoryParams>) -> Markup {
(story.title)
}
p {
(PreEscaped(story.description.replace("\n", "<br/>")))
(PreEscaped(story.description.replace('\n', "<br/>")))
}
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)
}

Loading…
Cancel
Save