Blog attempt 5
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

password protect admin stuffs

+360 -39
+1
.gitignore
··· 3 3 *.db3-shm 4 4 *.db3-wal 5 5 **/.DS_Store 6 + .secret
+1
.secret.example
··· 1 + password
+205 -6
Cargo.lock
··· 18 18 checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 19 20 20 [[package]] 21 + name = "aead" 22 + version = "0.5.2" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 + dependencies = [ 26 + "crypto-common", 27 + "generic-array", 28 + ] 29 + 30 + [[package]] 31 + name = "aes" 32 + version = "0.8.4" 33 + source = "registry+https://github.com/rust-lang/crates.io-index" 34 + checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 + dependencies = [ 36 + "cfg-if", 37 + "cipher", 38 + "cpufeatures", 39 + ] 40 + 41 + [[package]] 42 + name = "aes-gcm" 43 + version = "0.10.3" 44 + source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 46 + dependencies = [ 47 + "aead", 48 + "aes", 49 + "cipher", 50 + "ctr", 51 + "ghash", 52 + "subtle", 53 + ] 54 + 55 + [[package]] 21 56 name = "aho-corasick" 22 57 version = "1.1.3" 23 58 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 96 131 "chrono", 97 132 "maud", 98 133 "poem", 99 - "rand", 134 + "rand 0.9.2", 100 135 "rusqlite", 101 136 "rusqlite_migration", 102 137 "serde", ··· 187 222 ] 188 223 189 224 [[package]] 225 + name = "cipher" 226 + version = "0.4.4" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 229 + dependencies = [ 230 + "crypto-common", 231 + "inout", 232 + ] 233 + 234 + [[package]] 235 + name = "cookie" 236 + version = "0.18.1" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 239 + dependencies = [ 240 + "aes-gcm", 241 + "base64", 242 + "hkdf", 243 + "hmac", 244 + "percent-encoding", 245 + "rand 0.8.5", 246 + "sha2", 247 + "subtle", 248 + "time", 249 + "version_check", 250 + ] 251 + 252 + [[package]] 190 253 name = "core-foundation-sys" 191 254 version = "0.8.7" 192 255 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 208 271 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 209 272 dependencies = [ 210 273 "generic-array", 274 + "rand_core 0.6.4", 211 275 "typenum", 212 276 ] 213 277 214 278 [[package]] 279 + name = "ctr" 280 + version = "0.9.2" 281 + source = "registry+https://github.com/rust-lang/crates.io-index" 282 + checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 283 + dependencies = [ 284 + "cipher", 285 + ] 286 + 287 + [[package]] 215 288 name = "darling" 216 289 version = "0.20.11" 217 290 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 294 367 dependencies = [ 295 368 "block-buffer", 296 369 "crypto-common", 370 + "subtle", 297 371 ] 298 372 299 373 [[package]] ··· 417 491 418 492 [[package]] 419 493 name = "getrandom" 494 + version = "0.2.16" 495 + source = "registry+https://github.com/rust-lang/crates.io-index" 496 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 497 + dependencies = [ 498 + "cfg-if", 499 + "libc", 500 + "wasi", 501 + ] 502 + 503 + [[package]] 504 + name = "getrandom" 420 505 version = "0.3.4" 421 506 source = "registry+https://github.com/rust-lang/crates.io-index" 422 507 checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" ··· 425 510 "libc", 426 511 "r-efi", 427 512 "wasip2", 513 + ] 514 + 515 + [[package]] 516 + name = "ghash" 517 + version = "0.5.1" 518 + source = "registry+https://github.com/rust-lang/crates.io-index" 519 + checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 520 + dependencies = [ 521 + "opaque-debug", 522 + "polyval", 428 523 ] 429 524 430 525 [[package]] ··· 514 609 ] 515 610 516 611 [[package]] 612 + name = "hkdf" 613 + version = "0.12.4" 614 + source = "registry+https://github.com/rust-lang/crates.io-index" 615 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 616 + dependencies = [ 617 + "hmac", 618 + ] 619 + 620 + [[package]] 621 + name = "hmac" 622 + version = "0.12.1" 623 + source = "registry+https://github.com/rust-lang/crates.io-index" 624 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 625 + dependencies = [ 626 + "digest", 627 + ] 628 + 629 + [[package]] 517 630 name = "http" 518 631 version = "1.3.1" 519 632 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 744 857 ] 745 858 746 859 [[package]] 860 + name = "inout" 861 + version = "0.1.4" 862 + source = "registry+https://github.com/rust-lang/crates.io-index" 863 + checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 864 + dependencies = [ 865 + "generic-array", 866 + ] 867 + 868 + [[package]] 747 869 name = "io-uring" 748 870 version = "0.7.10" 749 871 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 766 888 source = "registry+https://github.com/rust-lang/crates.io-index" 767 889 checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 768 890 dependencies = [ 769 - "getrandom", 891 + "getrandom 0.3.4", 770 892 "libc", 771 893 ] 772 894 ··· 976 1098 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 977 1099 978 1100 [[package]] 1101 + name = "opaque-debug" 1102 + version = "0.3.1" 1103 + source = "registry+https://github.com/rust-lang/crates.io-index" 1104 + checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1105 + 1106 + [[package]] 979 1107 name = "parking_lot" 980 1108 version = "0.12.5" 981 1109 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1030 1158 dependencies = [ 1031 1159 "bytes", 1032 1160 "chrono", 1161 + "cookie", 1033 1162 "futures-util", 1034 1163 "headers", 1035 1164 "http", ··· 1052 1181 "smallvec", 1053 1182 "sync_wrapper", 1054 1183 "thiserror", 1184 + "time", 1055 1185 "tokio", 1056 1186 "tokio-util", 1057 1187 "tracing", ··· 1068 1198 "proc-macro2", 1069 1199 "quote", 1070 1200 "syn", 1201 + ] 1202 + 1203 + [[package]] 1204 + name = "polyval" 1205 + version = "0.6.2" 1206 + source = "registry+https://github.com/rust-lang/crates.io-index" 1207 + checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 1208 + dependencies = [ 1209 + "cfg-if", 1210 + "cpufeatures", 1211 + "opaque-debug", 1212 + "universal-hash", 1071 1213 ] 1072 1214 1073 1215 [[package]] ··· 1141 1283 1142 1284 [[package]] 1143 1285 name = "rand" 1286 + version = "0.8.5" 1287 + source = "registry+https://github.com/rust-lang/crates.io-index" 1288 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1289 + dependencies = [ 1290 + "libc", 1291 + "rand_chacha 0.3.1", 1292 + "rand_core 0.6.4", 1293 + ] 1294 + 1295 + [[package]] 1296 + name = "rand" 1144 1297 version = "0.9.2" 1145 1298 source = "registry+https://github.com/rust-lang/crates.io-index" 1146 1299 checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1147 1300 dependencies = [ 1148 - "rand_chacha", 1149 - "rand_core", 1301 + "rand_chacha 0.9.0", 1302 + "rand_core 0.9.3", 1303 + ] 1304 + 1305 + [[package]] 1306 + name = "rand_chacha" 1307 + version = "0.3.1" 1308 + source = "registry+https://github.com/rust-lang/crates.io-index" 1309 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1310 + dependencies = [ 1311 + "ppv-lite86", 1312 + "rand_core 0.6.4", 1150 1313 ] 1151 1314 1152 1315 [[package]] ··· 1156 1319 checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1157 1320 dependencies = [ 1158 1321 "ppv-lite86", 1159 - "rand_core", 1322 + "rand_core 0.9.3", 1323 + ] 1324 + 1325 + [[package]] 1326 + name = "rand_core" 1327 + version = "0.6.4" 1328 + source = "registry+https://github.com/rust-lang/crates.io-index" 1329 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1330 + dependencies = [ 1331 + "getrandom 0.2.16", 1160 1332 ] 1161 1333 1162 1334 [[package]] ··· 1165 1337 source = "registry+https://github.com/rust-lang/crates.io-index" 1166 1338 checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1167 1339 dependencies = [ 1168 - "getrandom", 1340 + "getrandom 0.3.4", 1169 1341 ] 1170 1342 1171 1343 [[package]] ··· 1350 1522 ] 1351 1523 1352 1524 [[package]] 1525 + name = "sha2" 1526 + version = "0.10.9" 1527 + source = "registry+https://github.com/rust-lang/crates.io-index" 1528 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1529 + dependencies = [ 1530 + "cfg-if", 1531 + "cpufeatures", 1532 + "digest", 1533 + ] 1534 + 1535 + [[package]] 1353 1536 name = "shlex" 1354 1537 version = "1.3.0" 1355 1538 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1388 1571 version = "0.11.1" 1389 1572 source = "registry+https://github.com/rust-lang/crates.io-index" 1390 1573 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1574 + 1575 + [[package]] 1576 + name = "subtle" 1577 + version = "2.6.1" 1578 + source = "registry+https://github.com/rust-lang/crates.io-index" 1579 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1391 1580 1392 1581 [[package]] 1393 1582 name = "syn" ··· 1625 1814 version = "1.0.19" 1626 1815 source = "registry+https://github.com/rust-lang/crates.io-index" 1627 1816 checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 1817 + 1818 + [[package]] 1819 + name = "universal-hash" 1820 + version = "0.5.1" 1821 + source = "registry+https://github.com/rust-lang/crates.io-index" 1822 + checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 1823 + dependencies = [ 1824 + "crypto-common", 1825 + "subtle", 1826 + ] 1628 1827 1629 1828 [[package]] 1630 1829 name = "url"
+1 -1
Cargo.toml
··· 7 7 [dependencies] 8 8 chrono = { version = "0.4", features = ["serde"] } 9 9 maud = { version = "0.27", features = ["poem"] } 10 - poem = { version = "3", features = ["static-files", "chrono"] } 10 + poem = { version = "3", features = ["static-files", "chrono", "cookie"] } 11 11 rand = "0.9" 12 12 rusqlite = { version = "0.37.0", features = ["chrono"] } 13 13 rusqlite_migration = "2.3.0"
+4 -2
src/main.rs
··· 5 5 6 6 use crate::{ 7 7 other_pages::{contact, index, projects}, 8 - post::{list_posts, new_post, submit_new_post, update_draft, view_post}, 8 + post::{list_posts, login, new_post, submit_new_post, update_draft, view_post}, 9 9 }; 10 10 use poem::{ 11 11 EndpointExt, Route, Server, endpoint::StaticFilesEndpoint, get, listener::TcpListener, 12 - web::Data, 12 + middleware::CookieJarManager, web::Data, 13 13 }; 14 14 use rusqlite::Connection; 15 15 use std::sync::{Arc, Mutex}; ··· 30 30 .at("/blog/new", get(new_post).post(submit_new_post)) 31 31 .at("/blog/new/sync", poem::post(update_draft)) 32 32 .at("/blog/:slug", get(view_post)) 33 + .at("/login", poem::post(login)) 33 34 .nest("/static", StaticFilesEndpoint::new("./static/")) 35 + .with(CookieJarManager::new()) 34 36 .data(conn.clone()); 35 37 36 38 Server::new(TcpListener::bind("0.0.0.0:3000"))
+94 -19
src/post.rs
··· 1 + use std::fs::read_to_string; 2 + 1 3 use crate::{ 2 4 D, W, 3 - template::{footer, header, navbar, page, page_article}, 5 + template::{footer, header, header_extra, navbar, page, page_article}, 4 6 }; 5 7 use chrono::{DateTime, Local}; 6 8 use maud::{Markup, PreEscaped, html}; 7 9 use poem::{ 8 - IntoResponse, handler, 9 - web::{Data, Form, Json, Path, Redirect}, 10 + Body, IntoResponse, handler, 11 + http::StatusCode, 12 + web::{ 13 + Data, Form, Json, Path, Redirect, 14 + cookie::{Cookie, CookieJar}, 15 + }, 10 16 }; 11 17 use serde::Deserialize; 12 18 ··· 87 93 }) 88 94 .unwrap(); 89 95 90 - page_article( 91 - html! { 96 + { 97 + let markup = html! { 92 98 h1.blog-head { (post.title)} 93 - span.blog-subhead { em { "subtitle" }} 99 + @let subtitle = post.subtitle.unwrap_or("".to_string()); 100 + span.blog-subhead { em { (subtitle) }} 94 101 hr.frontmatter; 95 102 p.blog-publish { 96 103 ( clock_icon() ) ··· 98 105 } 99 106 100 107 (PreEscaped(post.contents)) 101 - }, 102 - &format!("/blog/{}", post.slug), 103 - ) 108 + }; 109 + let endpoint = &format!("/blog/{}", post.slug); 110 + html! { 111 + ( header_extra(html! { script defer src="/static/js/bsky-comments.js" {} }) ) 112 + div.wrapper { 113 + (navbar(endpoint)) 114 + article { 115 + (markup) 116 + } 117 + 118 + @if post.bsky_uri.is_some() { 119 + section.page { 120 + noop {} 121 + bsky-comments post=[post.bsky_uri] {} 122 + } 123 + } 124 + 125 + ( footer() ) 126 + } 127 + } 128 + } 129 + } 130 + 131 + fn read_secret() -> String { 132 + read_to_string("./.secret").unwrap().trim().to_string() 104 133 } 105 134 106 135 #[handler] 107 - pub fn new_post(Data(conn): D<&W>) -> Markup { 136 + pub async fn login(body: String, cookie_jar: &CookieJar) -> impl IntoResponse { 137 + cookie_jar.add(Cookie::new_with_str("secret_pass", body)); 138 + 139 + html! {(cookie_jar.get("secret_pass").unwrap())}.into_response() 140 + } 141 + 142 + #[handler] 143 + pub fn new_post(cookie_jar: &CookieJar, Data(conn): D<&W>) -> impl IntoResponse { 144 + // Auth check! 145 + let secret = read_secret(); 146 + match cookie_jar.get("secret_pass") { 147 + Some(cookie) if cookie.value_str() == secret => {} 148 + _ => return (StatusCode::UNAUTHORIZED, "Unauthorized.").into_response(), 149 + } 150 + 108 151 let conn = conn.lock().unwrap(); 109 152 110 153 let mut stmt = conn.prepare("SELECT title, contents, slug, subtitle, category, bsky_uri, creation_datetime FROM draft").unwrap(); ··· 128 171 h1 { "Make a new post" } 129 172 form method="POST" { 130 173 label { 131 - "Title" 174 + "Title: " 132 175 input name="title" value=(post.title) {} 133 176 } 134 - br; 135 177 label { 136 - "Slug" 178 + "Slug: " 137 179 input name="slug" value=(post.slug) {} 138 180 } 139 - br; 140 181 label { 141 - "dtl" 182 + "dtl: " 142 183 input name="creation_datetime" type="datetime-local" value=(post.creation_datetime.format(ISO8601_DATE)) {} 143 184 } 185 + label { 186 + "Subtitle: " 187 + input name="subtitle" value=[post.subtitle] {} 188 + } 189 + label { 190 + "Category: " 191 + input name="category" value=[post.category] {} 192 + } 193 + label { 194 + "bsky_uri: " 195 + input name="bsky_uri" value=[post.bsky_uri] {} 196 + a href="https://pdsls.dev" {"pdsls"} 197 + } 198 + br; 144 199 br; 145 200 div style="display: flex; flex-direction: row; gap: 0.5rem; height: 100%; width: 100%; " { 146 201 147 202 textarea #editor name="contents" style="width: 100%; height: 50ch" { (post.contents) } 148 203 div #editorPreview style="border: 1px solid red; padding: 0 0.5rem; background-color: var(--bg-surface1); width: 100%" { "hii :3" } 149 204 } 150 - 151 205 br; 152 206 span { "Your draft is auto saved..." } 153 207 ··· 159 213 160 214 script type="module" src="/static/js/post-new.js" {} 161 215 } 162 - } 216 + }.into_response() 163 217 } 164 218 165 219 #[derive(Deserialize)] ··· 175 229 176 230 #[handler] 177 231 pub fn submit_new_post( 232 + cookie_jar: &CookieJar, 178 233 Form(form): Form<SubmitNewPostForm>, 179 234 Data(conn): D<&W>, 180 235 ) -> impl IntoResponse { 236 + // Auth check! 237 + let secret = read_secret(); 238 + match cookie_jar.get("secret_pass") { 239 + Some(cookie) if cookie.value_str() == secret => {} 240 + _ => return (StatusCode::UNAUTHORIZED, "Unauthorized.").into_response(), 241 + } 242 + 181 243 let conn = conn.lock().unwrap(); 182 244 183 245 let creation_datetime = ··· 198 260 )) 199 261 .expect("failed query"); 200 262 201 - Redirect::see_other(format!("/blog/{}", form.slug)) 263 + Redirect::see_other(format!("/blog/{}", form.slug)).into_response() 202 264 } 203 265 204 266 #[handler] 205 - pub fn update_draft(Json(form): Json<SubmitNewPostForm>, Data(conn): D<&W>) { 267 + pub fn update_draft( 268 + cookie_jar: &CookieJar, 269 + Json(form): Json<SubmitNewPostForm>, 270 + Data(conn): D<&W>, 271 + ) -> impl IntoResponse { 272 + // Auth check! 273 + let secret = read_secret(); 274 + match cookie_jar.get("secret_pass") { 275 + Some(cookie) if cookie.value_str() == secret => {} 276 + _ => return (StatusCode::UNAUTHORIZED, "Unauthorized.").into_response(), 277 + } 278 + 206 279 let conn = conn.lock().unwrap(); 207 280 208 281 let creation_datetime = ··· 234 307 creation_datetime, 235 308 )) 236 309 .expect("failed query"); 310 + 311 + return StatusCode::OK.into_response(); 237 312 }
+8 -1
src/template.rs
··· 16 16 link rel="stylesheet" type="text/css" href="/static/css/nav.css"; 17 17 link rel="stylesheet" type="text/css" href="/static/css/dialog.css"; 18 18 19 + script type="module" src="/static/js/login.js" defer {} 20 + 19 21 meta name="theme-color" content="#b497ee"; 20 22 meta name="apple-mobile-web-app-status-bar-style" content="#b497ee"; 21 23 ··· 34 36 pub fn footer() -> Markup { 35 37 html! { 36 38 footer #page-footer { 37 - div.tablet-hide { 39 + div { 38 40 span { 39 41 "This website is running on " 40 42 a href="https://tangled.org/@j0.lol/bog" style="color: var(--fg-header)" ··· 44 46 { samp { (env!("VERGEN_GIT_SHA")[..8]) } " (" (env!("VERGEN_GIT_COMMIT_DATE")) ")" } 45 47 "." 46 48 } 49 + br; 50 + button .subtle.clickme #login-button { "auth" } 51 + span .subtle {" • "} 52 + a .subtle.clickme href="/blog/new" { "new" } 47 53 } 54 + 48 55 (PreEscaped(" 49 56 <div class=\"_88x31s\"> 50 57 <span class=\"sr-only\">Miscellaneous links:</span>
+14 -7
static/css/style.css
··· 218 218 width: 100%; 219 219 margin: 0 auto; 220 220 border: none; 221 + box-shadow: none; 221 222 } 222 223 } 223 224 ··· 425 426 426 427 @media (width <= 48rem) { 427 428 & { 428 - flex-direction: row-reverse; 429 + flex-direction: column; 429 430 } 430 431 } 431 432 432 433 @media (width <= 27rem) { 433 434 & { 434 435 padding: 0.5em; 435 - font-size: 2em; 436 436 } 437 437 } 438 438 } ··· 481 481 .mobile-hide { 482 482 display: inherit; 483 483 } 484 - 485 - footer#page-footer { 486 - padding: 0.5em; 487 - font-size: 2em; 488 - } 489 484 } 490 485 491 486 .sr-only { ··· 704 699 padding-right: 0.20em; 705 700 padding-left: 0.20rem; 706 701 } 702 + 703 + .subtle { 704 + background: none; 705 + border: none; 706 + font-size: 0.8em; 707 + padding: 0; 708 + color: rgba(34, 30, 46, 0.3) !important; 709 + } 710 + .subtle.clickme { 711 + text-decoration: underline; 712 + cursor: pointer; 713 + }
+21
static/js/login.js
··· 1 + async function login() { 2 + const pass = prompt("What's the code?"); 3 + 4 + if (pass != null) { 5 + const resp = await fetch(`/login`, { 6 + method: "POST", 7 + credentials: "include", 8 + headers: { 9 + "Content-Type": "application/json", 10 + }, 11 + body: pass, 12 + }); 13 + 14 + const txt = await resp.text(); 15 + console.log(txt); 16 + } 17 + 18 + } 19 + 20 + document.querySelector("#login-button") 21 + .addEventListener('click', (e) => { login() });
+11 -3
static/js/post-new.js
··· 25 25 const inputTitle = document.querySelector('input[name=\"title\"]'); 26 26 const inputSlug = document.querySelector('input[name=\"slug\"]'); 27 27 const inputDt = document.querySelector('input[name=\"creation_datetime\"]'); 28 + const inputSubtitle = document.querySelector('input[name=\"subtitle\"]'); 29 + const inputCategory = document.querySelector('input[name=\"category\"]'); 30 + const inputBskyUri = document.querySelector('input[name=\"bsky_uri\"]'); 31 + 28 32 const editor = document.querySelector('#editor'); 29 33 const editorPreview = document.querySelector('#editorPreview'); 30 34 31 35 function preview() { 32 36 editorPreview.innerHTML = ` 33 - <h1>${inputTitle.value}</h1> 37 + <h1 class="blog-head">${inputTitle.value}</h1> 38 + <span class="blog-subhead"><em>${inputSubtitle.value}</em></span> 34 39 <hr class='frontmatter'> 35 40 <p class='blog-publish'> 36 41 🕒 ${inputDt.value} ··· 50 55 title: inputTitle.value, 51 56 slug: inputSlug.value, 52 57 contents: editor.value, 53 - creation_datetime: inputDt.value 58 + creation_datetime: inputDt.value, 59 + subtitle: inputSubtitle.value != "" ? inputSubtitle.value : null, 60 + category: inputCategory.value != "" ? inputCategory.value : null, 61 + bsky_uri: inputBskyUri.value != "" ? inputBskyUri.value : null, 54 62 }), 55 63 }); 56 64 } 57 65 58 - [editor, inputTitle, inputSlug, inputDt].forEach((el) => { 66 + [editor, inputTitle, inputSubtitle, inputSlug, inputDt].forEach((el) => { 59 67 el.addEventListener('input', (event) => { 60 68 preview(); 61 69 syncDraft();