Go to Comment endpoint + notification fixes
* Add endpoint /go/comment?id= that finds the right page that a comment can be seen on for the current user and redirects there. * Resolves issues with link discrepancies in comment notifications, if the recipient sees different page numbers depending on blocklist status. * Supports copyable permalinks to any comment on the site reliably.
This commit is contained in:
parent
7555dee944
commit
70402b42c9
75
pkg/controller/comment/goto_comment.go
Normal file
75
pkg/controller/comment/goto_comment.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package comment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
)
|
||||
|
||||
// GoToComment finds the correct link to view a comment.
|
||||
func GoToComment() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Query params.
|
||||
var (
|
||||
commentID uint64
|
||||
)
|
||||
|
||||
// Parse the ID param.
|
||||
if idStr := r.FormValue("id"); idStr == "" {
|
||||
session.FlashError(w, r, "Comment ID required.")
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
} else {
|
||||
if idInt, err := strconv.Atoi(idStr); err != nil {
|
||||
session.FlashError(w, r, "Comment ID invalid.")
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
} else {
|
||||
commentID = uint64(idInt)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Locate this comment.
|
||||
comment, err := models.GetComment(commentID)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't find that comment: %s", err)
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Where is this comment at?
|
||||
switch comment.TableName {
|
||||
case "threads":
|
||||
// A forum thread, find what page it is on.
|
||||
page, err := models.FindPageByComment(currentUser, comment, config.PageSizeThreadList)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't find that comment, but bringing you to the forum thread instead.")
|
||||
page = 1
|
||||
}
|
||||
|
||||
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d?page=%d#p%d", comment.TableID, page, comment.ID))
|
||||
return
|
||||
case "photos":
|
||||
// A photo comment thread, easy: only one page.
|
||||
templates.Redirect(w, fmt.Sprintf("/photo/view?id=%d#p%d", comment.TableID, comment.ID))
|
||||
return
|
||||
default:
|
||||
session.FlashError(w, r, "Unknown type of comment.")
|
||||
}
|
||||
|
||||
templates.Redirect(w, "/")
|
||||
})
|
||||
}
|
|
@ -177,7 +177,7 @@ func PostComment() http.HandlerFunc {
|
|||
TableName: comment.TableName,
|
||||
TableID: comment.TableID,
|
||||
Message: message,
|
||||
Link: fmt.Sprintf("%s#p%d", fromURL, comment.ID),
|
||||
Link: fmt.Sprintf("/go/comment?id=%d", comment.ID),
|
||||
}
|
||||
if err := models.CreateNotification(notif); err != nil {
|
||||
log.Error("Couldn't create Comment notification: %s", err)
|
||||
|
@ -201,7 +201,7 @@ func PostComment() http.HandlerFunc {
|
|||
TableName: comment.TableName,
|
||||
TableID: comment.TableID,
|
||||
Message: message,
|
||||
Link: fromURL,
|
||||
Link: fmt.Sprintf("/go/comment?id=%d", comment.ID),
|
||||
}
|
||||
if err := models.CreateNotification(notif); err != nil {
|
||||
log.Error("Couldn't create Comment notification for subscriber %d: %s", userID, err)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -108,6 +110,50 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin
|
|||
return cs, result.Error
|
||||
}
|
||||
|
||||
// FindPageByComment finds out what page a comment ID exists on for the current user, taking into
|
||||
// account their block lists and comment visibility.
|
||||
//
|
||||
// Note: the comments are assumed ordered by created_at asc.
|
||||
func FindPageByComment(user *User, comment *Comment, pageSize int) (int, error) {
|
||||
var (
|
||||
allCommentIDs []uint64
|
||||
blockedUserIDs = BlockedUserIDs(user)
|
||||
wheres = []string{}
|
||||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
// Get the complete set of comment IDs that this comment is on a thread of.
|
||||
wheres = append(wheres, "table_name = ? AND table_id = ?")
|
||||
placeholders = append(placeholders, comment.TableName, comment.TableID)
|
||||
|
||||
if len(blockedUserIDs) > 0 {
|
||||
wheres = append(wheres, "user_id NOT IN ?")
|
||||
placeholders = append(placeholders, blockedUserIDs)
|
||||
}
|
||||
|
||||
result := DB.Table(
|
||||
"comments",
|
||||
).Select(
|
||||
"id",
|
||||
).Where(
|
||||
strings.Join(wheres, " AND "),
|
||||
placeholders...,
|
||||
).Order("created_at asc").Scan(&allCommentIDs)
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
|
||||
// Scan the comment thread to find it.
|
||||
for i, cid := range allCommentIDs {
|
||||
if cid == comment.ID {
|
||||
var page = int(math.Ceil(float64(i) / float64(pageSize)))
|
||||
return page, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, errors.New("comment not visible to current user")
|
||||
}
|
||||
|
||||
// ListComments returns a complete set of comments without paging.
|
||||
func ListComments(user *User, tableName string, tableID uint64) ([]*Comment, error) {
|
||||
var (
|
||||
|
|
|
@ -112,6 +112,9 @@ func New() http.Handler {
|
|||
mux.Handle("/v1/barertc/report", barertc.Report())
|
||||
mux.Handle("/v1/barertc/profile", barertc.Profile())
|
||||
|
||||
// Redirect endpoints.
|
||||
mux.Handle("/go/comment", middleware.LoginRequired(comment.GoToComment()))
|
||||
|
||||
// Static files.
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(config.StaticPath))))
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
<div class="columns is-multiline is-gapless mb-0">
|
||||
<div class="column is-narrow">
|
||||
<a href="/forum/thread/{{.ThreadID}}?page=-1" class="has-text-grey">
|
||||
<a href="/go/comment?id={{.Comment.ID}}" class="has-text-grey">
|
||||
<h2 class="is-size-5 py-0 is-italic has-text-grey">
|
||||
<span class="icon"><i class="fa fa-reply"></i></span>
|
||||
Comment added by
|
||||
|
@ -131,7 +131,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
<a href="/forum/thread/{{.ThreadID}}?page=-1" class="has-text-dark">
|
||||
<a href="/go/comment?id={{.Comment.ID}}" class="has-text-dark">
|
||||
{{TrimEllipses .Comment.Message 256}}
|
||||
</a>
|
||||
|
||||
|
|
|
@ -298,6 +298,15 @@
|
|||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Copy link to clipboard -->
|
||||
<div class="column is-narrow">
|
||||
<a href="/go/comment?id={{.ID}}" onclick="navigator.clipboard.writeText(this.href); window.location='#p{{.ID}}'; return false"
|
||||
class="has-text-dark"
|
||||
title="Copy link to clipboard">
|
||||
<span class="icon"><i class="fa fa-paragraph"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if $Root.CurrentUser.IsAdmin}}
|
||||
|
|
|
@ -289,6 +289,15 @@
|
|||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Copy link to clipboard -->
|
||||
<div class="column is-narrow">
|
||||
<a href="/go/comment?id={{.ID}}" onclick="navigator.clipboard.writeText(this.href); window.location='#p{{.ID}}'; return false"
|
||||
class="has-text-dark"
|
||||
title="Copy link to clipboard">
|
||||
<span class="icon"><i class="fa fa-paragraph"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user