diff --git a/src/archive.rs b/src/archive.rs index 838b7c4..e784a50 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -60,7 +60,7 @@ impl Domain { Document::new(&self.name, path, self.dir.parent().unwrap().to_path_buf()) } - pub fn paths(&self, path: &str) -> Vec { + pub fn paths(&self, path: &str) -> (Vec, bool) { let mut base_path = self.dir.clone(); for p in path.split('/') { @@ -71,44 +71,31 @@ impl Domain { let mut ret = Vec::new(); + let mut is_doc = false; + for entry in dir_content { let url_path = format!("{path}/{entry}"); - let is_doc = read_dir(&base_path.join(entry)) - .into_iter() - .any(|x| x.starts_with("index_") && x.ends_with(".html")); - if is_doc { - ret.push(PathEntry::Document(Document::new( - &self.name, - &url_path, - self.dir.parent().unwrap().to_path_buf(), - ))); - } else { - ret.push(PathEntry::Path(self.name.clone(), url_path)); + if entry.starts_with("index_") && entry.ends_with(".html") { + is_doc = true; + continue; } + + ret.push(PathEntry(self.name.clone(), url_path)); } - ret + (ret, is_doc) } } -pub enum PathEntry { - Path(String, String), - Document(Document), -} +pub struct PathEntry(String, String); impl PathEntry { pub fn url(&self) -> String { - match self { - PathEntry::Path(domain, path) => format!("/d/{domain}/{path}"), - PathEntry::Document(document) => document.url(), - } + format!("/d/{}/{}", self.0, self.1) } - pub fn path(&self) -> String { - match self { - PathEntry::Path(_, path) => path.to_string(), - PathEntry::Document(document) => document.path.clone(), - } + pub fn path(&self) -> &String { + &self.1 } } @@ -170,7 +157,13 @@ impl Document { } pub fn versions(&self) -> Vec { - read_dir(&self.doc_dir()) + let mut res: Vec = read_dir(&self.doc_dir()) + .into_iter() + .filter(|x| x.starts_with("index_") && x.ends_with(".html")) + .collect(); + res.sort(); + res.reverse(); + res } } diff --git a/src/favicon.rs b/src/favicon.rs index 1f98b9d..bfe0e45 100644 --- a/src/favicon.rs +++ b/src/favicon.rs @@ -17,8 +17,11 @@ pub async fn download_favicon(domain: &str) -> Option> { pub async fn download_fav_for(site: &str) { if let Some(fav) = download_favicon(site).await { - std::fs::write(std::path::Path::new("./favicon").join(site), fav).unwrap(); - log::info!("Writting favicon for {site}"); + let fav_path = std::path::Path::new("./favicon").join(site); + if !fav_path.exists() { + std::fs::write(fav_path, fav).unwrap(); + log::info!("Writting favicon for {site}"); + } } } diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 8ca3ab0..fe7d566 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -7,7 +7,7 @@ use based::{ use maud::{html, PreEscaped}; use rocket::{get, State}; -use crate::archive::{PathEntry, WebsiteArchive}; +use crate::archive::WebsiteArchive; pub async fn render_page(content: PreEscaped, ctx: RequestContext) -> StringResponse { based::page::render_page( @@ -17,6 +17,7 @@ pub async fn render_page(content: PreEscaped, ctx: RequestContext) -> St &Shell::new( html! { script src="https://cdn.tailwindcss.com" {}; + meta name="viewport" content="width=device-width, initial-scale=1.0" {}; }, html! {}, Some("bg-zinc-950 text-white min-h-screen flex pt-8 justify-center".to_string()), @@ -72,6 +73,35 @@ pub fn arrow_icon(color: &str) -> PreEscaped { } } +pub fn slash_seperator() -> PreEscaped { + html! { + p class="font-bold p-2 text-gray-400" { " / " }; + } +} + +pub fn gen_path_link( + path: &str, + index: usize, + path_seperations: &[&str], + domain: &str, +) -> PreEscaped { + let upto: Vec<&str> = path_seperations.iter().take(index + 1).cloned().collect(); + html! { + a href=(format!("/d/{}/{}", domain, upto.join("/"))) { (path)} + } +} + +pub fn gen_path_header(path_seperations: Vec<&str>, domain: &str) -> PreEscaped { + html! { + @for (index, path) in path_seperations.iter().enumerate() { + (gen_path_link(path, index, &path_seperations, domain)) + @if index < path_seperations.len()-1 { + (slash_seperator()) + }; + }; + } +} + #[get("/d//")] pub async fn domain_info_route( ctx: RequestContext, @@ -80,19 +110,56 @@ pub async fn domain_info_route( arc: &State, ) -> StringResponse { let domain = arc.get_domain(domain); - let paths = domain.paths(paths.to_str().unwrap()); + let document = domain.path(paths.to_str().unwrap()); + let versions: Vec = document + .versions() + .into_iter() + .map(|x| { + x.trim_start_matches("index_") + .trim_end_matches(".html") + .to_string() + }) + .collect(); + let (path_entries, is_doc) = domain.paths(paths.to_str().unwrap()); + let path_seperations: Vec<&str> = paths.to_str().unwrap().split('/').collect(); let content = html! { - h2 class="text-xl font-bold mb-4" { (domain.name) }; - div class="max-w-md mx-auto p-4 bg-neutral-900 rounded-lg shadow-md" { - ul class="space-y-2" { - @for path in paths { - a href=(path.url()) class="flex items-center gap-2 p-3 border bg-neutral-800 rounded hover:shadow-lg transition" { - @if matches!(path, PathEntry::Document(_)) { - (arrow_icon("red")) + h2 class="text-xl font-bold mb-4 flex items-center" { + img class="p-2" src=(format!("/favicon/{}", &domain.name)) {}; + a href=(format!("/d/{}", &domain.name)) { (domain.name) }; + (slash_seperator()) + (gen_path_header(path_seperations, &domain.name)) + }; + + @if !versions.is_empty() { + div class="max-w-md mx-auto p-4 bg-neutral-900 rounded-lg shadow-md mb-8" { + h3 class="font-bold mb-2" { "Saved Versions:" }; + @for (index, ver) in versions.into_iter().enumerate() { + a href=(format!("/s/{}/{}?time={}", &domain.name, paths.to_str().unwrap(), ver)) class="text-xs text-blue-400" { + + @if index == 0 { + (format!("Latest ({ver})")) } @else { - (arrow_icon("blue")) - }; + (ver) + } + }; + } + }; + } + + @if path_entries.is_empty() && !is_doc { + div class="max-w-md mx-auto p-4 bg-neutral-900 rounded-lg shadow-md" { + p class="p-4 text-xl" { "Nothing found" }; + } + } + + @if !path_entries.is_empty() { + div class="max-w-md mx-auto p-4 bg-neutral-900 rounded-lg shadow-md" { + h3 class="font-bold mb-2" { "Paths:" }; + ul class="space-y-2" { + @for path in path_entries { + a href=(path.url()) class="flex items-center gap-2 p-3 border bg-neutral-800 rounded hover:shadow-lg transition" { + (arrow_icon("blue")) span class="font-medium" { (path.path()) }; }; @@ -100,6 +167,7 @@ pub async fn domain_info_route( }; }; }; + }; render_page(content, ctx).await }