Implements ability to lock threads.
This commit is contained in:
parent
0f58f5f7a0
commit
c9538ec703
5 changed files with 124 additions and 5 deletions
|
|
@ -241,6 +241,10 @@ $views-color: #545d70;
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.fas {
|
||||||
|
margin-right: $control-padding-x-sm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.posts, .about {
|
.posts, .about {
|
||||||
|
|
@ -384,6 +388,7 @@ $views-color: #545d70;
|
||||||
.btn {
|
.btn {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
margin-left: $control-padding-x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -419,10 +419,28 @@ proc executeReply(c: TForumData, threadId: int, content: string,
|
||||||
if rateLimitCheck(c):
|
if rateLimitCheck(c):
|
||||||
raise newForumError("You're posting too fast!")
|
raise newForumError("You're posting too fast!")
|
||||||
|
|
||||||
|
if content.strip().len == 0:
|
||||||
|
raise newForumError("Message cannot be empty")
|
||||||
|
|
||||||
if not validateRst(c, content):
|
if not validateRst(c, content):
|
||||||
raise newForumError("Message needs to be valid RST", @["msg"])
|
raise newForumError("Message needs to be valid RST", @["msg"])
|
||||||
|
|
||||||
# Verify that content can be parsed as RST.
|
# Ensure that the thread isn't locked.
|
||||||
|
let isLocked = getValue(
|
||||||
|
db,
|
||||||
|
sql"""
|
||||||
|
select isLocked from thread where id in (
|
||||||
|
select thread from post where id = ?
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
threadId
|
||||||
|
)
|
||||||
|
if isLocked.len == 0:
|
||||||
|
raise newForumError("Thread not found.")
|
||||||
|
|
||||||
|
if isLocked == "1":
|
||||||
|
raise newForumError("Cannot reply to a locked thread.")
|
||||||
|
|
||||||
let retID = insertID(
|
let retID = insertID(
|
||||||
db,
|
db,
|
||||||
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
||||||
|
|
@ -630,6 +648,14 @@ proc executeUnlike(c: TForumData, postId: int) =
|
||||||
# Delete the like.
|
# Delete the like.
|
||||||
exec(db, crud(crDelete, "like"), likeId)
|
exec(db, crud(crDelete, "like"), likeId)
|
||||||
|
|
||||||
|
proc executeLockState(c: TForumData, threadId: int, locked: bool) =
|
||||||
|
# Verify that the logged in user has the correct permissions.
|
||||||
|
if c.rank < Moderator:
|
||||||
|
raise newForumError("You cannot lock this thread.")
|
||||||
|
|
||||||
|
# Save the like.
|
||||||
|
exec(db, crud(crUpdate, "thread", "isLocked"), locked.int, threadId)
|
||||||
|
|
||||||
proc executeDeletePost(c: TForumData, postId: int) =
|
proc executeDeletePost(c: TForumData, postId: int) =
|
||||||
# Verify that this post belongs to the user.
|
# Verify that this post belongs to the user.
|
||||||
const postQuery = sql"""
|
const postQuery = sql"""
|
||||||
|
|
@ -1127,6 +1153,33 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
|
post re"/(lock|unlock)":
|
||||||
|
createTFD()
|
||||||
|
if not c.loggedIn():
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Not logged in."
|
||||||
|
)
|
||||||
|
resp Http401, $(%err), "application/json"
|
||||||
|
|
||||||
|
let formData = request.formData
|
||||||
|
cond "id" in formData
|
||||||
|
|
||||||
|
let threadId = getInt(formData["id"].body, -1)
|
||||||
|
cond threadId != -1
|
||||||
|
|
||||||
|
try:
|
||||||
|
case request.path
|
||||||
|
of "/lock":
|
||||||
|
executeLockState(c, threadId, true)
|
||||||
|
of "/unlock":
|
||||||
|
executeLockState(c, threadId, false)
|
||||||
|
else:
|
||||||
|
assert false
|
||||||
|
resp Http200, "{}", "application/json"
|
||||||
|
except ForumError as exc:
|
||||||
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
post re"/delete(Post|Thread)":
|
post re"/delete(Post|Thread)":
|
||||||
createTFD()
|
createTFD()
|
||||||
if not c.loggedIn():
|
if not c.loggedIn():
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ when defined(js):
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax/[kajax, kdom]
|
import karax/[kajax, kdom]
|
||||||
|
|
||||||
import error, karaxutils, post, user
|
import error, karaxutils, post, user, threadlist
|
||||||
|
|
||||||
type
|
type
|
||||||
PostButton* = ref object
|
PostButton* = ref object
|
||||||
|
|
@ -142,4 +142,61 @@ when defined(js):
|
||||||
span(class="like-count"):
|
span(class="like-count"):
|
||||||
text $post.likes.len
|
text $post.likes.len
|
||||||
|
|
||||||
italic(class=class({"far": not liked, "fas": liked}, "fa-heart"))
|
italic(class=class({"far": not liked, "fas": liked}, "fa-heart"))
|
||||||
|
|
||||||
|
type
|
||||||
|
LockButton* = ref object
|
||||||
|
error: Option[PostError]
|
||||||
|
loading: bool
|
||||||
|
|
||||||
|
proc newLockButton*(): LockButton =
|
||||||
|
LockButton()
|
||||||
|
|
||||||
|
proc onPost(httpStatus: int, response: kstring, state: LockButton,
|
||||||
|
thread: var Thread) =
|
||||||
|
postFinished:
|
||||||
|
thread.isLocked = not thread.isLocked
|
||||||
|
|
||||||
|
proc onLockClick(ev: Event, n: VNode, state: LockButton, thread: var Thread) =
|
||||||
|
if state.loading: return
|
||||||
|
|
||||||
|
state.loading = true
|
||||||
|
state.error = none[PostError]()
|
||||||
|
|
||||||
|
# TODO: This is a hack, karax should support this.
|
||||||
|
var formData = newFormData()
|
||||||
|
formData.append("id", $thread.id)
|
||||||
|
let uri =
|
||||||
|
if thread.isLocked:
|
||||||
|
makeUri("/unlock")
|
||||||
|
else:
|
||||||
|
makeUri("/lock")
|
||||||
|
ajaxPost(uri, @[], cast[cstring](formData),
|
||||||
|
(s: int, r: kstring) =>
|
||||||
|
onPost(s, r, state, thread))
|
||||||
|
|
||||||
|
ev.preventDefault()
|
||||||
|
|
||||||
|
proc render*(state: LockButton, thread: var Thread,
|
||||||
|
currentUser: Option[User]): VNode =
|
||||||
|
if currentUser.isNone() or
|
||||||
|
currentUser.get().rank < Moderator:
|
||||||
|
return buildHtml(tdiv())
|
||||||
|
|
||||||
|
let tooltip =
|
||||||
|
if state.error.isSome(): state.error.get().message
|
||||||
|
else: ""
|
||||||
|
|
||||||
|
result = buildHtml():
|
||||||
|
button(class="btn btn-secondary",
|
||||||
|
onClick=(e: Event, n: VNode) =>
|
||||||
|
onLockClick(e, n, state, thread),
|
||||||
|
"data-tooltip"=tooltip,
|
||||||
|
onmouseleave=(e: Event, n: VNode) =>
|
||||||
|
(state.error = none[PostError]())):
|
||||||
|
if thread.isLocked:
|
||||||
|
italic(class="fas fa-unlock-alt")
|
||||||
|
text " Unlock Thread"
|
||||||
|
else:
|
||||||
|
italic(class="fas fa-lock")
|
||||||
|
text " Lock Thread"
|
||||||
|
|
@ -32,6 +32,7 @@ when defined(js):
|
||||||
editBox: EditBox
|
editBox: EditBox
|
||||||
likeButton: LikeButton
|
likeButton: LikeButton
|
||||||
deleteModal: DeleteModal
|
deleteModal: DeleteModal
|
||||||
|
lockButton: LockButton
|
||||||
|
|
||||||
proc onReplyPosted(id: int)
|
proc onReplyPosted(id: int)
|
||||||
proc onEditPosted(id: int, content: string, subject: Option[string])
|
proc onEditPosted(id: int, content: string, subject: Option[string])
|
||||||
|
|
@ -47,7 +48,8 @@ when defined(js):
|
||||||
replyBox: newReplyBox(onReplyPosted),
|
replyBox: newReplyBox(onReplyPosted),
|
||||||
editBox: newEditBox(onEditPosted, onEditCancelled),
|
editBox: newEditBox(onEditPosted, onEditCancelled),
|
||||||
likeButton: newLikeButton(),
|
likeButton: newLikeButton(),
|
||||||
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil)
|
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil),
|
||||||
|
lockButton: newLockButton()
|
||||||
)
|
)
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
@ -364,6 +366,8 @@ when defined(js):
|
||||||
italic(class="fas fa-reply")
|
italic(class="fas fa-reply")
|
||||||
text " Reply"
|
text " Reply"
|
||||||
|
|
||||||
|
render(state.lockButton, list.thread, currentUser)
|
||||||
|
|
||||||
render(state.replyBox, list.thread, state.replyingTo, false)
|
render(state.replyBox, list.thread, state.replyingTo, false)
|
||||||
|
|
||||||
render(state.deleteModal)
|
render(state.deleteModal)
|
||||||
|
|
@ -121,7 +121,7 @@ when defined(js):
|
||||||
value=state.text)
|
value=state.text)
|
||||||
|
|
||||||
if state.error.isSome():
|
if state.error.isSome():
|
||||||
p(class="text-error",
|
span(class="text-error",
|
||||||
style=style(StyleAttr.marginTop, "0.4rem")):
|
style=style(StyleAttr.marginTop, "0.4rem")):
|
||||||
text state.error.get().message
|
text state.error.get().message
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue