Compare commits

..

4 Commits

Author SHA1 Message Date
august 5848b7360c Add date editing for posts 2025-01-09 18:29:24 -05:00
august d0a4b0f6a6 Update Cargo.lock 2025-01-09 12:06:01 -05:00
august 59a6205a1e RSS fixes 2025-01-09 12:05:42 -05:00
august 4bfce7c928 Add RSS and fixups 2025-01-07 17:59:07 -05:00
16 changed files with 271 additions and 30 deletions
Generated
+74 -6
View File
@@ -67,6 +67,21 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.95" version = "1.0.95"
@@ -346,6 +361,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.4" version = "0.4.4"
@@ -398,6 +427,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.16"
@@ -658,9 +693,9 @@ dependencies = [
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.3.1" version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"parking", "parking",
@@ -1042,6 +1077,29 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
@@ -1295,9 +1353,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "litemap" name = "litemap"
@@ -2093,9 +2151,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.42" version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@@ -2222,6 +2280,7 @@ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
"blogdb", "blogdb",
"chrono",
"constcat", "constcat",
"dotenvy", "dotenvy",
"futures", "futures",
@@ -3277,6 +3336,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
+13
View File
@@ -75,6 +75,19 @@ impl BlogDb {
.await?; .await?;
Ok(()) Ok(())
} }
/// Delete post by id
pub async fn set_post_date<S, T>(&self, id: S, date: T) -> anyhow::Result<()>
where
S: AsRef<str>,
T: AsRef<str>,
{
sqlx::query("UPDATE posts SET date=? WHERE id=?")
.bind(date.as_ref())
.bind(id.as_ref())
.execute(&self.db)
.await?;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
+1
View File
@@ -46,6 +46,7 @@ tracing-subscriber = { version = "0.3.18", features = [
"std", "std",
] } ] }
phf = "0.11.2" phf = "0.11.2"
chrono = { version = "0.4.39", features = ["alloc"] }
[build-dependencies] [build-dependencies]
dotenvy = "0.15.7" dotenvy = "0.15.7"
+18 -2
View File
@@ -17,7 +17,7 @@ fn main() {
println!("cargo:rustc-env={key}={value}"); println!("cargo:rustc-env={key}={value}");
} }
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
let mut file = BufWriter::new(File::create(&path).unwrap()); let mut outfile = BufWriter::new(File::create(&path).unwrap());
let template_dir = [&env::var("CARGO_MANIFEST_DIR").unwrap(), "/src/templates"].concat(); let template_dir = [&env::var("CARGO_MANIFEST_DIR").unwrap(), "/src/templates"].concat();
println!("cargo:rerun-if-changed={}", template_dir); println!("cargo:rerun-if-changed={}", template_dir);
let pattern = [&template_dir, "/**/*.html"].concat(); let pattern = [&template_dir, "/**/*.html"].concat();
@@ -47,8 +47,24 @@ fn main() {
Err(_) => todo!(), Err(_) => todo!(),
} }
} }
// this is all a mess cause i'm lazy lol
let feed_template_path = [&template_dir, "/feed.xml"].concat();
let mut feed_file = File::open(feed_template_path).unwrap();
let mut content = "r####\"".to_string();
feed_file.read_to_string(&mut content).unwrap();
content.push_str("\"####");
let feed_template = content;
map.entry("feed".to_string(), &feed_template);
let item_template_path = [&template_dir, "/item.xml"].concat();
let mut item_file = File::open(item_template_path).unwrap();
let mut content = "r####\"".to_string();
item_file.read_to_string(&mut content).unwrap();
content.push_str("\"####");
let item_template = content;
map.entry("item".to_string(), &item_template);
writeln!( writeln!(
&mut file, &mut outfile,
"static TEMPLATES: phf::Map<&'static str, &'static str> = {};", "static TEMPLATES: phf::Map<&'static str, &'static str> = {};",
map.build() map.build()
) )
+3 -3
View File
@@ -23,7 +23,7 @@ main {
} }
.admin-widget:not(.admin-widget-user) { .admin-widget:not(.admin-widget-user) {
max-inline-size: 50%; max-inline-size: calc(50% - (0.5 * var(--default-padding)));
} }
.admin-widget-user { .admin-widget-user {
@@ -122,8 +122,8 @@ main {
.blog-admin { .blog-admin {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
gap: var(--default-padding);
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--default-padding);
box-sizing: border-box; box-sizing: border-box;
min-block-size: 0; min-block-size: 0;
flex: 1; flex: 1;
@@ -246,7 +246,7 @@ main {
a.entry-content { a.entry-content {
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
inline-size: 90%; max-inline-size: 90%;
transition: all 0.3s ease; transition: all 0.3s ease;
+6
View File
@@ -91,6 +91,7 @@ main {
@media all and (max-width: 650px) { @media all and (max-width: 650px) {
.ce-toolbar__actions { .ce-toolbar__actions {
&>*:nth-child(1), &>*:nth-child(1),
&>*:nth-child(2) { &>*:nth-child(2) {
border-radius: 0; border-radius: 0;
@@ -192,6 +193,11 @@ main {
background-color: var(--color-selection) !important; background-color: var(--color-selection) !important;
} }
.cdx-quote__text {
border-block-end: none !important;
margin-block-end: 0 !important;
}
.embed-tool__caption { .embed-tool__caption {
display: none; display: none;
} }
+11
View File
@@ -0,0 +1,11 @@
const editDateButton = document.getElementById("date-update");
const postActions = document.getElementById("post-actions");
const children = postActions.innerHTML;
dateInput = `<button class="form-action" formaction="/admin" formmethod="get" formnovalidate>✕</button><input class="form-action" type="date" name="date" placeholder="Enter Date" autocomplete="off" aria-label="New Date" required /> <button class="form-action" type="submit" formaction="/api/posts/date">Update Date</button>`;
editDateButton.addEventListener("click", (ev) => {
ev.preventDefault();
console.log(ev, postActions, dateInput);
postActions.innerHTML = dateInput;
});
+1 -11
View File
@@ -3239,16 +3239,6 @@
static get sanitize() { static get sanitize() {
return { text: { br: !0 }, caption: { br: !0 }, alignment: {} }; return { text: { br: !0 }, caption: { br: !0 }, alignment: {} };
} }
renderSettings() {
const t = (n) => n && n[0].toUpperCase() + n.slice(1);
return this.settings.map((n) => ({
icon: n.icon,
label: this.api.i18n.t(`Align ${t(n.name)}`),
onActivate: () => this._toggleTune(n.name),
isActive: this._data.alignment === n.name,
closeOnActivate: !0,
}));
}
_toggleTune(t) { _toggleTune(t) {
(this._data.alignment = t), this._block.dispatchChange(); (this._data.alignment = t), this._block.dispatchChange();
} }
@@ -13662,7 +13652,7 @@ var editor = new EditorJS({
inlineToolbar: true, inlineToolbar: true,
config: { config: {
quotePlaceholder: "Enter a quote", quotePlaceholder: "Enter a quote",
captionPlaceholder: "Quote's author", captionPlaceholder: "Add author or a caption, leave blank to hide",
}, },
shortcut: "CMD+SHIFT+O", shortcut: "CMD+SHIFT+O",
}, },
+87 -4
View File
@@ -1,4 +1,4 @@
use std::borrow::Borrow; use chrono::prelude::*;
use std::{env, vec}; use std::{env, vec};
use blogdb::posts::Post; use blogdb::posts::Post;
@@ -295,8 +295,7 @@ async fn admin_entries(entry_type: EntryType, session_id: i64, db: &BlogDb) -> S
let is_empty: bool; let is_empty: bool;
let entries_html = match entry_type { let entries_html = match entry_type {
EntryType::Post => { EntryType::Post => {
let mut entries = db.get_posts().await.unwrap_or(vec![]); let entries = db.get_posts().await.unwrap_or(vec![]);
entries.reverse();
let mut entry_list_html = String::new(); let mut entry_list_html = String::new();
is_empty = entries.is_empty(); is_empty = entries.is_empty();
if !is_empty { if !is_empty {
@@ -773,7 +772,7 @@ where
make_page(message, PageSettings::title("Not found")) make_page(message, PageSettings::title("Not found"))
} }
pub(crate) fn animate_anchors<S>(input: S) -> String pub(crate) fn zhuzh_anchors<S>(input: S) -> String
where where
S: AsRef<str>, S: AsRef<str>,
{ {
@@ -781,6 +780,11 @@ where
input.as_ref(), input.as_ref(),
RewriteStrSettings { RewriteStrSettings {
element_content_handlers: vec![element!("a", move |t| { element_content_handlers: vec![element!("a", move |t| {
if let Some(href) = t.get_attribute("href") {
if !href.starts_with("/") {
t.set_attribute("target", "_blank").unwrap_or_default();
}
}
match t.get_attribute("class") { match t.get_attribute("class") {
Some(class) => t Some(class) => t
.set_attribute("class", &[&class, " animated-link-underline"].concat()) .set_attribute("class", &[&class, " animated-link-underline"].concat())
@@ -811,3 +815,82 @@ pub(crate) fn sanitize<S: AsRef<str>>(input: S) -> String {
) )
.unwrap_or_default() .unwrap_or_default()
} }
pub(crate) async fn rss(db: &BlogDb) -> String {
let posts = db.get_posts().await.unwrap_or_default();
let mut items = String::new();
for post in posts {
let url = {
#[cfg(debug_assertions)]
{
["http://localhost/blog/", &post.id].concat()
}
#[cfg(not(debug_assertions))]
{
["https://", env!("DOMAIN"), "/blog/", &post.id].concat()
}
};
let item = rewrite_str(
template!("item"),
RewriteStrSettings {
element_content_handlers: vec![
element!("title", |title| {
title.set_inner_content(&post.title, ContentType::Text);
Ok(())
}),
element!("link", |link| {
link.replace(&["<link>", &url, "</link>"].concat(), ContentType::Html); // slight hack to get around lol_html weirdness
Ok(())
}),
element!("guid", |guid| {
guid.set_inner_content(&url, ContentType::Text);
Ok(())
}),
element!("pubDate", |pub_date| {
let date = {
let date: [u32; 3] = post
.date
.splitn(3, "-")
.map(|d| d.parse::<u32>().unwrap_or_default())
.collect::<Vec<u32>>()
.try_into()
.unwrap_or_default();
let date_time: DateTime<FixedOffset> =
FixedOffset::west_opt(5 * 3600) // New York City
.unwrap()
.with_ymd_and_hms(date[0] as i32, date[1], date[2], 0, 0, 0)
.unwrap();
date_time.to_rfc2822()
};
pub_date.set_inner_content(&date, ContentType::Text);
Ok(())
}),
element!("description", |description| {
let post_content: Blocks =
serde_json::from_str(&post.content).unwrap_or_default();
let desc = ["<![CDATA[", &post_content.to_html(), "]]>"].concat();
description.set_inner_content(&desc, ContentType::Html);
Ok(())
}),
],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
items.push_str(&item);
}
let feed = rewrite_str(
template!("feed"),
RewriteStrSettings {
element_content_handlers: vec![element!("channel", move |channel| {
channel.append(&items, ContentType::Html);
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
feed
}
+13 -2
View File
@@ -102,12 +102,23 @@ impl Block {
] ]
.concat(), .concat(),
Block::image { file, caption } => { Block::image { file, caption } => {
["<img src=\"", &file.url, "\" alt=\"", &caption.replace(r#"""#, "&quot;").replace("<br>", ""), "\"/>"].concat() let src = {
#[cfg(debug_assertions)]
{
&file.url
}
#[cfg(not(debug_assertions))]
{
&["https://", env!("DOMAIN"), &file.url].concat()
}
};
["<img src=\"", src, "\" alt=\"", &caption.replace(r#"""#, "&quot;").replace("<br>", ""), "\"/>"].concat()
} }
Block::delimiter {} => "<div style=\"inline-size: 100%; block-size: 1px; background: var(--color-text)\"></div>".to_string(), Block::delimiter {} => "<div style=\"inline-size: 100%; block-size: 1px; background: var(--color-text)\"></div>".to_string(),
}; };
html::animate_anchors(html::remove_el(&text, "br")) html::zhuzh_anchors(html::remove_el(&text, "br"))
} }
fn to_plaintext(&self) -> String { fn to_plaintext(&self) -> String {
+8
View File
@@ -38,6 +38,7 @@ pub(super) fn api(state: BlogState) -> Router {
struct Entries { struct Entries {
#[serde(default)] #[serde(default)]
item: Vec<String>, item: Vec<String>,
date: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -45,6 +46,7 @@ struct Entries {
enum PostsEndpoints { enum PostsEndpoints {
delete, delete,
unpublish, unpublish,
date,
} }
async fn posts( async fn posts(
@@ -71,6 +73,12 @@ async fn posts(
state.index_delete(term); state.index_delete(term);
} }
} }
PostsEndpoints::date => {
let date = &data.date.clone().unwrap_or("1970-01-01".to_string());
for post_id in data.item.iter() {
let _ = db.set_post_date(post_id, &date).await;
}
}
} }
state.index_commit(); state.index_commit();
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
+10
View File
@@ -62,6 +62,7 @@ pub(super) async fn make_router(state: BlogState) -> Router {
.nest("/api", api(state.clone())) .nest("/api", api(state.clone()))
.nest("/login", login(State(state.clone()))) .nest("/login", login(State(state.clone())))
.route("/", get(pages).with_state(state.clone())) .route("/", get(pages).with_state(state.clone()))
.route("/feed.xml", get(rss).with_state(state.clone()))
.route("/*path", get(pages).with_state(state.clone())) .route("/*path", get(pages).with_state(state.clone()))
.route("/search", get(search_empty)) .route("/search", get(search_empty))
.route("/search/*query", get(search).with_state(state.clone())) .route("/search/*query", get(search).with_state(state.clone()))
@@ -308,6 +309,15 @@ async fn admin(jar: PrivateCookieJar, State(state): State<BlogState>) -> Respons
(headers, Html(page)).into_response() (headers, Html(page)).into_response()
} }
async fn rss(State(state): State<BlogState>) -> Response {
let mut headers = HeaderMap::new();
headers.append(
CONTENT_TYPE,
HeaderValue::from_str("application/xml").unwrap(),
);
(headers, html::rss(state.db()).await).into_response()
}
#[derive(Serialize, Deserialize, Default, Debug)] #[derive(Serialize, Deserialize, Default, Debug)]
pub(crate) struct SearchResponse(Vec<SearchResponseEntry>); pub(crate) struct SearchResponse(Vec<SearchResponseEntry>);
impl SearchResponse { impl SearchResponse {
@@ -3,11 +3,14 @@
<h1>Published Posts</h1> <h1>Published Posts</h1>
</div> </div>
<form method="post"> <form method="post">
<div class="form-actions"> <div class="form-actions" id="post-actions">
<button class="form-action" type="submit" formaction="/api/posts/delete">Delete posts <button class="form-action" type="submit" formaction="/api/posts/delete">Delete posts
</button> </button>
<button class=" form-action" type="submit" formaction="/api/posts/unpublish">Unpublish Posts <button class=" form-action" type="submit" formaction="/api/posts/unpublish">Unpublish Posts
</button> </button>
<button id="date-update" class="form-action" type="submit" formaction="#update-date" formmethod="get">Edit
Date
</button>
</div> </div>
<ul tabindex="-1"> <ul tabindex="-1">
</ul> </ul>
+1
View File
@@ -3,3 +3,4 @@
<admin-widget type="drafts"></admin-widget> <admin-widget type="drafts"></admin-widget>
<admin-widget type="posts"></admin-widget> <admin-widget type="posts"></admin-widget>
</div> </div>
<script src="/assets/js/admin.js"></script>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Evie Ippolito's Blog</title>
<link>https://evieippolito.com/blog</link>
<atom:link href="https://evieippolito.com/feed.xml" rel="self" type="application/rss+xml" />
<description>Evie's blog!</description>
<language>en-us</language>
<ttl>720</ttl>
<managingEditor>evieippolito@duck.com (Evie Ippolito)</managingEditor>
<webMaster>me@augustkline.com (august kline)</webMaster>
</channel>
</rss>
+7
View File
@@ -0,0 +1,7 @@
<item>
<title></title>
<link>
<guid isPermaLink="true"></guid>
<pubDate></pubDate>
<description></description>
</item>