(defmodule nemoptera_ui
(export all))
(defun render (page-title current mode logged-in? main)
(let* ((site (nemoptera_site:all))
(name (maps:get 'site_name site "Nemoptera"))
(tagline (maps:get 'site_tagline site "")))
(lists:flatten
(list
"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<title>" (escape page-title) "</title><style>" (css) "</style></head><body>"
"<h1 class=\"site\">" (escape name) "</h1>"
(if (=:= "" tagline) "" (list "<p class=\"tagline\">" (escape tagline) "</p>"))
"<p class=\"recent\">Recent changes: <a href=\"/timeline\">" (escape (recent-label)) "</a>"
" | <a href=\"/rss\">RSS</a> | <a href=\"/atom\">Atom</a></p><hr class=\"rule\">"
"<div class=\"frame\"><nav class=\"shortcuts\"><h2>Shortcuts</h2>"
(shortcuts current mode logged-in?)
"</nav><div class=\"bar\"></div><main class=\"main\">"
main
"</main></div></body></html>"))))
(defun recent-label ()
(if (nemoptera_setup:installed?)
(nemoptera_site:recent-stamp)
"no changes yet"))
(defun shortcuts (current mode logged-in?)
(let ((items (if (=:= mode 'admin)
(admin-links logged-in?)
(public-links logged-in?))))
(lists:map (lambda (item) (render-shortcut item current)) items)))
(defun render-shortcut (item current)
(case item
(`#(,key ,href ,label ,target)
(list "<a href=\"" (escape href) "\""
(if (=:= target 'blank) " target=\"_blank\" rel=\"noopener\"" "")
">"
(if (=:= key current) (list "<strong>" (escape label) "</strong>") (escape label))
"</a>"))
(`#(,key ,href ,label)
(list "<a href=\"" (escape href) "\">"
(if (=:= key current) (list "<strong>" (escape label) "</strong>") (escape label))
"</a>"))))
(defun public-links (logged-in?)
(lists:append
(list (tuple 'home "/" "Home")
(tuple 'repos "/#repositories" "Repositories")
(tuple 'orgs "/#organizations" "Organizations")
(tuple 'timeline "/timeline" "Timeline"))
(lists:append
(nemoptera_site:custom-shortcuts)
(if logged-in?
(list (tuple 'admin "/admin" "Admin") (tuple 'logout "/logout" "Logout"))
(list (tuple 'login "/login" "Login"))))))
(defun admin-links (_logged-in?)
(lists:append
(list (tuple 'home "/admin" "Home")
(tuple 'repos "/admin/repos" "Repositories")
(tuple 'orgs "/admin/orgs" "Organizations")
(tuple 'settings "/admin/settings" "Settings")
(tuple 'public "/" "Public site"))
(lists:append
(nemoptera_site:custom-shortcuts)
(list (tuple 'logout "/logout" "Logout")))))
(defun escape (value)
(let ((text (nemoptera_text:to-str value)))
(string:replace
(string:replace
(string:replace
(string:replace
(string:replace text "&" "&" 'all)
"<" "<" 'all)
">" ">" 'all)
"\"" """ 'all)
"'" "'" 'all)))
(defun css ()
"body{margin:0;background:#fff;color:#111;font:15px/1.45 Verdana,Geneva,sans-serif;}
h1.site{margin:26px 12px 0;text-align:center;font:700 40px/1.1 Georgia,'Times New Roman',Times,serif;letter-spacing:-.4px;}
.tagline{margin:8px 16px 0;text-align:center;}
.recent{margin:14px 12px 10px;text-align:center;}
a:link,a:visited{color:#00e;}
hr.rule{border:0;border-top:1px solid #000;margin:0;}
.frame{display:flex;align-items:stretch;max-width:1040px;margin:18px auto 56px;padding:0 12px;}
.shortcuts{width:148px;flex:0 0 148px;padding:2px 14px 0 4px;}
.shortcuts h2{margin:0 0 10px;font-style:italic;font-weight:normal;font-size:16px;}
.shortcuts a{display:block;margin:3px 0;}
.bar{width:1px;background:#000;flex:0 0 1px;}
.main{flex:1;min-width:0;padding:0 4px 0 22px;}
.cats{columns:2 280px;column-gap:42px;}
.cat{break-inside:avoid;margin:0 0 22px;}
.cat h2{margin:0 0 4px;font-size:20px;}
.cat h2 a{text-decoration:underline;}
.blurb,.repoline{margin:0 0 6px;}
h1.page{margin:0 0 10px;font-size:22px;}
p{margin:8px 0;}
table{border-collapse:collapse;width:100%;}
th,td{text-align:left;vertical-align:top;padding:5px 12px 5px 0;border-bottom:1px solid #ddd;}
th{border-bottom:1px solid #000;font-weight:normal;}
label{display:block;margin:12px 0 3px;}
input[type=text],input[type=password],input[type=url],textarea,select{font:inherit;width:min(34rem,100%);border:1px solid #777;padding:3px 6px;background:#fff;}
textarea{min-height:5.5rem;}
button,.button{font:inherit;background:#fff;border:1px solid #777;padding:3px 12px;color:#00e;cursor:pointer;}
.note{color:#333;}
.error{color:#900;}
.actions{margin-top:14px;}
.inline{display:inline;}
.check{margin-top:10px;}
")