gitsite: serve usable styled layout
ober
7dc0be16d577e87d5f6082a0a3089f5725e4fe0f
--- a/src/gitsite/views.ss +++ b/src/gitsite/views.ss @@ -1,27 +1,39 @@ (import (jerboa prelude) - (std markup html) - (only (std text html) html-escape)) + (std markup html)) (export page-html css-string) -(def css-string "body{font-family:system-ui,sans-serif;margin:0;background:#fafafa;color:#222}a{color:#06c;text-decoration:none}a:hover{text-decoration:underline}header.top{display:flex;align-items:center;padding:.6em 1em;border-bottom:1px solid #ddd;background:#f5f5f5}header.top nav a{margin-left:1em}main{max-width:900px;margin:auto;padding:1em}ul.repolist{list-style:none;padding:0}ul.repolist li{padding:.4em 0;border-bottom:1px solid #eee}.desc{color:#666;margin-left:.6em}pre{background:#f5f5f5;padding:1em;overflow:auto}footer{color:#999;padding:1em;border-top:1px solid #eee;margin-top:2em}") +(def css-string + (string-append + ":root{--ink:#18212b;--muted:#607080;--line:#d9e2ea;--page:#f5f7f8;--panel:#fff;--blue:#2457c5;--green:#217857;--red:#b54c2f;--shadow:0 18px 50px rgba(24,33,43,.10)}" + "*{box-sizing:border-box}html{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;color:var(--ink);background:var(--page)}body{margin:0;font-size:16px;line-height:1.6;background:radial-gradient(circle at top left,#e7f5f5 0,#f5f7f8 26rem),linear-gradient(180deg,#fff 0,#f5f7f8 38rem);min-width:320px}a{color:var(--blue);text-decoration:none}a:hover{text-decoration:underline}" + ".navbar{position:sticky;top:0;z-index:10;min-height:4.25rem;background:rgba(255,255,255,.92);border-bottom:1px solid rgba(217,226,234,.9);backdrop-filter:saturate(160%) blur(18px);display:flex;align-items:center;justify-content:space-between;padding:.75rem clamp(1rem,3vw,2.4rem);gap:1rem}.brand{display:flex;align-items:center;color:var(--ink);font-size:1.08rem;font-weight:850}.brand-mark{display:grid;place-items:center;width:2.4rem;height:2.4rem;margin-right:.7rem;border:1px solid #bfd0da;border-radius:8px;background:linear-gradient(135deg,#fff7dc,#e9fbff 60%,#fff2f7);box-shadow:0 8px 24px rgba(24,33,43,.10);font-family:Georgia,serif;font-weight:900}.nav{display:flex;gap:.35rem;align-items:center;font-size:.94rem;white-space:nowrap}.nav-link{color:#33465a;border-radius:6px;padding:.45rem .64rem}.nav-link.active{color:var(--ink);background:#eaf2f4;font-weight:800}.user-pill{color:#516579;border-left:1px solid var(--line);padding-left:.85rem;margin-left:.35rem}" + "main{max-width:1080px;margin:0 auto;padding:clamp(1.1rem,3vw,2.4rem)}main h1{font-size:clamp(2rem,4vw,3.4rem);line-height:1;margin:.2rem 0 1rem;color:#101923;letter-spacing:-.02em}main h2{margin-top:2rem;color:#172230}.card,form{border:1px solid var(--line);border-radius:8px;background:rgba(255,255,255,.86);padding:1rem;box-shadow:0 10px 30px rgba(24,33,43,.05)}form p{display:grid;gap:.35rem;margin:.75rem 0}label{font-weight:800;color:#33465a}input,textarea,select{width:100%;max-width:44rem;border:1px solid #c9d5de;border-radius:6px;background:#fff;color:#172230;font:inherit;padding:.58rem .68rem}button{border:1px solid #172230;border-radius:6px;background:#172230;color:#fff;font:inherit;font-weight:850;min-height:2.55rem;padding:.58rem .9rem;cursor:pointer}button:hover{background:#283847}.inline-form{display:inline;padding:0;border:0;background:transparent;box-shadow:none}.inline-form button{min-height:1.9rem;padding:.25rem .55rem;font-size:.9rem}" + "ul.repolist{list-style:none;padding:0;margin:1rem 0;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff}ul.repolist li{display:flex;gap:.75rem;align-items:baseline;padding:.75rem .9rem;border-bottom:1px solid #edf1f4}ul.repolist li:last-child{border-bottom:0}.desc{color:var(--muted);margin-left:.4rem}.tabs{display:flex;gap:.35rem;flex-wrap:wrap;margin:0 0 1rem;border-bottom:1px solid var(--line);padding-bottom:.65rem}.tabs a{color:#33465a;border-radius:6px;padding:.42rem .62rem}.tabs a.active{background:#eaf2f4;color:var(--ink);font-weight:800}pre{background:#fff;border:1px solid var(--line);border-radius:8px;padding:1rem;overflow:auto}table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line)}td,th{padding:.55rem .7rem;border-bottom:1px solid #edf1f4;text-align:left}footer{color:#728396;text-align:center;padding:2rem;border-top:1px solid var(--line);margin-top:2rem}@media(max-width:720px){.navbar{position:static;align-items:flex-start;flex-direction:column}.nav{width:100%;overflow:auto;padding-bottom:.15rem}ul.repolist li{display:block}.user-pill{border-left:0;padding-left:0;margin-left:0}}")) + +(def (nav-link href label) + `(a (@ (href ,href) (class "nav-link")) ,label)) (def (page-html title user body) - `(html + `(html (@ (lang "en")) (head (meta (@ (charset "utf-8"))) + (meta (@ (name "viewport") (content "width=device-width,initial-scale=1"))) (title ,(str title " - gitsite")) - (style ,css-string)) + (link (@ (rel "stylesheet") (href "/assets/site.css")))) (body - (header (@ (class "top")) - (a (@ (href "/")) "gitsite") - (nav - (a (@ (href "/")) "home") - ,(if user - '(a (@ (href "/new")) "new repo") - '(a (@ (href "/login")) "login")) - ,(if user - '(a (@ (href "/logout")) "logout") - '(a (@ (href "/register")) "register")))) + (header (@ (class "navbar")) + (a (@ (class "brand") (href "/")) + (span (@ (class "brand-mark")) "λ") + (span "gitsite")) + (nav (@ (class "nav") (aria-label "Main")) + ,(nav-link "/" "Home") + ,(if user + (nav-link "/new" "New repo") + (nav-link "/login" "Login")) + ,(if user + (nav-link "/logout" "Logout") + (nav-link "/register" "Register")) + ,@(if user `((span (@ (class "user-pill")) ,(str "@" user))) '()))) (main ,@body) (footer "gitsite - a tiny forge")))) --- a/src/gitsite/web.ss +++ b/src/gitsite/web.ss @@ -14,7 +14,7 @@ (only (gitsite git_http) git-bridge) (only (gitsite builds) submit-job! list-repo-jobs get-job) (only (gitsite worker) run-one-job) - (only (gitsite views) page-html) + (only (gitsite views) page-html css-string) (only (sinatra request) sinatra-request-raw sinatra-request-method sinatra-request-body-params) (only (sinatra security) secure-headers!)) @@ -222,6 +222,13 @@ (body! "User-agent: *\nAllow: /\n") (halt)) + (GET "/assets/site.css" + (status! 200) + (header! "Content-Type" "text/css; charset=utf-8") + (header! "Cache-Control" "public, max-age=300") + (body! css-string) + (halt)) + (GET "/git/~:owner/:name*" (git-route-handler)) (POST "/git/~:owner/:name*" (git-route-handler)) @@ -368,7 +375,7 @@ `(ul ,@(map (lambda (k) `(li (strong ,(cdr (assq 'title k))) (span (@ (class "desc")) ,(str " - " (cdr (assq 'fingerprint k)))) - (form (@ (method "post") (action ,(str "/settings/keys/" (cdr (assq 'id k)) "/delete")) (style "display:inline")) + (form (@ (method "post") (action ,(str "/settings/keys/" (cdr (assq 'id k)) "/delete")) (class "inline-form")) ,(csrf-input) (button (@ (type "submit")) "delete")))) keys))))))))) @@ -428,7 +435,7 @@ (if (and (string? lu) (not (string=? lu ""))) (str " - created " (cdr (assq 'created_at t)) ", last used " lu) (str " - created " (cdr (assq 'created_at t)))))) - (form (@ (method "post") (action ,(str "/settings/tokens/" (cdr (assq 'id t)) "/delete")) (style "display:inline")) + (form (@ (method "post") (action ,(str "/settings/tokens/" (cdr (assq 'id t)) "/delete")) (class "inline-form")) ,(csrf-input) (button (@ (type "submit")) "delete")))) tokens)))))))))