Implements posting of replies.

This commit is contained in:
Dominik Picheta 2018-05-14 21:11:18 +01:00
commit 7635478b34
3 changed files with 143 additions and 21 deletions

View file

@ -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")

View file

@ -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",

View file

@ -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"