parent
2f83d5f136
commit
877ce477cc
3 changed files with 177 additions and 24 deletions
103
src/archive.rs
103
src/archive.rs
|
@ -4,6 +4,22 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn read_dir(dir: &PathBuf) -> Vec<String> {
|
||||||
|
let mut list = Vec::new();
|
||||||
|
|
||||||
|
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||||
|
for entry in entries {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
if let Some(file_name) = entry.file_name().to_str() {
|
||||||
|
list.push(file_name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list
|
||||||
|
}
|
||||||
|
|
||||||
fn internalize_urls(input: &str) -> String {
|
fn internalize_urls(input: &str) -> String {
|
||||||
let url_pattern = r"https?://([a-zA-Z0-9.-]+)(/[\w./-]*)";
|
let url_pattern = r"https?://([a-zA-Z0-9.-]+)(/[\w./-]*)";
|
||||||
let re = regex::Regex::new(url_pattern).unwrap();
|
let re = regex::Regex::new(url_pattern).unwrap();
|
||||||
|
@ -39,6 +55,57 @@ impl Domain {
|
||||||
pub fn path(&self, path: &str) -> Document {
|
pub fn path(&self, path: &str) -> Document {
|
||||||
Document::new(&self.name, path, self.dir.parent().unwrap().to_path_buf())
|
Document::new(&self.name, path, self.dir.parent().unwrap().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paths(&self, path: &str) -> Vec<PathEntry> {
|
||||||
|
let mut base_path = self.dir.clone();
|
||||||
|
|
||||||
|
for p in path.split('/') {
|
||||||
|
base_path = base_path.join(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir_content = read_dir(&base_path);
|
||||||
|
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PathEntry {
|
||||||
|
Path(String, String),
|
||||||
|
Document(Document),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathEntry {
|
||||||
|
pub fn url(&self) -> String {
|
||||||
|
match self {
|
||||||
|
PathEntry::Path(domain, path) => format!("/d/{domain}/{path}"),
|
||||||
|
PathEntry::Document(document) => document.url(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> String {
|
||||||
|
match self {
|
||||||
|
PathEntry::Path(_, path) => path.to_string(),
|
||||||
|
PathEntry::Document(document) => document.path.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
|
@ -56,17 +123,17 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_local(&self, version: Option<String>) -> Option<String> {
|
pub fn url(&self) -> String {
|
||||||
let mut file_path = self.base_dir.join(&self.domain);
|
format!("/s/{}/{}", self.domain, self.path)
|
||||||
|
}
|
||||||
|
|
||||||
for p in self.path.split('/') {
|
pub fn render_local(&self, version: Option<String>) -> Option<String> {
|
||||||
file_path = file_path.join(p);
|
let mut file_path = self.doc_dir();
|
||||||
}
|
|
||||||
|
|
||||||
let latest_version = if let Some(version) = version {
|
let latest_version = if let Some(version) = version {
|
||||||
format!("index_{version}.html")
|
format!("index_{version}.html")
|
||||||
} else {
|
} else {
|
||||||
let versions = Self::versions(&file_path);
|
let versions = self.versions();
|
||||||
versions.first().cloned()?
|
versions.first().cloned()?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,20 +148,18 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn versions(path: &PathBuf) -> Vec<String> {
|
pub fn doc_dir(&self) -> PathBuf {
|
||||||
let mut version_list = Vec::new();
|
let mut file_path = self.base_dir.join(&self.domain);
|
||||||
|
|
||||||
if let Ok(entries) = std::fs::read_dir(path) {
|
for p in self.path.split('/') {
|
||||||
for entry in entries {
|
file_path = file_path.join(p);
|
||||||
if let Ok(entry) = entry {
|
|
||||||
if let Some(file_name) = entry.file_name().to_str() {
|
|
||||||
version_list.push(file_name.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version_list
|
file_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn versions(&self) -> Vec<String> {
|
||||||
|
read_dir(&self.doc_dir())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +170,10 @@ impl WebsiteArchive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn domains(&self) -> Vec<String> {
|
||||||
|
read_dir(&self.dir)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_domain(&self, domain: &str) -> Domain {
|
pub fn get_domain(&self, domain: &str) -> Domain {
|
||||||
Domain::new(domain, self.dir.join(domain))
|
Domain::new(domain, self.dir.join(domain))
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,13 @@ async fn launch() -> _ {
|
||||||
let arc = WebsiteArchive::new("./websites");
|
let arc = WebsiteArchive::new("./websites");
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount("/", routes![pages::index, pages::render_website])
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![
|
||||||
|
pages::index,
|
||||||
|
pages::render_website,
|
||||||
|
pages::domain_info_route
|
||||||
|
],
|
||||||
|
)
|
||||||
.manage(arc)
|
.manage(arc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,92 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use based::request::{respond_html, StringResponse};
|
use based::{
|
||||||
use maud::html;
|
page::Shell,
|
||||||
|
request::{respond_html, RequestContext, StringResponse},
|
||||||
|
};
|
||||||
|
use maud::{html, PreEscaped};
|
||||||
use rocket::{get, State};
|
use rocket::{get, State};
|
||||||
|
|
||||||
use crate::archive::WebsiteArchive;
|
use crate::archive::{PathEntry, WebsiteArchive};
|
||||||
|
|
||||||
|
pub async fn render_page(content: PreEscaped<String>, ctx: RequestContext) -> StringResponse {
|
||||||
|
based::page::render_page(
|
||||||
|
content,
|
||||||
|
"Website Archive",
|
||||||
|
ctx,
|
||||||
|
&Shell::new(
|
||||||
|
html! {
|
||||||
|
script src="https://cdn.tailwindcss.com" {};
|
||||||
|
},
|
||||||
|
html! {},
|
||||||
|
Some("bg-zinc-950 text-white min-h-screen flex pt-8 justify-center".to_string()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index() -> StringResponse {
|
pub async fn index(ctx: RequestContext, arc: &State<WebsiteArchive>) -> StringResponse {
|
||||||
// TODO : websites overview grid
|
let websites = arc.domains();
|
||||||
unimplemented!()
|
|
||||||
|
let content = html! {
|
||||||
|
div class="container mx-auto p-4" {
|
||||||
|
h1 class="text-5xl font-bold text-center mb-10" { "Websites" };
|
||||||
|
div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6 w-screen" {
|
||||||
|
|
||||||
|
@for site in websites {
|
||||||
|
a href=(format!("/d/{site}")) class="bg-neutral-900 shadow-md rounded-lg hover:bg-neutral-800 bg-gray-1 hover:cursor-pointer transition-all duration-300 flex flex-col items-center justify-center aspect-square max-w-60" {
|
||||||
|
div class="bg-blue-500 text-white rounded-full p-4" {
|
||||||
|
img class="h-8 w-8" src=(format!("/favicon/{site}")) {};
|
||||||
|
};
|
||||||
|
p class="mt-4 text-base font-medium" { (site) };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render_page(content, ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrow_icon(color: &str) -> PreEscaped<String> {
|
||||||
|
html! {
|
||||||
|
svg class=(format!("w-5 h-5 text-{color}-500")) xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" {
|
||||||
|
path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/d/<domain>/<paths..>")]
|
||||||
|
pub async fn domain_info_route(
|
||||||
|
ctx: RequestContext,
|
||||||
|
domain: &str,
|
||||||
|
paths: PathBuf,
|
||||||
|
arc: &State<WebsiteArchive>,
|
||||||
|
) -> StringResponse {
|
||||||
|
let domain = arc.get_domain(domain);
|
||||||
|
let paths = domain.paths(paths.to_str().unwrap());
|
||||||
|
|
||||||
|
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"))
|
||||||
|
} @else {
|
||||||
|
(arrow_icon("blue"))
|
||||||
|
};
|
||||||
|
|
||||||
|
span class="font-medium" { (path.path()) };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
render_page(content, ctx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/s/<domain>/<path..>?<time>")]
|
#[get("/s/<domain>/<path..>?<time>")]
|
||||||
|
|
Loading…
Add table
Reference in a new issue