1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
|
#!/usr/bin/env wsapi.cgi
local orbit = require "orbit"
local orcache = require "orbit.cache"
local markdown = require "markdown"
local wsutil = require "wsapi.util"
--
-- Declares that this is module is an Orbit app
--
local blog = setmetatable(orbit.new(), { __index = _G })
if _VERSION == "Lua 5.2" then
_ENV = blog
else
setfenv(1, blog)
end
--
-- Loads configuration data
--
wsutil.loadfile("blog_config.lua", blog)()
--
-- Initializes DB connection for Orbit's default model mapper
--
local luasql = require("luasql." .. database.driver)
local env = luasql[database.driver]()
mapper.conn = env:connect(unpack(database.conn_data))
mapper.driver = database.driver
-- Initializes page cache
local cache = orbit.cache.new(blog, cache_path)
--
-- Models for this application. Orbit calls mapper:new for each model,
-- so if you want to replace Orbit's default ORM mapper by another
-- one (file-based, for example) just redefine the mapper global variable
--
posts = blog:model "post"
function posts:find_comments()
return comments:find_all_by_post_id{ self.id }
end
function posts:find_recent()
return self:find_all("published_at is not null",
{ order = "published_at desc",
count = recent_count })
end
function posts:find_by_month_and_year(month, year)
local s = os.time({ year = year, month = month, day = 1 })
local e = os.time({ year = year + math.floor(month / 12),
month = (month % 12) + 1,
day = 1 })
return self:find_all("published_at >= ? and published_at < ?",
{ s, e, order = "published_at desc" })
end
function posts:find_months()
local months = {}
local previous_month = {}
local posts = self:find_all({ order = "published_at desc" })
for _, post in ipairs(posts) do
local date = os.date("*t", post.published_at)
if previous_month.month ~= date.month or
previous_month.year ~= date.year then
previous_month = { month = date.month, year = date.year,
date_str = os.date("%Y/%m", post.published_at) }
months[#months + 1] = previous_month
end
end
return months
end
comments = blog:model "comment"
function comments:make_link()
local author = self.author or strings.anonymous_author
if self.url and self.url ~= "" then
return "<a href=\"" .. self.url .. "\">" .. author .. "</a>"
elseif self.email and self.email ~= "" then
return "<a href=\"mailto:" .. self.email .. "\">" .. author .. "</a>"
else
return author
end
end
pages = blog:model "page"
--
-- Controllers for this application
--
function index(web)
local ps = posts:find_recent()
local ms = posts:find_months()
local pgs = pgs or pages:find_all()
return render_index(web, { posts = ps, months = ms,
recent = ps, pages = pgs })
end
blog:dispatch_get(cache(index), "/", "/index")
function view_post(web, post_id, comment_missing)
local post = posts:find(tonumber(post_id))
if post then
local recent = posts:find_recent()
local pgs = pages:find_all()
post.comments = post:find_comments()
local months = posts:find_months()
return render_post(web, { post = post, months = months,
recent = recent, pages = pgs,
comment_missing = comment_missing })
else
return not_found(web)
end
end
blog:dispatch_get(cache(view_post), "/post/(%d+)")
function add_comment(web, post_id)
local input = web.input
if string.find(input.comment, "^%s*$") then
return view_post(web, post_id, true)
else
local comment = comments:new()
comment.post_id = tonumber(post_id)
comment.body = markdown(input.comment)
if not string.find(input.author, "^%s*$") then
comment.author = input.author
end
if not string.find(input.email, "^%s*$") then
comment.email = input.email
end
if not string.find(input.url, "^%s*$") then
comment.url = input.url
end
comment:save()
local post = posts:find(tonumber(post_id))
post.n_comments = (post.n_comments or 0) + 1
post:save()
cache:invalidate("/")
cache:invalidate("/post/" .. post_id)
cache:invalidate("/archive/" .. os.date("%Y/%m", post.published_at))
return web:redirect(web:link("/post/" .. post_id))
end
end
blog:dispatch_post(add_comment, "/post/(%d+)/addcomment")
function view_archive(web, year, month)
local ps = posts:find_by_month_and_year(tonumber(month),
tonumber(year))
local months = posts:find_months()
local recent = posts:find_recent()
local pgs = pages:find_all()
return render_index(web, { posts = ps, months = months,
recent = recent, pages = pgs })
end
blog:dispatch_get(cache(view_archive), "/archive/(%d%d%d%d)/(%d%d)")
blog:dispatch_static("/head%.jpg", "/style%.css")
function view_page(web, page_id)
local page = pages:find(tonumber(page_id))
if page then
local recent = posts:find_recent()
local months = posts:find_months()
local pgs = pages:find_all()
return render_page(web, { page = page, months = months,
recent = recent, pages = pgs })
else
not_found(web)
end
end
blog:dispatch_get(cache(view_page), "/page/(%d+)")
--
-- Views for this application
--
function layout(web, args, inner_html)
return html{
head{
title(blog_title),
meta{ ["http-equiv"] = "Content-Type",
content = "text/html; charset=utf-8" },
link{ rel = 'stylesheet', type = 'text/css',
href = web:static_link('/style.css'), media = 'screen' }
},
body{
div{ id = "container",
div{ id = "header", title = "sitename" },
div{ id = "mainnav",
_menu(web, args)
},
div{ id = "menu",
_sidebar(web, args)
},
div{ id = "contents", inner_html },
div{ id = "footer", copyright_notice }
}
}
}
end
function _menu(web, args)
local res = { li(a{ href= web:link("/"), strings.home_page_name }) }
for _, page in pairs(args.pages) do
res[#res + 1] = li(a{ href = web:link("/page/" .. page.id), page.title })
end
return ul(res)
end
function _blogroll(web, blogroll)
local res = {}
for _, blog_link in ipairs(blogroll) do
res[#res + 1] = li(a{ href=blog_link[1], blog_link[2] })
end
return ul(res)
end
function _sidebar(web, args)
return {
h3(strings.about_title),
ul(li(about_blurb)),
h3(strings.last_posts),
_recent(web, args),
h3(strings.blogroll_title),
_blogroll(web, blogroll),
h3(strings.archive_title),
_archives(web, args)
}
end
function _recent(web, args)
local res = {}
for _, post in ipairs(args.recent) do
res[#res + 1] = li(a{ href=web:link("/post/" .. post.id), post.title })
end
return ul(res)
end
function _archives(web, args)
local res = {}
for _, month in ipairs(args.months) do
res[#res + 1] = li(a{ href=web:link("/archive/" .. month.date_str),
blog.month(month) })
end
return ul(res)
end
function render_index(web, args)
if #args.posts == 0 then
return layout(web, args, p(strings.no_posts))
else
local res = {}
local cur_time
for _, post in pairs(args.posts) do
local str_time = date(post.published_at)
if cur_time ~= str_time then
cur_time = str_time
res[#res + 1] = h2(str_time)
end
res[#res + 1] = h3(post.title)
res[#res + 1] = _post(web, post)
end
return layout(web, args, div.blogentry(res))
end
end
function _post(web, post)
return {
markdown(post.body),
p.posted{
strings.published_at .. " " ..
os.date("%H:%M", post.published_at), " | ",
a{ href = web:link("/post/" .. post.id .. "#comments"), strings.comments ..
" (" .. (post.n_comments or "0") .. ")" }
}
}
end
function _comment(web, comment)
return { p(comment.body),
p.posted{
strings.written_by .. " " .. comment:make_link(),
" " .. strings.on_date .. " " ..
time(comment.created_at)
}
}
end
function render_page(web, args)
return layout(web, args, div.blogentry(markdown(args.page.body)))
end
function render_post(web, args)
local res = {
h2(span{ style="position: relative; float:left", args.post.title }
.. " "),
h3(date(args.post.published_at)),
_post(web, args.post)
}
res[#res + 1] = a{ name = "comments" }
if #args.post.comments > 0 then
res[#res + 1] = h2(strings.comments)
for _, comment in pairs(args.post.comments) do
res[#res + 1 ] = _comment(web, comment)
end
end
res[#res + 1] = h2(strings.new_comment)
local err_msg = ""
if args.comment_missing then
err_msg = span{ style="color: red", strings.no_comment }
end
res[#res + 1] = form{
method = "post",
action = web:link("/post/" .. args.post.id .. "/addcomment"),
p{ strings.form_name, br(), input{ type="text", name="author",
value = web.input.author },
br(), br(),
strings.form_email, br(), input{ type="text", name="email",
value = web.input.email },
br(), br(),
strings.form_url, br(), input{ type="text", name="url",
value = web.input.url },
br(), br(),
strings.comments .. ":", br(), err_msg,
textarea{ name="comment", rows="10", cols="60", web.input.comment },
br(),
em(" *" .. strings.italics .. "* "),
strong(" **" .. strings.bold .. "** "),
" [" .. a{ href="/url", strings.link } .. "](http://url) ",
br(), br(),
input.button{ type="submit", value=strings.send }
}
}
return layout(web, args, div.blogentry(res))
end
-- Adds html functions to the view functions
orbit.htmlify(blog, "layout", "_.+", "render_.+")
return blog
|