From 4bfce7c92824f1574c0284ecdb98ac347ab6eaf5 Mon Sep 17 00:00:00 2001 From: august kline Date: Tue, 7 Jan 2025 17:59:07 -0500 Subject: [PATCH] Add RSS and fixups --- Cargo.lock | 68 +++++++++++++++++++ server/Cargo.toml | 1 + server/build.rs | 20 +++++- server/src/client/assets/css/editor.css | 8 ++- server/src/client/assets/js/editor.js | 12 +--- server/src/html.rs | 86 ++++++++++++++++++++++++- server/src/server/routes/api/editor.rs | 2 +- server/src/server/routes/mod.rs | 10 +++ server/src/templates/feed.xml | 13 ++++ server/src/templates/item.xml | 7 ++ 10 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 server/src/templates/feed.xml create mode 100644 server/src/templates/item.xml diff --git a/Cargo.lock b/Cargo.lock index e373eac..9b9aac6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "anyhow" version = "1.0.95" @@ -346,6 +361,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "cipher" version = "0.4.4" @@ -398,6 +427,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.16" @@ -1042,6 +1077,29 @@ dependencies = [ "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]] name = "icu_collections" version = "1.5.0" @@ -2222,6 +2280,7 @@ dependencies = [ "axum", "axum-extra", "blogdb", + "chrono", "constcat", "dotenvy", "futures", @@ -3277,6 +3336,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "windows-sys" version = "0.48.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 440952d..0d23c73 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -46,6 +46,7 @@ tracing-subscriber = { version = "0.3.18", features = [ "std", ] } phf = "0.11.2" +chrono = { version = "0.4.39", features = ["alloc"] } [build-dependencies] dotenvy = "0.15.7" diff --git a/server/build.rs b/server/build.rs index 4d988bf..ed33a6d 100644 --- a/server/build.rs +++ b/server/build.rs @@ -17,7 +17,7 @@ fn main() { println!("cargo:rustc-env={key}={value}"); } 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(); println!("cargo:rerun-if-changed={}", template_dir); let pattern = [&template_dir, "/**/*.html"].concat(); @@ -47,8 +47,24 @@ fn main() { 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!( - &mut file, + &mut outfile, "static TEMPLATES: phf::Map<&'static str, &'static str> = {};", map.build() ) diff --git a/server/src/client/assets/css/editor.css b/server/src/client/assets/css/editor.css index c2dc1ee..a6b7cea 100644 --- a/server/src/client/assets/css/editor.css +++ b/server/src/client/assets/css/editor.css @@ -91,6 +91,7 @@ main { @media all and (max-width: 650px) { .ce-toolbar__actions { + &>*:nth-child(1), &>*:nth-child(2) { border-radius: 0; @@ -141,7 +142,7 @@ main { } * { - border-radius: 0 !important; + border-radius: 0 !important; } .ce-block__content { @@ -192,6 +193,11 @@ main { background-color: var(--color-selection) !important; } +.cdx-quote__text { + border-block-end: none !important; + margin-block-end: 0 !important; +} + .embed-tool__caption { display: none; } diff --git a/server/src/client/assets/js/editor.js b/server/src/client/assets/js/editor.js index 562e675..3de3ba2 100644 --- a/server/src/client/assets/js/editor.js +++ b/server/src/client/assets/js/editor.js @@ -3239,16 +3239,6 @@ static get sanitize() { 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) { (this._data.alignment = t), this._block.dispatchChange(); } @@ -13662,7 +13652,7 @@ var editor = new EditorJS({ inlineToolbar: true, config: { quotePlaceholder: "Enter a quote", - captionPlaceholder: "Quote's author", + captionPlaceholder: "Add author or a caption, leave blank to hide", }, shortcut: "CMD+SHIFT+O", }, diff --git a/server/src/html.rs b/server/src/html.rs index 55a06bf..4fc227d 100644 --- a/server/src/html.rs +++ b/server/src/html.rs @@ -1,4 +1,4 @@ -use std::borrow::Borrow; +use chrono::prelude::*; use std::{env, vec}; use blogdb::posts::Post; @@ -773,7 +773,7 @@ where make_page(message, PageSettings::title("Not found")) } -pub(crate) fn animate_anchors(input: S) -> String +pub(crate) fn zhuzh_anchors(input: S) -> String where S: AsRef, { @@ -781,6 +781,11 @@ where input.as_ref(), RewriteStrSettings { 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") { Some(class) => t .set_attribute("class", &[&class, " animated-link-underline"].concat()) @@ -811,3 +816,80 @@ pub(crate) fn sanitize>(input: S) -> String { ) .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/", &entry.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(&["", &url, ""].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::().unwrap_or_default()) + .collect::>() + .try_into() + .unwrap_or_default(); + let date_time: DateTime = Utc + .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 = [""].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 +} diff --git a/server/src/server/routes/api/editor.rs b/server/src/server/routes/api/editor.rs index b296bc9..01041f4 100644 --- a/server/src/server/routes/api/editor.rs +++ b/server/src/server/routes/api/editor.rs @@ -107,7 +107,7 @@ impl Block { Block::delimiter {} => "
".to_string(), }; - html::animate_anchors(html::remove_el(&text, "br")) + html::zhuzh_anchors(html::remove_el(&text, "br")) } fn to_plaintext(&self) -> String { diff --git a/server/src/server/routes/mod.rs b/server/src/server/routes/mod.rs index 897e6aa..97aa824 100644 --- a/server/src/server/routes/mod.rs +++ b/server/src/server/routes/mod.rs @@ -62,6 +62,7 @@ pub(super) async fn make_router(state: BlogState) -> Router { .nest("/api", api(state.clone())) .nest("/login", login(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("/search", get(search_empty)) .route("/search/*query", get(search).with_state(state.clone())) @@ -308,6 +309,15 @@ async fn admin(jar: PrivateCookieJar, State(state): State) -> Respons (headers, Html(page)).into_response() } +async fn rss(State(state): State) -> 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)] pub(crate) struct SearchResponse(Vec); impl SearchResponse { diff --git a/server/src/templates/feed.xml b/server/src/templates/feed.xml new file mode 100644 index 0000000..a69be4c --- /dev/null +++ b/server/src/templates/feed.xml @@ -0,0 +1,13 @@ + + + + Evie Ippolito's Blog + https://evieippolito.com/blog + + Evie's blog! + en-us + 720 + evieippolito@duck.com (Evie Ippolito) + me@augustkline.com (august kline) + + diff --git a/server/src/templates/item.xml b/server/src/templates/item.xml new file mode 100644 index 0000000..303520d --- /dev/null +++ b/server/src/templates/item.xml @@ -0,0 +1,7 @@ + + + + + + +