Spit and polish
* Show follower counts on forums * Sort by popularity (follow count)
This commit is contained in:
parent
56a6190ce9
commit
242333d8b7
|
@ -124,7 +124,7 @@ const (
|
||||||
ThreadViewDebounceCooldown = 1 * time.Hour
|
ThreadViewDebounceCooldown = 1 * time.Hour
|
||||||
|
|
||||||
// Enable user-owned forums (feature flag)
|
// Enable user-owned forums (feature flag)
|
||||||
UserForumsEnabled = false
|
UserForumsEnabled = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// User-Owned Forums: Quota settings for how many forums a user can own.
|
// User-Owned Forums: Quota settings for how many forums a user can own.
|
||||||
|
|
|
@ -23,6 +23,7 @@ func Explore() http.HandlerFunc {
|
||||||
|
|
||||||
// Special sort handlers.
|
// Special sort handlers.
|
||||||
// See PaginateForums for expanded handlers for these.
|
// See PaginateForums for expanded handlers for these.
|
||||||
|
"by_followers",
|
||||||
"by_latest",
|
"by_latest",
|
||||||
"by_threads",
|
"by_threads",
|
||||||
"by_posts",
|
"by_posts",
|
||||||
|
@ -99,6 +100,7 @@ func Explore() http.HandlerFunc {
|
||||||
"Categories": categorized,
|
"Categories": categorized,
|
||||||
"ForumMap": forumMap,
|
"ForumMap": forumMap,
|
||||||
"FollowMap": followMap,
|
"FollowMap": followMap,
|
||||||
|
"FollowersMap": models.MapForumFollowers(forums),
|
||||||
|
|
||||||
// Search filters
|
// Search filters
|
||||||
"SearchTerm": searchTerm,
|
"SearchTerm": searchTerm,
|
||||||
|
|
|
@ -81,12 +81,13 @@ func Forum() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"Forum": forum,
|
"Forum": forum,
|
||||||
"ForumModerators": mods,
|
"ForumModerators": mods,
|
||||||
"IsForumSubscribed": models.IsForumSubscribed(currentUser, forum),
|
"ForumSubscriberCount": models.CountForumMemberships(forum),
|
||||||
"Threads": threads,
|
"IsForumSubscribed": models.IsForumSubscribed(currentUser, forum),
|
||||||
"ThreadMap": threadMap,
|
"Threads": threads,
|
||||||
"Pager": pager,
|
"ThreadMap": threadMap,
|
||||||
|
"Pager": pager,
|
||||||
}
|
}
|
||||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
@ -76,10 +76,11 @@ func Landing() http.HandlerFunc {
|
||||||
followMap := models.MapForumMemberships(currentUser, forums)
|
followMap := models.MapForumMemberships(currentUser, forums)
|
||||||
|
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"Pager": pager,
|
"Pager": pager,
|
||||||
"Categories": categorized,
|
"Categories": categorized,
|
||||||
"ForumMap": forumMap,
|
"ForumMap": forumMap,
|
||||||
"FollowMap": followMap,
|
"FollowMap": followMap,
|
||||||
|
"FollowersMap": models.MapForumFollowers(forums),
|
||||||
|
|
||||||
// Current viewer's forum quota.
|
// Current viewer's forum quota.
|
||||||
"ForumQuota": models.ComputeForumQuota(currentUser),
|
"ForumQuota": models.ComputeForumQuota(currentUser),
|
||||||
|
|
|
@ -168,6 +168,12 @@ func PaginateForums(user *User, categories []string, search *Search, subscribed
|
||||||
|
|
||||||
// Custom SORT parameters.
|
// Custom SORT parameters.
|
||||||
switch pager.Sort {
|
switch pager.Sort {
|
||||||
|
case "by_followers":
|
||||||
|
pager.Sort = `(
|
||||||
|
SELECT count(forum_memberships.id)
|
||||||
|
FROM forum_memberships
|
||||||
|
WHERE forum_memberships.forum_id = forums.id
|
||||||
|
) DESC`
|
||||||
case "by_latest":
|
case "by_latest":
|
||||||
pager.Sort = `(
|
pager.Sort = `(
|
||||||
SELECT MAX(threads.updated_at)
|
SELECT MAX(threads.updated_at)
|
||||||
|
|
|
@ -166,6 +166,16 @@ func (u *User) HasForumSubscriptions() bool {
|
||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountForumMemberships counts how many subscribers a forum has.
|
||||||
|
func CountForumMemberships(forum *Forum) int64 {
|
||||||
|
var count int64
|
||||||
|
DB.Model(&ForumMembership{}).Where(
|
||||||
|
"forum_id = ?",
|
||||||
|
forum.ID,
|
||||||
|
).Count(&count)
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// Save a forum membership.
|
// Save a forum membership.
|
||||||
func (f *ForumMembership) Save() error {
|
func (f *ForumMembership) Save() error {
|
||||||
return DB.Save(f).Error
|
return DB.Save(f).Error
|
||||||
|
@ -233,3 +243,49 @@ func MapForumMemberships(user *User, forums []*Forum) ForumMembershipMap {
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForumFollowerMap maps table IDs to counts of memberships.
|
||||||
|
type ForumFollowerMap map[uint64]int64
|
||||||
|
|
||||||
|
// Get like stats from the map.
|
||||||
|
func (fm ForumFollowerMap) Get(id uint64) int64 {
|
||||||
|
return fm[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapForumFollowers maps out the count of followers for a set of forums.
|
||||||
|
func MapForumFollowers(forums []*Forum) ForumFollowerMap {
|
||||||
|
var (
|
||||||
|
result = ForumFollowerMap{}
|
||||||
|
forumIDs = []uint64{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the result set.
|
||||||
|
for _, forum := range forums {
|
||||||
|
forumIDs = append(forumIDs, forum.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold the result of the grouped count query.
|
||||||
|
type group struct {
|
||||||
|
ID uint64
|
||||||
|
Followers int64
|
||||||
|
}
|
||||||
|
var groups = []group{}
|
||||||
|
|
||||||
|
// Map the counts of likes to each of these IDs.
|
||||||
|
if res := DB.Model(
|
||||||
|
&ForumMembership{},
|
||||||
|
).Select(
|
||||||
|
"forum_id AS id, count(id) AS followers",
|
||||||
|
).Where(
|
||||||
|
"forum_id IN ?",
|
||||||
|
forumIDs,
|
||||||
|
).Group("forum_id").Scan(&groups); res.Error != nil {
|
||||||
|
log.Error("MapLikes: count query: %s", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range groups {
|
||||||
|
result[row.ID] = row.Followers
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -200,6 +200,35 @@
|
||||||
<label class="label"><i class="fa fa-info-circle"></i> Forum Info</label>
|
<label class="label"><i class="fa fa-info-circle"></i> Forum Info</label>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
Created on: <span title="{{.Forum.CreatedAt}}">{{.Forum.CreatedAt.Format "Jan _2 2006"}}</span>
|
Created on: <span title="{{.Forum.CreatedAt}}">{{.Forum.CreatedAt.Format "Jan _2 2006"}}</span>
|
||||||
|
|
||||||
|
{{if .ForumSubscriberCount}}
|
||||||
|
<div class="has-text-info mt-2">
|
||||||
|
<i class="fa fa-book-bookmark mr-1"></i>
|
||||||
|
{{.ForumSubscriberCount}} {{if eq .ForumSubscriberCount 1}}person follows{{else}}people people{{end}} this forum.
|
||||||
|
|
||||||
|
<!-- Follow/Unfollow This Forum -->
|
||||||
|
<form action="/forum/subscribe" method="POST" class="is-inline">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="fragment" value="{{.Forum.Fragment}}">
|
||||||
|
|
||||||
|
{{if .IsForumSubscribed}}
|
||||||
|
<button type="submit" class="button is-small ml-2"
|
||||||
|
name="intent" value="unfollow"
|
||||||
|
onclick="return confirm('Do you want to remove this forum from your list?')">
|
||||||
|
<span class="icon"><i class="fa fa-bookmark"></i></span>
|
||||||
|
<span>Unfollow</span>
|
||||||
|
</button>
|
||||||
|
{{else}}
|
||||||
|
<button type="submit" class="button is-small ml-2"
|
||||||
|
name="intent" value="follow">
|
||||||
|
<span class="icon"><i class="fa-regular fa-bookmark has-text-success"></i></span>
|
||||||
|
<span>Follow it too?</span>
|
||||||
|
</button>
|
||||||
|
{{end}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{{if .Forum.Explicit}}
|
{{if .Forum.Explicit}}
|
||||||
<span class="tag is-danger is-light">
|
<span class="tag is-danger is-light">
|
||||||
|
|
|
@ -91,12 +91,13 @@
|
||||||
<option value="title asc"{{if eq .Sort "title asc"}} selected{{end}}>Title (A-Z)</option>
|
<option value="title asc"{{if eq .Sort "title asc"}} selected{{end}}>Title (A-Z)</option>
|
||||||
<option value="title desc"{{if eq .Sort "title desc"}} selected{{end}}>Title (Z-A)</option>
|
<option value="title desc"{{if eq .Sort "title desc"}} selected{{end}}>Title (Z-A)</option>
|
||||||
<option value="created_at desc"{{if eq .Sort "created_at desc"}} selected{{end}}>Recently created</option>
|
<option value="created_at desc"{{if eq .Sort "created_at desc"}} selected{{end}}>Recently created</option>
|
||||||
|
<option value="by_followers"{{if eq .Sort "by_followers"}} selected{{end}}>Popularity (follower count)</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="Contents">
|
<optgroup label="Contents">
|
||||||
<option value="by_latest"{{if eq .Sort "by_latest"}} selected{{end}}>Latest post</option>
|
<option value="by_latest"{{if eq .Sort "by_latest"}} selected{{end}}>Latest post</option>
|
||||||
<option value="by_threads"{{if eq .Sort "by_threads"}} selected{{end}}>Topic count</option>
|
<option value="by_threads"{{if eq .Sort "by_threads"}} selected{{end}}>Topics (count of threads)</option>
|
||||||
<option value="by_posts"{{if eq .Sort "by_posts"}} selected{{end}}>Post count</option>
|
<option value="by_posts"{{if eq .Sort "by_posts"}} selected{{end}}>Posts (count of threads and replies)</option>
|
||||||
<option value="by_users"{{if eq .Sort "by_users"}} selected{{end}}>User count</option>
|
<option value="by_users"{{if eq .Sort "by_users"}} selected{{end}}>Users (distinct members who have posted)</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -204,23 +205,28 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Owner line -->
|
<!-- Owner line -->
|
||||||
{{if .Category}}
|
|
||||||
<div class="mt-2 has-text-grey" style="font-size: smaller">
|
<div class="mt-2 has-text-grey" style="font-size: smaller">
|
||||||
by <a href="/f/{{.Fragment}}">{{PrettyTitle}}</a>
|
{{if .Category}}
|
||||||
</div>
|
by <a href="/f/{{.Fragment}}">{{PrettyTitle}}</a>
|
||||||
{{else}}
|
|
||||||
<div class="mt-2 has-text-grey" style="font-size: smaller">
|
|
||||||
by
|
|
||||||
{{template "avatar-16x16" .Owner}}
|
|
||||||
{{if .Owner.Username}}
|
|
||||||
<a href="/u/{{.Owner.Username}}" class="has-text-grey">
|
|
||||||
<strong>{{or .Owner.Username "[unavailable]"}}</strong>
|
|
||||||
</a>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
[unavailable]
|
by
|
||||||
|
{{template "avatar-16x16" .Owner}}
|
||||||
|
{{if .Owner.Username}}
|
||||||
|
<a href="/u/{{.Owner.Username}}" class="has-text-grey">
|
||||||
|
<strong>{{or .Owner.Username "[unavailable]"}}</strong>
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
[unavailable]
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{$FollowerCount := $Root.FollowersMap.Get .ID}}
|
||||||
|
{{if $FollowerCount}}
|
||||||
|
<span class="has-text-success ml-2" title="This forum is followed by {{$FollowerCount}} member{{Pluralize64 $FollowerCount}}.">
|
||||||
|
<i class="fa fa-book-bookmark mr-1"></i> {{$FollowerCount}}
|
||||||
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column py-1">
|
<div class="column py-1">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user