diff --git a/pkg/controller/account/profile.go b/pkg/controller/account/profile.go index 3082b99..333a074 100644 --- a/pkg/controller/account/profile.go +++ b/pkg/controller/account/profile.go @@ -61,10 +61,11 @@ func Profile() http.HandlerFunc { likeMap := models.MapLikes(currentUser, "users", []uint64{user.ID}) vars := map[string]interface{}{ - "User": user, - "LikeMap": likeMap, - "IsFriend": isFriend, - "IsPrivate": isPrivate, + "User": user, + "LikeMap": likeMap, + "IsFriend": isFriend, + "IsPrivate": isPrivate, + "PhotoCount": models.CountPhotos(user.ID), } if err := tmpl.Execute(w, r, vars); err != nil { diff --git a/pkg/controller/photo/user_gallery.go b/pkg/controller/photo/user_gallery.go index 3738454..90cb21f 100644 --- a/pkg/controller/photo/user_gallery.go +++ b/pkg/controller/photo/user_gallery.go @@ -102,6 +102,7 @@ func UserPhotos() http.HandlerFunc { "IsOwnPhotos": currentUser.ID == user.ID, "User": user, "Photos": photos, + "PhotoCount": models.CountPhotos(user.ID), "Pager": pager, "LikeMap": likeMap, "ViewStyle": viewStyle, diff --git a/pkg/models/comment.go b/pkg/models/comment.go index 4197fd7..dadfb78 100644 --- a/pkg/models/comment.go +++ b/pkg/models/comment.go @@ -30,6 +30,21 @@ func GetComment(id uint64) (*Comment, error) { return c, result.Error } +// GetComments queries a set of comment IDs and returns them mapped. +func GetComments(IDs []uint64) (map[uint64]*Comment, error) { + var ( + mt = map[uint64]*Comment{} + ts = []*Comment{} + ) + + result := (&Comment{}).Preload().Where("id IN ?", IDs).Find(&ts) + for _, row := range ts { + mt[row.ID] = row + } + + return mt, result.Error +} + // AddComment about anything. func AddComment(user *User, tableName string, tableID uint64, message string) (*Comment, error) { c := &Comment{ diff --git a/pkg/models/forum_stats.go b/pkg/models/forum_stats.go index 8985ecc..b56bb10 100644 --- a/pkg/models/forum_stats.go +++ b/pkg/models/forum_stats.go @@ -5,6 +5,7 @@ import "git.kirsle.net/apps/gosocial/pkg/log" // ForumStatistics queries for forum-level statistics. type ForumStatistics struct { RecentThread *Thread + RecentPost *Comment // latest post on the recent thread Threads uint64 Posts uint64 Users uint64 @@ -30,6 +31,7 @@ func MapForumStatistics(forums []*Forum) ForumStatsMap { result.generatePostCount(IDs) result.generateUserCount(IDs) result.generateRecentThreads(IDs) + result.generateRecentPosts(IDs) return result } @@ -201,3 +203,65 @@ func (ts ForumStatsMap) generateRecentThreads(IDs []uint64) { } } } + +// Compute the recent post on each recent thread of each forum. +func (ts ForumStatsMap) generateRecentPosts(IDs []uint64) { + // We already have the RecentThread of each forum - map these Thread IDs to recent comments. + var ( + threadIDs = []uint64{} + threadStatsMap = map[uint64]*ForumStatistics{} + ) + for _, stats := range ts { + if stats.RecentThread != nil { + threadIDs = append(threadIDs, stats.RecentThread.ID) + threadStatsMap[stats.RecentThread.ID] = stats + } + } + + // The newest posts in these threads. + type scanner struct { + ThreadID uint64 + CommentID uint64 + } + var scan []scanner + err := DB.Table( + "comments", + ).Select( + "table_id AS thread_id, id AS comment_id", + // "forum_id, id AS thread_id, updated_at", + ).Where( + `table_name='threads' AND table_id IN ? + AND updated_at = (SELECT MAX(updated_at) + FROM comments c2 + WHERE c2.table_name=comments.table_name + AND c2.table_id=comments.table_id + )`, + threadIDs, + ).Order( + "updated_at desc", + ).Scan(&scan) + if err.Error != nil { + log.Error("Getting most recent post IDs: %s", err.Error) + } + + // Gather the ThreadID:CommentID map. + var ( + commentIDs = []uint64{} + commentStatsMap = map[uint64]*ForumStatistics{} + ) + for _, row := range scan { + if stats, ok := threadStatsMap[row.ThreadID]; ok { + commentStatsMap[row.CommentID] = stats + commentIDs = append(commentIDs, row.CommentID) + } + } + + // Select all these comments and map them in. + if commentMap, err := GetComments(commentIDs); err == nil { + for commentId, comment := range commentMap { + if stats, ok := commentStatsMap[commentId]; ok { + stats.RecentPost = comment + } + } + } +} diff --git a/pkg/models/photo.go b/pkg/models/photo.go index 3d4a10e..8a1b4f4 100644 --- a/pkg/models/photo.go +++ b/pkg/models/photo.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "git.kirsle.net/apps/gosocial/pkg/log" "gorm.io/gorm" ) @@ -122,13 +123,16 @@ func PaginateUserPhotos(userID uint64, visibility []PhotoVisibility, explicitOK } // CountPhotos returns the total number of photos on a user's account. -func CountPhotos(userID uint64) (int64, error) { +func CountPhotos(userID uint64) int64 { var count int64 result := DB.Where( "user_id = ?", userID, ).Model(&Photo{}).Count(&count) - return count, result.Error + if result.Error != nil { + log.Error("CountPhotos(%d): %s", userID, result.Error) + } + return count } // CountExplicitPhotos returns the number of explicit photos a user has (so non-explicit viewers can see some do exist) diff --git a/pkg/photo/quota.go b/pkg/photo/quota.go index 97d9b27..4cd435e 100644 --- a/pkg/photo/quota.go +++ b/pkg/photo/quota.go @@ -8,7 +8,7 @@ import ( // QuoteForUser returns the current photo usage quota for a given user. func QuotaForUser(u *models.User) (current, allowed int) { // Count their photos. - count, _ := models.CountPhotos(u.ID) + count := models.CountPhotos(u.ID) // What is their quota at? var quota int diff --git a/web/static/css/theme.css b/web/static/css/theme.css index 6364721..c3113c2 100644 --- a/web/static/css/theme.css +++ b/web/static/css/theme.css @@ -43,7 +43,7 @@ /* Mobile: notification badge near the hamburger menu */ .nonshy-mobile-notification { position: absolute; - top: 12px; + top: 10px; right: 50px; z-index: 1000; } diff --git a/web/static/js/bulma.js b/web/static/js/bulma.js index 3b1eda2..f116d2e 100644 --- a/web/static/js/bulma.js +++ b/web/static/js/bulma.js @@ -42,6 +42,11 @@ document.addEventListener('DOMContentLoaded', () => { // Touching the user drop-down button toggles it. userMenu.addEventListener("touchstart", (e) => { + // On mobile/tablet screens they had to hamburger menu their way here anyway, let it thru. + if (screen.width < 1024) { + return; + } + e.preventDefault(); if (userMenu.classList.contains(activeClass)) { userMenu.classList.remove(activeClass); diff --git a/web/templates/account/profile.html b/web/templates/account/profile.html index f162679..1d66b98 100644 --- a/web/templates/account/profile.html +++ b/web/templates/account/profile.html @@ -194,7 +194,10 @@ - Photos + + Photos + {{if .PhotoCount}}{{.PhotoCount}}{{end}} + diff --git a/web/templates/base.html b/web/templates/base.html index 3d80f33..0f61998 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -47,16 +47,16 @@ Home - - - Gallery - - Forum + + + Gallery + + Friends @@ -83,23 +83,29 @@ {{end}} - About + + About {{PrettyTitle}} - FAQ + + FAQ - Terms of Service + + Terms of Service - Privacy Policy + + Privacy Policy - Contact + + Contact - Report an issue + + Report an issue @@ -129,7 +135,8 @@ {{ else }} @@ -173,33 +196,45 @@ - {{if gt .NavTotalNotifications 0}} + {{if .LoggedIn}}
- {{if gt .NavFriendRequests 0}} - - - {{.NavFriendRequests}} - - {{end}} + + + - {{if gt .NavUnreadMessages 0}} - - - {{.NavUnreadMessages}} - - {{end}} + + + + + + + {{if gt .NavFriendRequests 0}} + {{.NavFriendRequests}} + {{end}} + + + + + + {{if gt .NavUnreadMessages 0}} + {{.NavUnreadMessages}} + {{end}} + {{if gt .NavUnreadNotifications 0}} - + - {{.NavUnreadNotifications}} + {{.NavUnreadNotifications}} {{end}} {{if gt .NavAdminNotifications 0}} - + - {{.NavAdminNotifications}} + {{.NavAdminNotifications}} {{end}}
@@ -228,7 +263,7 @@ {{template "content" .}} -
+
© {{.YYYY}} {{.Title}}
diff --git a/web/templates/faq.html b/web/templates/faq.html index f07f58d..13f483d 100644 --- a/web/templates/faq.html +++ b/web/templates/faq.html @@ -118,6 +118,77 @@ content from other users -- by default this site is "normal nudes" friendly!

+

Forum FAQs

+ +

What do the various badges on the forum mean?

+ +

+ You may see some of these badges on the forums or their posts. These are their meanings: +

+ +
    +
  • + + + Explicit + - + on a forum it means the entire forum is "NSFW"; + but individual topics within an otherwise non-explicit forum may also opt in to the + Explicit tag if its content is border-line. You will not see any Explicit forums or + posts unless you opt-in to see explicit content in your settings. +
  • +
  • + + + Privileged + - + only a forum's moderators can create new topics in a Privileged forum (such as the + forum for Site Announcements). Moderators include the site admins, the creator of + the forum, and any additional moderators appointed by the forum creator. +
  • +
  • + + + Pinned + - + these forum posts are pinned to the top of a forum, appearing above regular posts + on the first page of the forum. +
  • +
  • + + + No Reply + - + topics with this badge can not accept any new replies. Some types of announcement + posts may start with this badge from the beginning; other threads that are locked + by a moderator may gain this badge if the conversation was going off the rails. +
  • +
+ +

Can I create my own forums?

+ +

+ This feature is coming soon! Users will be allowed to create their own forums and + act as moderator within their own board. The forum admin pages need a bit more + spit & polish before it's ready! +

+ +

+ Some related features with managing your own forums will include: +

+ +
    +
  • + You'll be able to make your forum "invite-only" if you want, where only approved + members can see and reply to threads. +
  • +
  • + You'll be able to choose other users to help you moderate your forum. As the forum + owner, you'll retain admin control of your forum unless you assign ownership away + to another member. +
  • +
+

Technical FAQs

Why did you build a custom website?

diff --git a/web/templates/forum/board_index.html b/web/templates/forum/board_index.html index 5b446cc..15662cd 100644 --- a/web/templates/forum/board_index.html +++ b/web/templates/forum/board_index.html @@ -17,7 +17,7 @@ {{$Root := .}} -
+