Mercurial > prosody-modules
changeset 1581:9f6cd252d233
mod_http_muc_log: Revamp template system
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 02 Dec 2014 15:12:01 +0100 |
parents | 63571115302f |
children | 8e282eb0c70c |
files | mod_http_muc_log/mod_http_muc_log.lua |
diffstat | 1 files changed, 122 insertions(+), 149 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_http_muc_log/mod_http_muc_log.lua Tue Dec 02 14:17:08 2014 +0100 +++ b/mod_http_muc_log/mod_http_muc_log.lua Tue Dec 02 15:12:01 2014 +0100 @@ -5,6 +5,9 @@ local uuid = require"util.uuid".generate; local it = require"util.iterators"; local gettime = require"socket".gettime; +local url = require"socket.url"; +local xml_escape = st.xml_escape; +local t_concat = table.concat; local archive = module:open_store("muc_log", "archive"); @@ -28,128 +31,114 @@ module:depends"http"; -local function template(data) +local function render(template, values) --[[ DOC - Like util.template, but deals with plain text - Returns a closure that is called with a table of values {name} is substituted for values["name"] and is XML escaped {name!} is substituted without XML escaping {name?} is optional and is replaced with an empty string if no value exists + {name# sub-template } renders a sub-template using an array of values ]] - return function(values) - return (data:gsub("{([^}]-)(%p?)}", function (name, opt) - local value = values[name]; - if value then - if opt ~= "!" then - return st.xml_escape(value); - end - return value; - elseif opt == "?" then - return ""; + return (template:gsub("%b{}", function (block) + local name, opt, e = block:sub(2, -2):match("([%a_][%w_]*)(%p?)()"); + local value = values[name]; + if opt == '#' then + if not value or not value[1] then return ""; end + local out, subtpl = {}, block:sub(e+1, -2); + for i=1, #value do + out[i] = render(subtpl, value[i]); end - end)); - end + return t_concat(out); + end + if value ~= nil then + if type(value) ~= "string" then + value = tostring(value); + end + if opt ~= '!' then + return xml_escape(value); + end + return value; + elseif opt == '?' then + return block:sub(e+1, -2); + end + end)); end --- TODO Move templates into files -local base = template(template[[ +-- TODO Move template into a file +local template = [=[ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> -<link rel="canonical" href="{canonical}"> <title>{title}</title> <style> +:link,:visited{text-decoration:none;color:#2e3436;text-decoration:none;} +:link:hover,:visited:hover{color:#3465a4;} body{background-color:#eeeeec;margin:1ex 0;padding-bottom:3em;font-family:Arial,Helvetica,sans-serif;} -header,footer{margin:1ex 1em;} -footer{font-size:smaller;color:#babdb6;} -.content{background-color:white;padding:1em;list-style-position:inside;} -nav{font-size:large;margin:1ex 1ex;clear:both;line-height:1.5em;} -nav a{padding: 1ex;text-decoration:none;} -nav a.up{font-size:smaller;} -nav a.prev{float:left;} -nav a.next{float:right;} -nav a.next::after{content:" →";} -nav a.prev::before{content:"← ";} -nav a:empty::after,nav a:empty::before{content:""} -@media screen and (min-width: 460px) { -nav{font-size:x-large;margin:1ex 1em;} -} -a:link,a:visited{color:#2e3436;text-decoration:none;} -a:link:hover,a:visited:hover{color:#3465a4;} ul,ol{padding:0;} li{list-style:none;} hr{visibility:hidden;clear:both;} br{clear:both;} -li time{float:right;font-size:small;opacity:0.2;} -li:hover time{opacity:1;} -.room-list .description{font-size:smaller;} -q.body::before,q.body::after{content:"";} +header,footer{margin:1ex 1em;} +footer{font-size:smaller;color:#babdb6;} +nav{font-size:large;margin:1ex 1ex;clear:both;line-height:1.5em;} +footer nav .up{display:none;} +@media screen and (min-width: 460px) { +nav {font-size:x-large;margin:1ex 1em;} +} +nav a{padding: 1ex;} +nav .up{font-size:smaller;display:block;clear:both;} +nav .prev{float:left;} +nav .next{float:right;} +nav .next::after{content:" →";} +nav .prev::before{content:"← ";} +nav :empty::after,nav :empty::before{content:""} +.content{background-color:white;padding:1em;list-style-position:inside;} +.time{float:right;font-size:small;opacity:0.2;} +li:hover .time{opacity:1;} +.description{font-size:smaller;} +.body{white-space:pre-line;} +.body::before,.body::after{content:"";} .presence .verb{font-style:normal;color:#30c030;} -.presence.unavailable .verb{color:#c03030;} +.unavailable .verb{color:#c03030;} </style> </head> <body> <header> -<h1>{title}</h1> -{header!} +<h1 title="xmpp:{jid?}">{title}</h1> +<nav>{links# +<a class="{rel?}" href="{href}" rel="{rel?}">{text}</a>} +</nav> </header> <hr> <div class="content"> -{body!} +<nav> +<dl class="room-list"> +{rooms# +<dt class="name"><a href="{href}">{name}</a></dt> +<dd class="description">{description?}</dd>} +</dl> +<ul class="dates">{dates# +<li><a href="{date}">{date}</a></li>} +</ul> +</nav> +<ol class="chat-logs">{lines# +<li class="{st_name} {st_type?}" id="{key}"> +<a class="time" href="#{key}"><time datetime="{datetime}">{time}</time></a> +<b class="nick">{nick}</b> +<em class="verb">{verb?}</em> +<q class="body">{body?}</q> +</li>} +</ol> </div> <hr> <footer> -{footer!} -<br> -<div class="powered-by">Prosody {prosody_version?}</div> -</footer> -</body> -</html> -]] { prosody_version = prosody.version }); - -local dates_template = template(base{ - title = "Logs for room {room}"; - header = [[ -<nav> -<a href=".." class="up">Back to room list</a> -</nav> -]]; - body = [[ -<nav> -<ul class="dates"> -{lines!}</ul> +<nav>{links# +<a class="{rel?}" href="{href}" rel="{rel?}">{text}</a>} </nav> -]]; - footer = ""; -}) - -local date_line_template = template[[ -<li><a href="{date}">{date}</a></li> -]]; - -local page_template = template(base{ - title = "Logs for room {room} on {date}"; - header = [[ -<nav> -<a class="up" href=".">Back to date list</a> <br> -<a class="prev" href="{prev}">{prev}</a> -<a class="next" href="{next}">{next}</a> -</nav> -]]; - body = [[ -<ol class="chat-logs"> -{logs!}</ol> -]]; - footer = [[ -<nav> -<div> -<a class="prev" href="{prev}">{prev}</a> -<a class="next" href="{next}">{next}</a> -</div> -</nav> +<div class="powered-by">Prosody</div> +</footer> <script> /* * Local timestamps @@ -167,37 +156,20 @@ } })(); </script> -]]; -}); - -local line_template = template[[ -<li class="{st_name} {st_type?}" id="{key}"> - <span class="time"> - <a href="#{key}"><time datetime="{datetime}">{time}</time></a> - </span> - <b class="nick">{nick}</b> - <em class="verb">{verb?}</em> - <q class="body">{body?}</q> -</li> -]]; +</body> +</html> +]=]; -local room_list_template = template(base{ - title = "Rooms on {host}"; - header = ""; - body = [[ -<nav> -<dl class="room-list"> -{rooms!} -</dl> -</nav> -]]; - footer = ""; -}); - -local room_item_template = template[[ -<dt class="name"><a href="{room}/">{name}</a></dt> -<dd class="description">{description?}</dd> -]]; +local base_url = module:http_url() .. '/'; +local get_link do + local link, path = { path = '/' }, { "", "", is_directory = true }; + function get_link(room, date) + path[1], path[2] = room, date; + path.is_directory = not date; + link.path = url.build_path(path); + return url.build(link); + end +end local function public_room(room) if type(room) == "string" then @@ -229,7 +201,7 @@ next_day = nil; for key, message, when in iter do next_day = datetime.date(when); - dates[i], i = date_line_template{ + dates[i], i = { date = next_day; }, i + 1; next_day = datetime.parse(next_day .. "T23:59:59Z") + 1; @@ -238,12 +210,14 @@ until not next_day; response.headers.content_type = "text/html; charset=utf-8"; - return dates_template{ - host = module.host; - canonical = module:http_url() .. "/" .. path; - room = room; - lines = table.concat(dates); - }; + return render(template, { + title = get_room(room):get_name(); + jid = get_room(room).jid; + dates = dates; + links = { + { href = "../", rel = "up", text = "Back to room list" }, + }; + }); end local function logs_page(event, path) @@ -278,7 +252,7 @@ verb = item.attr.type == "unavailable" and "has left" or "has joined"; end if body or verb then - logs[i], i = line_template { + logs[i], i = { key = key; datetime = datetime.datetime(when); time = datetime.time(when); @@ -317,35 +291,37 @@ end response.headers.content_type = "text/html; charset=utf-8"; - return page_template{ - canonical = module:http_url() .. "/" .. path; - host = module.host; - room = room; - date = date; - logs = table.concat(logs); - next = next_when; - prev = prev_when; - }; + return render(template, { + title = ("%s - %s"):format(get_room(room):get_name(), date); + jid = get_room(room).jid; + lines = logs; + links = { + { href = "./", rel = "up", text = "Back to date list" }, + { href = prev_when, rel = "prev", text = prev_when}, + { href = next_when, rel = "next", text = next_when}, + }; + }); end local function list_rooms(event) + local request, response = event.request, event.response; local room_list, i = {}, 1; for room in each_room() do if public_room(room) then - room_list[i], i = room_item_template { - room = jid_split(room.jid); + room_list[i], i = { + href = get_link(jid_split(room.jid), nil); name = room:get_name(); description = room:get_description(); }, i + 1; end end - event.response.headers.content_type = "text/html; charset=utf-8"; - return room_list_template { - host = module.host; - canonical = module:http_url() .. "/"; - rooms = table.concat(room_list); - }; + response.headers.content_type = "text/html; charset=utf-8"; + return render(template, { + title = module:get_option_string("name", "Prosody Chatrooms"); + jid = module.host; + rooms = room_list; + }); end local cache = setmetatable({}, {__mode = 'v'}); @@ -390,13 +366,10 @@ local room = event.room; local room_name = jid_split(room.jid); local today = datetime.date(); - cache[ room_name .. "/" .. today ] = nil; - if cache[room_name] and cache[room_name].date ~= today then - cache[room_name] = nil; - end + cache[get_link(room_name)] = nil; + cache[get_link(room_name, today)] = nil; end); -module:log("info", module:http_url()); module:provides("http", { route = { ["GET /"] = list_rooms;