Add categories page

This commit is contained in:
Joey Payne 2019-01-25 16:40:04 -07:00 • committed by Andreas Rumpf
commit 9d19d70558
6 changed files with 187 additions and 41 deletions

View file

@ -22,6 +22,7 @@ table th {
// Custom styles. // Custom styles.
// - Navigation bar. // - Navigation bar.
$navbar-height: 60px; $navbar-height: 60px;
$default-category-color: #98c766;
$logo-height: $navbar-height - 20px; $logo-height: $navbar-height - 20px;
.navbar-button { .navbar-button {
@ -166,6 +167,33 @@ $logo-height: $navbar-height - 20px;
} }
} }
.thread-list {
@extend .container;
@extend .grid-xl;
}
.category-list {
@extend .thread-list;
.category-title {
@extend .thread-title;
a, a:hover {
color: lighten($body-font-color, 10%);
text-decoration: none;
}
}
.category-description {
opacity: 0.6;
}
}
#categories-list .category {
border-left: 6px solid;
border-left-color: $default-category-color;
}
$super-popular-color: #f86713; $super-popular-color: #f86713;
$popular-color: darken($super-popular-color, 25%); $popular-color: darken($super-popular-color, 25%);
$threads-meta-color: #545d70; $threads-meta-color: #545d70;
@ -217,7 +245,7 @@ $threads-meta-color: #545d70;
.category-color { .category-color {
width: 0; width: 0;
height: 0; height: 0;
border: 0.3rem solid #98c766; border: 0.3rem solid $default-category-color;
display: inline-block; display: inline-block;
margin-right: 5px; margin-right: 5px;
} }
@ -726,8 +754,3 @@ hr {
margin-top: $control-padding-y*2; margin-top: $control-padding-y*2;
} }
} }
// - Hide features that have not been implemented yet.
#main-buttons > section.navbar-section:nth-child(1) {
display: none;
}

View file

@ -806,14 +806,28 @@ routes:
var var
start = getInt(@"start", 0) start = getInt(@"start", 0)
count = getInt(@"count", 30) count = getInt(@"count", 30)
categoryId = getInt(@"categoryId", -1)
var
categorySection = ""
categoryArgs: seq[string] = @[$start, $count]
countQuery = sql"select count(*) from thread;"
countArgs: seq[string] = @[]
if categoryId != -1:
categorySection = "c.id == ? and "
countQuery = sql"select count(*) from thread t, category c where category == c.id and c.id == ?;"
countArgs.add($categoryId)
categoryArgs.insert($categoryId, 0)
const threadsQuery = const threadsQuery =
sql"""select t.id, t.name, views, strftime('%s', modified), isLocked, """select t.id, t.name, views, strftime('%s', modified), isLocked,
c.id, c.name, c.description, c.color, c.id, c.name, c.description, c.color,
u.name, u.email, strftime('%s', u.lastOnline), u.name, u.email, strftime('%s', u.lastOnline),
strftime('%s', u.previousVisitAt), u.status, u.isDeleted strftime('%s', u.previousVisitAt), u.status, u.isDeleted
from thread t, category c, person u from thread t, category c, person u
where t.isDeleted = 0 and category = c.id and where t.isDeleted = 0 and category = c.id and $#
u.status <> 'Spammer' and u.status <> 'Troll' and u.status <> 'Spammer' and u.status <> 'Troll' and
u.id in ( u.id in (
select u.id from post p, person u select u.id from post p, person u
@ -823,11 +837,11 @@ routes:
) )
order by modified desc limit ?, ?;""" order by modified desc limit ?, ?;"""
let thrCount = getValue(db, sql"select count(*) from thread;").parseInt() let thrCount = getValue(db, countQuery, countArgs).parseInt()
let moreCount = max(0, thrCount - (start + count)) let moreCount = max(0, thrCount - (start + count))
var list = ThreadList(threads: @[], moreCount: moreCount) var list = ThreadList(threads: @[], moreCount: moreCount)
for data in getAllRows(db, threadsQuery, start, count): for data in getAllRows(db, sql(threadsQuery % categorySection), categoryArgs):
let thread = selectThread(data[0 .. 8], selectUser(data[9 .. ^1])) let thread = selectThread(data[0 .. 8], selectUser(data[9 .. ^1]))
list.threads.add(thread) list.threads.add(thread)

View file

@ -0,0 +1,83 @@
import strformat, times, options, json, httpcore, sugar, strutils, uri
import category
when defined(js):
include karax/prelude
import karax / [vstyles, kajax, kdom]
import karaxutils, error, user, mainbuttons
type
State = ref object
list: Option[CategoryList]
loading: bool
status: HttpCode
proc newState(): State =
State(
list: none[CategoryList](),
loading: false,
status: Http200
)
var
state = newState()
proc genCategory(category: Category, noBorder = false): VNode =
result = buildHtml():
tr(class=class({"no-border": noBorder})):
td(style=style((StyleAttr.borderLeftColor, kstring("#" & category.color))), class="category"):
h4(class="category-title"):
a(href=makeUri("/c/" & $category.id)):
tdiv():
tdiv(class="category-name"):
text category.name
tdiv(class="category-description"):
text category.description
td(class="topics"):
text "Topics"
proc onCategoriesRetrieved(httpStatus: int, response: kstring) =
state.loading = false
state.status = httpStatus.HttpCode
if state.status != Http200: return
let parsed = parseJson($response)
let list = to(parsed, CategoryList)
if state.list.isSome:
state.list.get().categories.add(list.categories)
else:
state.list = some(list)
proc renderCategories(): VNode =
if state.status != Http200:
return renderError("Couldn't retrieve threads.", state.status)
if state.list.isNone:
if not state.loading:
state.loading = true
ajaxGet(makeUri("categories.json"), @[], onCategoriesRetrieved)
return buildHtml(tdiv(class="loading loading-lg"))
let list = state.list.get()
return buildHtml():
section(class="category-list"):
table(id="categories-list", class="table"):
thead():
tr:
th(text "Category")
th(text "Topics")
tbody():
for i in 0 ..< list.categories.len:
let category = list.categories[i]
let isLastCategory = i+1 == list.categories.len
genCategory(category, noBorder=isLastCategory)
proc renderCategoryList*(currentUser: Option[User]): VNode =
result = buildHtml(tdiv):
renderMainButtons(currentUser)
renderCategories()

View file

@ -5,6 +5,7 @@ include karax/prelude
import jester/[patterns] import jester/[patterns]
import threadlist, postlist, header, profile, newthread, error, about import threadlist, postlist, header, profile, newthread, error, about
import categorylist
import resetpassword, activateemail, search import resetpassword, activateemail, search
import karaxutils import karaxutils
@ -81,6 +82,14 @@ proc render(): VNode =
result = buildHtml(tdiv()): result = buildHtml(tdiv()):
renderHeader() renderHeader()
route([ route([
r("/categories",
(params: Params) =>
(renderCategoryList(getLoggedInUser()))
),
r("/c/@id",
(params: Params) =>
(renderThreadList(getLoggedInUser(), params["id"].parseInt))
),
r("/newthread", r("/newthread",
(params: Params) => (params: Params) =>
(render(state.newThread, getLoggedInUser())) (render(state.newThread, getLoggedInUser()))

View file

@ -0,0 +1,37 @@
import options
import user
when defined(js):
include karax/prelude
import karax / [vstyles, kajax, kdom]
import karaxutils, error, user
let buttons = [
(name: "Latest", url: makeUri("/"), id: "latest-btn"),
(name: "Categories", url: makeUri("/categories"), id: "categories-btn"),
]
proc renderMainButtons*(currentUser: Option[User]): VNode =
result = buildHtml():
section(class="navbar container grid-xl", id="main-buttons"):
section(class="navbar-section"):
#[tdiv(class="dropdown"):
a(href="#", class="btn dropdown-toggle"):
text "Filter "
italic(class="fas fa-caret-down")
ul(class="menu"):
li: text "community"
li: text "dev" ]#
for btn in buttons:
let active = btn.url == window.location.href
a(id=btn.id, href=btn.url):
button(class=class({"btn-primary": active, "btn-link": not active}, "btn")):
text btn.name
section(class="navbar-section"):
if currentUser.isSome():
a(id="new-thread-btn", href=makeUri("/newthread"), onClick=anchorCB):
button(class="btn btn-secondary"):
italic(class="fas fa-plus")
text " New Thread"

View file

@ -29,7 +29,7 @@ when defined(js):
include karax/prelude include karax/prelude
import karax / [vstyles, kajax, kdom] import karax / [vstyles, kajax, kdom]
import karaxutils, error, user import karaxutils, error, user, mainbuttons
type type
State = ref object State = ref object
@ -65,27 +65,6 @@ when defined(js):
return true return true
proc genTopButtons(currentUser: Option[User]): VNode =
result = buildHtml():
section(class="navbar container grid-xl", id="main-buttons"):
section(class="navbar-section"):
tdiv(class="dropdown"):
a(href="#", class="btn dropdown-toggle"):
text "Filter "
italic(class="fas fa-caret-down")
ul(class="menu"):
li: text "community"
li: text "dev"
button(class="btn btn-primary"): text "Latest"
button(class="btn btn-link"): text "Most Active"
button(class="btn btn-link"): text "Categories"
section(class="navbar-section"):
if currentUser.isSome():
a(id="new-thread-btn", href=makeUri("/newthread"), onClick=anchorCB):
button(class="btn btn-secondary"):
italic(class="fas fa-plus")
text " New Thread"
proc genUserAvatars(users: seq[User]): VNode = proc genUserAvatars(users: seq[User]): VNode =
result = buildHtml(td): result = buildHtml(td):
for user in users: for user in users:
@ -168,10 +147,10 @@ when defined(js):
else: else:
state.list = some(list) state.list = some(list)
proc onLoadMore(ev: Event, n: VNode) = proc onLoadMore(ev: Event, n: VNode, categoryId: int) =
state.loading = true state.loading = true
let start = state.list.get().threads.len let start = state.list.get().threads.len
ajaxGet(makeUri("threads.json?start=" & $start), @[], onThreadList) ajaxGet(makeUri("threads.json?start=" & $start & "&categoryId=" & $categoryId), @[], onThreadList)
proc getInfo( proc getInfo(
list: seq[Thread], i: int, currentUser: Option[User] list: seq[Thread], i: int, currentUser: Option[User]
@ -196,20 +175,20 @@ when defined(js):
isNew: thread.creation > previousVisitAt isNew: thread.creation > previousVisitAt
) )
proc genThreadList(currentUser: Option[User]): VNode = proc genThreadList(currentUser: Option[User], categoryId: int): VNode =
if state.status != Http200: if state.status != Http200:
return renderError("Couldn't retrieve threads.", state.status) return renderError("Couldn't retrieve threads.", state.status)
if state.list.isNone: if state.list.isNone:
if not state.loading: if not state.loading:
state.loading = true state.loading = true
ajaxGet(makeUri("threads.json"), @[], onThreadList) ajaxGet(makeUri("threads.json?categoryId=" & $categoryId), @[], onThreadList)
return buildHtml(tdiv(class="loading loading-lg")) return buildHtml(tdiv(class="loading loading-lg"))
let list = state.list.get() let list = state.list.get()
result = buildHtml(): result = buildHtml():
section(class="container grid-xl"): # TODO: Rename to `.thread-list`. section(class="thread-list"):
table(class="table", id="threads-list"): table(class="table", id="threads-list"):
thead(): thead():
tr: tr:
@ -239,10 +218,11 @@ when defined(js):
td(colspan="6"): td(colspan="6"):
tdiv(class="loading loading-lg") tdiv(class="loading loading-lg")
else: else:
td(colspan="6", onClick=onLoadMore): td(colspan="6",
onClick = (ev: Event, n: VNode) => (onLoadMore(ev, n, categoryId))):
span(text "load more threads") span(text "load more threads")
proc renderThreadList*(currentUser: Option[User]): VNode = proc renderThreadList*(currentUser: Option[User], categoryId = -1): VNode =
result = buildHtml(tdiv): result = buildHtml(tdiv):
genTopButtons(currentUser) renderMainButtons(currentUser)
genThreadList(currentUser) genThreadList(currentUser, categoryId)