Forum search added and some modifications done.
This commit is contained in:
parent
4f7396d059
commit
3f08e94e21
4 changed files with 299 additions and 20 deletions
95
forms.tmpl
95
forms.tmpl
|
|
@ -119,6 +119,7 @@
|
||||||
<div id="talk-thread">
|
<div id="talk-thread">
|
||||||
# for row in posts:
|
# for row in posts:
|
||||||
# inc(count)
|
# inc(count)
|
||||||
|
<a name="${%postId}"></a>
|
||||||
<div>
|
<div>
|
||||||
<div class="author">
|
<div class="author">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -240,3 +241,97 @@
|
||||||
<b>${stats.totalThreads}</b> threads | <b>${stats.totalPosts}</b> posts |
|
<b>${stats.totalThreads}</b> threads | <b>${stats.totalPosts}</b> posts |
|
||||||
newest member: <a href="$profileUrl">${stats.newestMember.nick}</a>
|
newest member: <a href="$profileUrl">${stats.newestMember.nick}</a>
|
||||||
#end proc
|
#end proc
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#proc genSearchResults(c: var TForumData,
|
||||||
|
# results: iterator: db_sqlite.TRow {.closure, tags: [FReadDB].},
|
||||||
|
# count: var int): string =
|
||||||
|
# const threadId = 0
|
||||||
|
# const threadName = 1
|
||||||
|
# const postId = 2
|
||||||
|
# const postHeader = 3
|
||||||
|
# const postContent = 4
|
||||||
|
# const userName = 5
|
||||||
|
# const postCreation = 6
|
||||||
|
# const postAuthor = 7
|
||||||
|
# const userEmail = 8
|
||||||
|
# const what = 9
|
||||||
|
# result = ""
|
||||||
|
# count = 0
|
||||||
|
# var whCount: array[bool, int]
|
||||||
|
<div id="talk-head">
|
||||||
|
<div class="info-post">
|
||||||
|
<div>
|
||||||
|
Search results for: <i style="color: #332299">${xmlEncode(c.search.replace(""","\""))}</i>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="talk-thread" class="searchResults">
|
||||||
|
# for row in results():
|
||||||
|
# inc(count)
|
||||||
|
# let isThread = %what == "0"
|
||||||
|
# inc(whCount[isThread])
|
||||||
|
# let postUrl = c.genThreadUrl(%postId,"",%threadId,"0")
|
||||||
|
# let threadUrl = c.genThreadUrl("","",%threadId)
|
||||||
|
# var headersDiffer = false
|
||||||
|
<div>
|
||||||
|
<div class="author">
|
||||||
|
<div>
|
||||||
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(%userName)
|
||||||
|
<div><a href="$profileUrl">${genGravatar(%userEmail, 40)}</a></div>
|
||||||
|
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(%userName)}</a></div>
|
||||||
|
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
||||||
|
<hr/><a href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
||||||
|
#elif c.isAdmin and c.currentPost.subject.len == 0:
|
||||||
|
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
||||||
|
#end if
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="topic">
|
||||||
|
<div>
|
||||||
|
#if %threadName != %postHeader and "Re: " & %threadName != %postHeader:
|
||||||
|
#headersDiffer = true
|
||||||
|
<h4>
|
||||||
|
<a href="${threadUrl}">
|
||||||
|
<span class="titleHeader">Thread:</span>
|
||||||
|
<span style="font-size: 1.2em">${%threadName}</span>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
#end if
|
||||||
|
#if not headersDiffer or %postHeader != "":
|
||||||
|
<h5>
|
||||||
|
<a href="${postUrl}">
|
||||||
|
<span class="titleHeader">Post:</span>
|
||||||
|
<span>${%postHeader}</span>
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
#end if
|
||||||
|
#if not isThread:
|
||||||
|
#try:
|
||||||
|
${(%postContent)}
|
||||||
|
#except EParseError:
|
||||||
|
# c.errorMsg = getCurrentExceptionMsg()
|
||||||
|
#end
|
||||||
|
#end if
|
||||||
|
<span class="date">${xmlEncode(%postCreation)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
# end for
|
||||||
|
</div>
|
||||||
|
# if c.pageNum > 1:
|
||||||
|
<form action="/search/${$(c.pageNum-1)}" method="post" class="searchNav">
|
||||||
|
<input type="hidden" name="q" value="${c.search}">
|
||||||
|
<input type="submit" value="Previous ${ThreadsPerPage} results">
|
||||||
|
</form>
|
||||||
|
# end if
|
||||||
|
# if whCount[true] == ThreadsPerPage or whCount[false] == ThreadsPerPage:
|
||||||
|
<form action="/search/${$(c.pageNum+1)}" method="post" class="searchNav">
|
||||||
|
<input type="hidden" name="q" value="${c.search}">
|
||||||
|
<input type="submit" value="Next ${ThreadsPerPage} results (if any)">
|
||||||
|
</form>
|
||||||
|
# end if
|
||||||
|
#end proc
|
||||||
|
#
|
||||||
|
|
|
||||||
126
forum.nim
126
forum.nim
|
|
@ -8,22 +8,23 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
|
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
|
||||||
rst, rstgen, captchas, scgi, jester, asyncdispatch, asyncnet, cache
|
rst, rstgen, captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils
|
||||||
from htmlgen import tr, th, td, span
|
from htmlgen import tr, th, td, span
|
||||||
|
|
||||||
const
|
const
|
||||||
unselectedThread = -1
|
unselectedThread = -1
|
||||||
transientThread = 0
|
transientThread = 0
|
||||||
|
|
||||||
ThreadsPerPage = 15
|
ThreadsPerPage = 2
|
||||||
PostsPerPage = 10
|
PostsPerPage = 3
|
||||||
|
MaxPagesFromCurrent = 2
|
||||||
noPageNums = ["/login", "/register", "/dologin", "/doregister", "/profile"]
|
noPageNums = ["/login", "/register", "/dologin", "/doregister", "/profile"]
|
||||||
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister", "/profile"]
|
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister", "/profile"]
|
||||||
|
|
||||||
type
|
type
|
||||||
TCrud = enum crCreate, crRead, crUpdate, crDelete
|
TCrud = enum crCreate, crRead, crUpdate, crDelete
|
||||||
|
|
||||||
TSession = object of TObject
|
TSession = object of RootObj
|
||||||
threadid: int
|
threadid: int
|
||||||
postid: int
|
postid: int
|
||||||
userName, userPass, email: string
|
userName, userPass, email: string
|
||||||
|
|
@ -42,6 +43,8 @@ type
|
||||||
isThreadsList: bool
|
isThreadsList: bool
|
||||||
pageNum: int
|
pageNum: int
|
||||||
totalPosts: int
|
totalPosts: int
|
||||||
|
search: string
|
||||||
|
noPagenumumNav: bool
|
||||||
|
|
||||||
TStyledButton = tuple[text: string, link: string]
|
TStyledButton = tuple[text: string, link: string]
|
||||||
|
|
||||||
|
|
@ -61,7 +64,8 @@ type
|
||||||
|
|
||||||
var
|
var
|
||||||
db: TDbConn
|
db: TDbConn
|
||||||
docConfig: PStringTable
|
docConfig: StringTableRef
|
||||||
|
isFTSAvailable: bool
|
||||||
|
|
||||||
proc init(c: var TForumData) =
|
proc init(c: var TForumData) =
|
||||||
c.userPass = ""
|
c.userPass = ""
|
||||||
|
|
@ -76,6 +80,8 @@ proc init(c: var TForumData) =
|
||||||
c.invalidField = ""
|
c.invalidField = ""
|
||||||
c.currentPost = (subject: "", content: "")
|
c.currentPost = (subject: "", content: "")
|
||||||
|
|
||||||
|
c.search = ""
|
||||||
|
|
||||||
proc loggedIn(c: TForumData): bool =
|
proc loggedIn(c: TForumData): bool =
|
||||||
result = c.userName.len > 0
|
result = c.userName.len > 0
|
||||||
|
|
||||||
|
|
@ -114,6 +120,8 @@ proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNu
|
||||||
result.add("?action=" & action)
|
result.add("?action=" & action)
|
||||||
if postId != "":
|
if postId != "":
|
||||||
result.add("&postid=" & postid)
|
result.add("&postid=" & postid)
|
||||||
|
elif postId != "":
|
||||||
|
result.add("/" & postId & "#" & postId)
|
||||||
result = c.req.makeUri(result, absolute = false)
|
result = c.req.makeUri(result, absolute = false)
|
||||||
|
|
||||||
proc FormSession(c: var TForumData, nextAction: string): string =
|
proc FormSession(c: var TForumData, nextAction: string): string =
|
||||||
|
|
@ -160,7 +168,7 @@ proc toInterval(diff: int64): TimeInterval =
|
||||||
months.int, years.int)
|
months.int, years.int)
|
||||||
|
|
||||||
proc formatTimestamp(t: int): string =
|
proc formatTimestamp(t: int): string =
|
||||||
let t2 = TTime(t)
|
let t2 = Time(t)
|
||||||
let now = getTime()
|
let now = getTime()
|
||||||
let diff = (now - t2).toInterval()
|
let diff = (now - t2).toInterval()
|
||||||
if diff.years > 0:
|
if diff.years > 0:
|
||||||
|
|
@ -186,7 +194,8 @@ proc getGravatarUrl(email: string, size = 80): string =
|
||||||
"&d=identicon")
|
"&d=identicon")
|
||||||
|
|
||||||
proc genGravatar(email: string, size: int = 80): string =
|
proc genGravatar(email: string, size: int = 80): string =
|
||||||
result = "<img src=\"$1\" />" % getGravatarUrl(email, size)
|
result = "<img width=\"$1\" height=\"$2\" src=\"$3\" />" %
|
||||||
|
[$size, $size, getGravatarUrl(email, size)]
|
||||||
|
|
||||||
proc randomSalt(): string =
|
proc randomSalt(): string =
|
||||||
result = ""
|
result = ""
|
||||||
|
|
@ -212,7 +221,7 @@ proc makeSalt(): string =
|
||||||
## Creates a salt using a cryptographically secure random number generator.
|
## Creates a salt using a cryptographically secure random number generator.
|
||||||
try:
|
try:
|
||||||
result = devRandomSalt()
|
result = devRandomSalt()
|
||||||
except EIO:
|
except IOError:
|
||||||
result = randomSalt()
|
result = randomSalt()
|
||||||
|
|
||||||
proc makePassword(password, salt: string): string =
|
proc makePassword(password, salt: string): string =
|
||||||
|
|
@ -387,6 +396,8 @@ template setPreviewData(c: expr) {.immediate, dirty.} =
|
||||||
template writeToDb(c, cr, setPostId: expr) =
|
template writeToDb(c, cr, setPostId: expr) =
|
||||||
let retID = insertID(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
|
let retID = insertID(db, crud(cr, "post", "author", "ip", "header", "content", "thread"),
|
||||||
c.userId, c.req.ip, subject, content, $c.threadId, "")
|
c.userId, c.req.ip, subject, content, $c.threadId, "")
|
||||||
|
discard tryExec(db, crud(cr, "post_fts", "id", "header", "content"),
|
||||||
|
retID.int, subject, content)
|
||||||
if setPostId:
|
if setPostId:
|
||||||
c.postId = retID.int
|
c.postId = retID.int
|
||||||
|
|
||||||
|
|
@ -399,17 +410,21 @@ proc edit(c: var TForumData, postId: int): bool =
|
||||||
checkOwnership(c, $postId)
|
checkOwnership(c, $postId)
|
||||||
if not tryExec(db, crud(crDelete, "post"), $postId):
|
if not tryExec(db, crud(crDelete, "post"), $postId):
|
||||||
return setError(c, "", "database error")
|
return setError(c, "", "database error")
|
||||||
|
discard tryExec(db, crud(crDelete, "post_fts"), $postId)
|
||||||
# delete corresponding thread:
|
# delete corresponding thread:
|
||||||
if execAffectedRows(db,
|
if execAffectedRows(db,
|
||||||
sql"delete from thread where id not in (select thread from post)") > 0:
|
sql"delete from thread where id not in (select thread from post)") > 0:
|
||||||
# whole thread has been deleted, so:
|
# whole thread has been deleted, so:
|
||||||
c.threadId = unselectedThread
|
c.threadId = unselectedThread
|
||||||
|
discard tryExec(db, sql"delete from thread_fts where id not in (select thread from post)")
|
||||||
result = true
|
result = true
|
||||||
else:
|
else:
|
||||||
checkOwnership(c, $postId)
|
checkOwnership(c, $postId)
|
||||||
retrPost(c)
|
retrPost(c)
|
||||||
exec(db, crud(crUpdate, "post", "header", "content"),
|
exec(db, crud(crUpdate, "post", "header", "content"),
|
||||||
subject, content, $postId)
|
subject, content, $postId)
|
||||||
|
exec(db, crud(crUpdate, "post_fts", "header", "content"),
|
||||||
|
subject, content, $postId)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc reply(c: var TForumData): bool =
|
proc reply(c: var TForumData): bool =
|
||||||
|
|
@ -434,7 +449,11 @@ proc newThread(c: var TForumData): bool =
|
||||||
else:
|
else:
|
||||||
c.threadID = tryInsertID(db, query, c.req.params["subject"]).int
|
c.threadID = tryInsertID(db, query, c.req.params["subject"]).int
|
||||||
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
|
if c.threadID < 0: return setError(c, "subject", "Subject already exists")
|
||||||
|
discard tryExec(db, crud(crCreate, "thread_fts", "id", "name"),
|
||||||
|
c.threadID, c.req.params["subject"])
|
||||||
writeToDb(c, crCreate, false)
|
writeToDb(c, crCreate, false)
|
||||||
|
discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')")
|
||||||
|
discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')")
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc login(c: var TForumData, name, pass: string): bool =
|
proc login(c: var TForumData, name, pass: string): bool =
|
||||||
|
|
@ -500,7 +519,7 @@ proc getStats(c: var TForumData, simple: bool): TForumStats =
|
||||||
sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person"
|
sql"select id, name, admin, strftime('%s', lastOnline), strftime('%s', creation) from person"
|
||||||
for row in fastRows(db, getUsersQuery):
|
for row in fastRows(db, getUsersQuery):
|
||||||
let secs = if row[3] == "": 0 else: row[3].parseint
|
let secs = if row[3] == "": 0 else: row[3].parseint
|
||||||
let lastOnlineSeconds = getTime() - TTime(secs)
|
let lastOnlineSeconds = getTime() - Time(secs)
|
||||||
if lastOnlineSeconds < (60 * 5): # 5 minutes
|
if lastOnlineSeconds < (60 * 5): # 5 minutes
|
||||||
result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool))
|
result.activeUsers.add((row[1], row[0].parseInt, row[2].parseBool))
|
||||||
if row[4].parseInt > newestMemberCreation:
|
if row[4].parseInt > newestMemberCreation:
|
||||||
|
|
@ -543,12 +562,17 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
||||||
else:
|
else:
|
||||||
firstTag = htmlgen.a(href=firstUrl, "<<")
|
firstTag = htmlgen.a(href=firstUrl, "<<")
|
||||||
prevTag = htmlgen.a(href=prevUrl, "<••")
|
prevTag = htmlgen.a(href=prevUrl, "<••")
|
||||||
|
prevTag.add(htmlgen.link(rel="previous", href=prevUrl))
|
||||||
result.add(firstTag)
|
result.add(firstTag)
|
||||||
result.add(prevTag)
|
result.add(prevTag)
|
||||||
|
|
||||||
# Numbers
|
# Numbers
|
||||||
var pages = "" # Tags
|
var pages = "" # Tags
|
||||||
for i in 1..totalPages:
|
# cutting numbers to the left and to the right tp MaxPagesFromCurrent
|
||||||
|
let firstToShow = max(1, c.pageNum - MaxPagesFromCurrent)
|
||||||
|
let lastToShow = min(totalPages, c.pageNum + MaxPagesFromCurrent)
|
||||||
|
if firstToShow > 1: pages.add(span("..."))
|
||||||
|
for i in firstToShow .. lastToShow:
|
||||||
if i == c.pageNum:
|
if i == c.pageNum:
|
||||||
pages.add(span($(i)))
|
pages.add(span($(i)))
|
||||||
else:
|
else:
|
||||||
|
|
@ -559,6 +583,16 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
||||||
pageUrl = c.req.makeUri(firstUrl & "/" & $(i))
|
pageUrl = c.req.makeUri(firstUrl & "/" & $(i))
|
||||||
|
|
||||||
pages.add(htmlgen.a(href = pageUrl, $(i)))
|
pages.add(htmlgen.a(href = pageUrl, $(i)))
|
||||||
|
if lastToShow < totalPages: pages.add(span("..."))
|
||||||
|
# a number input for quick jump to a page
|
||||||
|
pages.add(htmlgen.form(
|
||||||
|
action = c.req.makeUri(if c.isThreadsList: "/page" else: firstUrl),
|
||||||
|
class = "pagenumJump",
|
||||||
|
when false: htmlgen.input(`type` = "number", name = "page", value = $c.pageNum, max = $totalPages)
|
||||||
|
else: "<input type=\"number\" name=\"page\" value=\"$1\" min=\"1\" max=\"$2\" />" %
|
||||||
|
[$c.pageNum, $totalPages],
|
||||||
|
htmlgen.input(`type` = "submit", value = "►", title = "Go to the page", class = "jump") ))
|
||||||
|
# max attribute for inputs not supported in htmlgen
|
||||||
result.add(pages)
|
result.add(pages)
|
||||||
|
|
||||||
# Right
|
# Right
|
||||||
|
|
@ -570,6 +604,7 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
||||||
else:
|
else:
|
||||||
lastTag = htmlgen.a(href=lastUrl, ">>")
|
lastTag = htmlgen.a(href=lastUrl, ">>")
|
||||||
nextTag = htmlgen.a(href=nextUrl, "••>")
|
nextTag = htmlgen.a(href=nextUrl, "••>")
|
||||||
|
nextTag.add(htmlgen.link(rel="next",href=nextUrl))
|
||||||
result.add(nextTag)
|
result.add(nextTag)
|
||||||
result.add(lastTag)
|
result.add(lastTag)
|
||||||
|
|
||||||
|
|
@ -639,7 +674,7 @@ proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
|
||||||
proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
||||||
result = ""
|
result = ""
|
||||||
result.add(htmlgen.`div`(id = "avatar", genGravatar(ui.email, 250)))
|
result.add(htmlgen.`div`(id = "avatar", genGravatar(ui.email, 250)))
|
||||||
let t2 = if ui.lastOnline != -1: getGMTime(TTime(ui.lastOnline))
|
let t2 = if ui.lastOnline != -1: getGMTime(Time(ui.lastOnline))
|
||||||
else: getGMTime(getTime())
|
else: getGMTime(getTime())
|
||||||
|
|
||||||
result.add(htmlgen.`div`(id = "info",
|
result.add(htmlgen.`div`(id = "info",
|
||||||
|
|
@ -708,14 +743,19 @@ routes:
|
||||||
createTFD()
|
createTFD()
|
||||||
resp genPostsRSS(c), "application/atom+xml"
|
resp genPostsRSS(c), "application/atom+xml"
|
||||||
|
|
||||||
get "/t/@threadid/?@page?/?":
|
get "/t/@threadid/?@page?/?@postid?/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
parseInt(@"threadid", c.threadId, -1..1000_000)
|
parseInt(@"threadid", c.threadId, -1..1000_000)
|
||||||
if @"page".len > 0:
|
|
||||||
parseInt(@"page", c.pageNum, 0..1000_000)
|
|
||||||
cond (c.pageNum > 0)
|
|
||||||
if (@"postid").len > 0:
|
if (@"postid").len > 0:
|
||||||
parseInt(@"postid", c.postId, -1..1000_000)
|
parseInt(@"postid", c.postId, -1..1000_000)
|
||||||
|
if @"page".len > 0:
|
||||||
|
parseInt(@"page", c.pageNum, 0..1000_000)
|
||||||
|
# for direct links to posts (with no thread id passed); used in search results
|
||||||
|
if c.pageNum == 0 and c.postId > 0:
|
||||||
|
const sqlGetPostsUnder = sql"select count(*) from post where thread = ? and id < ?"
|
||||||
|
let postsUnder = db.getValue(sqlGetPostsUnder, c.threadId, c.postId).parseInt
|
||||||
|
c.pageNum = (postsUnder div PostsPerPage) + 1
|
||||||
|
cond (c.pageNum > 0)
|
||||||
var count = 0
|
var count = 0
|
||||||
var pSubject = getThreadTitle(c.threadid, c.pageNum)
|
var pSubject = getThreadTitle(c.threadid, c.pageNum)
|
||||||
cond validThreadId(c)
|
cond validThreadId(c)
|
||||||
|
|
@ -739,6 +779,7 @@ routes:
|
||||||
let content = ||row[1]
|
let content = ||row[1]
|
||||||
body = genFormPost(c, "doedit", "Edit", header, content, true)
|
body = genFormPost(c, "doedit", "Edit", header, content, true)
|
||||||
title = "Editing post"
|
title = "Editing post"
|
||||||
|
else: discard
|
||||||
resp c.genMain(body, title & " - Nimrod Forum")
|
resp c.genMain(body, title & " - Nimrod Forum")
|
||||||
else:
|
else:
|
||||||
incrementViews(c)
|
incrementViews(c)
|
||||||
|
|
@ -746,7 +787,7 @@ routes:
|
||||||
cond count != 0
|
cond count != 0
|
||||||
resp genMain(c, posts, pSubject & " - Nimrod Forum")
|
resp genMain(c, posts, pSubject & " - Nimrod Forum")
|
||||||
|
|
||||||
get "/page/@page/?":
|
get "/page/?@page?/?":
|
||||||
createTFD()
|
createTFD()
|
||||||
c.isThreadsList = true
|
c.isThreadsList = true
|
||||||
cond (@"page" != "")
|
cond (@"page" != "")
|
||||||
|
|
@ -799,7 +840,8 @@ routes:
|
||||||
body.add genPostPreview(c, @"subject", @"content",
|
body.add genPostPreview(c, @"subject", @"content",
|
||||||
c.userName, $getGMTime(getTime()))
|
c.userName, $getGMTime(getTime()))
|
||||||
body.add genFormPost(c, action, topText, reuseText, reuseText, isEdit)
|
body.add genFormPost(c, action, topText, reuseText, reuseText, isEdit)
|
||||||
resp genMain(c, body(), "Nimrod Forum - Error")
|
resp genMain(c, body(), "Nimrod Forum - " &
|
||||||
|
(if c.isPreview: "Preview" else: "Error"))
|
||||||
|
|
||||||
post "/dologin":
|
post "/dologin":
|
||||||
createTFD()
|
createTFD()
|
||||||
|
|
@ -848,7 +890,7 @@ routes:
|
||||||
createTFD()
|
createTFD()
|
||||||
readIDs()
|
readIDs()
|
||||||
if edit(c, c.postId):
|
if edit(c, c.postId):
|
||||||
redirect(c.genThreadUrl())
|
redirect(c.genThreadUrl(pageNum = "0", postId = $c.postId))
|
||||||
else:
|
else:
|
||||||
body = ""
|
body = ""
|
||||||
handleError("doedit", "Edit", true)
|
handleError("doedit", "Edit", true)
|
||||||
|
|
@ -863,11 +905,59 @@ routes:
|
||||||
createTFD()
|
createTFD()
|
||||||
resp genMain(c, rstToHtml(licenseRst), "Content license - Nimrod Forum")
|
resp genMain(c, rstToHtml(licenseRst), "Content license - Nimrod Forum")
|
||||||
|
|
||||||
|
post "/search/?@page?":
|
||||||
|
if not isFTSAvailable:
|
||||||
|
redirect(uri("/"))
|
||||||
|
createTFD()
|
||||||
|
c.isThreadsList = true
|
||||||
|
c.noPagenumumNav = true
|
||||||
|
var count = 0
|
||||||
|
var q = @"q"
|
||||||
|
for i in 0 .. q.len-1:
|
||||||
|
if q[i].int < 32: q[i] = ' '
|
||||||
|
elif q[i] == '\'': q[i] = '"'
|
||||||
|
c.search = q.replace("\"",""");
|
||||||
|
if @"page".len > 0:
|
||||||
|
parseInt(@"page", c.pageNum, 0..1000_000)
|
||||||
|
cond (c.pageNum > 0)
|
||||||
|
iterator searchResults(): db_sqlite.TRow {.closure, tags: [FReadDB].} =
|
||||||
|
const queryFT = "fts.sql".slurp.sql
|
||||||
|
for rowFT in fastRows(db, queryFT,
|
||||||
|
[q,q,$ThreadsPerPage,$c.pageNum,$ThreadsPerPage,q,
|
||||||
|
q,q,$ThreadsPerPage,$c.pageNum,$ThreadsPerPage,q]):
|
||||||
|
yield rowFT
|
||||||
|
resp genMain(c, genSearchResults(c, searchResults, count),
|
||||||
|
additionalHeaders = genRSSHeaders(c), showRssLinks = true)
|
||||||
|
|
||||||
|
# tries first to read html, then to read rst, convert ot html, cache and return
|
||||||
|
template textPage(path: string): stmt =
|
||||||
|
createTFD()
|
||||||
|
#c.isThreadsList = true
|
||||||
|
var page = ""
|
||||||
|
if existsFile(path):
|
||||||
|
page = readFile(path)
|
||||||
|
else:
|
||||||
|
let basePath =
|
||||||
|
if path[path.high] == '/': path & "index"
|
||||||
|
elif path.endsWith(".html"): path[-5 .. -1]
|
||||||
|
else: path
|
||||||
|
if existsFile(basePath & ".html"):
|
||||||
|
page = readFile(basePath & ".html")
|
||||||
|
elif existsFile(basePath & ".rst"):
|
||||||
|
page = readFile(basePath & ".rst").rstToHtml
|
||||||
|
writeFile(basePath & ".html", page)
|
||||||
|
resp genMain(c, page)
|
||||||
|
get "/search-help":
|
||||||
|
textPage "static/search-help"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
docConfig = rstgen.defaultConfig()
|
docConfig = rstgen.defaultConfig()
|
||||||
math.randomize()
|
math.randomize()
|
||||||
db = open(connection="nimforum.db", user="postgres", password="",
|
db = open(connection="nimforum.db", user="postgres", password="",
|
||||||
database="nimforum")
|
database="nimforum")
|
||||||
|
isFTSAvailable = db.getValue(sql"select 1 from post_fts limit 1")=="1"
|
||||||
var http = true
|
var http = true
|
||||||
if paramCount() > 0:
|
if paramCount() > 0:
|
||||||
if paramStr(1) == "scgi":
|
if paramStr(1) == "scgi":
|
||||||
|
|
|
||||||
17
main.tmpl
17
main.tmpl
|
|
@ -59,9 +59,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
#if not c.noPagenumumNav:
|
||||||
<div id="talk-nav">
|
<div id="talk-nav">
|
||||||
${genPagenumNav(c, stats)}
|
${genPagenumNav(c, stats)}
|
||||||
</div>
|
</div>
|
||||||
|
#end if
|
||||||
#elif hasReplyBtn(c):
|
#elif hasReplyBtn(c):
|
||||||
<div id="talk-info">
|
<div id="talk-info">
|
||||||
<div class="info-post">
|
<div class="info-post">
|
||||||
|
|
@ -82,8 +84,19 @@
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<div class="title">Search</div>
|
<div class="title">Search</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<input type="text" />
|
#if isFTSAvailable:
|
||||||
<a href="#" class="button search">Search</a>
|
<a href="/search-help" target="_blank" class="searchHelp">?</a>
|
||||||
|
<form method="post" action="/search" class="searchForm">
|
||||||
|
<input type="text" name="q" maxlength="255" value="${c.search}" title="Search this forum" />
|
||||||
|
<input type="submit" value="Search" class="button search">
|
||||||
|
</form>
|
||||||
|
#else:
|
||||||
|
<form method="get" action="http://www.google.com/search" target="_blank" class="searchForm">
|
||||||
|
<input type="text" name="q" maxlength="255" value="${c.search}" title="Search this forum" />
|
||||||
|
<input type="submit" value="Search" class="button search">
|
||||||
|
<input type="hidden" name="sitesearch" value="http://forum.nimrod-lang.org" />
|
||||||
|
</form>
|
||||||
|
#end if
|
||||||
</div>
|
</div>
|
||||||
#if c.loggedIn:
|
#if c.loggedIn:
|
||||||
<div class="title">Your account</div>
|
<div class="title">Your account</div>
|
||||||
|
|
|
||||||
|
|
@ -475,3 +475,84 @@ article#content form
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* highlighting current post */
|
||||||
|
|
||||||
|
.post.current {
|
||||||
|
background-color: #fefff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* full-text search */
|
||||||
|
|
||||||
|
.searchResults h4 b,
|
||||||
|
.searchResults h5 b {
|
||||||
|
border-bottom: 1px dotted #ffffff;
|
||||||
|
}
|
||||||
|
.titleHeader {
|
||||||
|
margin-right: 1em;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForm {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-right: 1em;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchHelp {
|
||||||
|
color: #000000 !important;
|
||||||
|
float: right;
|
||||||
|
font-size: 11px;
|
||||||
|
left: -17px;
|
||||||
|
top: 3px;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: #FFFF00 1px 1px 2px;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk-thread.searchResults > div > div > div {
|
||||||
|
margin: 15px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.searchNav {
|
||||||
|
display: inline;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchNav input {
|
||||||
|
background: #858C97;
|
||||||
|
color: #000000;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* page numbers form */
|
||||||
|
|
||||||
|
.pagenumJump {
|
||||||
|
display: inline;
|
||||||
|
border-width: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagenumJump input {
|
||||||
|
margin: 0 .2em;
|
||||||
|
width: 3.2em;
|
||||||
|
background: #d0d6e3;
|
||||||
|
padding: 1px 2px;
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagenumJump .jump {
|
||||||
|
border-width: 0px;
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
width: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue