Compare commits
4 Commits
d6e68ac8f7
...
b1e713fd18
| Author | SHA1 | Date | |
|---|---|---|---|
| b1e713fd18 | |||
|
f2eab97c15
|
|||
|
d56fcc3f4c
|
|||
|
6906cec2c3
|
@@ -113,6 +113,24 @@ impl Quote {
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_chronological_cursorscroll(
|
||||||
|
conn: &Connection,
|
||||||
|
cursor: Option<Uuid>,
|
||||||
|
amount: i64,
|
||||||
|
) -> Result<Vec<Quote>, 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::<Result<Vec<Uuid>, _>>()?,
|
||||||
|
None => conn
|
||||||
|
.prepare("SELECT id FROM quotes ORDER BY id DESC LIMIT ?1")?
|
||||||
|
.query_map((amount,), |r| r.get(0))?
|
||||||
|
.collect::<Result<Vec<Uuid>, _>>()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
ids.iter().map(|id| Self::get_by_id(conn, *id)).collect()
|
||||||
|
}
|
||||||
pub fn create(
|
pub fn create(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
lines: Vec<(String, Name)>,
|
lines: Vec<(String, Name)>,
|
||||||
|
|||||||
1
src/web/icons/calendar-arrow-down.svg
Normal file
1
src/web/icons/calendar-arrow-down.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-arrow-down-icon lucide-calendar-arrow-down"><path d="m14 18 4 4 4-4"/><path d="M16 2v4"/><path d="M18 14v8"/><path d="M21 11.354V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7.343"/><path d="M3 10h18"/><path d="M8 2v4"/></svg>
|
||||||
|
After Width: | Height: | Size: 442 B |
@@ -1,6 +1,7 @@
|
|||||||
// Below icons sourced from https://lucide.dev
|
// Below icons sourced from https://lucide.dev
|
||||||
pub const ARROW_RIGHT: &str = include_str!("arrow-right.svg");
|
pub const ARROW_RIGHT: &str = include_str!("arrow-right.svg");
|
||||||
pub const CALENDAR_1: &str = include_str!("calendar-1.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 CIRCLE_MINUS: &str = include_str!("circle-minus.svg");
|
||||||
pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg");
|
pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg");
|
||||||
pub const CLOCK: &str = include_str!("clock.svg");
|
pub const CLOCK: &str = include_str!("clock.svg");
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use maud::{Markup, PreEscaped, html};
|
use maud::{Markup, PreEscaped, html};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{self},
|
database::{self},
|
||||||
error::CompositeError,
|
error::CompositeError,
|
||||||
persons::{Name, Person},
|
persons::Person,
|
||||||
quotes::{Quote, QuoteLine},
|
quotes::Quote,
|
||||||
tags::Tag,
|
tags::Tag,
|
||||||
users::{User, auth::UserAuthenticate},
|
users::{User, auth::UserAuthenticate},
|
||||||
web::{
|
web::{
|
||||||
@@ -33,7 +32,7 @@ pub async fn page(req: Request) -> Result<Markup, CompositeError> {
|
|||||||
html!(
|
html!(
|
||||||
(nav(u.as_ref(), req.uri().path()))
|
(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" {
|
div class="flex flex-col" {
|
||||||
p {"Newest Quote"}
|
p {"Newest Quote"}
|
||||||
@if let Some(q) = newest_quote {
|
@if let Some(q) = newest_quote {
|
||||||
@@ -46,13 +45,13 @@ pub async fn page(req: Request) -> Result<Markup, CompositeError> {
|
|||||||
p class="text-neutral-500 font-light mb-4" {"No quotes yet."}
|
p class="text-neutral-500 font-light mb-4" {"No quotes yet."}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div class="flex flex-col" {
|
// div class="flex flex-col" {
|
||||||
p {"Quote of the Day"}
|
// 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."}
|
// 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-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"}
|
p class="mb-2" {"Quick access"}
|
||||||
div class="flex gap-4" {
|
div class="flex gap-4" {
|
||||||
@for (title, url, icon) in LINKS {
|
@for (title, url, icon) in LINKS {
|
||||||
@@ -66,7 +65,7 @@ pub async fn page(req: Request) -> Result<Markup, CompositeError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
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!({
|
(chip(html!({
|
||||||
@match Quote::total_count(&conn) {
|
@match Quote::total_count(&conn) {
|
||||||
Ok(count) => {(count) " QUOTES TOTAL"},
|
Ok(count) => {(count) " QUOTES TOTAL"},
|
||||||
@@ -98,41 +97,6 @@ pub async fn page(req: Request) -> Result<Markup, CompositeError> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Utc>) -> String {
|
fn format_time_ago(dt: DateTime<Utc>) -> String {
|
||||||
let secs = Utc::now().signed_duration_since(dt).num_seconds();
|
let secs = Utc::now().signed_duration_since(dt).num_seconds();
|
||||||
match secs {
|
match secs {
|
||||||
|
|||||||
@@ -5,20 +5,31 @@ use axum::{
|
|||||||
use maud::{PreEscaped, html};
|
use maud::{PreEscaped, html};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
database,
|
||||||
error::CompositeError,
|
error::CompositeError,
|
||||||
users::{User, auth::UserAuthenticate},
|
quotes::Quote,
|
||||||
web::{components::nav::nav, icons, pages::base},
|
users::{
|
||||||
|
User,
|
||||||
|
auth::{UserAuthRequired, UserAuthenticate},
|
||||||
|
},
|
||||||
|
web::{
|
||||||
|
components::{nav::nav, quote::quote},
|
||||||
|
icons,
|
||||||
|
pages::base,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod add;
|
pub mod add;
|
||||||
|
|
||||||
pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
||||||
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(
|
Ok(base(
|
||||||
"Quotes | Mnemosyne",
|
"Quotes | Mnemosyne",
|
||||||
html!(
|
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="max-w-4xl mx-auto px-2" {
|
||||||
div class="my-4 flex justify-between" {
|
div class="my-4 flex justify-between" {
|
||||||
@@ -26,16 +37,24 @@ pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
|||||||
span class="text-neutral-500" {(PreEscaped(icons::SCROLL_TEXT))}
|
span class="text-neutral-500" {(PreEscaped(icons::SCROLL_TEXT))}
|
||||||
span class="text-2xl font-semibold font-lora" {"Quotes"}
|
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" {
|
||||||
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" {(PreEscaped(icons::PLUS))}
|
span class="text-neutral-300 group-hover:text-neutral-200" {"Add quote"}
|
||||||
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"
|
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...";
|
placeholder="Search not yet implemented.";
|
||||||
div class="text-center p-4" {"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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LINE_ADD_RM_SCRIPT: &str = include_str!("line-add-rm.js");
|
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<Response, CompositeError> {
|
pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
||||||
let u = User::authenticate(req.headers())?;
|
let u = User::authenticate(req.headers())?;
|
||||||
@@ -58,6 +59,7 @@ pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
script {(PreEscaped(LINE_ADD_RM_SCRIPT))}
|
script {(PreEscaped(LINE_ADD_RM_SCRIPT))}
|
||||||
|
script {(PreEscaped(PREFILL_TIME_SCRIPT))}
|
||||||
div class="flex gap-4 justify-between" {
|
div class="flex gap-4 justify-between" {
|
||||||
div class="flex flex-col flex-1" {
|
div class="flex flex-col flex-1" {
|
||||||
label class="w-full"{
|
label class="w-full"{
|
||||||
|
|||||||
15
src/web/pages/quotes/prefill-time.js
Normal file
15
src/web/pages/quotes/prefill-time.js
Normal file
@@ -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}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user