iPad Friendly Nav Bar + Mobile NavBar Notifications
* iPad in landscape mode was "desktop" size so got the full nav bar but the "More" drop-down was unusable. Add work-arounds for large touch devices to make the nav bar functional. * "Click" on the "More" button will pin it open so that the drop-down doesn't rely solely on mouseover events. Clicking off the open drop-down or clicking again on "More" toggles it hidden. * The logged-in user menu now drops its menu on hover like "More" did. * The logged-in user menu adds "TouchStart" events: touching the menu button toggles its drop-down to appear, canceling the link to "/me" that clicking the menu button does on desktops. Clicking off the open drop-down closes it. * Add notification indicators for "mobile" devices which only showed the brand and hamburger menu by default. Next to the hamburger button will be badges for number of friend requests or messages, with icons. Click the badge to go to the relevant page, or it hints that there are notifications in the drop-down.
This commit is contained in:
parent
4e4d18470f
commit
fb0e3651b0
|
@ -28,13 +28,18 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
// Defaults
|
// Defaults
|
||||||
m["LoggedIn"] = false
|
m["LoggedIn"] = false
|
||||||
m["CurrentUser"] = nil
|
m["CurrentUser"] = nil
|
||||||
m["NavUnreadMessages"] = 0
|
|
||||||
m["NavFriendRequests"] = 0
|
|
||||||
m["NavAdminNotifications"] = 0 // total count of admin notifications for nav
|
|
||||||
m["NavCertificationPhotos"] = 0 // admin indicator for certification photos
|
|
||||||
m["NavAdminFeedback"] = 0 // admin indicator for unread feedback
|
|
||||||
m["SessionImpersonated"] = false
|
m["SessionImpersonated"] = false
|
||||||
|
|
||||||
|
// User notification counts for nav bar.
|
||||||
|
m["NavUnreadMessages"] = 0 // New messages
|
||||||
|
m["NavFriendRequests"] = 0 // Friend requests
|
||||||
|
m["NavTotalNotifications"] = 0 // Total of above
|
||||||
|
|
||||||
|
// Admin notification counts for nav bar.
|
||||||
|
m["NavCertificationPhotos"] = 0 // Cert. photos needing approval
|
||||||
|
m["NavAdminFeedback"] = 0 // Unacknowledged feedback
|
||||||
|
m["NavAdminNotifications"] = 0 // Total of above
|
||||||
|
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,9 +50,21 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
m["LoggedIn"] = true
|
m["LoggedIn"] = true
|
||||||
m["CurrentUser"] = user
|
m["CurrentUser"] = user
|
||||||
|
|
||||||
|
// Collect notification counts.
|
||||||
|
var (
|
||||||
|
// For users
|
||||||
|
countMessages int64
|
||||||
|
countFriendReqs int64
|
||||||
|
|
||||||
|
// For admins
|
||||||
|
countCertPhotos int64
|
||||||
|
countFeedback int64
|
||||||
|
)
|
||||||
|
|
||||||
// Get unread message count.
|
// Get unread message count.
|
||||||
if count, err := models.CountUnreadMessages(user.ID); err == nil {
|
if count, err := models.CountUnreadMessages(user.ID); err == nil {
|
||||||
m["NavUnreadMessages"] = count
|
m["NavUnreadMessages"] = count
|
||||||
|
countMessages = count
|
||||||
} else {
|
} else {
|
||||||
log.Error("MergeUserVars: couldn't CountUnreadMessages for %d: %s", user.ID, err)
|
log.Error("MergeUserVars: couldn't CountUnreadMessages for %d: %s", user.ID, err)
|
||||||
}
|
}
|
||||||
|
@ -55,6 +72,7 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
// Get friend request count.
|
// Get friend request count.
|
||||||
if count, err := models.CountFriendRequests(user.ID); err == nil {
|
if count, err := models.CountFriendRequests(user.ID); err == nil {
|
||||||
m["NavFriendRequests"] = count
|
m["NavFriendRequests"] = count
|
||||||
|
countFriendReqs = count
|
||||||
} else {
|
} else {
|
||||||
log.Error("MergeUserVars: couldn't CountFriendRequests for %d: %s", user.ID, err)
|
log.Error("MergeUserVars: couldn't CountFriendRequests for %d: %s", user.ID, err)
|
||||||
}
|
}
|
||||||
|
@ -62,15 +80,16 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
||||||
// Are we admin?
|
// Are we admin?
|
||||||
if user.IsAdmin {
|
if user.IsAdmin {
|
||||||
// Any pending certification photos or feedback?
|
// Any pending certification photos or feedback?
|
||||||
var (
|
countCertPhotos = models.CountCertificationPhotosNeedingApproval()
|
||||||
certPhotos = models.CountCertificationPhotosNeedingApproval()
|
countFeedback = models.CountUnreadFeedback()
|
||||||
feedback = models.CountUnreadFeedback()
|
m["NavCertificationPhotos"] = countCertPhotos
|
||||||
)
|
m["NavAdminFeedback"] = countFeedback
|
||||||
m["NavCertificationPhotos"] = certPhotos
|
|
||||||
m["NavAdminFeedback"] = feedback
|
|
||||||
|
|
||||||
// Total notification count for admin actions.
|
// Total notification count for admin actions.
|
||||||
m["NavAdminNotifications"] = certPhotos + feedback
|
m["NavAdminNotifications"] = countCertPhotos + countFeedback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Total count for user notifications.
|
||||||
|
m["NavTotalNotifications"] = countMessages + countFriendReqs + countCertPhotos + countFeedback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,17 @@
|
||||||
.tag:not(body).is-private.is-light {
|
.tag:not(body).is-private.is-light {
|
||||||
color: #CC00CC;
|
color: #CC00CC;
|
||||||
background-color: #FFEEFF;
|
background-color: #FFEEFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: notification badge near the hamburger menu */
|
||||||
|
.nonshy-mobile-notification {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 50px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
.nonshy-mobile-notification {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,29 +1,75 @@
|
||||||
// Hamburger menu script for mobile.
|
// Hamburger menu script for mobile.
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
// Get all "navbar-burger" elements
|
// Hamburger menu script.
|
||||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
(function() {
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||||
|
|
||||||
// Add a click event on each of them
|
// Add a click event on each of them
|
||||||
$navbarBurgers.forEach( el => {
|
$navbarBurgers.forEach( el => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
|
|
||||||
// Get the target from the "data-target" attribute
|
// Get the target from the "data-target" attribute
|
||||||
const target = el.dataset.target;
|
const target = el.dataset.target;
|
||||||
const $target = document.getElementById(target);
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
el.classList.toggle('is-active');
|
el.classList.toggle('is-active');
|
||||||
$target.classList.toggle('is-active');
|
$target.classList.toggle('is-active');
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
})();
|
||||||
|
|
||||||
|
// Allow the "More" drop-down to work on mobile (toggle is-active on click instead of requiring mouseover)
|
||||||
|
(function() {
|
||||||
|
const menu = document.querySelector("#navbar-more"),
|
||||||
|
userMenu = document.querySelector("#navbar-user"),
|
||||||
|
activeClass = "is-active";
|
||||||
|
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
// Click the "More" menu to permanently toggle the menu.
|
||||||
|
menu.addEventListener("click", (e) => {
|
||||||
|
if (menu.classList.contains(activeClass)) {
|
||||||
|
menu.classList.remove(activeClass);
|
||||||
|
} else {
|
||||||
|
menu.classList.add(activeClass);
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touching the user drop-down button toggles it.
|
||||||
|
userMenu.addEventListener("touchstart", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (userMenu.classList.contains(activeClass)) {
|
||||||
|
userMenu.classList.remove(activeClass);
|
||||||
|
} else {
|
||||||
|
userMenu.classList.add(activeClass);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touching a link from the user menu lets it click thru.
|
||||||
|
(document.querySelectorAll(".navbar-dropdown") || []).forEach(node => {
|
||||||
|
node.addEventListener("touchstart", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clicking the rest of the body will close an active navbar-dropdown.
|
||||||
|
(document.addEventListener("click", (e) => {
|
||||||
|
(document.querySelectorAll(".navbar-dropdown.is-active, .navbar-item.is-active") || []).forEach(node => {
|
||||||
|
node.classList.remove(activeClass);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
|
||||||
// Common event handlers for bulma modals.
|
// Common event handlers for bulma modals.
|
||||||
(document.querySelectorAll(".modal-background, .modal-close, .photo-modal") || []).forEach(node => {
|
(document.querySelectorAll(".modal-background, .modal-close, .photo-modal") || []).forEach(node => {
|
||||||
const target = node.closest(".modal");
|
const target = node.closest(".modal");
|
||||||
node.addEventListener("click", () => {
|
node.addEventListener("click", () => {
|
||||||
target.classList.remove("is-active");
|
target.classList.remove("is-active");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
});
|
});
|
|
@ -57,7 +57,7 @@
|
||||||
<span>Forums</span>
|
<span>Forums</span>
|
||||||
</a> -->
|
</a> -->
|
||||||
|
|
||||||
<a class="navbar-item" href="/friends">
|
<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>
|
||||||
{{if .NavFriendRequests}}<span class="tag is-warning ml-1">{{.NavFriendRequests}}</span>{{end}}
|
{{if .NavFriendRequests}}<span class="tag is-warning ml-1">{{.NavFriendRequests}}</span>{{end}}
|
||||||
|
@ -70,16 +70,18 @@
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div id="navbar-more" class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
More
|
More
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown is-active">
|
||||||
|
{{if .LoggedIn}}
|
||||||
<a class="navbar-item" href="/members">
|
<a class="navbar-item" href="/members">
|
||||||
<span class="icon"><i class="fa fa-people-group"></i></span>
|
<span class="icon"><i class="fa fa-people-group"></i></span>
|
||||||
<span>People</span>
|
<span>People</span>
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
<a class="navbar-item" href="/about">
|
<a class="navbar-item" href="/about">
|
||||||
About
|
About
|
||||||
</a>
|
</a>
|
||||||
|
@ -105,7 +107,7 @@
|
||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{{if .LoggedIn }}
|
{{if .LoggedIn }}
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div id="navbar-user" class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link" href="/me">
|
<a class="navbar-link" href="/me">
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
|
@ -124,7 +126,7 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown is-right">
|
<div class="navbar-dropdown is-right is-hoverable">
|
||||||
<a class="navbar-item" href="/me">Dashboard</a>
|
<a class="navbar-item" href="/me">Dashboard</a>
|
||||||
<a class="navbar-item" href="/u/{{.CurrentUser.Username}}">My Profile</a>
|
<a class="navbar-item" href="/u/{{.CurrentUser.Username}}">My Profile</a>
|
||||||
<a class="navbar-item" href="/photo/u/{{.CurrentUser.Username}}">My Photos</a>
|
<a class="navbar-item" href="/photo/u/{{.CurrentUser.Username}}">My Photos</a>
|
||||||
|
@ -161,6 +163,32 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- Mobile: notifications badge next to hamburger menu -->
|
||||||
|
{{if gt .NavTotalNotifications 0}}
|
||||||
|
<div class="mobile nonshy-mobile-notification">
|
||||||
|
{{if gt .NavFriendRequests 0}}
|
||||||
|
<a class="tag is-warning" href="/friends?view=requests">
|
||||||
|
<span class="icon"><i class="fa fa-user-group"></i></span>
|
||||||
|
<span>{{.NavFriendRequests}}</span>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if gt .NavUnreadMessages 0}}
|
||||||
|
<a class="tag is-warning" href="/messages">
|
||||||
|
<span class="icon"><i class="fa fa-envelope"></i></span>
|
||||||
|
<span>{{.NavUnreadMessages}}</span>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if gt .NavAdminNotifications 0}}
|
||||||
|
<a class="tag is-danger" href="/admin">
|
||||||
|
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||||
|
<span>{{.NavAdminNotifications}}</span>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="container is-fullhd">
|
<div class="container is-fullhd">
|
||||||
{{if .Flashes}}
|
{{if .Flashes}}
|
||||||
<div class="notification block is-success">
|
<div class="notification block is-success">
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<em>Sent on {{.CreatedAt.Format "2006-01-02 15:04:05"}}</em>
|
<em>Sent on {{.CreatedAt.Format "2006-01-02 15:04:05"}}</em>
|
||||||
|
{{if not .Read}}<span class="tag is-success ml-2">UNREAD</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="block">
|
<hr class="block">
|
||||||
|
@ -157,7 +158,9 @@
|
||||||
{{$User := $UserMap.Get .SourceUserID}}
|
{{$User := $UserMap.Get .SourceUserID}}
|
||||||
<strong>From {{$User.Username}}</strong>
|
<strong>From {{$User.Username}}</strong>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if not .Read}}<span class="tag is-success">NEW</span>{{end}}
|
{{if not .Read}}
|
||||||
|
<span class="tag is-success">{{if $IsSentBox}}UNREAD{{else}}NEW{{end}}</span>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<em>
|
<em>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user