Forums - Spit & polish
* On Forums landing page, show who was the most recent commenter on each board's most recently updated post. * Show photo count on Profile Pages on the "Photos" tab. * Revise the mobile and tablet top nav bar: * Always show small badge icons linking to the Site Gallery & Forum * Always show Friends & Messages badges. If no new notifications, they display as grey instead of yellow w/ a number. * Put icons next to most nav bar items, especially the User Menu * Tighten the sprawling page layouts in the Forums to be more compact for mobile screens. * Fix bug where some pages scrolled horizontally on mobile: the root cause was divs with class="content p-2", needs minimum p-3 (but p-4 is used) to provide enough padding to overcome column margins which were pushing the page too wide on mobile.
This commit is contained in:
parent
bb08ec56ce
commit
82f3914ae6
|
@ -61,10 +61,11 @@ func Profile() http.HandlerFunc {
|
||||||
likeMap := models.MapLikes(currentUser, "users", []uint64{user.ID})
|
likeMap := models.MapLikes(currentUser, "users", []uint64{user.ID})
|
||||||
|
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"User": user,
|
"User": user,
|
||||||
"LikeMap": likeMap,
|
"LikeMap": likeMap,
|
||||||
"IsFriend": isFriend,
|
"IsFriend": isFriend,
|
||||||
"IsPrivate": isPrivate,
|
"IsPrivate": isPrivate,
|
||||||
|
"PhotoCount": models.CountPhotos(user.ID),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
|
|
@ -102,6 +102,7 @@ func UserPhotos() http.HandlerFunc {
|
||||||
"IsOwnPhotos": currentUser.ID == user.ID,
|
"IsOwnPhotos": currentUser.ID == user.ID,
|
||||||
"User": user,
|
"User": user,
|
||||||
"Photos": photos,
|
"Photos": photos,
|
||||||
|
"PhotoCount": models.CountPhotos(user.ID),
|
||||||
"Pager": pager,
|
"Pager": pager,
|
||||||
"LikeMap": likeMap,
|
"LikeMap": likeMap,
|
||||||
"ViewStyle": viewStyle,
|
"ViewStyle": viewStyle,
|
||||||
|
|
|
@ -30,6 +30,21 @@ func GetComment(id uint64) (*Comment, error) {
|
||||||
return c, result.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.
|
// AddComment about anything.
|
||||||
func AddComment(user *User, tableName string, tableID uint64, message string) (*Comment, error) {
|
func AddComment(user *User, tableName string, tableID uint64, message string) (*Comment, error) {
|
||||||
c := &Comment{
|
c := &Comment{
|
||||||
|
|
|
@ -5,6 +5,7 @@ import "git.kirsle.net/apps/gosocial/pkg/log"
|
||||||
// ForumStatistics queries for forum-level statistics.
|
// ForumStatistics queries for forum-level statistics.
|
||||||
type ForumStatistics struct {
|
type ForumStatistics struct {
|
||||||
RecentThread *Thread
|
RecentThread *Thread
|
||||||
|
RecentPost *Comment // latest post on the recent thread
|
||||||
Threads uint64
|
Threads uint64
|
||||||
Posts uint64
|
Posts uint64
|
||||||
Users uint64
|
Users uint64
|
||||||
|
@ -30,6 +31,7 @@ func MapForumStatistics(forums []*Forum) ForumStatsMap {
|
||||||
result.generatePostCount(IDs)
|
result.generatePostCount(IDs)
|
||||||
result.generateUserCount(IDs)
|
result.generateUserCount(IDs)
|
||||||
result.generateRecentThreads(IDs)
|
result.generateRecentThreads(IDs)
|
||||||
|
result.generateRecentPosts(IDs)
|
||||||
|
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/gosocial/pkg/log"
|
||||||
"gorm.io/gorm"
|
"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.
|
// 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
|
var count int64
|
||||||
result := DB.Where(
|
result := DB.Where(
|
||||||
"user_id = ?",
|
"user_id = ?",
|
||||||
userID,
|
userID,
|
||||||
).Model(&Photo{}).Count(&count)
|
).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)
|
// CountExplicitPhotos returns the number of explicit photos a user has (so non-explicit viewers can see some do exist)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
// QuoteForUser returns the current photo usage quota for a given user.
|
// QuoteForUser returns the current photo usage quota for a given user.
|
||||||
func QuotaForUser(u *models.User) (current, allowed int) {
|
func QuotaForUser(u *models.User) (current, allowed int) {
|
||||||
// Count their photos.
|
// Count their photos.
|
||||||
count, _ := models.CountPhotos(u.ID)
|
count := models.CountPhotos(u.ID)
|
||||||
|
|
||||||
// What is their quota at?
|
// What is their quota at?
|
||||||
var quota int
|
var quota int
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
/* Mobile: notification badge near the hamburger menu */
|
/* Mobile: notification badge near the hamburger menu */
|
||||||
.nonshy-mobile-notification {
|
.nonshy-mobile-notification {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 10px;
|
||||||
right: 50px;
|
right: 50px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
// Touching the user drop-down button toggles it.
|
// Touching the user drop-down button toggles it.
|
||||||
userMenu.addEventListener("touchstart", (e) => {
|
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();
|
e.preventDefault();
|
||||||
if (userMenu.classList.contains(activeClass)) {
|
if (userMenu.classList.contains(activeClass)) {
|
||||||
userMenu.classList.remove(activeClass);
|
userMenu.classList.remove(activeClass);
|
||||||
|
|
|
@ -194,7 +194,10 @@
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<i class="fa fa-image"></i>
|
<i class="fa fa-image"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Photos</span>
|
<span>
|
||||||
|
Photos
|
||||||
|
{{if .PhotoCount}}<span class="tag is-link is-light ml-1">{{.PhotoCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -47,16 +47,16 @@
|
||||||
<span>Home</span>
|
<span>Home</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="navbar-item" href="/photo/gallery">
|
|
||||||
<span class="icon"><i class="fa fa-image"></i></span>
|
|
||||||
<span>Gallery</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="navbar-item" href="/forum">
|
<a class="navbar-item" href="/forum">
|
||||||
<span class="icon"><i class="fa fa-comments"></i></span>
|
<span class="icon"><i class="fa fa-comments"></i></span>
|
||||||
<span>Forum</span>
|
<span>Forum</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="navbar-item" href="/photo/gallery">
|
||||||
|
<span class="icon"><i class="fa fa-image"></i></span>
|
||||||
|
<span>Gallery</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a class="navbar-item" href="/friends{{if gt .NavFriendRequests 0}}?view=requests{{end}}">
|
<a class="navbar-item" href="/friends{{if gt .NavFriendRequests 0}}?view=requests{{end}}">
|
||||||
<span class="icon"><i class="fa fa-user-group"></i></span>
|
<span class="icon"><i class="fa fa-user-group"></i></span>
|
||||||
<span>Friends</span>
|
<span>Friends</span>
|
||||||
|
@ -83,23 +83,29 @@
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a class="navbar-item" href="/about">
|
<a class="navbar-item" href="/about">
|
||||||
About
|
<span class="icon"><i class="fa fa-circle-info"></i></span>
|
||||||
|
<span>About {{PrettyTitle}}</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="/faq">
|
<a class="navbar-item" href="/faq">
|
||||||
FAQ
|
<span class="icon"><i class="fa fa-circle-question"></i></span>
|
||||||
|
<span>FAQ</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="/tos">
|
<a class="navbar-item" href="/tos">
|
||||||
Terms of Service
|
<span class="icon"><i class="fa fa-list"></i></span>
|
||||||
|
<span>Terms of Service</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="/privacy">
|
<a class="navbar-item" href="/privacy">
|
||||||
Privacy Policy
|
<span class="icon"><i class="fa fa-file-shield"></i></span>
|
||||||
|
<span>Privacy Policy</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="/contact">
|
<a class="navbar-item" href="/contact">
|
||||||
Contact
|
<span class="icon"><i class="fa fa-message"></i></span>
|
||||||
|
<span>Contact</span>
|
||||||
</a>
|
</a>
|
||||||
<hr class="navbar-divider">
|
<hr class="navbar-divider">
|
||||||
<a class="navbar-item" href="/contact?intent=report">
|
<a class="navbar-item" href="/contact?intent=report">
|
||||||
Report an issue
|
<span class="icon"><i class="fa fa-triangle-exclamation"></i></span>
|
||||||
|
<span>Report an issue</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,7 +135,8 @@
|
||||||
|
|
||||||
<div class="navbar-dropdown is-right is-hoverable">
|
<div class="navbar-dropdown is-right is-hoverable">
|
||||||
<a class="navbar-item" href="/me{{if .NavUnreadNotifications}}#notifications{{end}}">
|
<a class="navbar-item" href="/me{{if .NavUnreadNotifications}}#notifications{{end}}">
|
||||||
Dashboard
|
<span class="icon"><i class="fa fa-home-user"></i></span>
|
||||||
|
<span>Dashboard</span>
|
||||||
{{if .NavUnreadNotifications}}
|
{{if .NavUnreadNotifications}}
|
||||||
<span class="tag is-warning ml-1">
|
<span class="tag is-warning ml-1">
|
||||||
<span class="icon"><i class="fa fa-bell"></i></span>
|
<span class="icon"><i class="fa fa-bell"></i></span>
|
||||||
|
@ -137,13 +144,26 @@
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="/u/{{.CurrentUser.Username}}">My Profile</a>
|
<a class="navbar-item" href="/u/{{.CurrentUser.Username}}">
|
||||||
<a class="navbar-item" href="/photo/u/{{.CurrentUser.Username}}">My Photos</a>
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
<a class="navbar-item" href="/photo/upload">Upload Photo</a>
|
<span>My Profile</span>
|
||||||
<a class="navbar-item" href="/settings">Settings</a>
|
</a>
|
||||||
|
<a class="navbar-item" href="/photo/u/{{.CurrentUser.Username}}">
|
||||||
|
<span class="icon"><i class="fa fa-image"></i></span>
|
||||||
|
<span>My Photos</span>
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/photo/upload">
|
||||||
|
<span class="icon"><i class="fa fa-upload"></i></span>
|
||||||
|
<span>Upload Photo</span>
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/settings">
|
||||||
|
<span class="icon"><i class="fa fa-gear"></i></span>
|
||||||
|
<span>Settings</span>
|
||||||
|
</a>
|
||||||
{{if .CurrentUser.IsAdmin}}
|
{{if .CurrentUser.IsAdmin}}
|
||||||
<a class="navbar-item has-text-danger" href="/admin">
|
<a class="navbar-item has-text-danger" href="/admin">
|
||||||
Admin
|
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||||
|
<span>Admin</span>
|
||||||
{{if .NavAdminNotifications}}<span class="tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
{{if .NavAdminNotifications}}<span class="tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -153,7 +173,10 @@
|
||||||
<span>Unimpersonate</span>
|
<span>Unimpersonate</span>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a class="navbar-item" href="/logout">Log out</a>
|
<a class="navbar-item" href="/logout">
|
||||||
|
<span class="icon"><i class="fa fa-arrow-right-from-bracket"></i></span>
|
||||||
|
<span>Log out</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
@ -173,33 +196,45 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Mobile: notifications badge next to hamburger menu -->
|
<!-- Mobile: notifications badge next to hamburger menu -->
|
||||||
{{if gt .NavTotalNotifications 0}}
|
{{if .LoggedIn}}
|
||||||
<div class="mobile nonshy-mobile-notification">
|
<div class="mobile nonshy-mobile-notification">
|
||||||
{{if gt .NavFriendRequests 0}}
|
<a class="tag is-grey py-4"
|
||||||
<a class="tag is-warning" href="/friends?view=requests">
|
href="/forum">
|
||||||
<span class="icon"><i class="fa fa-user-group"></i></span>
|
<span class="icon"><i class="fa fa-comments"></i></span>
|
||||||
<span>{{.NavFriendRequests}}</span>
|
</a>
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if gt .NavUnreadMessages 0}}
|
<a class="tag is-grey py-4"
|
||||||
<a class="tag is-warning" href="/messages">
|
href="/photo/gallery">
|
||||||
<span class="icon"><i class="fa fa-envelope"></i></span>
|
<span class="icon"><i class="fa fa-image"></i></span>
|
||||||
<span>{{.NavUnreadMessages}}</span>
|
</a>
|
||||||
</a>
|
|
||||||
{{end}}
|
<a class="tag {{if gt .NavFriendRequests 0}}is-warning{{else}}is-grey{{end}} py-4"
|
||||||
|
href="/friends{{if gt .NavFriendRequests 0}}?view=requests{{end}}">
|
||||||
|
<span class="icon"><i class="fa fa-user-group"></i></span>
|
||||||
|
{{if gt .NavFriendRequests 0}}
|
||||||
|
<small>{{.NavFriendRequests}}</small>
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<a class="tag {{if gt .NavUnreadMessages 0}}is-warning{{else}}is-grey{{end}} py-4" href="/messages">
|
||||||
|
<span class="icon"><i class="fa fa-envelope"></i></span>
|
||||||
|
{{if gt .NavUnreadMessages 0}}
|
||||||
|
<small>{{.NavUnreadMessages}}</small>
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
|
||||||
{{if gt .NavUnreadNotifications 0}}
|
{{if gt .NavUnreadNotifications 0}}
|
||||||
<a class="tag is-warning" href="/me#notifications">
|
<a class="tag is-warning py-4" href="/me#notifications">
|
||||||
<span class="icon"><i class="fa fa-bell"></i></span>
|
<span class="icon"><i class="fa fa-bell"></i></span>
|
||||||
<span>{{.NavUnreadNotifications}}</span>
|
<small>{{.NavUnreadNotifications}}</small>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if gt .NavAdminNotifications 0}}
|
{{if gt .NavAdminNotifications 0}}
|
||||||
<a class="tag is-danger" href="/admin">
|
<a class="tag is-danger py-4" href="/admin">
|
||||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||||
<span>{{.NavAdminNotifications}}</span>
|
<small>{{.NavAdminNotifications}}</small>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -228,7 +263,7 @@
|
||||||
|
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
|
|
||||||
<div class="block has-text-centered has-text-grey">
|
<div class="block p-4 has-text-centered has-text-grey">
|
||||||
© {{.YYYY}} {{.Title}}
|
© {{.YYYY}} {{.Title}}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|
|
@ -118,6 +118,77 @@
|
||||||
content from other users -- by default this site is "normal nudes" friendly!
|
content from other users -- by default this site is "normal nudes" friendly!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h1>Forum FAQs</h1>
|
||||||
|
|
||||||
|
<h3>What do the various badges on the forum mean?</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You may see some of these badges on the forums or their posts. These are their meanings:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="tag is-danger is-light">
|
||||||
|
<span class="icon"><i class="fa fa-fire"></i></span>
|
||||||
|
<span>Explicit</span>
|
||||||
|
</span> -
|
||||||
|
on a forum it means the entire forum is "<abbr title="Not Safe For Work">NSFW</abbr>";
|
||||||
|
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 <a href="/settings">settings</a>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="tag is-warning is-light">
|
||||||
|
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||||
|
<span>Privileged</span>
|
||||||
|
</span> -
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="tag is-success is-light">
|
||||||
|
<span class="icon"><i class="fa fa-thumbtack"></i></span>
|
||||||
|
<span>Pinned</span>
|
||||||
|
</span> -
|
||||||
|
these forum posts are pinned to the top of a forum, appearing above regular posts
|
||||||
|
on the first page of the forum.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="tag is-warning is-light">
|
||||||
|
<span class="icon"><i class="fa fa-ban"></i></span>
|
||||||
|
<span>No Reply</span>
|
||||||
|
</span> -
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Can I create my own forums?</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Some related features with managing your own forums will include:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
You'll be able to make your forum "invite-only" if you want, where only approved
|
||||||
|
members can see and reply to threads.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h1>Technical FAQs</h1>
|
<h1>Technical FAQs</h1>
|
||||||
|
|
||||||
<h3>Why did you build a custom website?</h3>
|
<h3>Why did you build a custom website?</h3>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
{{$Root := .}}
|
{{$Root := .}}
|
||||||
|
|
||||||
<div class="block px-4">
|
<div class="block p-4">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||||
|
@ -77,12 +77,12 @@
|
||||||
<div class="block p-2">
|
<div class="block p-2">
|
||||||
{{range .Threads}}
|
{{range .Threads}}
|
||||||
{{$Stats := $Root.ThreadMap.Get .ID}}
|
{{$Stats := $Root.ThreadMap.Get .ID}}
|
||||||
<div class="box has-background-link-light">
|
<div class="box has-background-success-light">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-2 has-text-centered">
|
<div class="column is-2 has-text-centered pt-0 pb-1">
|
||||||
<div>
|
<div>
|
||||||
<a href="/u/{{.Comment.User.Username}}">
|
<a href="/u/{{.Comment.User.Username}}">
|
||||||
<figure class="image is-96x96 is-inline-block">
|
<figure class="image is-64x64 is-inline-block">
|
||||||
{{if .Comment.User.ProfilePhoto.ID}}
|
{{if .Comment.User.ProfilePhoto.ID}}
|
||||||
<img src="{{PhotoURL .Comment.User.ProfilePhoto.CroppedFilename}}">
|
<img src="{{PhotoURL .Comment.User.ProfilePhoto.CroppedFilename}}">
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -93,16 +93,16 @@
|
||||||
</div>
|
</div>
|
||||||
<a href="/u/{{.Comment.User.Username}}">{{.Comment.User.Username}}</a>
|
<a href="/u/{{.Comment.User.Username}}">{{.Comment.User.Username}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column content">
|
<div class="column content pt-1 pb-0">
|
||||||
<a href="/forum/thread/{{.ID}}" class="has-text-dark">
|
<a href="/forum/thread/{{.ID}}" class="has-text-dark">
|
||||||
<h1 class="title pt-0">
|
<h2 class="is-size-4 pt-0">
|
||||||
{{if .Pinned}}<sup class="fa fa-thumbtack has-text-success mr-2 is-size-5" title="Pinned"></sup>{{end}}
|
{{if .Pinned}}<sup class="fa fa-thumbtack has-text-success mr-2 is-size-6" title="Pinned"></sup>{{end}}
|
||||||
{{or .Title "Untitled"}}
|
{{or .Title "Untitled"}}
|
||||||
</h1>
|
</h2>
|
||||||
{{TrimEllipses .Comment.Message 256}}
|
{{TrimEllipses .Comment.Message 256}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<hr class="mb-1">
|
<hr class="has-background-success my-2">
|
||||||
<div>
|
<div>
|
||||||
{{if .Pinned}}
|
{{if .Pinned}}
|
||||||
<span class="tag is-success is-light mr-2">
|
<span class="tag is-success is-light mr-2">
|
||||||
|
@ -128,10 +128,10 @@
|
||||||
<em title="{{.UpdatedAt.Format "2006-01-02 15:04:05"}}">Updated {{SincePrettyCoarse .UpdatedAt}} ago</em>
|
<em title="{{.UpdatedAt.Format "2006-01-02 15:04:05"}}">Updated {{SincePrettyCoarse .UpdatedAt}} ago</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow pb-1 pt-0 px-1">
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered pr-1">
|
||||||
<div class="box">
|
<div class="box p-2">
|
||||||
<p class="is-size-7">Replies</p>
|
<p class="is-size-7">Replies</p>
|
||||||
{{if $Stats}}
|
{{if $Stats}}
|
||||||
{{$Stats.Replies}}
|
{{$Stats.Replies}}
|
||||||
|
@ -140,8 +140,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered pl-1">
|
||||||
<div class="box">
|
<div class="box p-2">
|
||||||
<p class="is-size-7">Views</p>
|
<p class="is-size-7">Views</p>
|
||||||
{{if $Stats}}
|
{{if $Stats}}
|
||||||
{{$Stats.Views}}
|
{{$Stats.Views}}
|
||||||
|
|
|
@ -4,32 +4,32 @@
|
||||||
<section class="hero is-light is-success">
|
<section class="hero is-light is-success">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">
|
<div class="level">
|
||||||
<span class="icon mr-4"><i class="fa fa-comments"></i></span>
|
<div class="level-left">
|
||||||
<span>Forums</span>
|
<h1 class="title">
|
||||||
</h1>
|
<span class="icon mr-4"><i class="fa fa-comments"></i></span>
|
||||||
|
<span>Forums</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{{if .CurrentUser.IsAdmin}}
|
||||||
|
<div class="level-right">
|
||||||
|
<div>
|
||||||
|
<a href="/forum/admin" class="button is-small has-text-danger">
|
||||||
|
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||||
|
<span>Manage Forums</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block p-2">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">To Do</div>
|
|
||||||
{{if .CurrentUser.IsAdmin}}
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<a href="/forum/admin" class="button is-small has-text-danger">
|
|
||||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
|
||||||
<span>Manage Forums</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{$Root := .}}
|
{{$Root := .}}
|
||||||
{{range .Categories}}
|
{{range .Categories}}
|
||||||
<div class="block p-2">
|
<div class="block p-4">
|
||||||
<h1 class="title">{{.Category}}</h1>
|
<h1 class="title">{{.Category}}</h1>
|
||||||
|
|
||||||
{{if eq (len .Forums) 0}}
|
{{if eq (len .Forums) 0}}
|
||||||
|
@ -41,20 +41,15 @@
|
||||||
{{range .Forums}}
|
{{range .Forums}}
|
||||||
{{$Stats := $Root.ForumMap.Get .ID}}
|
{{$Stats := $Root.ForumMap.Get .ID}}
|
||||||
<div class="card block has-background-primary-light">
|
<div class="card block has-background-primary-light">
|
||||||
<!-- <header class="card-header has-background-success">
|
|
||||||
<p class="card-header-title has-text-light">
|
|
||||||
{{.Title}}
|
|
||||||
</p>
|
|
||||||
</header> -->
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column is-3 pt-0 pb-1">
|
||||||
|
|
||||||
<h1 class="title">
|
<h2 class="is-size-4">
|
||||||
<a href="/f/{{.Fragment}}">{{.Title}}</a>
|
<strong><a href="/f/{{.Fragment}}">{{.Title}}</a></strong>
|
||||||
</h1>
|
</h2>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content mb-1">
|
||||||
{{if .Description}}
|
{{if .Description}}
|
||||||
{{ToMarkdown .Description}}
|
{{ToMarkdown .Description}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -79,26 +74,31 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column py-1">
|
||||||
<div class="box has-background-success-light">
|
<div class="box has-background-success-light">
|
||||||
<h2 class="subtitle mb-0">Latest Post</h2>
|
<h2 class="subtitle mb-1">Latest Post</h2>
|
||||||
{{if $Stats.RecentThread}}
|
{{if $Stats.RecentThread}}
|
||||||
<a href="/forum/thread/{{$Stats.RecentThread.ID}}">
|
<a href="/forum/thread/{{$Stats.RecentThread.ID}}">
|
||||||
<strong>{{$Stats.RecentThread.Title}}</strong>
|
<strong>{{$Stats.RecentThread.Title}}</strong>
|
||||||
</a>
|
</a>
|
||||||
<em>by {{$Stats.RecentThread.Comment.User.Username}}</em>
|
<em>by {{$Stats.RecentThread.Comment.User.Username}}</em>
|
||||||
<div>
|
<div>
|
||||||
<small title="{{$Stats.RecentThread.UpdatedAt.Format "2006-01-02 15:04:05"}}">Last updated {{SincePrettyCoarse $Stats.RecentThread.UpdatedAt}} ago</small>
|
<em>
|
||||||
|
{{if and $Stats.RecentPost (not (eq $Stats.RecentPost.ID $Stats.RecentThread.CommentID))}}
|
||||||
|
<small>Last comment by {{$Stats.RecentPost.User.Username}}</small>
|
||||||
|
{{end}}
|
||||||
|
<small title="{{$Stats.RecentThread.UpdatedAt.Format "2006-01-02 15:04:05"}}">{{SincePrettyCoarse $Stats.RecentThread.UpdatedAt}} ago</small>
|
||||||
|
</em>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<em>No posts found.</em>
|
<em>No posts found.</em>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-3">
|
<div class="column is-3 py-1">
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered mr-1">
|
||||||
<div class="box has-background-warning-light">
|
<div class="box has-background-warning-light p-2">
|
||||||
<p class="is-size-7">Topics</p>
|
<p class="is-size-7">Topics</p>
|
||||||
{{if $Stats}}
|
{{if $Stats}}
|
||||||
{{$Stats.Threads}}
|
{{$Stats.Threads}}
|
||||||
|
@ -107,8 +107,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered mx-1">
|
||||||
<div class="box has-background-warning-light">
|
<div class="box has-background-warning-light p-2">
|
||||||
<p class="is-size-7">Posts</p>
|
<p class="is-size-7">Posts</p>
|
||||||
{{if $Stats}}
|
{{if $Stats}}
|
||||||
{{$Stats.Posts}}
|
{{$Stats.Posts}}
|
||||||
|
@ -117,8 +117,8 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered ml-1">
|
||||||
<div class="box has-background-warning-light">
|
<div class="box has-background-warning-light p-2">
|
||||||
<p class="is-size-7">Users</p>
|
<p class="is-size-7">Users</p>
|
||||||
{{if $Stats}}
|
{{if $Stats}}
|
||||||
{{$Stats.Users}}
|
{{$Stats.Users}}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block p-4">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column content is-three-quarters p-4">
|
<div class="column content is-three-quarters p-4">
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -144,7 +144,10 @@
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<i class="fa fa-image"></i>
|
<i class="fa fa-image"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Photos</span>
|
<span>
|
||||||
|
Photos
|
||||||
|
{{if .PhotoCount}}<span class="tag is-link is-light ml-1">{{.PhotoCount}}</span>{{end}}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -177,7 +180,7 @@
|
||||||
|
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<div class="tabs is-toggle is-small">
|
<div class="tabs is-toggle is-small is-hidden-mobile">
|
||||||
<ul>
|
<ul>
|
||||||
<li{{if eq .ViewStyle "cards"}} class="is-active"{{end}}>
|
<li{{if eq .ViewStyle "cards"}} class="is-active"{{end}}>
|
||||||
<a href="{{.Request.URL.Path}}?view=cards">Cards</a>
|
<a href="{{.Request.URL.Path}}?view=cards">Cards</a>
|
||||||
|
|
Reference in New Issue
Block a user