document chrono view

This commit is contained in:
JMARyA 2025-02-09 23:38:17 +01:00
parent e2b755aeaf
commit 64956bf2f2
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
4 changed files with 152 additions and 10 deletions

2
Cargo.lock generated
View file

@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "based"
version = "0.1.0"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#c05d0dcc0a46e721e8664a15e6e2f264aa8d4b53"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#12e709d722e7fe9d61ca3d76b3642ff2ef5301c2"
dependencies = [
"bcrypt",
"chrono",

View file

@ -1,4 +1,7 @@
use std::{collections::HashSet, path::PathBuf};
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
};
use crate::{
blacklist::{check_blacklist, check_blacklist_path},
@ -9,6 +12,7 @@ use crate::{
mod document;
mod domain;
use based::get_pg;
use chrono::NaiveDate;
pub use document::Document;
pub use domain::*;
@ -292,3 +296,25 @@ pub async fn index_document(doc: &Document) {
}
}
}
pub struct DocumentIndex {}
impl DocumentIndex {
pub async fn get_documents_of_day(day: NaiveDate) -> HashMap<String, Vec<String>> {
let res: Vec<(String, String)> =
sqlx::query_as("SELECT domain, path FROM document_index WHERE version = $1")
.bind(day)
.fetch_all(get_pg!())
.await
.unwrap();
let mut ret = HashMap::new();
for (domain, path) in res {
let d: &mut Vec<String> = ret.entry(domain).or_default();
d.push(path);
}
ret
}
}

View file

@ -1,6 +1,6 @@
use based::asset::AssetRoutes;
use based::get_pg;
use based::ui::components::Shell;
use based::ui::components::{NavBar, Shell};
use based::ui::prelude::*;
use rocket::routes;
use webarc::ai::EmbedStore;
@ -57,7 +57,8 @@ async fn main() {
pages::domain_info_route,
pages::favicon_route,
pages::vector_search,
pages::render_txt_website
pages::render_txt_website,
pages::timeline_route
],
)
.manage(arc)
@ -189,6 +190,7 @@ pub fn get_shell() -> Shell {
.color(Zinc::_950),
)
.use_ui()
.with_navbar(NavBar("Web Archive"))
}
// TODO : archive cleanup code

View file

@ -1,19 +1,30 @@
use std::{io::Read, path::PathBuf, sync::Arc};
use std::{collections::HashMap, io::Read, path::PathBuf, sync::Arc};
use based::{
page,
request::{
api::GeneratedPager, assets::DataResponse, respond_json, RequestContext, StringResponse,
api::GeneratedPager, assets::DataResponse, respond_html, respond_json, RequestContext,
StringResponse,
},
ui::{
components::{
prelude::{InfinityScroll, Timeline, TimelineElement},
ColoredSpinner, Search, Shell,
},
primitives::flex::Column,
UIWidget,
},
ui::components::{Search, Shell},
};
use chrono::NaiveDate;
use maud::{html, PreEscaped};
use rocket::{get, request::FromSegments, State};
pub mod component;
use based::ui::prelude::*;
use component::*;
use serde_json::json;
use webarc::archive::Document;
use webarc::archive::{Document, DocumentIndex};
use webarc::{
ai::{generate_embedding, remove_data_urls, EmbedStore, SearchResult},
archive::{extract_domains, WebsiteArchive},
@ -21,10 +32,113 @@ use webarc::{
render_page,
};
// TODO : Implement archive timeline page (chrono sorted documents)
const SEARCH_BAR_STYLE: &str = "w-full px-4 mb-4 py-2 text-white bg-black border-2 border-neon-blue placeholder-neon-blue focus:ring-2 focus:ring-neon-pink focus:outline-none font-mono text-lg";
pub fn WebsiteIcon(domain: &str) -> PreEscaped<String> {
html! {
h2 class="text-xl font-bold mb-4 -ml-2 flex items-center w-fit" {
img class="p-2" src=(format!("/favicon/{domain}")) {};
a href=(format!("/d/{domain}")) { (domain) };
};
}
}
pub fn build_timeline(
domains: HashMap<String, Vec<String>>,
the_day: NaiveDate,
vanish: bool,
) -> PreEscaped<String> {
let mut tl = Timeline();
let yesterday = the_day - chrono::Duration::days(1);
let mut sorted_keys: Vec<_> = domains.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
let (domain, paths) = (key, domains.get(key).unwrap());
tl = tl.add_element(TimelineElement::new(
&the_day.to_string(),
WebsiteIcon(domain),
Column(
paths
.into_iter()
.map(|path| Link(&format!("/s/{domain}/{path}"), Text(&path).large()))
.collect(),
)
.gap(ScreenValue::_2),
));
}
tl = tl.add_after(InfinityScroll(
ColoredSpinner(Purple::_500),
&format!("/timeline?before={}", yesterday.to_string()),
));
if vanish {
tl = tl.vanish();
}
tl.render_with_class("")
}
pub async fn get_domains(before: &str) -> HashMap<String, Vec<String>> {
let today = NaiveDate::parse_from_str(before, "%Y-%m-%d").unwrap();
DocumentIndex::get_documents_of_day(today).await
}
pub async fn get_domains_lookback(
before: &str,
mut days: u64,
) -> (HashMap<String, Vec<String>>, NaiveDate) {
let mut the_day = NaiveDate::parse_from_str(before, "%Y-%m-%d").unwrap();
while days > 0 {
let domains = get_domains(&the_day.to_string()).await;
if !domains.is_empty() {
return (domains, the_day);
}
the_day -= chrono::Duration::days(1);
days -= 1;
}
(HashMap::new(), the_day)
}
#[get("/timeline?<before>")]
pub async fn timeline_route(
ctx: RequestContext,
shell: &State<Shell>,
before: Option<&str>,
) -> StringResponse {
let today = chrono::Local::now().date_naive();
let (domains, day) = if let Some(before) = before {
get_domains_lookback(before, 30 * 12).await
} else {
get_domains_lookback(&today.to_string(), 30 * 12).await
};
if domains.is_empty() {
return respond_html(Nothing().0);
}
if ctx.is_htmx && !ctx.htmx_redirect {
return respond_html(build_timeline(domains, day, true).0);
}
let tl = Screen::medium(Padding(Nothing()).all(ScreenValue::_6))
.on(Column(vec![
Margin(Text("Document Timeline")._3xl().extrabold())
.left(ScreenValue::_6)
.render_with_class(""),
build_timeline(domains, day, false),
]))
.render_with_class("");
page!(shell, ctx, "Timeline", tl)
}
/// Get the favicon of a domain
#[get("/favicon/<domain>")]
pub async fn favicon_route(domain: &str) -> Option<DataResponse> {