WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

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

at main 142 lines 4.9 kB view raw
1import type { FC, PropsWithChildren } from "hono/jsx"; 2import { tokensToCss } from "../lib/theme.js"; 3import { sanitizeCss } from "@atbb/css-sanitizer"; 4import type { ResolvedTheme } from "../lib/theme-resolution.js"; 5import type { WebSession } from "../lib/session.js"; 6 7const NavContent: FC<{ auth?: WebSession; colorScheme: "light" | "dark" }> = ({ 8 auth, 9 colorScheme, 10}) => { 11 const toggleLabel = 12 colorScheme === "light" ? "Switch to dark mode" : "Switch to light mode"; 13 const toggleIcon = colorScheme === "light" ? "\u263D" : "\u2600"; 14 return ( 15 <> 16 <button 17 class="color-scheme-toggle" 18 onclick="toggleColorScheme()" 19 aria-label={toggleLabel} 20 title={toggleLabel} 21 > 22 {toggleIcon} 23 </button> 24 {auth?.authenticated ? ( 25 <> 26 <span class="site-header__handle">{auth.handle}</span> 27 <a href="/settings" class="site-header__settings-link"> 28 Settings 29 </a> 30 <form action="/logout" method="post" class="site-header__logout-form"> 31 <button type="submit" class="site-header__logout-btn"> 32 Log out 33 </button> 34 </form> 35 </> 36 ) : ( 37 <a href="/login" class="site-header__login-link"> 38 Log in 39 </a> 40 )} 41 </> 42 ); 43}; 44 45export const BaseLayout: FC< 46 PropsWithChildren<{ 47 title?: string; 48 auth?: WebSession; 49 resolvedTheme: ResolvedTheme; 50 }> 51> = (props) => { 52 const { auth, resolvedTheme } = props; 53 54 let rootCss = ""; 55 try { 56 rootCss = sanitizeCss(`:root { ${tokensToCss(resolvedTheme.tokens)} }`); 57 } catch (err) { 58 console.error("Failed to sanitize root CSS tokens — rendering without tokens", { 59 error: String(err), 60 }); 61 } 62 63 let overridesCss: string | null = null; 64 if (resolvedTheme.cssOverrides) { 65 try { 66 overridesCss = sanitizeCss(resolvedTheme.cssOverrides); 67 } catch (err) { 68 console.error("Failed to sanitize CSS overrides — rendering without overrides", { 69 error: String(err), 70 }); 71 } 72 } 73 74 return ( 75 <html lang="en"> 76 <head> 77 <meta charset="UTF-8" /> 78 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 79 <meta http-equiv="Accept-CH" content="Sec-CH-Prefers-Color-Scheme" /> 80 <title>{props.title ?? "atBB Forum"}</title> 81 <style dangerouslySetInnerHTML={{ __html: rootCss }} /> 82 {overridesCss && ( 83 <style dangerouslySetInnerHTML={{ __html: overridesCss }} /> 84 )} 85 {resolvedTheme.fontUrls && resolvedTheme.fontUrls.length > 0 && (() => { 86 const safeFontUrls = resolvedTheme.fontUrls!.filter((url) => url.startsWith("https://")); 87 return safeFontUrls.length > 0 ? ( 88 <> 89 <link rel="preconnect" href="https://fonts.googleapis.com" /> 90 <link 91 rel="preconnect" 92 href="https://fonts.gstatic.com" 93 crossorigin="anonymous" 94 /> 95 {safeFontUrls.map((url) => ( 96 <link rel="stylesheet" href={url} /> 97 ))} 98 </> 99 ) : null; 100 })()} 101 <link rel="stylesheet" href="/static/css/reset.css" /> 102 <link rel="stylesheet" href="/static/css/theme.css" /> 103 <link rel="icon" type="image/svg+xml" href="/static/favicon.svg" /> 104 <script src="https://unpkg.com/htmx.org@2.0.4" defer /> 105 </head> 106 <body> 107 <a href="#main-content" class="skip-link"> 108 Skip to main content 109 </a> 110 <header class="site-header"> 111 <div class="site-header__inner"> 112 <a href="/" class="site-header__title"> 113 atBB Forum 114 </a> 115 <nav class="desktop-nav" aria-label="Main navigation"> 116 <NavContent auth={auth} colorScheme={resolvedTheme.colorScheme} /> 117 </nav> 118 <details class="mobile-nav"> 119 <summary class="mobile-nav__toggle" aria-label="Menu"> 120 &#9776; 121 </summary> 122 <nav class="mobile-nav__menu" aria-label="Mobile navigation"> 123 <NavContent auth={auth} colorScheme={resolvedTheme.colorScheme} /> 124 </nav> 125 </details> 126 </div> 127 </header> 128 <main id="main-content" class="content-container"> 129 {props.children} 130 </main> 131 <footer class="site-footer"> 132 <p>Powered by atBB on the ATmosphere</p> 133 </footer> 134 <script 135 dangerouslySetInnerHTML={{ 136 __html: `function toggleColorScheme(){var m=document.cookie.match(/(?:^|;\\s*)atbb-color-scheme=(light|dark)/);var current=m?m[1]:'light';var next=current==='light'?'dark':'light';document.cookie='atbb-color-scheme='+next+';path=/;max-age=31536000;SameSite=Lax';location.reload();}`, 137 }} 138 /> 139 </body> 140 </html> 141 ); 142};