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;
|
||||
}
|
||||
|
||||
i.fas {
|
||||
margin-right: $control-padding-x-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.posts, .about {
|
||||
|
|
@ -384,6 +388,7 @@ $views-color: #545d70;
|
|||
.btn {
|
||||
float: right;
|
||||
margin-right: 0;
|
||||
margin-left: $control-padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -419,10 +419,28 @@ proc executeReply(c: TForumData, threadId: int, content: string,
|
|||
if rateLimitCheck(c):
|
||||
raise newForumError("You're posting too fast!")
|
||||
|
||||
if content.strip().len == 0:
|
||||
raise newForumError("Message cannot be empty")
|
||||
|
||||
if not validateRst(c, content):
|
||||
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(
|
||||
db,
|
||||
crud(crCreate, "post", "author", "ip", "content", "thread", "replyingTo"),
|
||||
|
|
@ -630,6 +648,14 @@ proc executeUnlike(c: TForumData, postId: int) =
|
|||
# Delete the like.
|
||||
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) =
|
||||
# Verify that this post belongs to the user.
|
||||
const postQuery = sql"""
|
||||
|
|
@ -1127,6 +1153,33 @@ routes:
|
|||
except ForumError as exc:
|
||||
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)":
|
||||
createTFD()
|
||||
if not c.loggedIn():
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ when defined(js):
|
|||
include karax/prelude
|
||||
import karax/[kajax, kdom]
|
||||
|
||||
import error, karaxutils, post, user
|
||||
import error, karaxutils, post, user, threadlist
|
||||
|
||||
type
|
||||
PostButton* = ref object
|
||||
|
|
@ -142,4 +142,61 @@ when defined(js):
|
|||
span(class="like-count"):
|
||||
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
|
||||
likeButton: LikeButton
|
||||
deleteModal: DeleteModal
|
||||
lockButton: LockButton
|
||||
|
||||
proc onReplyPosted(id: int)
|
||||
proc onEditPosted(id: int, content: string, subject: Option[string])
|
||||
|
|
@ -47,7 +48,8 @@ when defined(js):
|
|||
replyBox: newReplyBox(onReplyPosted),
|
||||
editBox: newEditBox(onEditPosted, onEditCancelled),
|
||||
likeButton: newLikeButton(),
|
||||
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil)
|
||||
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil),
|
||||
lockButton: newLockButton()
|
||||
)
|
||||
|
||||
var
|
||||
|
|
@ -364,6 +366,8 @@ when defined(js):
|
|||
italic(class="fas fa-reply")
|
||||
text " Reply"
|
||||
|
||||
render(state.lockButton, list.thread, currentUser)
|
||||
|
||||
render(state.replyBox, list.thread, state.replyingTo, false)
|
||||
|
||||
render(state.deleteModal)
|
||||
|
|
@ -121,7 +121,7 @@ when defined(js):
|
|||
value=state.text)
|
||||
|
||||
if state.error.isSome():
|
||||
p(class="text-error",
|
||||
span(class="text-error",
|
||||
style=style(StyleAttr.marginTop, "0.4rem")):
|
||||
text state.error.get().message
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue