diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..0c38d57
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,3 @@
+[target.x86_64-unknown-linux-gnu]
+linker = "clang"
+rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
diff --git a/Cargo.lock b/Cargo.lock
index 8aa65a0..ab124ac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1923,6 +1923,7 @@ dependencies = [
"maud",
"regex",
"serde",
+ "serde_json",
"tokio",
"tracing",
"tracing-subscriber",
@@ -2019,9 +2020,9 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wattpad"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9826298a0db9284b994af09fef8a564840af0e9e2621440aef46a5bd066481ed"
+checksum = "3d03f3af6f6244f85e6dd6a3981677de18daf8b8e1d8fd1d35642d47fc2a4e3f"
dependencies = [
"anyhow",
"regex",
diff --git a/Cargo.toml b/Cargo.toml
index 43ccd9f..0e0207a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.69"
-axum = { version = "0.6.10", features = ["macros"] }
+axum = { version = "0.6.10", features = ["macros", "json"] }
cached = "0.42.0"
chrono = "0.4.24"
grass = { version = "0.12.3", features = ["macro"] }
@@ -15,7 +15,8 @@ lazy_static = "1.4.0"
maud = { git = "https://github.com/lambda-fairy/maud", features = ["axum"] }
regex = "1.7.1"
serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.94"
tokio = { version = "1.26.0", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
-wattpad = "0.2.4"
+wattpad = "0.2.5"
diff --git a/css/index.scss b/css/index.scss
index 52182ad..4503e28 100644
--- a/css/index.scss
+++ b/css/index.scss
@@ -83,6 +83,7 @@ a:hover {
.story-details {
display: flex;
flex-direction: column;
+ align-self: flex-start;
}
}
diff --git a/scripts/History.js b/scripts/History.js
new file mode 100644
index 0000000..95e4c55
--- /dev/null
+++ b/scripts/History.js
@@ -0,0 +1,53 @@
+const oldOnloadHandler = window.onload ?? (() => {});
+
+async function historyLoad() {
+ const historyDiv = document.getElementById("story-list");
+ historyDiv.textContent = "";
+
+ const readingHistory = JSON.parse(
+ localStorage.getItem("readingHistory") ?? "[]"
+ );
+
+ for (let i = 0; i < readingHistory.length; i++) {
+ const story = await (
+ await fetch(`/api/getStory?id=${readingHistory[i]}`)
+ ).json();
+
+ const searchEle = document.createElement("a");
+ searchEle.className = "story";
+ searchEle.href =
+ "/story?id=" +
+ story.id +
+ (localStorage.getItem(`${story.id}_progress`)
+ ? `&chapter=${localStorage.getItem(`${story.id}_progress`)}`
+ : "");
+ const coverEle = document.createElement("img");
+ coverEle.src = story.cover;
+ const div = document.createElement("div");
+ const titleEle = document.createElement("h3");
+ titleEle.textContent = story.title;
+ div.appendChild(titleEle);
+ const descEle = document.createElement("p");
+ descEle.innerHTML = story.description.replace("\n", "
");
+ div.appendChild(descEle);
+ searchEle.appendChild(coverEle);
+ searchEle.appendChild(div);
+ historyDiv?.appendChild(searchEle);
+ }
+}
+
+window.onload = async () => {
+ oldOnloadHandler();
+
+ // render history
+ await historyLoad();
+
+ /* // clear history button
+ const button = document.getElementById("clear-button");
+
+ button.onclick = async () => {
+ localStorage.removeItem("readingHistory");
+ // rerender history
+ await historyLoad();
+ }; */
+};
diff --git a/src/cached_wattpad.rs b/src/cached_wattpad.rs
index 9e650cb..86bf288 100644
--- a/src/cached_wattpad.rs
+++ b/src/cached_wattpad.rs
@@ -18,7 +18,7 @@ pub async fn get_story(id: &str) -> Result {
.get(id)
{
let time_diff = current_time.signed_duration_since(cache_item.1);
- if time_diff.num_minutes() <= 5 {
+ if time_diff.num_minutes() <= 15 {
tracing::debug!("Cache hit for story {}", id);
return Ok(cache_item.0.clone());
}
diff --git a/src/main.rs b/src/main.rs
index 3e674e9..ff0d928 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,9 @@
mod cached_wattpad;
mod components;
mod routes;
+mod web_api;
-use routes::{index, not_found, story};
+use routes::{history, index, not_found, story};
use axum::{extract::Extension, routing::get, Router};
use lazy_static::lazy_static;
@@ -10,6 +11,7 @@ use maud::{html, Markup};
use std::{net::SocketAddr, sync::Arc};
use tokio::runtime::Runtime;
use wattpad::Wattpad;
+use web_api::get_story;
lazy_static! {
pub static ref TOKIO: Runtime = Runtime::new().unwrap();
@@ -25,6 +27,8 @@ fn main() {
let app = Router::new()
.route("/", get(index::render))
.route("/story", get(story::render))
+ .route("/history", get(history::render))
+ .route("/api/getStory", get(get_story))
.fallback(not_found::render);
tracing::info!("Listening on {}", addr);
diff --git a/src/routes/history.rs b/src/routes/history.rs
new file mode 100644
index 0000000..77e3000
--- /dev/null
+++ b/src/routes/history.rs
@@ -0,0 +1,60 @@
+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"};
+ meta name="viewport" content="width=device-width, initial-scale=1.0";
+ style {(PreEscaped(
+ grass::include!("./css/index.scss")
+ ))}
+ script {(PreEscaped(
+ include_str!("../../scripts/History.js")
+ ))}
+ (
+ 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
+ },
+ ], "/history".to_string())
+ )
+ div .index-content {
+ h1 { "History" }
+ noscript {
+ h3 {
+ "This won't work without JS. Run off now, GNU LibreJS looking motherfuckers."
+ }
+ }
+ div .story-list #story-list;
+ }
+ }
+}
diff --git a/src/routes/index.rs b/src/routes/index.rs
index 578ffb3..f007245 100644
--- a/src/routes/index.rs
+++ b/src/routes/index.rs
@@ -1,10 +1,12 @@
use crate::components::header::{Header, HeaderLink};
use cached::proc_macro::cached;
-use maud::{html, Markup, PreEscaped};
+use maud::{html, Markup, PreEscaped, DOCTYPE};
#[cached]
pub async fn render() -> Markup {
html! {
+ (DOCTYPE)
+ title {"Home | Voltpad"};
meta name="viewport" content="width=device-width, initial-scale=1.0";
style {(PreEscaped(
grass::include!("./css/index.scss")
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index 3ef05d6..f202a11 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -1,3 +1,4 @@
+pub mod history;
pub mod index;
pub mod not_found;
pub mod story;
diff --git a/src/routes/not_found.rs b/src/routes/not_found.rs
index aeb528b..d475913 100644
--- a/src/routes/not_found.rs
+++ b/src/routes/not_found.rs
@@ -8,6 +8,7 @@ pub async fn render() -> (StatusCode, Markup) {
(
StatusCode::NOT_FOUND,
html! {
+ title {"404 | Voltpad"};
meta name="viewport" content="width=device-width, initial-scale=1.0";
style {(PreEscaped(
grass::include!("./css/index.scss")
diff --git a/src/routes/story.rs b/src/routes/story.rs
index c359103..6b89869 100644
--- a/src/routes/story.rs
+++ b/src/routes/story.rs
@@ -27,6 +27,7 @@ pub async fn render(Query(params): Query) -> Markup {
(DOCTYPE)
html {
head {
+ title {(format!("{} | Voltpad", part.title))};
meta name="viewport" content="width=device-width, initial-scale=1.0";
style {(PreEscaped(
grass::include!("./css/index.scss")
@@ -76,6 +77,7 @@ pub async fn render(Query(params): Query) -> Markup {
(DOCTYPE)
html {
head {
+ title {(format!("{} | Voltpad", story.title))};
meta name="viewport" content="width=device-width, initial-scale=1.0";
style {(PreEscaped(
grass::include!("./css/index.scss")
diff --git a/src/web_api.rs b/src/web_api.rs
new file mode 100644
index 0000000..4d56449
--- /dev/null
+++ b/src/web_api.rs
@@ -0,0 +1,17 @@
+use crate::cached_wattpad::get_story as get_story_from_wattpad;
+use axum::extract::{Json, Query};
+use serde::Deserialize;
+use wattpad::Story;
+
+#[derive(Deserialize)]
+pub struct GetStoryParams {
+ pub id: String,
+}
+
+pub async fn get_story(Query(params): Query) -> Json {
+ let story = get_story_from_wattpad(¶ms.id)
+ .await
+ .expect("idk jump off a bridge");
+
+ Json(story)
+}