Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/notifications: implement popover based notification tray

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
committer
Lewis
date (May 29, 2026, 2:50 PM +0300) commit 6c83413a parent 71b07346 change-id nlrvwvnp
+92 -5
+25
appview/notifications/notifications.go
··· 38 38 r.Group(func(r chi.Router) { 39 39 r.Use(middleware.AuthMiddleware(n.oauth)) 40 40 r.With(middleware.Paginate).Get("/", n.notificationsPage) 41 + r.Get("/preview", n.previewHandler) 41 42 r.Post("/{id}/read", n.markRead) 42 43 r.Post("/read-all", n.markAllRead) 43 44 r.Delete("/{id}", n.deleteNotification) ··· 87 88 Page: page, 88 89 Total: total, 89 90 }) 91 + } 92 + 93 + func (n *Notifications) previewHandler(w http.ResponseWriter, r *http.Request) { 94 + l := n.logger.With("handler", "previewHandler") 95 + user := n.oauth.GetMultiAccountUser(r) 96 + 97 + notifications, err := db.GetNotificationsWithEntities( 98 + n.db, 99 + pagination.Page{Limit: 5, Offset: 0}, 100 + orm.FilterEq("recipient_did", user.Did), 101 + ) 102 + if err != nil { 103 + l.Error("failed to get notifications", "err", err) 104 + n.pages.Error500(w) 105 + return 106 + } 107 + 108 + err = n.pages.NotificationPreview(w, pages.NotificationPreviewParams{ 109 + LoggedInUser: user, 110 + Notifications: notifications, 111 + }) 112 + if err != nil { 113 + l.Error("failed to render notification preview", "err", err) 114 + } 90 115 } 91 116 92 117 func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
+9
appview/pages/pages.go
··· 473 473 return p.executePlain("notifications/fragments/count", w, params) 474 474 } 475 475 476 + type NotificationPreviewParams struct { 477 + LoggedInUser *oauth.MultiAccountUser 478 + Notifications []*models.NotificationWithEntity 479 + } 480 + 481 + func (p *Pages) NotificationPreview(w io.Writer, params NotificationPreviewParams) error { 482 + return p.executePlain("notifications/fragments/preview", w, params) 483 + } 484 + 476 485 type UserKeysSettingsParams struct { 477 486 LoggedInUser *oauth.MultiAccountUser 478 487 PubKeys []models.PublicKey
+39 -5
appview/pages/templates/notifications/fragments/bell.html
··· 1 1 {{define "notifications/fragments/bell"}} 2 - <div class="relative" 2 + <style> 3 + #notif-bell-btn { 4 + anchor-name: --notif-bell; 5 + } 6 + #notif-popup { 7 + position-anchor: --notif-bell; 8 + position-area: bottom span-left; 9 + } 10 + </style> 11 + {{/* mobile - lnik to notifications page */}} 12 + <a href="/notifications" 13 + class="md:hidden relative text-gray-500 dark:text-gray-400 flex gap-1 items-end" 3 14 hx-get="/notifications/count" 4 - hx-target="#notification-count" 15 + hx-target="#notification-count-mobile" 5 16 hx-trigger="load, every 30s"> 6 - <a href="/notifications" class="text-gray-500 dark:text-gray-400 flex gap-1 items-end group"> 7 17 {{ i "bell" "size-5" }} 8 - <span id="notification-count"></span> 18 + <span id="notification-count-mobile"></span> 9 19 </a> 10 - </div> 20 + 21 + {{/* desktop popover button */}} 22 + <button 23 + id="notif-bell-btn" 24 + popovertarget="notif-popup" 25 + popovertargetaction="toggle" 26 + class="hidden md:flex relative text-gray-500 dark:text-gray-400 gap-1 items-end cursor-pointer" 27 + hx-get="/notifications/count" 28 + hx-target="#notification-count-desktop" 29 + hx-trigger="load, every 30s"> 30 + {{ i "bell" "size-5" }} 31 + <span id="notification-count-desktop"></span> 32 + </button> 33 + 34 + <div 35 + popover 36 + id="notif-popup" 37 + hx-get="/notifications/preview?read=unread" 38 + hx-trigger="toggle[newState=='open'] once" 39 + hx-swap="innerHTML" 40 + class="mt-1 w-[36rem] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded drop-shadow-lg dark:text-white p-0 overflow-hidden"> 41 + <div class="px-4 py-8 text-sm text-center text-gray-400 dark:text-gray-500"> 42 + {{ i "loader-circle" "size-4 animate-spin mx-auto" }} 43 + </div> 44 + </div> 11 45 {{end}}
+19
appview/pages/templates/notifications/fragments/preview.html
··· 1 + {{ define "notifications/fragments/preview" }} 2 + {{ if .Notifications }} 3 + <div class="divide-y divide-gray-200 dark:divide-gray-700 bg-slate-100 dark:bg-gray-900"> 4 + {{ range .Notifications }} 5 + {{ template "notifications/fragments/item" . }} 6 + {{ end }} 7 + </div> 8 + {{ else }} 9 + <div class="px-4 py-8 text-sm text-center text-gray-400 dark:text-gray-500"> 10 + No notifications 11 + </div> 12 + {{ end }} 13 + <div class="flex items-center justify-end px-4 py-2 border-t border-gray-200 dark:border-gray-700 text-sm"> 14 + <a href="/notifications" 15 + class="flex items-center gap-1 hover:text-gray-600 dark:hover:text-gray-300 no-underline hover:no-underline"> 16 + view all {{ i "arrow-right" "size-4" }} 17 + </a> 18 + </div> 19 + {{ end }}