summaryrefslogtreecommitdiff
path: root/citrine.lua
diff options
context:
space:
mode:
Diffstat (limited to 'citrine.lua')
-rw-r--r--citrine.lua172
1 files changed, 172 insertions, 0 deletions
diff --git a/citrine.lua b/citrine.lua
new file mode 100644
index 0000000..3f9b41a
--- /dev/null
+++ b/citrine.lua
@@ -0,0 +1,172 @@
+local h = require 'html'
+
+local M = {}
+
+local site_sections = {
+ default = "account",
+}
+
+local function navbar(links)
+ h.nav({class = "site-nav"}, function()
+ for _, link in ipairs(links) do
+ h.a({
+ href = link[2],
+ class = link[2] == links.selected and "selected" or nil
+ }, link[1])
+ h.text " "
+ end
+ end)
+end
+
+function M.page(param)
+ return h.render_doc(function()
+ h.meta {charset = "utf-8"}
+ h.title(param.title and
+ param.title .. " — auth.citrons.xyz" or "auth.citrons.xyz")
+ h.meta {
+ name = "viewport",
+ content = "width=device-width, initial-scale=0.75"
+ }
+ h.link {rel = "stylesheet", href = "/static/citrine.css"}
+ h.div({id = "page-grid"}, function()
+ h.header({class = "site-header"}, function()
+ param.section = param.section or "default"
+ h.img({
+ src = "/static/headers/"..param.section..".png",
+ alt = site_sections[param.section]
+ })
+ end)
+ h.div({class = "content"}, function()
+ h.main(param[1])
+ h.footer({class = "site-footer"}, function()
+ h.p(function()
+ h.a({href =
+ "https://citrons.xyz/git/auth.git/"
+ }, "© 2023 citrons")
+ end)
+ end)
+ end)
+ h.div({class = "header-icons"}, function()
+ h.a({class = "help", href = "/account"}, function()
+ h.img {
+ src = "/static/gear.png", alt = "account settings",
+ class = "help"
+ }
+ end)
+ h.a({class = "help", href = "/"}, function()
+ h.img {
+ src = "/static/qmark.png", alt = "about",
+ class = "help"
+ }
+ end)
+ end)
+ end)
+ end)
+end
+
+local function class_article(e)
+ return function(...)
+ h[e]({class = "article"}, ...)
+ end
+end
+
+M.h1 = class_article "h1"
+M.h2 = class_article "h2"
+M.h3 = class_article "h3"
+M.h4 = class_article "h4"
+M.h5 = class_article "h5"
+M.h6 = class_article "h6"
+
+function M.quote(quote, attrib)
+ h.figure({class = attrib and "quote attrib" or "quote"}, function()
+ h.blockquote(function()
+ h.p({}, quote)
+ end)
+ if attrib then
+ h.figcaption({}, attrib)
+ end
+ end)
+end
+
+function M.dyk(content)
+ h.aside({class = "dyk"}, function()
+ h.h2 "did you know?"
+ if type(content) == "string" then
+ html.p(content)
+ else
+ content()
+ end
+ end)
+end
+
+function M.code_block(content)
+ h.pre({class = "code-block"}, content)
+end
+
+function M.apiobutton()
+ h.a({href = "https://citrons.xyz/a/memetic-apioform-page",
+ class="apiopage"}, function()
+ h.img {src = "https://citrons.xyz/a/static/apiopage.png",
+ alt = "memetic apiopage: click to view"}
+ end)
+end
+
+function M.redact(s)
+ h.del({class = "redaction", title = "redacted text"}, function()
+ h.span({}, s:gsub(utf8.charpattern, function(c)
+ if c ~= " " then return "X" end
+ end))
+ end)
+end
+
+function M.errmsg(e)
+ h.div({class = "errmsg"}, function()
+ h.p({}, e)
+ end)
+end
+
+local statuses = {
+ [400] = "400 Bad Request",
+ [401] = "401 Unauthorized",
+ [403] = "403 Forbidden",
+ [404] = "404 Not Found",
+ [405] = "405 Method Not Allowed",
+ [410] = "410 Gone",
+ [411] = "411 Length Required",
+ [413] = "413 Payload Too Large",
+ [429] = "429 Too Many Requests",
+ [500] = "500 Internal Server Error",
+ [501] = "501 Not Implemented",
+ [502] = "502 Bad Gateway",
+ [503] = "503 Service Unavailable",
+ [504] = "504 Gateway Timeout"
+}
+
+local messages = {
+ [400] = "your software has sent an invalid request to the server.",
+ [401] = "permission denied.",
+ [403] = "permission denied.",
+ [404] = "the page you attempted to access was not found.",
+ [405] = "your software has sent a request with an invalid method.",
+ [410] = "this content is no longer available.",
+ [411] = "your software failed to provide a length for the request body.",
+ [413] = "your software sent too much data to the server.",
+ [429] = "you are doing this too often. try again later.",
+ [500] = "a temporary internal error has occurred. this is my fault.",
+ [501] = "the requested feature is not implemented.",
+}
+
+function M.error(code, message)
+ return {
+ content_type = 'text/html', status = statuses[code] or code
+ }, M.page {
+ section = "error",
+ function()
+ M.h1((statuses[code] or ("error "..code)):lower())
+ h.p(message or messages[code] or
+ messages[code // 100 * 100] or "an error has occurred")
+ end
+ }
+end
+
+return M