diff --git a/pkg/templates/template_vars.go b/pkg/templates/template_vars.go index 9472d63..16f57bb 100644 --- a/pkg/templates/template_vars.go +++ b/pkg/templates/template_vars.go @@ -28,13 +28,18 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) { // Defaults m["LoggedIn"] = false 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 + // 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 { return } @@ -45,9 +50,21 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) { m["LoggedIn"] = true m["CurrentUser"] = user + // Collect notification counts. + var ( + // For users + countMessages int64 + countFriendReqs int64 + + // For admins + countCertPhotos int64 + countFeedback int64 + ) + // Get unread message count. if count, err := models.CountUnreadMessages(user.ID); err == nil { m["NavUnreadMessages"] = count + countMessages = count } else { 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. if count, err := models.CountFriendRequests(user.ID); err == nil { m["NavFriendRequests"] = count + countFriendReqs = count } else { 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? if user.IsAdmin { // Any pending certification photos or feedback? - var ( - certPhotos = models.CountCertificationPhotosNeedingApproval() - feedback = models.CountUnreadFeedback() - ) - m["NavCertificationPhotos"] = certPhotos - m["NavAdminFeedback"] = feedback + countCertPhotos = models.CountCertificationPhotosNeedingApproval() + countFeedback = models.CountUnreadFeedback() + m["NavCertificationPhotos"] = countCertPhotos + m["NavAdminFeedback"] = countFeedback // 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 } } diff --git a/web/static/css/theme.css b/web/static/css/theme.css index fef7769..82a67ca 100644 --- a/web/static/css/theme.css +++ b/web/static/css/theme.css @@ -31,4 +31,17 @@ .tag:not(body).is-private.is-light { color: #CC00CC; 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; + } } \ No newline at end of file diff --git a/web/static/js/bulma.js b/web/static/js/bulma.js index 3b2c15a..3b1eda2 100644 --- a/web/static/js/bulma.js +++ b/web/static/js/bulma.js @@ -1,29 +1,75 @@ // Hamburger menu script for mobile. document.addEventListener('DOMContentLoaded', () => { - // Get all "navbar-burger" elements - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); + // Hamburger menu script. + (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 - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { + // Add a click event on each of them + $navbarBurgers.forEach( el => { + el.addEventListener('click', () => { - // Get the target from the "data-target" attribute - const target = el.dataset.target; - const $target = document.getElementById(target); + // Get the target from the "data-target" attribute + const target = el.dataset.target; + const $target = document.getElementById(target); - // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); + // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" + el.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. (document.querySelectorAll(".modal-background, .modal-close, .photo-modal") || []).forEach(node => { const target = node.closest(".modal"); node.addEventListener("click", () => { target.classList.remove("is-active"); - }) - }) + }); + }); }); \ No newline at end of file diff --git a/web/templates/base.html b/web/templates/base.html index 6a120b6..f9bb1f8 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -57,7 +57,7 @@ Forums --> - + Friends {{if .NavFriendRequests}}{{.NavFriendRequests}}{{end}} @@ -70,16 +70,18 @@ {{end}} -