Comments on Photos
* Add permalink URL for photos to view their comment threads. * Commenters can Edit or Delete their own comments. * Photo owners can Delete any comment on it. * Update Privacy Policy
This commit is contained in:
parent
0690a9a5b0
commit
c1268ae9b1
192
pkg/controller/comment/post_comment.go
Normal file
192
pkg/controller/comment/post_comment.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package comment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PostComment view - for previewing or submitting your comment.
|
||||||
|
func PostComment() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("comment/post_comment.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Query params.
|
||||||
|
var (
|
||||||
|
tableName = r.FormValue("table_name")
|
||||||
|
tableID uint64
|
||||||
|
editCommentID = r.FormValue("edit") // edit your comment
|
||||||
|
isDelete = r.FormValue("delete") == "true"
|
||||||
|
intent = r.FormValue("intent") // preview or submit
|
||||||
|
message = r.PostFormValue("message") // comment body
|
||||||
|
comment *models.Comment // if editing a comment
|
||||||
|
fromURL = r.FormValue("next") // what page to send back to
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse the table ID param.
|
||||||
|
if idStr := r.FormValue("table_id"); idStr == "" {
|
||||||
|
session.FlashError(w, r, "Comment table ID required.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if idInt, err := strconv.Atoi(idStr); err != nil {
|
||||||
|
session.FlashError(w, r, "Comment table ID invalid.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
tableID = uint64(idInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect URL must be relative.
|
||||||
|
if !strings.HasPrefix(fromURL, "/") {
|
||||||
|
// Maybe it's URL encoded?
|
||||||
|
fromURL, _ = url.QueryUnescape(fromURL)
|
||||||
|
if !strings.HasPrefix(fromURL, "/") {
|
||||||
|
fromURL = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate everything else.
|
||||||
|
if _, ok := models.CommentableTables[tableName]; !ok {
|
||||||
|
session.FlashError(w, r, "You can not comment on that.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Who will we notify about this comment? e.g. if commenting on a photo,
|
||||||
|
// this is the user who owns the photo.
|
||||||
|
var notifyUser *models.User
|
||||||
|
switch tableName {
|
||||||
|
case "photos":
|
||||||
|
if photo, err := models.GetPhoto(tableID); err == nil {
|
||||||
|
if user, err := models.GetUser(photo.UserID); err == nil {
|
||||||
|
notifyUser = user
|
||||||
|
} else {
|
||||||
|
log.Error("Comments: couldn't get NotifyUser for photo ID %d (user ID %d): %s",
|
||||||
|
tableID, photo.UserID, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("Comments: couldn't get NotifyUser for photo ID %d: %s", tableID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 to manage:
|
||||||
|
// - If the current user posted it
|
||||||
|
// - If we are an admin
|
||||||
|
// - If we are the notifyUser for this comment (they can delete, not edit).
|
||||||
|
if currentUser.ID != comment.UserID && !currentUser.IsAdmin &&
|
||||||
|
!(notifyUser != nil && currentUser.ID == notifyUser.ID && isDelete) {
|
||||||
|
templates.ForbiddenPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the form w/ the content of this message.
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
message = comment.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we DELETING this comment?
|
||||||
|
if isDelete {
|
||||||
|
if err := comment.Delete(); err != nil {
|
||||||
|
session.FlashError(w, r, "Error deleting your commenting: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "Your comment has been deleted.")
|
||||||
|
}
|
||||||
|
templates.Redirect(w, fromURL)
|
||||||
|
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
|
||||||
|
|
||||||
|
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, fromURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the comment.
|
||||||
|
if comment, err := models.AddComment(
|
||||||
|
currentUser,
|
||||||
|
tableName,
|
||||||
|
tableID,
|
||||||
|
message,
|
||||||
|
); err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't create comment: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "Comment added!")
|
||||||
|
templates.Redirect(w, fromURL)
|
||||||
|
|
||||||
|
// Notify the recipient of the comment.
|
||||||
|
if notifyUser != nil && notifyUser.ID != currentUser.ID {
|
||||||
|
notif := &models.Notification{
|
||||||
|
UserID: notifyUser.ID,
|
||||||
|
AboutUser: *currentUser,
|
||||||
|
Type: models.NotificationComment,
|
||||||
|
TableName: comment.TableName,
|
||||||
|
TableID: comment.TableID,
|
||||||
|
Message: message,
|
||||||
|
Link: fromURL,
|
||||||
|
}
|
||||||
|
if err := models.CreateNotification(notif); err != nil {
|
||||||
|
log.Error("Couldn't create Comment notification: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Intent": intent,
|
||||||
|
"EditCommentID": editCommentID,
|
||||||
|
"Message": message,
|
||||||
|
"TableName": tableName,
|
||||||
|
"TableID": tableID,
|
||||||
|
"Next": fromURL,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -52,12 +52,14 @@ func SiteGallery() http.HandlerFunc {
|
||||||
photoIDs = append(photoIDs, p.ID)
|
photoIDs = append(photoIDs, p.ID)
|
||||||
}
|
}
|
||||||
likeMap := models.MapLikes(currentUser, "photos", photoIDs)
|
likeMap := models.MapLikes(currentUser, "photos", photoIDs)
|
||||||
|
commentMap := models.MapCommentCounts("photos", photoIDs)
|
||||||
|
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"IsSiteGallery": true,
|
"IsSiteGallery": true,
|
||||||
"Photos": photos,
|
"Photos": photos,
|
||||||
"UserMap": userMap,
|
"UserMap": userMap,
|
||||||
"LikeMap": likeMap,
|
"LikeMap": likeMap,
|
||||||
|
"CommentMap": commentMap,
|
||||||
"Pager": pager,
|
"Pager": pager,
|
||||||
"ViewStyle": viewStyle,
|
"ViewStyle": viewStyle,
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ func UserPhotos() http.HandlerFunc {
|
||||||
photoIDs = append(photoIDs, p.ID)
|
photoIDs = append(photoIDs, p.ID)
|
||||||
}
|
}
|
||||||
likeMap := models.MapLikes(currentUser, "photos", photoIDs)
|
likeMap := models.MapLikes(currentUser, "photos", photoIDs)
|
||||||
|
commentMap := models.MapCommentCounts("photos", photoIDs)
|
||||||
|
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"IsOwnPhotos": currentUser.ID == user.ID,
|
"IsOwnPhotos": currentUser.ID == user.ID,
|
||||||
|
@ -105,6 +106,7 @@ func UserPhotos() http.HandlerFunc {
|
||||||
"PhotoCount": models.CountPhotos(user.ID),
|
"PhotoCount": models.CountPhotos(user.ID),
|
||||||
"Pager": pager,
|
"Pager": pager,
|
||||||
"LikeMap": likeMap,
|
"LikeMap": likeMap,
|
||||||
|
"CommentMap": commentMap,
|
||||||
"ViewStyle": viewStyle,
|
"ViewStyle": viewStyle,
|
||||||
"ExplicitCount": explicitCount,
|
"ExplicitCount": explicitCount,
|
||||||
}
|
}
|
||||||
|
|
92
pkg/controller/photo/view.go
Normal file
92
pkg/controller/photo/view.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package photo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// View photo controller to see the comment thread.
|
||||||
|
func View() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("photo/permalink.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Required query param: the photo ID.
|
||||||
|
var photo *models.Photo
|
||||||
|
if idStr := r.FormValue("id"); idStr == "" {
|
||||||
|
session.FlashError(w, r, "Missing photo ID parameter.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if idInt, err := strconv.Atoi(idStr); err != nil {
|
||||||
|
session.FlashError(w, r, "Invalid ID parameter.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if found, err := models.GetPhoto(uint64(idInt)); err != nil {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
photo = found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the photo's owner.
|
||||||
|
user, err := models.GetUser(photo.UserID)
|
||||||
|
if err != nil {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the current user in case they are viewing their own page.
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
|
||||||
|
}
|
||||||
|
var isOwnPhoto = currentUser.ID == user.ID
|
||||||
|
|
||||||
|
// Is either one blocking?
|
||||||
|
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
|
||||||
|
templates.NotFoundPage(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this user private and we're not friends?
|
||||||
|
var (
|
||||||
|
areFriends = models.AreFriends(user.ID, currentUser.ID)
|
||||||
|
isPrivate = user.Visibility == models.UserVisibilityPrivate && !areFriends
|
||||||
|
)
|
||||||
|
if isPrivate && !currentUser.IsAdmin && !isOwnPhoto {
|
||||||
|
session.FlashError(w, r, "This user's profile page and photo gallery are private.")
|
||||||
|
templates.Redirect(w, "/u/"+user.Username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Likes information about these photos.
|
||||||
|
likeMap := models.MapLikes(currentUser, "photos", []uint64{photo.ID})
|
||||||
|
commentMap := models.MapCommentCounts("photos", []uint64{photo.ID})
|
||||||
|
|
||||||
|
// Get all the comments.
|
||||||
|
comments, err := models.ListComments("photos", photo.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Couldn't list comments for photo %d: %s", photo.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"IsOwnPhoto": currentUser.ID == user.ID,
|
||||||
|
"User": user,
|
||||||
|
"Photo": photo,
|
||||||
|
"LikeMap": likeMap,
|
||||||
|
"CommentMap": commentMap,
|
||||||
|
"Comments": comments,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +19,12 @@ type Comment struct {
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommentableTables are the set of table names that allow comments (via the
|
||||||
|
// generic "/comments" URI which accepts a table_name param)
|
||||||
|
var CommentableTables = map[string]interface{}{
|
||||||
|
"photos": nil,
|
||||||
|
}
|
||||||
|
|
||||||
// Preload related tables for the forum (classmethod).
|
// Preload related tables for the forum (classmethod).
|
||||||
func (c *Comment) Preload() *gorm.DB {
|
func (c *Comment) Preload() *gorm.DB {
|
||||||
return DB.Preload("User.ProfilePhoto")
|
return DB.Preload("User.ProfilePhoto")
|
||||||
|
@ -74,6 +81,16 @@ func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagin
|
||||||
return cs, result.Error
|
return cs, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListComments returns a complete set of comments without paging.
|
||||||
|
func ListComments(tableName string, tableID uint64) ([]*Comment, error) {
|
||||||
|
var cs []*Comment
|
||||||
|
result := (&Comment{}).Preload().Where(
|
||||||
|
"table_name = ? AND table_id = ?",
|
||||||
|
tableName, tableID,
|
||||||
|
).Order("created_at asc").Find(&cs)
|
||||||
|
return cs, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
// Save a comment.
|
// Save a comment.
|
||||||
func (c *Comment) Save() error {
|
func (c *Comment) Save() error {
|
||||||
return DB.Save(c).Error
|
return DB.Save(c).Error
|
||||||
|
@ -83,3 +100,50 @@ func (c *Comment) Save() error {
|
||||||
func (c *Comment) Delete() error {
|
func (c *Comment) Delete() error {
|
||||||
return DB.Delete(c).Error
|
return DB.Delete(c).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentCountMap map[uint64]int64
|
||||||
|
|
||||||
|
// MapCommentCounts collects total numbers of comments over a set of table IDs. Returns a
|
||||||
|
// map of table ID (uint64) to comment counts for each (int64).
|
||||||
|
func MapCommentCounts(tableName string, tableIDs []uint64) CommentCountMap {
|
||||||
|
var result = CommentCountMap{}
|
||||||
|
|
||||||
|
// Initialize the result set.
|
||||||
|
for _, id := range tableIDs {
|
||||||
|
result[id] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold the result of the grouped count query.
|
||||||
|
type group struct {
|
||||||
|
ID uint64
|
||||||
|
Comments int64
|
||||||
|
}
|
||||||
|
var groups = []group{}
|
||||||
|
|
||||||
|
// Map the counts of comments to each of these IDs.
|
||||||
|
if res := DB.Table(
|
||||||
|
"comments",
|
||||||
|
).Select(
|
||||||
|
"table_id AS id, count(id) AS comments",
|
||||||
|
).Where(
|
||||||
|
"table_name = ? AND table_id IN ?",
|
||||||
|
tableName, tableIDs,
|
||||||
|
).Group("table_id").Scan(&groups); res.Error != nil {
|
||||||
|
log.Error("MapCommentCounts: count query: %s", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the counts back in.
|
||||||
|
for _, row := range groups {
|
||||||
|
result[row.ID] = row.Comments
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a comment count for the given table ID from the map.
|
||||||
|
func (cc CommentCountMap) Get(id uint64) int64 {
|
||||||
|
if value, ok := cc[id]; ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Notification struct {
|
||||||
TableName string // on which of your tables (photos, comments, ...)
|
TableName string // on which of your tables (photos, comments, ...)
|
||||||
TableID uint64
|
TableID uint64
|
||||||
Message string // text associated, e.g. copy of comment added
|
Message string // text associated, e.g. copy of comment added
|
||||||
|
Link string // associated URL, e.g. for comments
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
@ -148,8 +149,8 @@ func MapNotifications(ns []*Notification) NotificationMap {
|
||||||
"notifications.id IN ?",
|
"notifications.id IN ?",
|
||||||
IDs,
|
IDs,
|
||||||
).Scan(&scan)
|
).Scan(&scan)
|
||||||
if err != nil {
|
if err.Error != nil {
|
||||||
log.Error("Couldn't select photo IDs for notifications: %s", err)
|
log.Error("Couldn't select photo IDs for notifications: %s", err.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect and load all the photos by ID.
|
// Collect and load all the photos by ID.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/admin"
|
"code.nonshy.com/nonshy/website/pkg/controller/admin"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/api"
|
"code.nonshy.com/nonshy/website/pkg/controller/api"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/block"
|
"code.nonshy.com/nonshy/website/pkg/controller/block"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/controller/comment"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/forum"
|
"code.nonshy.com/nonshy/website/pkg/controller/forum"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/friend"
|
"code.nonshy.com/nonshy/website/pkg/controller/friend"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/inbox"
|
"code.nonshy.com/nonshy/website/pkg/controller/inbox"
|
||||||
|
@ -41,6 +42,7 @@ func New() http.Handler {
|
||||||
mux.Handle("/u/", middleware.LoginRequired(account.Profile()))
|
mux.Handle("/u/", middleware.LoginRequired(account.Profile()))
|
||||||
mux.Handle("/photo/upload", middleware.LoginRequired(photo.Upload()))
|
mux.Handle("/photo/upload", middleware.LoginRequired(photo.Upload()))
|
||||||
mux.Handle("/photo/u/", middleware.LoginRequired(photo.UserPhotos()))
|
mux.Handle("/photo/u/", middleware.LoginRequired(photo.UserPhotos()))
|
||||||
|
mux.Handle("/photo/view", middleware.LoginRequired(photo.View()))
|
||||||
mux.Handle("/photo/edit", middleware.LoginRequired(photo.Edit()))
|
mux.Handle("/photo/edit", middleware.LoginRequired(photo.Edit()))
|
||||||
mux.Handle("/photo/delete", middleware.LoginRequired(photo.Delete()))
|
mux.Handle("/photo/delete", middleware.LoginRequired(photo.Delete()))
|
||||||
mux.Handle("/photo/certification", middleware.LoginRequired(photo.Certification()))
|
mux.Handle("/photo/certification", middleware.LoginRequired(photo.Certification()))
|
||||||
|
@ -51,6 +53,7 @@ func New() http.Handler {
|
||||||
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
|
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
|
||||||
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))
|
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))
|
||||||
mux.Handle("/users/blocked", middleware.LoginRequired(block.Blocked()))
|
mux.Handle("/users/blocked", middleware.LoginRequired(block.Blocked()))
|
||||||
|
mux.Handle("/comments", middleware.LoginRequired(comment.PostComment()))
|
||||||
mux.Handle("/admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
mux.Handle("/admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
||||||
|
|
||||||
// Certification Required. Pages that only full (verified) members can access.
|
// Certification Required. Pages that only full (verified) members can access.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -74,6 +75,13 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
|
||||||
"SubtractInt": func(a, b int) int {
|
"SubtractInt": func(a, b int) int {
|
||||||
return a - b
|
return a - b
|
||||||
},
|
},
|
||||||
|
"UrlEncode": func(values ...interface{}) string {
|
||||||
|
var result string
|
||||||
|
for _, value := range values {
|
||||||
|
result += url.QueryEscape(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -194,15 +194,32 @@
|
||||||
<a href="/u/{{.AboutUser.Username}}"><strong>{{.AboutUser.Username}}</strong></a>
|
<a href="/u/{{.AboutUser.Username}}"><strong>{{.AboutUser.Username}}</strong></a>
|
||||||
liked your
|
liked your
|
||||||
{{if eq .TableName "photos"}}
|
{{if eq .TableName "photos"}}
|
||||||
|
{{if $Body.Photo}}
|
||||||
|
<a href="/photo/view?id={{$Body.Photo.ID}}">photo</a>.
|
||||||
|
{{else}}
|
||||||
photo.
|
photo.
|
||||||
|
{{end}}
|
||||||
{{else if eq .TableName "users"}}
|
{{else if eq .TableName "users"}}
|
||||||
profile page.
|
profile page.
|
||||||
{{else}}
|
{{else}}
|
||||||
{{.TableName}}.
|
{{.TableName}}.
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
|
{{else if eq .Type "comment"}}
|
||||||
|
<span class="icon"><i class="fa fa-comment has-text-success"></i></span>
|
||||||
|
<span>
|
||||||
|
<a href="/u/{{.AboutUser.Username}}"><strong>{{.AboutUser.Username}}</strong></a>
|
||||||
|
commented on your
|
||||||
|
<a href="{{.Link}}">
|
||||||
|
{{if eq .TableName "photos"}}
|
||||||
|
photo:
|
||||||
|
{{else}}
|
||||||
|
{{.TableName}}:
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
{{else if eq .Type "friendship_approved"}}
|
{{else if eq .Type "friendship_approved"}}
|
||||||
<span class="icon"><i class="fa fa-user-group"></i></span>
|
<span class="icon"><i class="fa fa-user-group has-text-success"></i></span>
|
||||||
<span>
|
<span>
|
||||||
<a href="/u/{{.AboutUser.Username}}"><strong>{{.AboutUser.Username}}</strong></a>
|
<a href="/u/{{.AboutUser.Username}}"><strong>{{.AboutUser.Username}}</strong></a>
|
||||||
accepted your friend request!
|
accepted your friend request!
|
||||||
|
@ -224,7 +241,7 @@
|
||||||
|
|
||||||
<!-- Attached message? -->
|
<!-- Attached message? -->
|
||||||
{{if .Message}}
|
{{if .Message}}
|
||||||
<div class="block content">
|
<div class="block content mb-1">
|
||||||
{{ToMarkdown .Message}}
|
{{ToMarkdown .Message}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -232,7 +249,15 @@
|
||||||
<!-- Photo caption? -->
|
<!-- Photo caption? -->
|
||||||
{{if $Body.Photo}}
|
{{if $Body.Photo}}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
<!-- If it's a comment, have a link to view it -->
|
||||||
|
{{if eq .Type "comment"}}
|
||||||
|
<div class="is-size-7 pt-1">
|
||||||
|
<span class="icon"><i class="fa fa-arrow-right"></i></span>
|
||||||
|
<a href="{{.Link}}">See all comments</a>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<em>{{or $Body.Photo.Caption "No caption."}}</em>
|
<em>{{or $Body.Photo.Caption "No caption."}}</em>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -245,7 +270,9 @@
|
||||||
<!-- Attached photo? -->
|
<!-- Attached photo? -->
|
||||||
{{if $Body.PhotoID}}
|
{{if $Body.PhotoID}}
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
|
<a href="/photo/view?id={{$Body.Photo.ID}}">
|
||||||
<img src="{{PhotoURL $Body.Photo.Filename}}">
|
<img src="{{PhotoURL $Body.Photo.Filename}}">
|
||||||
|
</a>
|
||||||
|
|
||||||
{{if $Body.Photo.Caption}}
|
{{if $Body.Photo.Caption}}
|
||||||
<small>{{$Body.Photo.Caption}}</small>
|
<small>{{$Body.Photo.Caption}}</small>
|
||||||
|
|
91
web/templates/comment/post_comment.html
Normal file
91
web/templates/comment/post_comment.html
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
{{define "title"}}
|
||||||
|
{{if .EditCommentID}}
|
||||||
|
Edit Comment
|
||||||
|
{{else}}
|
||||||
|
New Comment
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero is-info is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
{{if .EditCommentID}}
|
||||||
|
Edit Comment
|
||||||
|
{{else}}
|
||||||
|
Add Comment
|
||||||
|
{{end}}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
|
||||||
|
<div class="card" style="width: 100%; max-width: 640px">
|
||||||
|
<header class="card-header has-background-link">
|
||||||
|
<p class="card-header-title has-text-light">
|
||||||
|
<span class="icon"><i class="fa fa-message"></i></span>
|
||||||
|
{{if .EditCommentID}}
|
||||||
|
Edit Comment
|
||||||
|
{{else}}
|
||||||
|
New Comment
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
|
||||||
|
{{if and (eq .Request.Method "POST") (ne .Message "")}}
|
||||||
|
<label class="label">Preview:</label>
|
||||||
|
<div class="box content has-background-warning-light">
|
||||||
|
{{ToMarkdown .Message}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<form action="{{.Request.URL.Path}}" method="POST">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="table_name" value="{{.TableName}}">
|
||||||
|
<input type="hidden" name="table_id" value="{{.TableID}}">
|
||||||
|
<input type="hidden" name="next" value="{{.Next}}">
|
||||||
|
<input type="hidden" name="edit" value="{{.EditCommentID}}">
|
||||||
|
|
||||||
|
<div class="field block">
|
||||||
|
<label for="message" class="label">Message</label>
|
||||||
|
<textarea class="textarea" cols="80" rows="8"
|
||||||
|
name="message"
|
||||||
|
id="message"
|
||||||
|
required
|
||||||
|
placeholder="Message">{{.Message}}</textarea>
|
||||||
|
<p class="help">
|
||||||
|
Markdown formatting supported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field has-text-centered">
|
||||||
|
<button type="submit"
|
||||||
|
name="intent"
|
||||||
|
value="preview"
|
||||||
|
class="button is-link">
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
name="intent"
|
||||||
|
value="submit"
|
||||||
|
class="button is-success">
|
||||||
|
Post Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -127,6 +127,14 @@
|
||||||
<div class="column content">
|
<div class="column content">
|
||||||
{{ToMarkdown .Message}}
|
{{ToMarkdown .Message}}
|
||||||
|
|
||||||
|
{{if .UpdatedAt.After .CreatedAt}}
|
||||||
|
<div class="mt-4">
|
||||||
|
<em title="{{.UpdatedAt.Format "2006-01-02 15:04:05"}}">
|
||||||
|
<small>Edited {{SincePrettyCoarse .UpdatedAt}} ago</small>
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<hr class="has-background-grey mb-2">
|
<hr class="has-background-grey mb-2">
|
||||||
|
|
||||||
<div class="columns is-mobile is-multiline is-size-7 mb-0">
|
<div class="columns is-mobile is-multiline is-size-7 mb-0">
|
||||||
|
|
|
@ -263,8 +263,9 @@
|
||||||
|
|
||||||
{{template "card-body" .}}
|
{{template "card-body" .}}
|
||||||
|
|
||||||
<!-- Like button -->
|
<!-- Like & Comments buttons -->
|
||||||
<div class="mt-4">
|
<div class="mt-4 columns is-centered is-mobile is-gapless">
|
||||||
|
<div class="column is-narrow mr-1">
|
||||||
{{$Like := $Root.LikeMap.Get .ID}}
|
{{$Like := $Root.LikeMap.Get .ID}}
|
||||||
<button type="button" class="button is-small nonshy-like-button"
|
<button type="button" class="button is-small nonshy-like-button"
|
||||||
data-table-name="photos" data-table-id="{{.ID}}"
|
data-table-name="photos" data-table-id="{{.ID}}"
|
||||||
|
@ -278,6 +279,14 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
{{$Comments := $Root.CommentMap.Get .ID}}
|
||||||
|
<a href="/photo/view?id={{.ID}}#comments" class="button is-small">
|
||||||
|
<span class="icon"><i class="fa fa-comment"></i></span>
|
||||||
|
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
|
@ -360,8 +369,9 @@
|
||||||
|
|
||||||
{{template "card-body" .}}
|
{{template "card-body" .}}
|
||||||
|
|
||||||
<!-- Like button -->
|
<!-- Like & Comments buttons -->
|
||||||
<div class="mt-4">
|
<div class="mt-4 columns is-centered is-mobile is-gapless">
|
||||||
|
<div class="column is-narrow mr-1">
|
||||||
{{$Like := $Root.LikeMap.Get .ID}}
|
{{$Like := $Root.LikeMap.Get .ID}}
|
||||||
<button type="button" class="button is-small nonshy-like-button"
|
<button type="button" class="button is-small nonshy-like-button"
|
||||||
data-table-name="photos" data-table-id="{{.ID}}"
|
data-table-name="photos" data-table-id="{{.ID}}"
|
||||||
|
@ -375,6 +385,14 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
{{$Comments := $Root.CommentMap.Get .ID}}
|
||||||
|
<a href="/photo/view?id={{.ID}}#comments" class="button is-small">
|
||||||
|
<span class="icon"><i class="fa fa-comment"></i></span>
|
||||||
|
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
|
@ -385,7 +403,7 @@
|
||||||
{{if not $Root.IsOwnPhotos}}
|
{{if not $Root.IsOwnPhotos}}
|
||||||
<a class="card-footer-item has-text-danger" href="/contact?intent=report&subject=report.photo&id={{.ID}}">
|
<a class="card-footer-item has-text-danger" href="/contact?intent=report&subject=report.photo&id={{.ID}}">
|
||||||
<span class="icon"><i class="fa fa-flag"></i></span>
|
<span class="icon"><i class="fa fa-flag"></i></span>
|
||||||
<span>Report</span>
|
<span class="is-hidden-desktop">Report</span>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
264
web/templates/photo/permalink.html
Normal file
264
web/templates/photo/permalink.html
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
{{define "title"}}Upload a Photo{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero is-info is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
<span class="icon mr-4">
|
||||||
|
<i class="fa fa-image"></i>
|
||||||
|
</span>
|
||||||
|
<span>{{or .Photo.Caption "Photo"}}</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ $Root := . }}
|
||||||
|
{{ $User := .CurrentUser }}
|
||||||
|
{{ $Comments := .CommentMap.Get .Photo.ID }}
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/u/{{.User.Username}}">
|
||||||
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
|
<span>{{.User.Username}}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/photo/u/{{.User.Username}}">Photos</a>
|
||||||
|
</li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="{{.Request.URL.Path}}" aria-current="page">Comments</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
|
||||||
|
<!-- Photo Card -->
|
||||||
|
<div class="card block">
|
||||||
|
<header class="card-header {{if .Photo.Explicit}}has-background-danger{{else}}has-background-link{{end}}">
|
||||||
|
<div class="card-header-title has-text-light">
|
||||||
|
<div class="columns is-mobile is-gapless nonshy-fullwidth">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<figure class="image is-24x24 mr-2">
|
||||||
|
{{if gt .User.ProfilePhoto.ID 0}}
|
||||||
|
<img src="{{PhotoURL .User.ProfilePhoto.CroppedFilename}}" class="is-rounded">
|
||||||
|
{{else}}
|
||||||
|
<img src="/static/img/shy.png" class="is-rounded has-background-warning">
|
||||||
|
{{end}}
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<a href="/u/{{.User.Username}}" class="has-text-light">
|
||||||
|
{{.User.Username}}
|
||||||
|
<i class="fa fa-external-link ml-2"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<span class="icon">
|
||||||
|
{{if eq .Photo.Visibility "friends"}}
|
||||||
|
<i class="fa fa-user-group has-text-warning" title="Friends"></i>
|
||||||
|
{{else if eq .Photo.Visibility "private"}}
|
||||||
|
<i class="fa fa-lock has-text-private-light" title="Private"></i>
|
||||||
|
{{else}}
|
||||||
|
<i class="fa fa-eye has-text-link-light" title="Public"></i>
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image">
|
||||||
|
<img src="{{PhotoURL .Photo.Filename}}">
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
{{if .Photo.Caption}}
|
||||||
|
{{.Photo.Caption}}
|
||||||
|
{{else}}<em>No caption</em>{{end}}
|
||||||
|
|
||||||
|
<!-- Like & Comments buttons -->
|
||||||
|
<div class="mt-4 mb-2 columns is-centered is-mobile is-gapless">
|
||||||
|
<div class="column is-narrow mr-2">
|
||||||
|
{{$Like := .LikeMap.Get .Photo.ID}}
|
||||||
|
<button type="button" class="button is-small nonshy-like-button"
|
||||||
|
data-table-name="photos" data-table-id="{{.Photo.ID}}"
|
||||||
|
title="Like">
|
||||||
|
<span class="icon{{if $Like.UserLikes}} has-text-danger{{end}}"><i class="fa fa-heart"></i></span>
|
||||||
|
<span class="nonshy-likes">
|
||||||
|
Like
|
||||||
|
{{if gt $Like.Count 0}}
|
||||||
|
({{$Like.Count}})
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/photo/view?id={{.Photo.ID}}#comments" class="button is-small">
|
||||||
|
<span class="icon"><i class="fa fa-comment"></i></span>
|
||||||
|
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Photo controls buttons (edit/delete/report) -->
|
||||||
|
<div class="my-0 columns is-centered is-mobile is-gapless">
|
||||||
|
<!-- Owned photo: have edit/delete buttons too -->
|
||||||
|
{{if or .IsOwnPhoto .CurrentUser.IsAdmin}}
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/photo/edit?id={{.Photo.ID}}" class="button is-small">
|
||||||
|
<span class="icon"><i class="fa fa-edit"></i></span>
|
||||||
|
<span>Edit</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow ml-2">
|
||||||
|
<a href="/photo/delete?id={{.Photo.ID}}" class="button is-small has-text-danger">
|
||||||
|
<span class="icon"><i class="fa fa-trash"></i></span>
|
||||||
|
<span>Delete</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Report button except on your own pic -->
|
||||||
|
{{if not .IsOwnPhoto}}
|
||||||
|
<div class="column is-narrow ml-2">
|
||||||
|
<a href="/contact?intent=report&subject=report.photo&id={{.Photo.ID}}" class="button is-small has-text-danger">
|
||||||
|
<span class="icon"><i class="fa fa-flag"></i></span>
|
||||||
|
<span>Report</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- /photo card -->
|
||||||
|
|
||||||
|
<!-- Comments Card -->
|
||||||
|
<div class="card" id="comments">
|
||||||
|
<header class="card-header has-background-success">
|
||||||
|
<p class="card-header-title has-text-light">
|
||||||
|
<span class="icon mr-2"><i class="fa fa-comment"></i></span>
|
||||||
|
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<form action="/comments" method="POST">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="table_name" value="photos">
|
||||||
|
<input type="hidden" name="table_id" value="{{.Photo.ID}}">
|
||||||
|
<input type="hidden" name="next" value="{{.Request.URL.Path}}?id={{.Photo.ID}}">
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="message">Add your comment</label>
|
||||||
|
<textarea class="textarea" cols="80" rows="4"
|
||||||
|
name="message" id="message"
|
||||||
|
placeholder="Add your comment"></textarea>
|
||||||
|
<p class="help">
|
||||||
|
Markdown formatting supported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field has-text-centered">
|
||||||
|
<button type="submit" class="button is-link"
|
||||||
|
name="intent" value="preview">
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="button is-success"
|
||||||
|
name="intent" value="submit">
|
||||||
|
Post Comment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="is-dark">
|
||||||
|
|
||||||
|
{{if eq $Comments 0}}
|
||||||
|
<p>
|
||||||
|
<em>There are no comments yet.</em>
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
{{range .Comments}}
|
||||||
|
<div class="box has-background-link-light">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-2 has-text-centered">
|
||||||
|
<div>
|
||||||
|
<a href="/u/{{.User.Username}}">
|
||||||
|
<figure class="image is-96x96 is-inline-block">
|
||||||
|
{{if .User.ProfilePhoto.ID}}
|
||||||
|
<img src="{{PhotoURL .User.ProfilePhoto.CroppedFilename}}">
|
||||||
|
{{else}}
|
||||||
|
<img src="/static/img/shy.png">
|
||||||
|
{{end}}
|
||||||
|
</figure>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a href="/u/{{.User.Username}}">{{.User.Username}}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column content">
|
||||||
|
{{ToMarkdown .Message}}
|
||||||
|
|
||||||
|
{{if .UpdatedAt.After .CreatedAt}}
|
||||||
|
<div class="mt-4">
|
||||||
|
<em title="{{.UpdatedAt.Format "2006-01-02 15:04:05"}}">
|
||||||
|
<small>Edited {{SincePrettyCoarse .UpdatedAt}} ago</small>
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<hr class="has-background-grey mb-2">
|
||||||
|
|
||||||
|
<div class="columns is-mobile is-multiline is-size-7 mb-0">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<span title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">
|
||||||
|
{{SincePrettyCoarse .CreatedAt}} ago
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/contact?intent=report&subject=report.comment&id={{.ID}}" class="has-text-dark">
|
||||||
|
<span class="icon"><i class="fa fa-flag"></i></span>
|
||||||
|
<span>Report</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if or $Root.CurrentUser.IsAdmin (eq $Root.CurrentUser.ID .User.ID)}}
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/comments?table_name=photos&table_id={{$Root.Photo.ID}}&edit={{.ID}}&next={{UrlEncode $Root.Request.URL.Path "?id=" $Root.Photo.ID}}" class="has-text-dark">
|
||||||
|
<span class="icon"><i class="fa fa-edit"></i></span>
|
||||||
|
<span>Edit</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- The poster, the photo owner, and the admin can delete the comment -->
|
||||||
|
{{if or $Root.CurrentUser.IsAdmin (eq $Root.CurrentUser.ID .User.ID) $Root.IsOwnPhoto}}
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/comments?table_name=photos&table_id={{$Root.Photo.ID}}&edit={{.ID}}&delete=true&next={{UrlEncode $Root.Request.URL.Path "?id=" $Root.Photo.ID}}" onclick="return confirm('Are you sure you want to delete this comment?')" class="has-text-dark">
|
||||||
|
<span class="icon"><i class="fa fa-trash"></i></span>
|
||||||
|
<span>Delete</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -25,7 +25,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This page was last updated on <strong>August 15, 2022.</strong>
|
This page was last updated on <strong>August 26, 2022.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -43,6 +43,21 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>
|
||||||
|
You may mark your entire profile as "Private" which limits some of the contact you
|
||||||
|
may receive:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Only users you have approved as a friend can see your profile and your
|
||||||
|
photo gallery.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Your photos will <strong>never</strong> appear on the Site Gallery - not
|
||||||
|
even to your friends. They will only see your photos by visiting your
|
||||||
|
profile page directly.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Profile photos have visibility settings including Public, Friends-only or Private:
|
Profile photos have visibility settings including Public, Friends-only or Private:
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -73,10 +88,12 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
When you are uploading or editing a photo, there is a checkbox labeled "Gallery" where you
|
When you are uploading or editing a photo, there is a checkbox labeled "Gallery" where you
|
||||||
can opt your photo in (or out) of the Site Gallery. Only <strong>public</strong> photos will
|
can opt your photo in (or out) of the Site Gallery. Only your <strong>public</strong> photos
|
||||||
ever appear on the Site Gallery (never private or friends-only photos). You are also able to
|
will appear on the Site Gallery by default; your <strong>friends-only</strong> photos may
|
||||||
<em>exclude</em> a public photo from the Site Gallery by unchecking the "Gallery" box on that
|
appear there for people you approved as a friend, or your private photos to people for whom
|
||||||
photo.
|
you have granted access. You are also able to <em>exclude</em> a photo from the Site Gallery
|
||||||
|
by unchecking the "Gallery" box on that photo -- then it will only be viewable on your own
|
||||||
|
profile page, given its other permissions (friends/private).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Deletion of User Data</h3>
|
<h3>Deletion of User Data</h3>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user