From 6906cec2c30a245582f8caac8ed18dd82d495458 Mon Sep 17 00:00:00 2001 From: jmanczak Date: Wed, 8 Apr 2026 02:21:10 +0200 Subject: [PATCH 1/3] make dashboard only hold newest quote for now --- src/web/pages/dashboard.rs | 56 +++++++------------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/src/web/pages/dashboard.rs b/src/web/pages/dashboard.rs index 7267021..34f68e8 100644 --- a/src/web/pages/dashboard.rs +++ b/src/web/pages/dashboard.rs @@ -1,13 +1,12 @@ use axum::extract::Request; use chrono::{DateTime, Utc}; use maud::{Markup, PreEscaped, html}; -use uuid::Uuid; use crate::{ database::{self}, error::CompositeError, - persons::{Name, Person}, - quotes::{Quote, QuoteLine}, + persons::Person, + quotes::Quote, tags::Tag, users::{User, auth::UserAuthenticate}, web::{ @@ -33,7 +32,7 @@ pub async fn page(req: Request) -> Result { html!( (nav(u.as_ref(), req.uri().path())) - div class="mx-auto max-w-4xl mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4" { + div class="mx-auto max-w-4xl px-2 mt-4 grid grid-cols-1 --sm:grid-cols-2 gap-4" { div class="flex flex-col" { p {"Newest Quote"} @if let Some(q) = newest_quote { @@ -46,13 +45,13 @@ pub async fn page(req: Request) -> Result { p class="text-neutral-500 font-light mb-4" {"No quotes yet."} } } - div class="flex flex-col" { - p {"Quote of the Day"} - p class="text-neutral-500 font-light mb-4" {"This quote was voiced a year ago today."} - div class="flex-1 [&>div]:h-full" {(quote(&sample_quote_2()))} - } + // div class="flex flex-col" { + // p {"Quote of the Day"} // maybe "Quote of the Moment" instead? idk, this algorithm needs to be crazy + // p class="text-neutral-500 font-light mb-4" {"This quote was voiced a year ago today."} + // div class="flex-1 [&>div]:h-full" {(quote(&sample_quote_2()))} + // } } - div class="mx-auto max-w-4xl mt-4" { + div class="mx-auto max-w-4xl px-2 mt-4" { p class="mb-2" {"Quick access"} div class="flex gap-4" { @for (title, url, icon) in LINKS { @@ -66,7 +65,7 @@ pub async fn page(req: Request) -> Result { } } - div class="mx-auto max-w-4xl mt-4 flex flex-row gap-2" { + div class="mx-auto max-w-4xl px-2 mt-4 flex flex-row gap-2" { (chip(html!({ @match Quote::total_count(&conn) { Ok(count) => {(count) " QUOTES TOTAL"}, @@ -98,41 +97,6 @@ pub async fn page(req: Request) -> Result { )) } -fn sample_quote_2() -> Quote { - Quote { - id: Uuid::now_v7(), - public: true, - location: Some(String::from("Discord VC")), - context: Some(String::from("O narysowanej dziewczynie")), - created_by: Uuid::max(), - timestamp: DateTime::from(Utc::now()), - lines: vec![ - QuoteLine { - id: Uuid::now_v7(), - content: String::from("Czy tu proporcje są zachowane?"), - attribution: Name { - id: Uuid::now_v7(), - created_by: Uuid::max(), - person_id: Uuid::now_v7(), - is_primary: true, - name: String::from("Adam"), - }, - }, - QuoteLine { - id: Uuid::now_v7(), - content: String::from("Adam, ona nie ma kolan."), - attribution: Name { - id: Uuid::nil(), - created_by: Uuid::max(), - person_id: Uuid::now_v7(), - is_primary: true, - name: String::from("Mollin"), - }, - }, - ], - } -} - fn format_time_ago(dt: DateTime) -> String { let secs = Utc::now().signed_duration_since(dt).num_seconds(); match secs { From d56fcc3f4c4a35f35435902b89fbeea3adde658f Mon Sep 17 00:00:00 2001 From: jmanczak Date: Wed, 8 Apr 2026 02:34:54 +0200 Subject: [PATCH 2/3] pre-fill time input when entering /quote/add --- src/web/pages/quotes/add.rs | 2 ++ src/web/pages/quotes/prefill-time.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/web/pages/quotes/prefill-time.js diff --git a/src/web/pages/quotes/add.rs b/src/web/pages/quotes/add.rs index 7a9b630..aa6a780 100644 --- a/src/web/pages/quotes/add.rs +++ b/src/web/pages/quotes/add.rs @@ -24,6 +24,7 @@ use crate::{ }; const LINE_ADD_RM_SCRIPT: &str = include_str!("line-add-rm.js"); +const PREFILL_TIME_SCRIPT: &str = include_str!("prefill-time.js"); pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())?; @@ -58,6 +59,7 @@ pub async fn page(req: Request) -> Result { } } script {(PreEscaped(LINE_ADD_RM_SCRIPT))} + script {(PreEscaped(PREFILL_TIME_SCRIPT))} div class="flex gap-4 justify-between" { div class="flex flex-col flex-1" { label class="w-full"{ diff --git a/src/web/pages/quotes/prefill-time.js b/src/web/pages/quotes/prefill-time.js new file mode 100644 index 0000000..ee3c6df --- /dev/null +++ b/src/web/pages/quotes/prefill-time.js @@ -0,0 +1,15 @@ +document.addEventListener("DOMContentLoaded", () => { + const timeInput = document.querySelector( + 'input[type="datetime-local"][name="time"]', + ); + if (timeInput && !timeInput.value) { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); + + timeInput.value = `${year}-${month}-${day}T${hours}:${minutes}`; + } +}); From f2eab97c15121679223fb168998577f828193b0e Mon Sep 17 00:00:00 2001 From: jmanczak Date: Wed, 8 Apr 2026 03:01:52 +0200 Subject: [PATCH 3/3] quote chronological cursor scroll, icon --- src/quotes/mod.rs | 18 ++++++++++++ src/web/icons/calendar-arrow-down.svg | 1 + src/web/icons/mod.rs | 1 + src/web/pages/quotes.rs | 41 ++++++++++++++++++++------- 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/web/icons/calendar-arrow-down.svg diff --git a/src/quotes/mod.rs b/src/quotes/mod.rs index 865219b..9272f90 100644 --- a/src/quotes/mod.rs +++ b/src/quotes/mod.rs @@ -113,6 +113,24 @@ impl Quote { None => Ok(None), } } + pub fn get_chronological_cursorscroll( + conn: &Connection, + cursor: Option, + amount: i64, + ) -> Result, QuoteError> { + let ids = match cursor { + Some(c) => conn + .prepare("SELECT id FROM quotes WHERE id < ?1 ORDER BY id DESC LIMIT ?2")? + .query_map((c, amount), |r| r.get(0))? + .collect::, _>>()?, + None => conn + .prepare("SELECT id FROM quotes ORDER BY id DESC LIMIT ?1")? + .query_map((amount,), |r| r.get(0))? + .collect::, _>>()?, + }; + + ids.iter().map(|id| Self::get_by_id(conn, *id)).collect() + } pub fn create( conn: &Connection, lines: Vec<(String, Name)>, diff --git a/src/web/icons/calendar-arrow-down.svg b/src/web/icons/calendar-arrow-down.svg new file mode 100644 index 0000000..b3cea70 --- /dev/null +++ b/src/web/icons/calendar-arrow-down.svg @@ -0,0 +1 @@ + diff --git a/src/web/icons/mod.rs b/src/web/icons/mod.rs index ba62ff8..b7c47f3 100644 --- a/src/web/icons/mod.rs +++ b/src/web/icons/mod.rs @@ -1,6 +1,7 @@ // Below icons sourced from https://lucide.dev pub const ARROW_RIGHT: &str = include_str!("arrow-right.svg"); pub const CALENDAR_1: &str = include_str!("calendar-1.svg"); +pub const CALENDAR_ARROW_DOWN: &str = include_str!("calendar-arrow-down.svg"); pub const CIRCLE_MINUS: &str = include_str!("circle-minus.svg"); pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg"); pub const CLOCK: &str = include_str!("clock.svg"); diff --git a/src/web/pages/quotes.rs b/src/web/pages/quotes.rs index b4b5da8..46d5c47 100644 --- a/src/web/pages/quotes.rs +++ b/src/web/pages/quotes.rs @@ -5,20 +5,31 @@ use axum::{ use maud::{PreEscaped, html}; use crate::{ + database, error::CompositeError, - users::{User, auth::UserAuthenticate}, - web::{components::nav::nav, icons, pages::base}, + quotes::Quote, + users::{ + User, + auth::{UserAuthRequired, UserAuthenticate}, + }, + web::{ + components::{nav::nav, quote::quote}, + icons, + pages::base, + }, }; pub mod add; pub async fn page(req: Request) -> Result { - let u = User::authenticate(req.headers())?; + let u = User::authenticate(req.headers())?.required()?; + let conn = database::conn()?; + let quotes = Quote::get_chronological_cursorscroll(&conn, None, 20)?; Ok(base( "Quotes | Mnemosyne", html!( - (nav(u.as_ref(), req.uri().path())) + (nav(Some(&u), req.uri().path())) div class="max-w-4xl mx-auto px-2" { div class="my-4 flex justify-between" { @@ -26,16 +37,24 @@ pub async fn page(req: Request) -> Result { span class="text-neutral-500" {(PreEscaped(icons::SCROLL_TEXT))} span class="text-2xl font-semibold font-lora" {"Quotes"} } - @if let Some(_) = u { - a href="/quotes/add" class="group border rounded flex items-center gap-1 px-2 border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-400/5 hover:bg-neutral-400/10" { - span class="text-neutral-300 group-hover:text-neutral-200" {(PreEscaped(icons::PLUS))} - span class="text-neutral-300 group-hover:text-neutral-200" {"Add quote"} - } + a href="/quotes/add" class="group border rounded flex items-center gap-1 px-2 border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-400/5 hover:bg-neutral-400/10" { + span class="text-neutral-300 group-hover:text-neutral-200" {(PreEscaped(icons::PLUS))} + span class="text-neutral-300 group-hover:text-neutral-200" {"Add quote"} } } input class="border w-full border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-950/50 p-2 rounded" - placeholder="Search for quotes..."; - div class="text-center p-4" {"Search not yet implemented."} + placeholder="Search not yet implemented."; + div class="my-2 w-full" { + p class="ml-auto w-fit text-neutral-500 text-sm flex items-center" { + span class="scale-[.6]" {(PreEscaped(icons::CALENDAR_ARROW_DOWN))} + "Chronological" + } + } + div class="flex flex-col gap-4" { + @for q in quotes { + (quote(&q)) + } + } } ), )