max / makenotwork
10 files changed,
+65 insertions,
-173 deletions
| @@ -20,7 +20,7 @@ use tower_sessions::Session; | |||
| 20 | 20 | ||
| 21 | 21 | use crate::AppState; | |
| 22 | 22 | ||
| 23 | - | // ── PKCE helpers ── | |
| 23 | + | // --- PKCE helpers | |
| 24 | 24 | ||
| 25 | 25 | fn generate_verifier() -> String { | |
| 26 | 26 | let mut bytes = [0u8; 32]; | |
| @@ -41,7 +41,7 @@ fn generate_state_nonce() -> String { | |||
| 41 | 41 | hex::encode(bytes) | |
| 42 | 42 | } | |
| 43 | 43 | ||
| 44 | - | // ── Session user ── | |
| 44 | + | // --- session user | |
| 45 | 45 | ||
| 46 | 46 | /// User info cached in the session after OAuth login. | |
| 47 | 47 | /// | |
| @@ -229,7 +229,7 @@ impl FromRequestParts<AppState> for PlatformAdmin { | |||
| 229 | 229 | } | |
| 230 | 230 | } | |
| 231 | 231 | ||
| 232 | - | // ── OAuth callback types ── | |
| 232 | + | // --- OAuth callback types | |
| 233 | 233 | ||
| 234 | 234 | #[derive(Deserialize)] | |
| 235 | 235 | pub struct CallbackQuery { | |
| @@ -432,7 +432,7 @@ pub async fn refresh_session( | |||
| 432 | 432 | Ok(info.perks) | |
| 433 | 433 | } | |
| 434 | 434 | ||
| 435 | - | // ── Handlers ── | |
| 435 | + | // --- handlers | |
| 436 | 436 | ||
| 437 | 437 | /// `GET /auth/login`, redirect to MNW OAuth authorize endpoint. | |
| 438 | 438 | #[tracing::instrument(skip_all)] | |
| @@ -593,7 +593,7 @@ async fn fetch_userinfo_with_retry( | |||
| 593 | 593 | return Err("userinfo_fetch_failed"); | |
| 594 | 594 | } | |
| 595 | 595 | Err(UserinfoError::Unauthorized) => { | |
| 596 | - | tracing::error!("userinfo unauthorized — token rejected"); | |
| 596 | + | tracing::error!("userinfo unauthorized: token rejected"); | |
| 597 | 597 | return Err("userinfo_fetch_failed"); | |
| 598 | 598 | } | |
| 599 | 599 | Err(UserinfoError::BadResponse | UserinfoError::RefreshUnavailable) => { |
| @@ -255,7 +255,7 @@ mod tests { | |||
| 255 | 255 | assert!(!constant_time_eq(b"hello", b"hell")); | |
| 256 | 256 | } | |
| 257 | 257 | ||
| 258 | - | // ── compute_internal_signature_v2 pins HMAC message construction ── | |
| 258 | + | // --- compute_internal_signature_v2 pins HMAC message construction | |
| 259 | 259 | ||
| 260 | 260 | fn v2(secret: &str, ts: &str, method: &str, path: &str, nonce: &str, body: &[u8]) -> String { | |
| 261 | 261 | compute_internal_signature_v2(secret, ts, method, path, nonce, body) | |
| @@ -333,7 +333,7 @@ mod tests { | |||
| 333 | 333 | ); | |
| 334 | 334 | } | |
| 335 | 335 | ||
| 336 | - | // ── verify_signed_request: signature + freshness + mandatory nonce ── | |
| 336 | + | // --- verify_signed_request: signature + freshness + mandatory nonce | |
| 337 | 337 | ||
| 338 | 338 | #[test] | |
| 339 | 339 | fn verify_accepts_valid_signature_at_now() { |
| @@ -200,8 +200,6 @@ mod permission_tests { | |||
| 200 | 200 | Uuid::from_bytes([b; 16]) | |
| 201 | 201 | } | |
| 202 | 202 | ||
| 203 | - | // ── check_footnote_permission ── | |
| 204 | - | ||
| 205 | 203 | #[test] | |
| 206 | 204 | fn footnote_author_can_add_when_not_removed_and_under_cap() { | |
| 207 | 205 | let me = uid(1); | |
| @@ -275,8 +273,6 @@ mod permission_tests { | |||
| 275 | 273 | ); | |
| 276 | 274 | } | |
| 277 | 275 | ||
| 278 | - | // ── check_endorsement_permission ── | |
| 279 | - | ||
| 280 | 276 | #[test] | |
| 281 | 277 | fn endorsement_other_user_on_live_post_is_allowed() { | |
| 282 | 278 | let me = uid(1); |
| @@ -497,8 +497,6 @@ pub(in crate::routes) async fn delete_thread_handler( | |||
| 497 | 497 | mod quote_tests { | |
| 498 | 498 | use super::*; | |
| 499 | 499 | ||
| 500 | - | // ── compute_quote_hash ── | |
| 501 | - | ||
| 502 | 500 | #[test] | |
| 503 | 501 | fn hash_is_eight_hex_chars() { | |
| 504 | 502 | let h = compute_quote_hash("hello world"); | |
| @@ -525,8 +523,6 @@ mod quote_tests { | |||
| 525 | 523 | assert_eq!(compute_quote_hash("a"), "ca978112"); | |
| 526 | 524 | } | |
| 527 | 525 | ||
| 528 | - | // ── find_quote_refs ── | |
| 529 | - | ||
| 530 | 526 | #[test] | |
| 531 | 527 | fn find_quote_refs_finds_zero_markers() { | |
| 532 | 528 | assert!(find_quote_refs("body with no markers").is_empty()); | |
| @@ -563,8 +559,6 @@ mod quote_tests { | |||
| 563 | 559 | assert!(find_quote_refs(body2).is_empty()); | |
| 564 | 560 | } | |
| 565 | 561 | ||
| 566 | - | // ── extract_preceding_quote_text ── | |
| 567 | - | ||
| 568 | 562 | #[test] | |
| 569 | 563 | fn extract_single_quoted_line() { | |
| 570 | 564 | let body = "> hello\nMARKER"; |
| @@ -283,7 +283,7 @@ pub(crate) fn check_community_state( | |||
| 283 | 283 | mod authz_tests { | |
| 284 | 284 | use super::*; | |
| 285 | 285 | ||
| 286 | - | // ── is_mod_or_owner / is_owner Option wrappers ── | |
| 286 | + | // --- is_mod_or_owner / is_owner Option wrappers | |
| 287 | 287 | ||
| 288 | 288 | #[test] | |
| 289 | 289 | fn is_mod_or_owner_none_role_is_false() { | |
| @@ -309,8 +309,6 @@ mod authz_tests { | |||
| 309 | 309 | assert!(!is_owner(Some(CommunityRole::Member))); | |
| 310 | 310 | } | |
| 311 | 311 | ||
| 312 | - | // ── community_state_denial_message ── | |
| 313 | - | ||
| 314 | 312 | #[test] | |
| 315 | 313 | fn state_denial_mod_bypasses_everything() { | |
| 316 | 314 | // Pins the `if is_mod_or_super { return None; }` early return, a mod |
| @@ -83,7 +83,7 @@ pub(crate) fn parse_duration(duration: &str) -> Result<Option<DateTime<Utc>>, Re | |||
| 83 | 83 | mod validation_tests { | |
| 84 | 84 | use super::*; | |
| 85 | 85 | ||
| 86 | - | // ── validate_title (1..=256 chars after trim) ── | |
| 86 | + | // --- validate_title (1..=256 chars after trim) | |
| 87 | 87 | ||
| 88 | 88 | #[test] | |
| 89 | 89 | fn title_rejects_empty() { | |
| @@ -131,7 +131,7 @@ mod validation_tests { | |||
| 131 | 131 | assert!(validate_title(&s).is_err(), "257 chars must reject"); | |
| 132 | 132 | } | |
| 133 | 133 | ||
| 134 | - | // ── validate_body (1..=max chars after trim) ── | |
| 134 | + | // --- validate_body (1..=max chars after trim) | |
| 135 | 135 | ||
| 136 | 136 | #[test] | |
| 137 | 137 | fn body_rejects_empty() { | |
| @@ -173,8 +173,6 @@ mod validation_tests { | |||
| 173 | 173 | assert!(validate_body(&s, 100, "Body").is_err()); | |
| 174 | 174 | } | |
| 175 | 175 | ||
| 176 | - | // ── parse_duration ── | |
| 177 | - | ||
| 178 | 176 | #[test] | |
| 179 | 177 | fn parse_duration_permanent_returns_none() { | |
| 180 | 178 | // Pins the `"permanent" => Ok(None)` arm; a mutation swapping arms | |
| @@ -237,8 +235,6 @@ mod validation_tests { | |||
| 237 | 235 | ); | |
| 238 | 236 | } | |
| 239 | 237 | ||
| 240 | - | // ── parse_uuid ── | |
| 241 | - | ||
| 242 | 238 | #[test] | |
| 243 | 239 | fn parse_uuid_valid_string() { | |
| 244 | 240 | let raw = "11111111-2222-3333-4444-555555555555"; |
| @@ -15,11 +15,11 @@ pub async fn run(pool: &PgPool) { | |||
| 15 | 15 | return; | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - | // ── Users ────────────────────────────────────────────────────────── | |
| 18 | + | // --- users | |
| 19 | 19 | ||
| 20 | 20 | let users = seed_users(pool).await; | |
| 21 | 21 | ||
| 22 | - | // ── Communities ──────────────────────────────────────────────────── | |
| 22 | + | // --- communities | |
| 23 | 23 | ||
| 24 | 24 | let rust_id = seed_community( | |
| 25 | 25 | pool, | |
| @@ -45,7 +45,7 @@ pub async fn run(pool: &PgPool) { | |||
| 45 | 45 | ) | |
| 46 | 46 | .await; | |
| 47 | 47 | ||
| 48 | - | // ── Categories ───────────────────────────────────────────────────── | |
| 48 | + | // --- categories | |
| 49 | 49 | ||
| 50 | 50 | // Rust | |
| 51 | 51 | let rust_general = seed_category( | |
| @@ -134,7 +134,7 @@ pub async fn run(pool: &PgPool) { | |||
| 134 | 134 | ) | |
| 135 | 135 | .await; | |
| 136 | 136 | ||
| 137 | - | // ── Memberships ──────────────────────────────────────────────────── | |
| 137 | + | // --- memberships | |
| 138 | 138 | ||
| 139 | 139 | for user in &users { | |
| 140 | 140 | seed_membership(pool, user.id, music_id, CommunityRole::Member).await; | |
| @@ -146,7 +146,7 @@ pub async fn run(pool: &PgPool) { | |||
| 146 | 146 | seed_membership_upsert(pool, users[1].id, rust_id, CommunityRole::Member).await; | |
| 147 | 147 | seed_membership_upsert(pool, users[1].id, music_id, CommunityRole::Moderator).await; | |
| 148 | 148 | ||
| 149 | - | // ── Rust community: a few threads (unchanged from before) ────────── | |
| 149 | + | // --- Rust community: a few threads | |
| 150 | 150 | ||
| 151 | 151 | let welcome_id = seed_thread( | |
| 152 | 152 | pool, | |
| @@ -182,7 +182,7 @@ pub async fn run(pool: &PgPool) { | |||
| 182 | 182 | .await; | |
| 183 | 183 | seed_post(pool, error_id, users[1].id, "What's the recommended way to handle errors in Axum handlers? Should I use `anyhow`, `thiserror`, or something else? I keep writing `.map_err(|e| ...)` everywhere.").await; | |
| 184 | 184 | ||
| 185 | - | // ── Self-Hosted: a few threads ───────────────────────────────────── | |
| 185 | + | // --- Self-Hosted: a few threads | |
| 186 | 186 | ||
| 187 | 187 | let caddy_id = seed_thread( | |
| 188 | 188 | pool, | |
| @@ -196,7 +196,7 @@ pub async fn run(pool: &PgPool) { | |||
| 196 | 196 | seed_post(pool, caddy_id, users[0].id, "I have been using nginx for years but Caddy's automatic HTTPS is tempting. Anyone made the switch? What are the tradeoffs?").await; | |
| 197 | 197 | seed_post(pool, caddy_id, users[3].id, "Switched last year. Caddy's config is so much simpler. Automatic cert renewal is great. Only downside is slightly higher memory usage but it is negligible for small setups.").await; | |
| 198 | 198 | ||
| 199 | - | // ── Music community: bulk seed ───────────────────────────────────── | |
| 199 | + | // --- Music community: bulk seed | |
| 200 | 200 | ||
| 201 | 201 | seed_music_general(pool, music_general, &users).await; | |
| 202 | 202 | seed_music_mixing(pool, music_mixing, &users).await; | |
| @@ -999,7 +999,7 @@ async fn seed_long_discussion(pool: &PgPool, thread_id: Uuid, users: &[SeedUser] | |||
| 999 | 999 | } | |
| 1000 | 1000 | } | |
| 1001 | 1001 | ||
| 1002 | - | // ── Helper functions ─────────────────────────────────────────────────── | |
| 1002 | + | // --- helper functions | |
| 1003 | 1003 | ||
| 1004 | 1004 | async fn seed_community(pool: &PgPool, name: &str, slug: &str, description: Option<&str>) -> Uuid { | |
| 1005 | 1005 | sqlx::query_scalar( |
| @@ -16,9 +16,7 @@ | |||
| 16 | 16 | } | |
| 17 | 17 | })(); | |
| 18 | 18 | ||
| 19 | - | /* =========================================== | |
| 20 | - | TOAST NOTIFICATIONS | |
| 21 | - | =========================================== */ | |
| 19 | + | /* --- TOAST NOTIFICATIONS */ | |
| 22 | 20 | ||
| 23 | 21 | function showToast(message, type) { | |
| 24 | 22 | var container = document.getElementById('notifications'); | |
| @@ -37,14 +35,12 @@ document.body.addEventListener('showToast', function(evt) { | |||
| 37 | 35 | showToast(evt.detail.message || 'Action completed', evt.detail.type || 'info'); | |
| 38 | 36 | }); | |
| 39 | 37 | ||
| 40 | - | /* =========================================== | |
| 41 | - | INLINE FORM ERRORS | |
| 38 | + | /* --- INLINE FORM ERRORS | |
| 42 | 39 | ||
| 43 | 40 | A failed submit (422) keeps the user on the page with their input intact and | |
| 44 | 41 | shows a persistent error attached to the form, and when the handler names | |
| 45 | 42 | the offending field via X-Form-Field, highlights and focuses that input. | |
| 46 | - | This replaces the transient error toast for validation failures. | |
| 47 | - | =========================================== */ | |
| 43 | + | This replaces the transient error toast for validation failures. */ | |
| 48 | 44 | ||
| 49 | 45 | function clearFormError(form) { | |
| 50 | 46 | var prev = form.querySelector('.form-error'); | |
| @@ -104,9 +100,7 @@ document.body.addEventListener('htmx:responseError', function(evt) { | |||
| 104 | 100 | }, 6000); | |
| 105 | 101 | }); | |
| 106 | 102 | ||
| 107 | - | /* =========================================== | |
| 108 | - | HTMX FORM STATE (loading buttons) | |
| 109 | - | =========================================== */ | |
| 103 | + | /* --- HTMX FORM STATE (loading buttons) */ | |
| 110 | 104 | ||
| 111 | 105 | document.body.addEventListener('htmx:beforeRequest', function(evt) { | |
| 112 | 106 | var form = evt.detail.elt.closest('form'); | |
| @@ -124,9 +118,7 @@ document.body.addEventListener('htmx:afterRequest', function(evt) { | |||
| 124 | 118 | } | |
| 125 | 119 | }); | |
| 126 | 120 | ||
| 127 | - | /* =========================================== | |
| 128 | - | SEARCH MODAL | |
| 129 | - | =========================================== */ | |
| 121 | + | /* --- SEARCH MODAL */ | |
| 130 | 122 | ||
| 131 | 123 | function openSearchModal() { | |
| 132 | 124 | var modal = document.getElementById('search-modal'); | |
| @@ -182,9 +174,7 @@ function closeSearchModal() { | |||
| 182 | 174 | }); | |
| 183 | 175 | })(); | |
| 184 | 176 | ||
| 185 | - | /* =========================================== | |
| 186 | - | KEYBOARD SHORTCUTS | |
| 187 | - | =========================================== */ | |
| 177 | + | /* --- KEYBOARD SHORTCUTS */ | |
| 188 | 178 | ||
| 189 | 179 | document.addEventListener('keydown', function(e) { | |
| 190 | 180 | // Search shortcut: / key (when not in input) | |
| @@ -208,9 +198,7 @@ document.addEventListener('keydown', function(e) { | |||
| 208 | 198 | } | |
| 209 | 199 | }); | |
| 210 | 200 | ||
| 211 | - | /* =========================================== | |
| 212 | - | IMAGE CLICK: open full size in new tab | |
| 213 | - | =========================================== */ | |
| 201 | + | /* --- IMAGE CLICK: open full size in new tab */ | |
| 214 | 202 | ||
| 215 | 203 | document.addEventListener('click', function(e) { | |
| 216 | 204 | var img = e.target; | |
| @@ -219,9 +207,7 @@ document.addEventListener('click', function(e) { | |||
| 219 | 207 | } | |
| 220 | 208 | }); | |
| 221 | 209 | ||
| 222 | - | /* =========================================== | |
| 223 | - | NAV TOGGLE | |
| 224 | - | =========================================== */ | |
| 210 | + | /* --- NAV TOGGLE */ | |
| 225 | 211 | ||
| 226 | 212 | document.addEventListener('click', function(e) { | |
| 227 | 213 | var toggle = document.getElementById('nav-toggle'); | |
| @@ -230,9 +216,7 @@ document.addEventListener('click', function(e) { | |||
| 230 | 216 | } | |
| 231 | 217 | }); | |
| 232 | 218 | ||
| 233 | - | /* =========================================== | |
| 234 | - | FORM POST WITH CSRF | |
| 235 | - | =========================================== */ | |
| 219 | + | /* --- FORM POST WITH CSRF */ | |
| 236 | 220 | ||
| 237 | 221 | document.addEventListener('submit', function(e) { | |
| 238 | 222 | // Another handler may have already cancelled this submit; respect it. | |
| @@ -282,9 +266,7 @@ document.addEventListener('submit', function(e) { | |||
| 282 | 266 | } | |
| 283 | 267 | }); | |
| 284 | 268 | ||
| 285 | - | /* =========================================== | |
| 286 | - | TOAST FROM URL PARAMETER | |
| 287 | - | =========================================== */ | |
| 269 | + | /* --- TOAST FROM URL PARAMETER */ | |
| 288 | 270 | ||
| 289 | 271 | (function() { | |
| 290 | 272 | var p = new URLSearchParams(window.location.search).get('toast'); | |
| @@ -294,9 +276,7 @@ document.addEventListener('submit', function(e) { | |||
| 294 | 276 | } | |
| 295 | 277 | })(); | |
| 296 | 278 | ||
| 297 | - | /* =========================================== | |
| 298 | - | DRAFT AUTO-SAVE | |
| 299 | - | =========================================== */ | |
| 279 | + | /* --- DRAFT AUTO-SAVE */ | |
| 300 | 280 | ||
| 301 | 281 | (function() { | |
| 302 | 282 | var body = document.getElementById('body') || document.getElementById('reply-body'); | |
| @@ -368,9 +348,7 @@ document.addEventListener('submit', function(e) { | |||
| 368 | 348 | // above), so a validation failure keeps the user's text. | |
| 369 | 349 | })(); | |
| 370 | 350 | ||
| 371 | - | /* =========================================== | |
| 372 | - | LOCAL UNREAD TRACKING (category pages) | |
| 373 | - | =========================================== */ | |
| 351 | + | /* --- LOCAL UNREAD TRACKING (category pages) */ | |
| 374 | 352 | ||
| 375 | 353 | (function() { | |
| 376 | 354 | if (localStorage.getItem('mt_tracking_enabled') === 'false') return; | |
| @@ -409,9 +387,7 @@ document.addEventListener('submit', function(e) { | |||
| 409 | 387 | }); | |
| 410 | 388 | })(); | |
| 411 | 389 | ||
| 412 | - | /* =========================================== | |
| 413 | - | TRACKING OPT-OUT TOGGLE | |
| 414 | - | =========================================== */ | |
| 390 | + | /* --- TRACKING OPT-OUT TOGGLE */ | |
| 415 | 391 | ||
| 416 | 392 | (function() { | |
| 417 | 393 | var checkbox = document.getElementById('tracking-opt-out'); | |
| @@ -427,9 +403,7 @@ document.addEventListener('submit', function(e) { | |||
| 427 | 403 | }); | |
| 428 | 404 | })(); | |
| 429 | 405 | ||
| 430 | - | /* =========================================== | |
| 431 | - | IMAGE UPLOAD (drag-and-drop + paste) | |
| 432 | - | =========================================== */ | |
| 406 | + | /* --- IMAGE UPLOAD (drag-and-drop + paste) */ | |
| 433 | 407 | ||
| 434 | 408 | (function() { | |
| 435 | 409 | var textarea = document.getElementById('body') || document.getElementById('reply-body'); | |
| @@ -519,9 +493,7 @@ document.addEventListener('submit', function(e) { | |||
| 519 | 493 | }); | |
| 520 | 494 | })(); | |
| 521 | 495 | ||
| 522 | - | /* =========================================== | |
| 523 | - | SELECT-TO-QUOTE (thread pages) | |
| 524 | - | =========================================== */ | |
| 496 | + | /* --- SELECT-TO-QUOTE (thread pages) */ | |
| 525 | 497 | ||
| 526 | 498 | (function() { | |
| 527 | 499 | var quoteBtn = null; |
| @@ -1,11 +1,6 @@ | |||
| 1 | - | /* =========================================== | |
| 2 | - | Multithreaded, Forum Stylesheet | |
| 3 | - | Adapted from Makenot.work design language | |
| 4 | - | =========================================== */ | |
| 1 | + | /* Multithreaded, Forum Stylesheet. Adapted from Makenot.work design language */ | |
| 5 | 2 | ||
| 6 | - | /* =========================================== | |
| 7 | - | FONTS | |
| 8 | - | =========================================== */ | |
| 3 | + | /* --- FONTS */ | |
| 9 | 4 | ||
| 10 | 5 | @font-face { | |
| 11 | 6 | font-family: "Young Serif"; | |
| @@ -46,9 +41,7 @@ | |||
| 46 | 41 | font-display: swap; | |
| 47 | 42 | } | |
| 48 | 43 | ||
| 49 | - | /* =========================================== | |
| 50 | - | VARIABLES | |
| 51 | - | =========================================== */ | |
| 44 | + | /* --- VARIABLES */ | |
| 52 | 45 | ||
| 53 | 46 | :root { | |
| 54 | 47 | --background: #ede8e1; | |
| @@ -73,9 +66,7 @@ | |||
| 73 | 66 | --focus-ring: #6c5ce7; | |
| 74 | 67 | } | |
| 75 | 68 | ||
| 76 | - | /* =========================================== | |
| 77 | - | RESET | |
| 78 | - | =========================================== */ | |
| 69 | + | /* --- RESET */ | |
| 79 | 70 | ||
| 80 | 71 | * { | |
| 81 | 72 | margin: 0; | |
| @@ -83,9 +74,7 @@ | |||
| 83 | 74 | box-sizing: border-box; | |
| 84 | 75 | } | |
| 85 | 76 | ||
| 86 | - | /* =========================================== | |
| 87 | - | BASE | |
| 88 | - | =========================================== */ | |
| 77 | + | /* --- BASE */ | |
| 89 | 78 | ||
| 90 | 79 | body { | |
| 91 | 80 | margin: 0; | |
| @@ -129,9 +118,7 @@ a:hover { | |||
| 129 | 118 | color: var(--highlight); | |
| 130 | 119 | } | |
| 131 | 120 | ||
| 132 | - | /* =========================================== | |
| 133 | - | LAYOUT | |
| 134 | - | =========================================== */ | |
| 121 | + | /* --- LAYOUT */ | |
| 135 | 122 | ||
| 136 | 123 | .container { | |
| 137 | 124 | max-width: 1200px; | |
| @@ -143,9 +130,7 @@ a:hover { | |||
| 143 | 130 | padding: 1.25rem; | |
| 144 | 131 | } | |
| 145 | 132 | ||
| 146 | - | /* =========================================== | |
| 147 | - | SITE HEADER & NAV | |
| 148 | - | =========================================== */ | |
| 133 | + | /* --- SITE HEADER & NAV */ | |
| 149 | 134 | ||
| 150 | 135 | .site-header { | |
| 151 | 136 | display: flex; | |
| @@ -231,9 +216,7 @@ a:hover { | |||
| 231 | 216 | transition: transform 0.2s ease, opacity 0.2s ease; | |
| 232 | 217 | } | |
| 233 | 218 | ||
| 234 | - | /* =========================================== | |
| 235 | - | BUTTONS | |
| 236 | - | =========================================== */ | |
| 219 | + | /* --- BUTTONS */ | |
| 237 | 220 | ||
| 238 | 221 | button { | |
| 239 | 222 | color: var(--detail); | |
| @@ -283,9 +266,7 @@ button.secondary:hover, | |||
| 283 | 266 | opacity: 0.8; | |
| 284 | 267 | } | |
| 285 | 268 | ||
| 286 | - | /* =========================================== | |
| 287 | - | FORMS | |
| 288 | - | =========================================== */ | |
| 269 | + | /* --- FORMS */ | |
| 289 | 270 | ||
| 290 | 271 | form { | |
| 291 | 272 | display: flex; | |
| @@ -412,9 +393,7 @@ form button:hover { | |||
| 412 | 393 | width: 6rem; | |
| 413 | 394 | } | |
| 414 | 395 | ||
| 415 | - | /* =========================================== | |
| 416 | - | DIRECTORY TABLE (forum home) | |
| 417 | - | =========================================== */ | |
| 396 | + | /* --- DIRECTORY TABLE (forum home) */ | |
| 418 | 397 | ||
| 419 | 398 | .directory-table { | |
| 420 | 399 | width: 100%; | |
| @@ -503,9 +482,7 @@ form button:hover { | |||
| 503 | 482 | color: var(--text-muted); | |
| 504 | 483 | } | |
| 505 | 484 | ||
| 506 | - | /* =========================================== | |
| 507 | - | DATA TABLE (dense thread listings) | |
| 508 | - | =========================================== */ | |
| 485 | + | /* --- DATA TABLE (dense thread listings) */ | |
| 509 | 486 | ||
| 510 | 487 | .data-table { | |
| 511 | 488 | width: 100%; | |
| @@ -625,9 +602,7 @@ form button:hover { | |||
| 625 | 602 | background: var(--border); | |
| 626 | 603 | } | |
| 627 | 604 | ||
| 628 | - | /* =========================================== | |
| 629 | - | BADGES | |
| 630 | - | =========================================== */ | |
| 605 | + | /* --- BADGES */ | |
| 631 | 606 | ||
| 632 | 607 | .badge { | |
| 633 | 608 | display: inline-block; | |
| @@ -700,9 +675,7 @@ form button:hover { | |||
| 700 | 675 | border: 1px solid var(--border); | |
| 701 | 676 | } | |
| 702 | 677 | ||
| 703 | - | /* =========================================== | |
| 704 | - | BREADCRUMBS | |
| 705 | - | =========================================== */ | |
| 678 | + | /* --- BREADCRUMBS */ | |
| 706 | 679 | ||
| 707 | 680 | .breadcrumb { | |
| 708 | 681 | font-size: 0.8rem; | |
| @@ -725,9 +698,7 @@ form button:hover { | |||
| 725 | 698 | opacity: 0.5; | |
| 726 | 699 | } | |
| 727 | 700 | ||
| 728 | - | /* =========================================== | |
| 729 | - | POST ITEM (thread view) | |
| 730 | - | =========================================== */ | |
| 701 | + | /* --- POST ITEM (thread view) */ | |
| 731 | 702 | ||
| 732 | 703 | .post-list { | |
| 733 | 704 | display: flex; | |
| @@ -860,9 +831,7 @@ form button:hover { | |||
| 860 | 831 | background: var(--light-background); | |
| 861 | 832 | } | |
| 862 | 833 | ||
| 863 | - | /* =========================================== | |
| 864 | - | DRAFT INDICATOR | |
| 865 | - | =========================================== */ | |
| 834 | + | /* --- DRAFT INDICATOR */ | |
| 866 | 835 | ||
| 867 | 836 | .draft-indicator { | |
| 868 | 837 | font-family: "IBM Plex Mono", monospace; | |
| @@ -880,9 +849,7 @@ form button:hover { | |||
| 880 | 849 | color: var(--danger); | |
| 881 | 850 | } | |
| 882 | 851 | ||
| 883 | - | /* =========================================== | |
| 884 | - | REPLY FORM | |
| 885 | - | =========================================== */ | |
| 852 | + | /* --- REPLY FORM */ | |
| 886 | 853 | ||
| 887 | 854 | .reply-section { | |
| 888 | 855 | padding: 1rem; | |
| @@ -893,9 +860,7 @@ form button:hover { | |||
| 893 | 860 | margin-bottom: 0.75rem; | |
| 894 | 861 | } | |
| 895 | 862 | ||
| 896 | - | /* =========================================== | |
| 897 | - | PAGE HEADER (title + actions row) | |
| 898 | - | =========================================== */ | |
| 863 | + | /* --- PAGE HEADER (title + actions row) */ | |
| 899 | 864 | ||
| 900 | 865 | .page-header { | |
| 901 | 866 | display: flex; | |
| @@ -915,9 +880,7 @@ form button:hover { | |||
| 915 | 880 | margin: 0; | |
| 916 | 881 | } | |
| 917 | 882 | ||
| 918 | - | /* =========================================== | |
| 919 | - | TOAST NOTIFICATIONS | |
| 920 | - | =========================================== */ | |
| 883 | + | /* --- TOAST NOTIFICATIONS */ | |
| 921 | 884 | ||
| 922 | 885 | .toast-container { | |
| 923 | 886 | position: fixed; | |
| @@ -986,9 +949,7 @@ form button:hover { | |||
| 986 | 949 | to { transform: translateX(100%); } | |
| 987 | 950 | } | |
| 988 | 951 | ||
| 989 | - | /* =========================================== | |
| 990 | - | SETTINGS PAGE | |
| 991 | - | =========================================== */ | |
| 952 | + | /* --- SETTINGS PAGE */ | |
| 992 | 953 | ||
| 993 | 954 | .settings-section { | |
| 994 | 955 | margin-bottom: 2rem; | |
| @@ -1064,9 +1025,7 @@ form button:hover { | |||
| 1064 | 1025 | white-space: nowrap; | |
| 1065 | 1026 | } | |
| 1066 | 1027 | ||
| 1067 | - | /* =========================================== | |
| 1068 | - | ACCESSIBILITY | |
| 1069 | - | =========================================== */ | |
| 1028 | + | /* --- ACCESSIBILITY */ | |
| 1070 | 1029 | ||
| 1071 | 1030 | .skip-to-main { | |
| 1072 | 1031 | position: absolute; | |
| @@ -1088,9 +1047,7 @@ form button:hover { | |||
| 1088 | 1047 | outline-offset: 2px; | |
| 1089 | 1048 | } | |
| 1090 | 1049 | ||
| 1091 | - | /* =========================================== | |
| 1092 | - | PAGINATION | |
| 1093 | - | =========================================== */ | |
| 1050 | + | /* --- PAGINATION */ | |
| 1094 | 1051 | ||
| 1095 | 1052 | .pagination { | |
| 1096 | 1053 | display: flex; | |
| @@ -1120,9 +1077,7 @@ form button:hover { | |||
| 1120 | 1077 | color: var(--text-muted); | |
| 1121 | 1078 | } | |
| 1122 | 1079 | ||
| 1123 | - | /* =========================================== | |
| 1124 | - | ERROR PAGES | |
| 1125 | - | =========================================== */ | |
| 1080 | + | /* --- ERROR PAGES */ | |
| 1126 | 1081 | ||
| 1127 | 1082 | .error-page { | |
| 1128 | 1083 | text-align: center; | |
| @@ -1140,9 +1095,7 @@ form button:hover { | |||
| 1140 | 1095 | margin-bottom: 1.5rem; | |
| 1141 | 1096 | } | |
| 1142 | 1097 | ||
| 1143 | - | /* =========================================== | |
| 1144 | - | EMPTY STATE | |
| 1145 | - | =========================================== */ | |
| 1098 | + | /* --- EMPTY STATE */ | |
| 1146 | 1099 | ||
| 1147 | 1100 | .empty-state { | |
| 1148 | 1101 | text-align: center; | |
| @@ -1151,9 +1104,7 @@ form button:hover { | |||
| 1151 | 1104 | font-family: "IBM Plex Mono", monospace; | |
| 1152 | 1105 | } | |
| 1153 | 1106 | ||
| 1154 | - | /* =========================================== | |
| 1155 | - | SITE FOOTER | |
| 1156 | - | =========================================== */ | |
| 1107 | + | /* --- SITE FOOTER */ | |
| 1157 | 1108 | ||
| 1158 | 1109 | .site-footer { | |
| 1159 | 1110 | text-align: center; | |
| @@ -1186,9 +1137,7 @@ form button:hover { | |||
| 1186 | 1137 | margin: 0 0.4rem; | |
| 1187 | 1138 | } | |
| 1188 | 1139 | ||
| 1189 | - | /* =========================================== | |
| 1190 | - | FOOTNOTES + IMMUTABLE POSTS | |
| 1191 | - | =========================================== */ | |
| 1140 | + | /* --- FOOTNOTES + IMMUTABLE POSTS */ | |
| 1192 | 1141 | ||
| 1193 | 1142 | .post-footnotes { | |
| 1194 | 1143 | margin-top: 0.75rem; | |
| @@ -1426,9 +1375,7 @@ tr.mentioned { | |||
| 1426 | 1375 | background: var(--light-background); | |
| 1427 | 1376 | } | |
| 1428 | 1377 | ||
| 1429 | - | /* =========================================== | |
| 1430 | - | UTILITY CLASSES | |
| 1431 | - | =========================================== */ | |
| 1378 | + | /* --- UTILITY CLASSES */ | |
| 1432 | 1379 | ||
| 1433 | 1380 | .form-inline { display: inline; } | |
| 1434 | 1381 | .form-inline-row { display: inline-flex; gap: 0.25rem; flex-direction: row; align-items: center; } | |
| @@ -1456,9 +1403,7 @@ tr.mentioned { | |||
| 1456 | 1403 | .community-desc { margin-bottom: 0.5rem; color: var(--text-muted); } | |
| 1457 | 1404 | .activity-category { color: var(--text-muted); font-size: 0.85rem; } | |
| 1458 | 1405 | ||
| 1459 | - | /* =========================================== | |
| 1460 | - | LINK PREVIEWS | |
| 1461 | - | =========================================== */ | |
| 1406 | + | /* --- LINK PREVIEWS */ | |
| 1462 | 1407 | ||
| 1463 | 1408 | .post-link-previews { | |
| 1464 | 1409 | margin-top: 0.75rem; | |
| @@ -1504,9 +1449,7 @@ tr.mentioned { | |||
| 1504 | 1449 | margin-top: 0.15rem; | |
| 1505 | 1450 | } | |
| 1506 | 1451 | ||
| 1507 | - | /* =========================================== | |
| 1508 | - | SEARCH MODAL | |
| 1509 | - | =========================================== */ | |
| 1452 | + | /* --- SEARCH MODAL */ | |
| 1510 | 1453 | ||
| 1511 | 1454 | .search-toggle { | |
| 1512 | 1455 | font-family: "IBM Plex Mono", monospace; | |
| @@ -1642,9 +1585,7 @@ tr.mentioned { | |||
| 1642 | 1585 | font-size: 0.85rem; | |
| 1643 | 1586 | } | |
| 1644 | 1587 | ||
| 1645 | - | /* =========================================== | |
| 1646 | - | POST ENDORSEMENTS | |
| 1647 | - | =========================================== */ | |
| 1588 | + | /* --- POST ENDORSEMENTS */ | |
| 1648 | 1589 | ||
| 1649 | 1590 | .endorsed { | |
| 1650 | 1591 | color: var(--highlight); | |
| @@ -1657,9 +1598,7 @@ tr.mentioned { | |||
| 1657 | 1598 | margin-left: 0.25rem; | |
| 1658 | 1599 | } | |
| 1659 | 1600 | ||
| 1660 | - | /* =========================================== | |
| 1661 | - | RESPONSIVE, 768px (tablet) | |
| 1662 | - | =========================================== */ | |
| 1601 | + | /* --- RESPONSIVE, 768px (tablet) */ | |
| 1663 | 1602 | ||
| 1664 | 1603 | @media (max-width: 768px) { | |
| 1665 | 1604 | .container { | |
| @@ -1750,9 +1689,7 @@ tr.mentioned { | |||
| 1750 | 1689 | } | |
| 1751 | 1690 | } | |
| 1752 | 1691 | ||
| 1753 | - | /* =========================================== | |
| 1754 | - | RESPONSIVE, 480px (phone) | |
| 1755 | - | =========================================== */ | |
| 1692 | + | /* --- RESPONSIVE, 480px (phone) */ | |
| 1756 | 1693 | ||
| 1757 | 1694 | @media (max-width: 480px) { | |
| 1758 | 1695 | .container { |
| @@ -218,8 +218,7 @@ async fn endorse_requires_login() { | |||
| 218 | 218 | .unwrap(); | |
| 219 | 219 | let post_id = posts[0].id; | |
| 220 | 220 | ||
| 221 | - | // Login as a different user, then logout by creating a fresh harness client | |
| 222 | - | // Actually, just don't login, create a fresh harness without login | |
| 221 | + | // Don't login; create a fresh harness without login | |
| 223 | 222 | let mut h2 = TestHarness::new().await; | |
| 224 | 223 | // GET some page for CSRF | |
| 225 | 224 | h2.client.get("/").await; |