Noah Petherbridge
bb08ec56ce
Finish implementing the basic forum features: * Pinned threads (admin or board owner only) * Edit Thread settings when you edit the top-most comment. * NoReply threads remove all the reply buttons. * Explicit forums and threads are filtered out unless opted-in (admins always see them). * Count the unique members who participated in each forum. * Get the most recently updated thread to show on forum list page. * Contact/Report page: handle receiving a comment ID to report on. Implement Likes & Notifications * Like buttons added to Photos and Profile Pages. Implemented via simple vanilla JS (likes.js) to make ajax requests to back-end to like/unlike. * Notifications: for your photo or profile being liked. If you unlike, the existing notifications about the like are revoked. * The notifications appear as an alert number in the nav bar and are read on the User Dashboard. Click to mark a notification as "read" or click the "mark all as read" button. Update DeleteUser to scrub likes, notifications, threads, and comments.
207 lines
6.3 KiB
Go
207 lines
6.3 KiB
Go
package forum
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.kirsle.net/apps/gosocial/pkg/markdown"
|
|
"git.kirsle.net/apps/gosocial/pkg/models"
|
|
"git.kirsle.net/apps/gosocial/pkg/session"
|
|
"git.kirsle.net/apps/gosocial/pkg/templates"
|
|
)
|
|
|
|
// NewPost view.
|
|
func NewPost() http.HandlerFunc {
|
|
tmpl := templates.Must("forum/new_post.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Query params.
|
|
var (
|
|
fragment = r.FormValue("to") // forum to (new post)
|
|
toThreadID = r.FormValue("thread") // add reply to a thread ID
|
|
quoteCommentID = r.FormValue("quote") // add reply to thread while quoting a comment
|
|
editCommentID = r.FormValue("edit") // edit your comment
|
|
intent = r.FormValue("intent") // preview or submit
|
|
title = r.FormValue("title") // for new forum post only
|
|
message = r.PostFormValue("message") // comment body
|
|
isPinned = r.PostFormValue("pinned") == "true" // owners or admins only
|
|
isExplicit = r.PostFormValue("explicit") == "true" // for thread only
|
|
isNoReply = r.PostFormValue("noreply") == "true" // for thread only
|
|
isDelete = r.FormValue("delete") == "true" // delete comment (along with edit=$id)
|
|
forum *models.Forum
|
|
thread *models.Thread // if replying to a thread
|
|
comment *models.Comment // if editing a comment
|
|
|
|
// If we are modifying a comment (post) and it's the OG post of the
|
|
// thread, we show and accept the thread settings to be updated as
|
|
// well (pinned, explicit, noreply)
|
|
isOriginalComment bool
|
|
)
|
|
|
|
// Get the current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't get current user: %s", err)
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Look up the forum itself.
|
|
if found, err := models.ForumByFragment(fragment); err != nil {
|
|
session.FlashError(w, r, "Couldn't post to forum %s: not found.", fragment)
|
|
templates.Redirect(w, "/forum")
|
|
return
|
|
} else {
|
|
forum = found
|
|
}
|
|
|
|
// Are we manipulating a reply to an existing thread?
|
|
if len(toThreadID) > 0 {
|
|
if i, err := strconv.Atoi(toThreadID); err == nil {
|
|
if found, err := models.GetThread(uint64(i)); err != nil {
|
|
session.FlashError(w, r, "Couldn't find that thread ID!")
|
|
templates.Redirect(w, fmt.Sprintf("/f/%s", forum.Fragment))
|
|
return
|
|
} else {
|
|
thread = found
|
|
}
|
|
}
|
|
}
|
|
|
|
// Are we pre-filling the message with a quotation of an existing comment?
|
|
if len(quoteCommentID) > 0 {
|
|
if i, err := strconv.Atoi(quoteCommentID); err == nil {
|
|
if comment, err := models.GetComment(uint64(i)); err == nil {
|
|
message = markdown.Quotify(comment.Message) + "\n\n"
|
|
}
|
|
}
|
|
}
|
|
|
|
// Are we editing or deleting our comment?
|
|
if len(editCommentID) > 0 {
|
|
if i, err := strconv.Atoi(editCommentID); err == nil {
|
|
if found, err := models.GetComment(uint64(i)); err == nil {
|
|
comment = found
|
|
|
|
// Verify that it is indeed OUR comment.
|
|
if currentUser.ID != comment.UserID && !currentUser.IsAdmin {
|
|
templates.ForbiddenPage(w, r)
|
|
return
|
|
}
|
|
|
|
// Initialize the form w/ the content of this message.
|
|
if r.Method == http.MethodGet {
|
|
message = comment.Message
|
|
}
|
|
|
|
// Is this the OG thread of the post?
|
|
if thread.CommentID == comment.ID {
|
|
isOriginalComment = true
|
|
|
|
// Restore the checkbox option form values from thread settings.
|
|
if r.Method == http.MethodGet {
|
|
isPinned = thread.Pinned
|
|
isExplicit = thread.Explicit
|
|
isNoReply = thread.NoReply
|
|
}
|
|
}
|
|
|
|
// Are we DELETING this comment?
|
|
if isDelete {
|
|
if err := thread.DeleteReply(comment); err != nil {
|
|
session.FlashError(w, r, "Error deleting your post: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Your post has been deleted.")
|
|
}
|
|
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d", thread.ID))
|
|
return
|
|
}
|
|
} else {
|
|
// Comment not found - show the Forbidden page anyway.
|
|
templates.ForbiddenPage(w, r)
|
|
return
|
|
}
|
|
} else {
|
|
templates.NotFoundPage(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Submitting the form.
|
|
if r.Method == http.MethodPost {
|
|
// Default intent is preview unless told to submit.
|
|
if intent == "submit" {
|
|
// Are we modifying an existing comment?
|
|
if comment != nil {
|
|
comment.Message = message
|
|
|
|
// Can we update the thread props?
|
|
if isOriginalComment {
|
|
thread.Pinned = isPinned
|
|
thread.Explicit = isExplicit
|
|
thread.NoReply = isNoReply
|
|
if err := thread.Save(); err != nil {
|
|
session.FlashError(w, r, "Couldn't save thread properties: %s", err)
|
|
}
|
|
}
|
|
|
|
if err := comment.Save(); err != nil {
|
|
session.FlashError(w, r, "Couldn't save comment: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Comment updated!")
|
|
}
|
|
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d", thread.ID))
|
|
return
|
|
}
|
|
|
|
// Are we replying to an existing thread?
|
|
if thread != nil {
|
|
if _, err := thread.Reply(currentUser, message); err != nil {
|
|
session.FlashError(w, r, "Couldn't add reply to thread: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Reply added to the thread!")
|
|
}
|
|
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d", thread.ID))
|
|
return
|
|
}
|
|
|
|
// Create a new thread?
|
|
if thread, err := models.CreateThread(
|
|
currentUser,
|
|
forum.ID,
|
|
title,
|
|
message,
|
|
isPinned,
|
|
isExplicit,
|
|
isNoReply,
|
|
); err != nil {
|
|
session.FlashError(w, r, "Couldn't create thread: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Thread created!")
|
|
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d", thread.ID))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"Forum": forum,
|
|
"Thread": thread,
|
|
"Intent": intent,
|
|
"PostTitle": title,
|
|
"EditCommentID": editCommentID,
|
|
"EditThreadSettings": isOriginalComment,
|
|
"Message": message,
|
|
|
|
// Thread settings (for editing the original comment esp.)
|
|
"IsPinned": isPinned,
|
|
"IsExplicit": isExplicit,
|
|
"IsNoReply": isNoReply,
|
|
}
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|