Implements posting of replies.
This commit is contained in:
parent
073f274e04
commit
7635478b34
3 changed files with 143 additions and 21 deletions
59
forum.nim
59
forum.nim
|
|
@ -1069,6 +1069,32 @@ proc selectThread(threadRow: seq[string]): Thread =
|
||||||
|
|
||||||
return thread
|
return thread
|
||||||
|
|
||||||
|
proc executeReply(c: TForumData, threadId: int, content: string,
|
||||||
|
replyingTo: int): int64 =
|
||||||
|
# TODO: Refactor TForumData.
|
||||||
|
assert c.loggedIn()
|
||||||
|
|
||||||
|
let subject = "" # TODO: Remove this redundant field.
|
||||||
|
if rateLimitCheck(c):
|
||||||
|
raise newException(ForumError, "You're posting too fast!")
|
||||||
|
|
||||||
|
# TODO: Replying to.
|
||||||
|
let retID = insertID(
|
||||||
|
db,
|
||||||
|
crud(crCreate, "post", "author", "ip", "header", "content", "thread"),
|
||||||
|
c.userId, c.req.ip, subject, content, $threadId, ""
|
||||||
|
)
|
||||||
|
discard tryExec(
|
||||||
|
db,
|
||||||
|
crud(crCreate, "post_fts", "id", "header", "content"),
|
||||||
|
retID.int, subject, content
|
||||||
|
)
|
||||||
|
|
||||||
|
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
||||||
|
$c.threadId)
|
||||||
|
|
||||||
|
return retID
|
||||||
|
|
||||||
initialise()
|
initialise()
|
||||||
|
|
||||||
routes:
|
routes:
|
||||||
|
|
@ -1237,6 +1263,39 @@ routes:
|
||||||
)
|
)
|
||||||
resp Http400, $(%err), "application/json"
|
resp Http400, $(%err), "application/json"
|
||||||
|
|
||||||
|
post "/karax/createPost":
|
||||||
|
createTFD()
|
||||||
|
if not c.loggedIn():
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Not logged in."
|
||||||
|
)
|
||||||
|
resp Http401, $(%err), "application/json"
|
||||||
|
|
||||||
|
let formData = request.formData
|
||||||
|
cond "msg" in formData
|
||||||
|
cond "threadId" in formData
|
||||||
|
|
||||||
|
let msg = formData["msg"].body
|
||||||
|
let threadId = getInt(formData["threadId"].body, -1)
|
||||||
|
cond threadId != -1
|
||||||
|
|
||||||
|
let replyingTo =
|
||||||
|
if "replyingTo" in formData:
|
||||||
|
getInt(formData["replyingTo"].body, -1)
|
||||||
|
else:
|
||||||
|
-1
|
||||||
|
|
||||||
|
try:
|
||||||
|
let id = executeReply(c, threadId, msg, replyingTo)
|
||||||
|
resp Http200, $(%id), "application/json"
|
||||||
|
except ForumError:
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: getCurrentExceptionMsg()
|
||||||
|
)
|
||||||
|
resp Http400, $(%err), "application/json"
|
||||||
|
|
||||||
get re"/karax/(.+)?":
|
get re"/karax/(.+)?":
|
||||||
resp readFile("redesign/karax.html")
|
resp readFile("redesign/karax.html")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,14 @@ when defined(js):
|
||||||
replyingTo: Option[Post]
|
replyingTo: Option[Post]
|
||||||
replyBox: ReplyBox
|
replyBox: ReplyBox
|
||||||
|
|
||||||
|
proc onReplyPosted(id: int)
|
||||||
proc newState(): State =
|
proc newState(): State =
|
||||||
State(
|
State(
|
||||||
list: none[PostList](),
|
list: none[PostList](),
|
||||||
loading: false,
|
loading: false,
|
||||||
status: Http200,
|
status: Http200,
|
||||||
replyingTo: none[Post](),
|
replyingTo: none[Post](),
|
||||||
replyBox: newReplyBox()
|
replyBox: newReplyBox(onReplyPosted)
|
||||||
)
|
)
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
@ -47,7 +48,7 @@ when defined(js):
|
||||||
|
|
||||||
state.list = some(list)
|
state.list = some(list)
|
||||||
|
|
||||||
proc onMorePosts(httpStatus: int, response: kstring, start: int, post: Post) =
|
proc onMorePosts(httpStatus: int, response: kstring, start: int) =
|
||||||
state.loading = false
|
state.loading = false
|
||||||
state.status = httpStatus.HttpCode
|
state.status = httpStatus.HttpCode
|
||||||
if state.status != Http200: return
|
if state.status != Http200: return
|
||||||
|
|
@ -62,20 +63,21 @@ when defined(js):
|
||||||
|
|
||||||
# Save a list of the IDs which have not yet been loaded into the top-most
|
# Save a list of the IDs which have not yet been loaded into the top-most
|
||||||
# post.
|
# post.
|
||||||
for id in post.moreBefore:
|
let postIndex = start+list.len
|
||||||
if id notin idsLoaded:
|
# The following check is necessary because we reuse this proc to load
|
||||||
state.list.get().posts[start].moreBefore.add(id)
|
# a newly created post.
|
||||||
post.moreBefore = @[]
|
if postIndex < state.list.get().posts.len:
|
||||||
|
let post = state.list.get().posts[postIndex]
|
||||||
|
var newPostIds: seq[int] = @[]
|
||||||
|
for id in post.moreBefore:
|
||||||
|
if id notin idsLoaded:
|
||||||
|
newPostIds.add(id)
|
||||||
|
post.moreBefore = newPostIds
|
||||||
|
|
||||||
proc onReplyClick(e: Event, n: VNode, p: Option[Post]) =
|
proc loadMore(start: int, ids: seq[int]) =
|
||||||
state.replyingTo = p
|
|
||||||
state.replyBox.show()
|
|
||||||
|
|
||||||
proc onLoadMore(ev: Event, n: VNode, start: int, post: Post) =
|
|
||||||
if state.loading: return
|
if state.loading: return
|
||||||
|
|
||||||
state.loading = true
|
state.loading = true
|
||||||
let ids = post.moreBefore # TODO: Don't load all!
|
|
||||||
let uri = makeUri(
|
let uri = makeUri(
|
||||||
"specific_posts.json",
|
"specific_posts.json",
|
||||||
[("ids", $(%ids))]
|
[("ids", $(%ids))]
|
||||||
|
|
@ -83,9 +85,20 @@ when defined(js):
|
||||||
ajaxGet(
|
ajaxGet(
|
||||||
uri,
|
uri,
|
||||||
@[],
|
@[],
|
||||||
(s: int, r: kstring) => onMorePosts(s, r, start, post)
|
(s: int, r: kstring) => onMorePosts(s, r, start)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc onReplyPosted(id: int) =
|
||||||
|
## Executed when a reply has been successfully posted.
|
||||||
|
loadMore(state.list.get().posts.len, @[id])
|
||||||
|
|
||||||
|
proc onReplyClick(e: Event, n: VNode, p: Option[Post]) =
|
||||||
|
state.replyingTo = p
|
||||||
|
state.replyBox.show()
|
||||||
|
|
||||||
|
proc onLoadMore(ev: Event, n: VNode, start: int, post: Post) =
|
||||||
|
loadMore(start, post.moreBefore) # TODO: Don't load all!
|
||||||
|
|
||||||
proc genLoadMore(post: Post, start: int): VNode =
|
proc genLoadMore(post: Post, start: int): VNode =
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv(class="information load-more-posts",
|
tdiv(class="information load-more-posts",
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,12 @@ when defined(js):
|
||||||
loading: bool
|
loading: bool
|
||||||
error: Option[PostError]
|
error: Option[PostError]
|
||||||
rendering: Option[kstring]
|
rendering: Option[kstring]
|
||||||
|
onPost: proc (id: int)
|
||||||
|
|
||||||
proc newReplyBox*(): ReplyBox =
|
proc newReplyBox*(onPost: proc (id: int)): ReplyBox =
|
||||||
ReplyBox(
|
ReplyBox(
|
||||||
text: ""
|
text: "",
|
||||||
|
onPost: onPost
|
||||||
)
|
)
|
||||||
|
|
||||||
proc performScroll() =
|
proc performScroll() =
|
||||||
|
|
@ -68,6 +70,45 @@ when defined(js):
|
||||||
ajaxPost(uri, @[], cast[cstring](formData),
|
ajaxPost(uri, @[], cast[cstring](formData),
|
||||||
(s: int, r: kstring) => onPreviewPost(s, r, state))
|
(s: int, r: kstring) => onPreviewPost(s, r, state))
|
||||||
|
|
||||||
|
proc onReplyPost(httpStatus: int, response: kstring, state: ReplyBox) =
|
||||||
|
state.loading = false
|
||||||
|
let status = httpStatus.HttpCode
|
||||||
|
if status == Http200:
|
||||||
|
state.text = ""
|
||||||
|
state.shown = false
|
||||||
|
state.onPost(parseJson($response).getInt())
|
||||||
|
else:
|
||||||
|
# TODO: login has similar code, abstract this.
|
||||||
|
try:
|
||||||
|
let parsed = parseJson($response)
|
||||||
|
let error = to(parsed, PostError)
|
||||||
|
|
||||||
|
state.error = some(error)
|
||||||
|
except:
|
||||||
|
kout(getCurrentExceptionMsg().cstring)
|
||||||
|
state.error = some(PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Unknown error occurred."
|
||||||
|
))
|
||||||
|
|
||||||
|
proc onReplyClick(e: Event, n: VNode, state: ReplyBox,
|
||||||
|
thread: Thread, replyingTo: Option[Post]) =
|
||||||
|
state.loading = true
|
||||||
|
state.error = none[PostError]()
|
||||||
|
|
||||||
|
let formData = newFormData()
|
||||||
|
formData.append("msg", state.text)
|
||||||
|
formData.append("threadId", $thread.id)
|
||||||
|
if replyingTo.isSome:
|
||||||
|
formData.append("replyingTo", $replyingTo.get().id)
|
||||||
|
let uri = makeUri("/createPost")
|
||||||
|
ajaxPost(uri, @[], cast[cstring](formData),
|
||||||
|
(s: int, r: kstring) => onReplyPost(s, r, state))
|
||||||
|
|
||||||
|
proc onCancelClick(e: Event, n: VNode, state: ReplyBox) =
|
||||||
|
# TODO: Double check reply box contents and ask user whether to discard.
|
||||||
|
state.shown = false
|
||||||
|
|
||||||
proc onChange(e: Event, n: VNode, state: ReplyBox) =
|
proc onChange(e: Event, n: VNode, state: ReplyBox) =
|
||||||
# TODO: There should be a karax-way to do this. I guess I can just call
|
# TODO: There should be a karax-way to do this. I guess I can just call
|
||||||
# `value` on the node? We need to document this better :)
|
# `value` on the node? We need to document this better :)
|
||||||
|
|
@ -111,10 +152,6 @@ when defined(js):
|
||||||
if state.preview:
|
if state.preview:
|
||||||
if state.loading:
|
if state.loading:
|
||||||
tdiv(class="loading")
|
tdiv(class="loading")
|
||||||
elif state.error.isSome():
|
|
||||||
tdiv(class="toast toast-error",
|
|
||||||
style=style(StyleAttr.marginTop, "0.4rem")):
|
|
||||||
text state.error.get().message
|
|
||||||
elif state.rendering.isSome():
|
elif state.rendering.isSome():
|
||||||
verbatim(state.rendering.get())
|
verbatim(state.rendering.get())
|
||||||
else:
|
else:
|
||||||
|
|
@ -122,8 +159,21 @@ when defined(js):
|
||||||
onChange=(e: Event, n: VNode) =>
|
onChange=(e: Event, n: VNode) =>
|
||||||
onChange(e, n, state),
|
onChange(e, n, state),
|
||||||
value=state.text)
|
value=state.text)
|
||||||
|
|
||||||
|
if state.error.isSome():
|
||||||
|
tdiv(class="toast toast-error",
|
||||||
|
style=style(StyleAttr.marginTop, "0.4rem")):
|
||||||
|
text state.error.get().message
|
||||||
|
|
||||||
tdiv(class="panel-footer"):
|
tdiv(class="panel-footer"):
|
||||||
button(class="btn btn-primary float-right"):
|
button(class=class(
|
||||||
|
{"loading": state.loading},
|
||||||
|
"btn btn-primary float-right"
|
||||||
|
),
|
||||||
|
onClick=(e: Event, n: VNode) =>
|
||||||
|
onReplyClick(e, n, state, thread, post)):
|
||||||
text "Reply"
|
text "Reply"
|
||||||
button(class="btn btn-link float-right"):
|
button(class="btn btn-link float-right",
|
||||||
|
onClick=(e: Event, n: VNode) =>
|
||||||
|
onCancelClick(e, n, state)):
|
||||||
text "Cancel"
|
text "Cancel"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue