Implements thread list in Karax and backend.
This commit is contained in:
parent
2efd694660
commit
8910e55ad1
7 changed files with 276 additions and 57 deletions
66
forms.tmpl
66
forms.tmpl
|
|
@ -1,6 +1,6 @@
|
||||||
#? stdtmpl | standard
|
#? stdtmpl | standard
|
||||||
#
|
#
|
||||||
#template `%`(idx: untyped): untyped =
|
#template `!`(idx: untyped): untyped =
|
||||||
# row[idx]
|
# row[idx]
|
||||||
#end template
|
#end template
|
||||||
#
|
#
|
||||||
|
|
@ -47,15 +47,15 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
<div>
|
<div>
|
||||||
<a href="${c.genThreadUrl(threadid = %threadid)}"
|
<a href="${c.genThreadUrl(threadid = !threadid)}"
|
||||||
title="${xmlEncode(%name)}">${xmlEncode(%name)}</a>
|
title="${xmlEncode(!name)}">${xmlEncode(!name)}</a>
|
||||||
${genPagenumLocalNav(c, (%threadid).parseInt)}
|
${genPagenumLocalNav(c, (!threadid).parseInt)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
#let users = getAllRows(db,
|
#let users = getAllRows(db,
|
||||||
# sql("select distinct name, email from person where id in " &
|
# sql("select distinct name, email from person where id in " &
|
||||||
# "(select author from post where thread = ?)"), %threadId)
|
# "(select author from post where thread = ?)"), !threadId)
|
||||||
<div class="users">
|
<div class="users">
|
||||||
<div>
|
<div>
|
||||||
#for i in 0 .. min(6, users.len-1):
|
#for i in 0 .. min(6, users.len-1):
|
||||||
|
|
@ -66,19 +66,19 @@
|
||||||
|
|
||||||
#let latestReplyAuthor = getValue(db, sql("select name from person where id = " &
|
#let latestReplyAuthor = getValue(db, sql("select name from person where id = " &
|
||||||
# "(select author from post where id = " &
|
# "(select author from post where id = " &
|
||||||
# "(select max(id) from post where thread = ?))"), %threadId)
|
# "(select max(id) from post where thread = ?))"), !threadId)
|
||||||
|
|
||||||
#let replyProfileUrl = c.req.makeUri("profile/", false) &
|
#let replyProfileUrl = c.req.makeUri("profile/", false) &
|
||||||
# xmlEncode(latestReplyAuthor)
|
# xmlEncode(latestReplyAuthor)
|
||||||
|
|
||||||
# let posts = getValue(db, sql"select count(*) from post where thread = ?", %threadId)
|
# let posts = getValue(db, sql"select count(*) from post where thread = ?", !threadId)
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div><div title="Views">${xmlEncode(%views)}</div></div>
|
<div><div title="Views">${xmlEncode(!views)}</div></div>
|
||||||
<div><div title="Posts">$posts</div></div>
|
<div><div title="Posts">$posts</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
#let latestReplyDate = getValue(db, sql("SELECT strftime('%s', " &
|
#let latestReplyDate = getValue(db, sql("SELECT strftime('%s', " &
|
||||||
# "(select creation from post where id = (select max(id) from post where thread = ?)))"), %threadId)
|
# "(select creation from post where id = (select max(id) from post where thread = ?)))"), !threadId)
|
||||||
#let timeStr = formatTimestamp(latestReplyDate.parseInt())
|
#let timeStr = formatTimestamp(latestReplyDate.parseInt())
|
||||||
<div class="activity">
|
<div class="activity">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -150,28 +150,28 @@
|
||||||
<div id="talk-thread">
|
<div id="talk-thread">
|
||||||
# for row in posts:
|
# for row in posts:
|
||||||
# inc(count)
|
# inc(count)
|
||||||
<a name="${%postId}"></a>
|
<a name="${!postId}"></a>
|
||||||
<div id="${%postId}">
|
<div id="${!postId}">
|
||||||
<div class="author">
|
<div class="author">
|
||||||
<div>
|
<div>
|
||||||
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(%userName)
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(!userName)
|
||||||
<div class="avatar">${genGravatar(%userEmail)}</div>
|
<div class="avatar">${genGravatar(!userEmail)}</div>
|
||||||
<a class="name" href="$profileUrl">${xmlEncode(%userName)}</a>
|
<a class="name" href="$profileUrl">${xmlEncode(!userName)}</a>
|
||||||
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
#if c.userId == !postAuthor and c.currentPost.subject.len == 0:
|
||||||
<hr/><a href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
<hr/><a href="${c.genThreadUrl(!postId, "edit")}">Edit post</a>
|
||||||
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
||||||
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit")}">Edit post</a>
|
<hr/><a style="color: red;" href="${c.genThreadUrl(!postId, "edit")}">Edit post</a>
|
||||||
#end if
|
#end if
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
<div>
|
<div>
|
||||||
#try:
|
#try:
|
||||||
${(%postContent).rstToHtml}
|
${(!postContent).rstToHtml}
|
||||||
#except EParseError:
|
#except EParseError:
|
||||||
# c.errorMsg = getCurrentExceptionMsg()
|
# c.errorMsg = getCurrentExceptionMsg()
|
||||||
#end
|
#end
|
||||||
<span class="date"><a href="${c.genThreadUrl(%postId, "", $threadId, $(c.pageNum))}">${xmlEncode(%postCreation)}</a></span>
|
<span class="date"><a href="${c.genThreadUrl(!postId, "", $threadId, $(c.pageNum))}">${xmlEncode(!postCreation)}</a></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -421,43 +421,43 @@
|
||||||
<div id="talk-thread" class="searchResults">
|
<div id="talk-thread" class="searchResults">
|
||||||
# for row in results():
|
# for row in results():
|
||||||
# inc(count)
|
# inc(count)
|
||||||
# let isThread = %what == "0"
|
# let isThread = !what == "0"
|
||||||
# inc(whCount[isThread])
|
# inc(whCount[isThread])
|
||||||
# let postUrl = c.genThreadUrl(%postId,"",%threadId,"")
|
# let postUrl = c.genThreadUrl(!postId,"",!threadId,"")
|
||||||
# let threadUrl = c.genThreadUrl("","",%threadId)
|
# let threadUrl = c.genThreadUrl("","",!threadId)
|
||||||
# var headersDiffer = false
|
# var headersDiffer = false
|
||||||
<div>
|
<div>
|
||||||
<div class="author">
|
<div class="author">
|
||||||
<div>
|
<div>
|
||||||
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(%userName)
|
#let profileUrl = c.req.makeUri("profile/", false) & xmlEncode(!userName)
|
||||||
<div><a href="$profileUrl">${genGravatar(%userEmail, 40)}</a></div>
|
<div><a href="$profileUrl">${genGravatar(!userEmail, 40)}</a></div>
|
||||||
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(%userName)}</a></div>
|
<div style="padding: 8px 0"><a href="$profileUrl">${xmlEncode(!userName)}</a></div>
|
||||||
#if c.userId == %postAuthor and c.currentPost.subject.len == 0:
|
#if c.userId == !postAuthor and c.currentPost.subject.len == 0:
|
||||||
<hr/><a href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
<hr/><a href="${c.genThreadUrl(!postId, "edit", !threadId)}">Edit post</a>
|
||||||
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
#elif c.rank >= Moderator and c.currentPost.subject.len == 0:
|
||||||
<hr/><a style="color: red;" href="${c.genThreadUrl(%postId, "edit", %threadId)}">Edit post</a>
|
<hr/><a style="color: red;" href="${c.genThreadUrl(!postId, "edit", !threadId)}">Edit post</a>
|
||||||
#end if
|
#end if
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
<div>
|
<div>
|
||||||
#if %postHeader != "":
|
#if !postHeader != "":
|
||||||
<div class="postTitle">
|
<div class="postTitle">
|
||||||
<span class="titleHeader">Post:</span>
|
<span class="titleHeader">Post:</span>
|
||||||
<a href="${postUrl}">
|
<a href="${postUrl}">
|
||||||
<span>${%postHeader}</span>
|
<span>${!postHeader}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
#end if
|
#end if
|
||||||
#if not isThread:
|
#if not isThread:
|
||||||
#try:
|
#try:
|
||||||
${(%postContent).rstToHtml}
|
${(!postContent).rstToHtml}
|
||||||
#except EParseError:
|
#except EParseError:
|
||||||
# c.errorMsg = getCurrentExceptionMsg()
|
# c.errorMsg = getCurrentExceptionMsg()
|
||||||
${xmlEncode(%postContent)}
|
${xmlEncode(!postContent)}
|
||||||
#end
|
#end
|
||||||
#end if
|
#end if
|
||||||
<span class="date">${xmlEncode(%postCreation)}</span>
|
<span class="date">${xmlEncode(!postCreation)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
64
forum.nim
64
forum.nim
|
|
@ -9,7 +9,9 @@
|
||||||
import
|
import
|
||||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite,
|
os, strutils, times, md5, strtabs, cgi, math, db_sqlite,
|
||||||
scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||||
parseutils, utils, random, rst, ranks, recaptcha
|
parseutils, utils, random, rst, ranks, recaptcha, json
|
||||||
|
|
||||||
|
import redesign/threadlist except User
|
||||||
|
|
||||||
when not defined(windows):
|
when not defined(windows):
|
||||||
import bcrypt # TODO
|
import bcrypt # TODO
|
||||||
|
|
@ -1024,6 +1026,66 @@ routes:
|
||||||
additionalHeaders = genRSSHeaders(c), showRssLinks = true)
|
additionalHeaders = genRSSHeaders(c), showRssLinks = true)
|
||||||
resp data
|
resp data
|
||||||
|
|
||||||
|
get "/karax.html":
|
||||||
|
resp readFile("redesign/karax.html")
|
||||||
|
get "/nimforum.css":
|
||||||
|
resp readFile("redesign/nimforum.css"), "text/css"
|
||||||
|
get "/nimcache/forum.js":
|
||||||
|
resp readFile("redesign/nimcache/forum.js"), "application/javascript"
|
||||||
|
get "/images/crown.png":
|
||||||
|
resp readFile("redesign/images/crown.png"), "image/png"
|
||||||
|
|
||||||
|
get "/threads.json":
|
||||||
|
var
|
||||||
|
start = 0
|
||||||
|
count = 30
|
||||||
|
parseInt(@"start", start, 0..1_000_000)
|
||||||
|
parseInt(@"count", start, 0..1_000_000)
|
||||||
|
|
||||||
|
const threadsQuery =
|
||||||
|
sql"""select id, name, views, strftime('%s', modified) from thread
|
||||||
|
order by modified desc limit ?, ?;"""
|
||||||
|
const postsQuery =
|
||||||
|
sql"""select count(*), strftime('%s', creation) from post
|
||||||
|
where thread = ?
|
||||||
|
order by creation asc limit 1;"""
|
||||||
|
const usersListQuery =
|
||||||
|
sql"""select distinct name, email, strftime('%s',lastOnline)
|
||||||
|
from person where id in
|
||||||
|
(select author from post where thread = ?);"""
|
||||||
|
|
||||||
|
let thrCount = getValue(db, sql"select count(*) from thread;").parseInt()
|
||||||
|
let moreCount = max(0, thrCount - (start + count))
|
||||||
|
|
||||||
|
var list = ThreadList(threads: @[], lastVisit: 0, moreCount: moreCount)
|
||||||
|
for data in getAllRows(db, threadsQuery, start, count):
|
||||||
|
let posts = getRow(db, postsQuery, data[0])
|
||||||
|
|
||||||
|
var thread = Thread(
|
||||||
|
id: data[0].parseInt,
|
||||||
|
topic: data[1],
|
||||||
|
category: Category(id: "", color: "#ff0000"), # TODO
|
||||||
|
users: @[],
|
||||||
|
replies: posts[0].parseInt,
|
||||||
|
views: data[2].parseInt,
|
||||||
|
activity: data[3].parseInt,
|
||||||
|
creation: posts[1].parseInt,
|
||||||
|
isLocked: false,
|
||||||
|
isSolved: false # TODO: ^ and this. Add a field to `post` to identify.
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gather the users list.
|
||||||
|
for user in getAllRows(db, usersListQuery, thread.id):
|
||||||
|
let isOnline = getTime().toUnix() - user[2].parseInt > (60*5)
|
||||||
|
thread.users.add(threadlist.User(
|
||||||
|
name: user[0],
|
||||||
|
avatarUrl: user[1].getGravatarUrl(),
|
||||||
|
isOnline: isOnline
|
||||||
|
))
|
||||||
|
list.threads.add(thread)
|
||||||
|
|
||||||
|
resp $(%list), "application/json"
|
||||||
|
|
||||||
get "/threadActivity.xml":
|
get "/threadActivity.xml":
|
||||||
createTFD()
|
createTFD()
|
||||||
c.isThreadsList = true
|
c.isThreadsList = true
|
||||||
|
|
|
||||||
40
main.tmpl
40
main.tmpl
|
|
@ -207,20 +207,20 @@
|
||||||
<updated>${recent}</updated>
|
<updated>${recent}</updated>
|
||||||
# for row in rows(db, query, 10):
|
# for row in rows(db, query, 10):
|
||||||
<entry>
|
<entry>
|
||||||
<title>${xmlEncode(%name)}</title>
|
<title>${xmlEncode(!name)}</title>
|
||||||
<id>urn:entry:${%threadid}</id>
|
<id>urn:entry:${!threadid}</id>
|
||||||
# let url = c.genThreadUrl(threadid = %threadid,
|
# let url = c.genThreadUrl(threadid = !threadid,
|
||||||
# pageNum = $(ceil(parseInt(%postCount) / PostsPerPage).int)) &
|
# pageNum = $(ceil(parseInt(!postCount) / PostsPerPage).int)) &
|
||||||
# "#" & %postId
|
# "#" & !postId
|
||||||
<link rel="alternate" type="text/html"
|
<link rel="alternate" type="text/html"
|
||||||
href="${c.req.makeUri(url)}"/>
|
href="${c.req.makeUri(url)}"/>
|
||||||
<published>${%threadDate}</published>
|
<published>${!threadDate}</published>
|
||||||
<updated>${%threadDate}</updated>
|
<updated>${!threadDate}</updated>
|
||||||
<author><name>${xmlEncode(%postAuthor)}</name></author>
|
<author><name>${xmlEncode(!postAuthor)}</name></author>
|
||||||
<content type="html"
|
<content type="html"
|
||||||
>Posts ${%postCount}, ${xmlEncode(%postAuthor)} said:
|
>Posts ${!postCount}, ${xmlEncode(!postAuthor)} said:
|
||||||
<p>
|
<p>
|
||||||
${xmlEncode(rstToHtml(%postContent))}</content>
|
${xmlEncode(rstToHtml(!postContent))}</content>
|
||||||
</entry>
|
</entry>
|
||||||
# end for
|
# end for
|
||||||
</feed>
|
</feed>
|
||||||
|
|
@ -256,20 +256,20 @@ ${xmlEncode(rstToHtml(%postContent))}</content>
|
||||||
<updated>${recent}</updated>
|
<updated>${recent}</updated>
|
||||||
# for row in rows(db, query, 10):
|
# for row in rows(db, query, 10):
|
||||||
<entry>
|
<entry>
|
||||||
<title>${xmlEncode(%postHeader)}</title>
|
<title>${xmlEncode(!postHeader)}</title>
|
||||||
<id>urn:entry:${%postId}</id>
|
<id>urn:entry:${!postId}</id>
|
||||||
# let url = c.genThreadUrl(threadid = %postThread,
|
# let url = c.genThreadUrl(threadid = !postThread,
|
||||||
# pageNum = $(ceil(parseInt(%postPosition) / PostsPerPage).int)) &
|
# pageNum = $(ceil(parseInt(!postPosition) / PostsPerPage).int)) &
|
||||||
# "#" & %postId
|
# "#" & !postId
|
||||||
<link rel="alternate" type="text/html"
|
<link rel="alternate" type="text/html"
|
||||||
href="${c.req.makeUri(url)}"/>
|
href="${c.req.makeUri(url)}"/>
|
||||||
<published>${%postRssDate}</published>
|
<published>${!postRssDate}</published>
|
||||||
<updated>${%postRssDate}</updated>
|
<updated>${!postRssDate}</updated>
|
||||||
<author><name>${xmlEncode(%postAuthor)}</name></author>
|
<author><name>${xmlEncode(!postAuthor)}</name></author>
|
||||||
<content type="html"
|
<content type="html"
|
||||||
>On ${xmlEncode(%postHumanDate)}, ${xmlEncode(%postAuthor)} said:
|
>On ${xmlEncode(!postHumanDate)}, ${xmlEncode(!postAuthor)} said:
|
||||||
<p>
|
<p>
|
||||||
${xmlEncode(rstToHtml(%postContent))}</content>
|
${xmlEncode(rstToHtml(!postContent))}</content>
|
||||||
</entry>
|
</entry>
|
||||||
# end for
|
# end for
|
||||||
</feed>
|
</feed>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,21 @@
|
||||||
include karax/prelude
|
import strformat, times, options, json
|
||||||
|
|
||||||
|
include karax/prelude
|
||||||
|
import karax / [vstyles, kajax]
|
||||||
|
|
||||||
|
import threadlist, karaxutils
|
||||||
|
|
||||||
|
type
|
||||||
|
State = ref object
|
||||||
|
list: Option[ThreadList]
|
||||||
|
|
||||||
|
proc newState(): State =
|
||||||
|
State(
|
||||||
|
list: none[ThreadList]()
|
||||||
|
)
|
||||||
|
|
||||||
|
const
|
||||||
|
baseUrl = "http://localhost:5000/"
|
||||||
|
|
||||||
proc genHeader(): VNode =
|
proc genHeader(): VNode =
|
||||||
result = buildHtml(header(id="main-navbar")):
|
result = buildHtml(header(id="main-navbar")):
|
||||||
|
|
@ -34,9 +50,29 @@ proc genTopButtons(): VNode =
|
||||||
section(class="navbar-section")
|
section(class="navbar-section")
|
||||||
|
|
||||||
|
|
||||||
proc createDom(): VNode =
|
var state = newState()
|
||||||
|
|
||||||
|
proc onThreadList(httpStatus: int, response: kstring) =
|
||||||
|
let parsed = parseJson($response)
|
||||||
|
let list = to(parsed, ThreadList)
|
||||||
|
|
||||||
|
if state.list.isSome:
|
||||||
|
state.list.get().threads.add(list.threads)
|
||||||
|
state.list.get().moreCount = list.moreCount
|
||||||
|
state.list.get().lastVisit = list.lastVisit
|
||||||
|
else:
|
||||||
|
state.list = some(list)
|
||||||
|
|
||||||
|
proc render(): VNode =
|
||||||
|
if state.list.isNone:
|
||||||
|
ajaxGet(baseUrl & "threads.json", @[], onThreadList)
|
||||||
|
|
||||||
result = buildHtml(tdiv()):
|
result = buildHtml(tdiv()):
|
||||||
genHeader()
|
genHeader()
|
||||||
genTopButtons()
|
genTopButtons()
|
||||||
|
if state.list.isNone:
|
||||||
|
tdiv(class="loading loading-lg")
|
||||||
|
else:
|
||||||
|
genThreadList(state.list.get())
|
||||||
|
|
||||||
setRenderer createDom
|
setRenderer render
|
||||||
1
redesign/forum.nim.cfg
Normal file
1
redesign/forum.nim.cfg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
-d:js
|
||||||
5
redesign/karaxutils.nim
Normal file
5
redesign/karaxutils.nim
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
proc class*(classes: varargs[tuple[name: string, present: bool]],
|
||||||
|
defaultClasses: string = ""): string =
|
||||||
|
result = defaultClasses & " "
|
||||||
|
for class in classes:
|
||||||
|
if class.present: result.add(class.name & " ")
|
||||||
115
redesign/threadlist.nim
Normal file
115
redesign/threadlist.nim
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import strformat, times
|
||||||
|
|
||||||
|
type
|
||||||
|
User* = object
|
||||||
|
name*: string
|
||||||
|
avatarUrl*: string
|
||||||
|
isOnline*: bool
|
||||||
|
|
||||||
|
Category* = object
|
||||||
|
id*: string
|
||||||
|
color*: string
|
||||||
|
|
||||||
|
Thread* = object
|
||||||
|
id*: int
|
||||||
|
topic*: string
|
||||||
|
category*: Category
|
||||||
|
users*: seq[User]
|
||||||
|
replies*: int
|
||||||
|
views*: int
|
||||||
|
activity*: int64 ## Unix timestamp
|
||||||
|
creation*: int64 ## Unix timestamp
|
||||||
|
isLocked*: bool
|
||||||
|
isSolved*: bool
|
||||||
|
|
||||||
|
ThreadList* = ref object
|
||||||
|
threads*: seq[Thread]
|
||||||
|
lastVisit*: int64 ## Unix timestamp
|
||||||
|
moreCount*: int ## How many more threads are left
|
||||||
|
|
||||||
|
when defined(js):
|
||||||
|
include karax/prelude
|
||||||
|
import karax / [vstyles]
|
||||||
|
|
||||||
|
import karaxutils
|
||||||
|
|
||||||
|
proc genUserAvatars(users: seq[User]): VNode =
|
||||||
|
result = buildHtml(td):
|
||||||
|
for user in users:
|
||||||
|
figure(class="avatar avatar-sm"):
|
||||||
|
img(src=user.avatarUrl, title=user.name)
|
||||||
|
if user.isOnline:
|
||||||
|
italic(class="avatar-presense online")
|
||||||
|
|
||||||
|
proc renderActivity(activity: int64): string =
|
||||||
|
let currentTime = getTime()
|
||||||
|
let activityTime = fromUnix(activity)
|
||||||
|
let duration = currentTime - activityTime
|
||||||
|
if duration.days > 300:
|
||||||
|
return activityTime.local().format("MMM yyyy")
|
||||||
|
elif duration.days > 30 and duration.days < 300:
|
||||||
|
return activityTime.local().format("MMM dd")
|
||||||
|
elif duration.days != 0:
|
||||||
|
return $duration.days & "d"
|
||||||
|
elif duration.hours != 0:
|
||||||
|
return $duration.hours & "h"
|
||||||
|
elif duration.minutes != 0:
|
||||||
|
return $duration.minutes & "m"
|
||||||
|
else:
|
||||||
|
return $duration.seconds & "s"
|
||||||
|
|
||||||
|
proc genThread(thread: Thread, isNew: bool, noBorder: bool): VNode =
|
||||||
|
result = buildHtml():
|
||||||
|
tr(class=class({"no-border": noBorder})):
|
||||||
|
td():
|
||||||
|
if thread.isLocked:
|
||||||
|
italic(class="fas fa-lock fa-xs")
|
||||||
|
text thread.topic
|
||||||
|
td():
|
||||||
|
tdiv(class="triangle",
|
||||||
|
style=style(
|
||||||
|
(StyleAttr.borderBottom, kstring"0.6rem solid " & thread.category.color)
|
||||||
|
)):
|
||||||
|
text thread.category.id
|
||||||
|
genUserAvatars(thread.users)
|
||||||
|
td(): text $thread.replies
|
||||||
|
td(class=class({
|
||||||
|
"views-text": thread.views < 999,
|
||||||
|
"popular-text": thread.views > 999 and thread.views < 5000,
|
||||||
|
"super-popular-text": thread.views > 5000
|
||||||
|
})):
|
||||||
|
if thread.views > 999:
|
||||||
|
text fmt"{thread.views/1000:.1f}"
|
||||||
|
else:
|
||||||
|
text $thread.views
|
||||||
|
td(class=class({"text-success": isNew, "text-gray": not isNew})): # TODO: Colors.
|
||||||
|
text renderActivity(thread.activity)
|
||||||
|
|
||||||
|
proc genThreadList*(list: ThreadList): VNode =
|
||||||
|
result = buildHtml():
|
||||||
|
section(class="container grid-xl"): # TODO: Rename to `.thread-list`.
|
||||||
|
table(class="table"):
|
||||||
|
thead():
|
||||||
|
tr:
|
||||||
|
th(text "Topic")
|
||||||
|
th(text "Category")
|
||||||
|
th(text "Users")
|
||||||
|
th(text "Replies")
|
||||||
|
th(text "Views")
|
||||||
|
th(text "Activity")
|
||||||
|
tbody():
|
||||||
|
for i in 0 ..< list.threads.len:
|
||||||
|
let thread = list.threads[i]
|
||||||
|
let isLastVisit =
|
||||||
|
i+1 < list.threads.len and list.threads[i].activity < list.lastVisit
|
||||||
|
let isNew = thread.creation < list.lastVisit
|
||||||
|
genThread(thread, isNew, noBorder=isLastVisit)
|
||||||
|
if isLastVisit:
|
||||||
|
tr(class="last-visit-separator"):
|
||||||
|
td(colspan="6"):
|
||||||
|
span(text "last visit")
|
||||||
|
|
||||||
|
if list.moreCount > 0:
|
||||||
|
tr(class="load-more-separator"):
|
||||||
|
td(colspan="6"):
|
||||||
|
span(text "load more threads")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue