From e80d4e6ad669e6cc73605b3e26310340f3272c3c Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 21 Aug 2022 21:24:36 -0700 Subject: [PATCH] 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. --- pkg/templates/template_vars.go | 43 ++++++++++++++------ web/static/css/theme.css | 13 ++++++ web/static/js/bulma.js | 74 +++++++++++++++++++++++++++------- web/templates/base.html | 38 ++++++++++++++--- web/templates/inbox/inbox.html | 5 ++- 5 files changed, 141 insertions(+), 32 deletions(-) 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}} -