Implemented paging.

This commit is contained in:
Dominik Picheta 2012-06-04 19:44:55 +01:00
commit 9beee9848c
5 changed files with 224 additions and 47 deletions

View file

@ -5,13 +5,14 @@
#end template
#
#
#proc genThreadsList(c: var TForumData): string =
# const query = sql"select id, name, views, modified from thread order by modified desc"
#proc genThreadsList(c: var TForumData, count: var int): string =
# const query = sql"select id, name, views, modified from thread order by modified desc limit ?, ?"
# const threadId = 0
# const name = 1
# const views = 2
#
# result = ""
# count = 0
<table id="threads">
<tr>
<th>Topics</th>
@ -20,7 +21,8 @@
<th>Views</th>
<th>Last reply</th>
</tr>
# for row in Rows(db, query):
# for row in Rows(db, query, $((c.pageNum-1) * ThreadsPerPage), $ThreadsPerPage):
# inc(count)
<tr>
<td class="topic">${UrlButton(c, XMLencode(%name), c.genThreadUrl(threadid = %threadid))}</td>
#let authorName = getValue(db, sql("select name from person where id = " &
@ -48,6 +50,7 @@
#proc genPostPreview(c: var TForumData,
# title, content, author, date: string): string =
# result = ""
<a name="preview"></a>
<table class="post">
<tr>
<th colspan="2">
@ -71,8 +74,9 @@
#end proc
#
#
#proc genPostsList(c: var TForumData, threadId: string): string =
# const query = sql"select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p, person u where u.id = p.author and p.thread = ? order by p.id"
#proc genPostsList(c: var TForumData, threadId: string, count: var int): string =
# const query = sql"""select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p,
# person u where u.id = p.author and p.thread = ? order by p.id limit ?, ?"""
# const postId = 0
# const userName = 1
# const postHeader = 2
@ -81,7 +85,10 @@
# const postAuthor = 5
# const userEmail = 6
# result = ""
# for row in FastRows(db, query, threadId):
# count = 0
# for row in FastRows(db, query, threadId, $((c.pageNum-1) * PostsPerPage), $PostsPerPage):
# inc(count)
<a name="${%postId}"></a>
<table class="post">
<tr>
<th colspan="2">
@ -123,7 +130,7 @@
<div id="replytop">
<span>${topText}</span>
</div>
<form action="${c.req.makeUri(action, false)}" method="POST">
<form action="${c.req.makeUri(action, false) & "#preview"}" method="POST">
${FieldValid(c, "subject", "Subject:")}
${TextWidget(c, "subject", title, maxlength=100)}
<br />
@ -189,9 +196,8 @@
#end proc
#
#
#proc genListOnline(c: var TForumData): string =
#proc genListOnline(c: var TForumData, stats: TForumStats): string =
# result = ""
# let stats = c.getStats()
<div id="whoisonline">
<div class="wioHeader">
<span>Who is online?<span>

186
forum.nim
View file

@ -8,12 +8,17 @@
import
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
rst, rstgen, captchas, sockets, scgi, jester
rst, rstgen, captchas, sockets, scgi, jester, htmlgen
const
unselectedThread = -1
transientThread = 0
ThreadsPerPage = 15
PostsPerPage = 10
noPageNums = ["/login", "/register", "/dologin", "/doregister"]
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister"]
type
TCrud = enum crCreate, crRead, crUpdate, crDelete
@ -33,7 +38,10 @@ type
invalidField: string
currentPost: TPost
startTime: float
isThreadsList: bool
pageNum: int
totalPosts: int
TStyledButton = tuple[text: string, link: string]
TForumStats = object
@ -90,8 +98,10 @@ proc FieldValid(c: TForumData, name, text: string): string =
else:
result = text
proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = ""): string =
proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNum = ""): string =
result = "/t/" & (if threadid == "": $c.threadId else: threadid)
if pageNum != "":
result.add("/" & pageNum)
if action != "":
result.add("?action=" & action)
if postId != "":
@ -328,9 +338,11 @@ template setPreviewData(c: expr) =
c.currentPost.subject = subject
c.currentPost.content = content
template writeToDb(c, cr, postId: expr) =
exec(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
c.userId, c.req.ip, subject, content, $c.threadId, postId)
template writeToDb(c, cr, setPostId: expr) =
let retID = insertID(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
c.userId, c.req.ip, subject, content, $c.threadId, "")
if setPostId:
c.postId = retID.int
proc edit(c: var TForumData, postId: int): bool =
checkLogin(c)
@ -360,7 +372,8 @@ proc reply(c: var TForumData): bool =
if c.isPreview:
setPreviewData(c)
else:
writeToDb(c, crCreate, "")
writeToDb(c, crCreate, true)
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
$c.threadId)
result = true
@ -375,7 +388,7 @@ proc newThread(c: var TForumData): bool =
else:
c.threadID = TryInsertID(db, query, c.req.params["subject"]).int
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
writeToDb(c, crCreate, "")
writeToDb(c, crCreate, false)
result = true
proc login(c: var TForumData, name, pass: string): bool =
@ -407,17 +420,18 @@ proc genActionMenu(c: var TForumData): string =
result = ""
var btns: seq[TStyledButton] = @[]
# TODO: Make this detection better?
if c.req.pathInfo notin ["/", "/login", "/register", "/dologin", "/doregister"]:
if c.req.pathInfo.normalizeUri notin noHomeBtn and not c.isThreadsList:
btns.add(("Thread List", c.req.makeUri("/", false)))
if c.loggedIn:
let hasReplyBtn = c.req.pathInfo != "/donewthread" and c.req.pathInfo != "/doreply"
if c.threadId >= 0 and hasReplyBtn:
let replyUrl = c.genThreadUrl("", "reply") & "#reply"
let replyUrl = c.genThreadUrl(action = "reply",
pageNum = $(ceil(c.totalPosts / postsPerPage).int)) & "#reply"
btns.add(("Reply", replyUrl))
btns.add(("New Thread", c.req.makeUri("/newthread", false)))
result = c.genButtons(btns)
proc getStats(c: var TForumData): TForumStats =
proc getStats(c: var TForumData, simple: bool): TForumStats =
const totalUsersQuery =
sql"select count(*) from person"
result.totalUsers = getValue(db, totalUsersQuery).parseInt
@ -427,19 +441,89 @@ proc getStats(c: var TForumData): TForumStats =
const totalThreadsQuery =
sql"select count(*) from thread"
result.totalThreads = getValue(db, totalThreadsQuery).parseInt
if not simple:
var newestMemberCreation = 0
result.activeUsers = @[]
const getUsersQuery =
sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person"
for row in fastRows(db, getUsersQuery):
let secs = if row[3] == "": 0 else: row[3].parseint
let lastOnlineSeconds = getTime() - TTime(secs)
if lastOnlineSeconds < (60 * 5): # 5 minutes
result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool))
if row[4].parseInt > newestMemberCreation:
result.newestMember = (row[1], row[0].parseInt, row[2].parseBool)
newestMemberCreation = row[4].parseInt
proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
result = ""
var
firstUrl = ""
prevUrl = ""
totalPages = 0
lastUrl = ""
nextUrl = ""
var newestMemberCreation = 0
result.activeUsers = @[]
const getUsersQuery =
sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person"
for row in fastRows(db, getUsersQuery):
let secs = if row[3] == "": 0 else: row[3].parseint
let lastOnlineSeconds = getTime() - TTime(secs)
if lastOnlineSeconds < (60 * 5): # 5 minutes
result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool))
if row[4].parseInt > newestMemberCreation:
result.newestMember = (row[1], row[0].parseInt, row[2].parseBool)
newestMemberCreation = row[4].parseInt
if c.isThreadsList:
firstUrl = c.req.makeUri("/")
prevUrl = c.req.makeUri(if c.pageNum == 1: "/" else: "/page/" & $(c.pageNum-1))
totalPages = ceil(stats.totalThreads / ThreadsPerPage).int
lastUrl = c.req.makeUri("/page/" & $(totalPages))
nextUrl = c.req.makeUri("/page/" & $(c.pageNum+1))
else:
firstUrl = c.req.makeUri("/t/" & $c.threadId)
if c.pageNum == 1:
prevUrl = firstUrl
else:
prevUrl = c.req.makeUri(firstUrl & "/" & $(c.pageNum-1))
totalPages = ceil(c.totalPosts / postsPerPage).int
lastUrl = c.req.makeUri(firstUrl & "/" & $(totalPages))
nextUrl = c.req.makeUri(firstUrl & "/" & $(c.pageNum+1))
if totalPages <= 1:
return ""
var firstTag = ""
var prevTag = ""
if c.pageNum == 1:
firstTag = span("First")
prevTag = span("Prev")
else:
firstTag = a(href=firstUrl, "First")
prevTag = a(href=prevUrl, "Prev")
result.add(htmlgen.`div`(class = "left",
firstTag,
prevTag))
# Right
var lastTag = ""
var nextTag = ""
if c.pageNum == totalPages:
lastTag = span("Last")
nextTag = span("Next")
else:
lastTag = a(href=lastUrl, "Last")
nextTag = a(href=nextUrl, "Next")
result.add(htmlgen.`div`(class = "right",
nextTag,
lastTag))
# Numbers
var pages = "" # Tags
for i in 1..totalPages:
if i == c.pageNum:
pages.add(span($(i)))
else:
var pageUrl = ""
if c.isThreadsList:
pageUrl = c.req.makeUri("/page/" & $(i))
else:
pageUrl = c.req.makeUri(firstUrl & "/" & $(i))
pages.add(a(href = pageUrl, $(i)))
result.add(htmlgen.`div`(class = "middle",
pages))
result = htmlgen.`div`(id = "pagenumbers", result)
include "forms.tmpl"
include "main.tmpl"
@ -455,27 +539,43 @@ template createTFD(): stmt =
init(c)
c.req = request
c.startTime = epochTime()
c.isThreadsList = false
c.pageNum = 1
if request.cookies.len > 0:
checkLoggedIn(c)
proc gatherData(c: var TForumData) =
if c.totalPosts > 0: return
# Gather some data.
const totalPostsQuery =
sql"select count(*) from post p, person u where u.id = p.author and p.thread = ?"
c.totalPosts = getValue(db, totalPostsQuery, $c.threadId).parseInt
get "/":
createTFD()
resp genMain(c, genThreadsList(c), true)
c.isThreadsList = true
var count = 0
resp genMain(c, genThreadsList(c, count))
get "/t/@threadid/?":
get "/t/@threadid/?@page?/?":
createTFD()
if @"page".len > 0:
parseInt(@"page", c.pageNum, 0..1000_000)
cond (c.pageNum > 0)
parseInt(@"threadid", c.threadId, -1..1000_000)
if (@"postid").len > 0:
parseInt(@"postid", c.postId, -1..1000_000)
var count = 0
cond validThreadId(c)
gatherData(c)
if (@"action").len > 0:
case @"action"
of "reply":
let subject = GetValue(db,
sql"select header from post where id = (select max(id) from post where thread = ?)",
$c.threadId).prependRe
body = genPostsList(c, $c.threadId)
echo(c.threadId)
body = genPostsList(c, $c.threadId, count)
cond count != 0
body.add genFormPost(c, "doreply", "Reply", subject, "", false)
of "edit":
cond c.postId != -1
@ -486,9 +586,22 @@ get "/t/@threadid/?":
body = genFormPost(c, "doedit", "Edit", header, content, true)
resp c.genMain(body)
else:
cond validThreadId(c)
incrementViews(c)
resp genMain(c, genPostsList(c, $c.threadId))
let posts = genPostsList(c, $c.threadId, count)
cond count != 0
resp genMain(c, posts)
get "/page/@page/?":
createTFD()
c.isThreadsList = true
cond (@"page" != "")
parseInt(@"page", c.pageNum, 0..1000_000)
cond (c.pageNum > 0)
var count = 0
let list = genThreadsList(c, count)
if count == 0:
pass()
resp genMain(c, list)
get "/login/?":
createTFD()
@ -504,12 +617,16 @@ get "/register/?":
resp genMain(c, genFormRegister(c))
template readIDs(): stmt =
# Retrieve the threadid and postid
# Retrieve the threadid, postid and pagenum
if (@"threadid").len > 0:
parseInt(@"threadid", c.threadId, -1..1000_000)
if (@"postid").len > 0:
parseInt(@"postid", c.postId, -1..1000_000)
proc getTotalPosts(c: var TForumData): int =
c.gatherData() # Get total post count
result = ceil(c.totalPosts / postsPerPage).int-1
template finishLogin(): stmt =
setCookie("sid", c.userpass, daysForward(7))
redirect(uri("/"))
@ -548,9 +665,12 @@ post "/doreply":
createTFD()
readIDs()
if reply(c):
redirect(c.genThreadUrl())
redirect(c.genThreadUrl(pageNum = $(c.getTotalPosts+1)) & "#" & $c.postId)
else:
body = genPostsList(c, $c.threadId)
var count = 0
if c.isPreview:
c.pageNum = c.getTotalPosts+1
body = genPostsList(c, $c.threadId, count)
handleError("doreply", "Reply", false)
post "/doedit":

View file

@ -1,6 +1,11 @@
#! stdtmpl
#proc genMain(c: var TForumData, content: string, mainPage = false): string =
#proc genMain(c: var TForumData, content: string): string =
# result = ""
# var stats: TForumStats
# if c.isThreadsList: stats = c.getStats(false)
# else:
# stats = c.getStats(true)
# end if
<!doctype html>
<html lang="en">
<head>
@ -35,12 +40,15 @@
$content
<span style="color:red">$c.errorMsg</span>
</div>
#if c.req.pathInfo.normalizeUri notin noPageNums:
${c.genPagenumNav(stats)}
#end if
<div id="topbar">
${c.genActionMenu}
</div>
#if mainPage:
${c.genListOnline}
#if c.isThreadsList:
${c.genListOnline(stats)}
#end if
</div>

View file

@ -3,4 +3,4 @@
--path:"$nimrod/packages/docutils"
--path:"$nimrod"
--path:"/home/dominik/code/nimrod/jester"
--path:"/home/dom/code/nimrod/jester"

View file

@ -240,6 +240,49 @@ div#replywrapper form {
padding: 8pt;
}
div#pagenumbers {
font-size: 11pt;
height: 21px;
margin: 5.9pt;
padding: 2pt;
padding-left: 4pt;
padding-right: 4pt;
border-top: 1px solid #9d9d9d;
border-bottom: 1px solid #9d9d9d;
background-color: #eee;
}
div#pagenumbers div.left {
float: left;
}
div#pagenumbers div.middle {
text-align: center;
}
div#pagenumbers div.middle a, div#pagenumbers div.middle span {
padding-right: 4pt;
}
div#pagenumbers div.middle span {
font-weight: bold;
}
div#pagenumbers div.left span, div#pagenumbers div.left a {
padding-right: 8pt;
}
div#pagenumbers div.right span, div#pagenumbers div.right a {
padding-left: 8pt;
}
div#pagenumbers div.right {
float: right;
}
/* For RST nimrod syntax highlighter */
span.DecNumber {color: blue}
span.BinNumber {color: blue}