live comments: favicon (#2400)

* live comments: stable navigator for new outlined comments

* favicons: FaviconProvider, handle new comments favicon via navigator

* navigator keyboard shortcuts: arrow right/escape key

* enhance: responsive fixed positioning; cleanup

enhance:
- two types of padding for desktop and mobile via CSS

cleanup:
- use appropriate <aside> for navigator
- reorder CSS

* Comments Navigator Context, new comments dot UI, refs autosorting, auto-untrack children

- Navigator Context for item pages

UI/UX
- WIP: compact comments dot UI on navbars
- long press to clear tracked refs
- auto-untrack node's children on scroll

Logic
- auto-sort comment refs via createdAt
- remove outline on untrack if called by scroll

* stable navigator dot UI positioning

* cleanup: better naming, clear structure

* re-instate favicon state updates on navigator

* CSS visibility tweaks

* scroll to start position of ref

* fix undefined navigator on other comment calls

* add explanation for early favicon clear

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
soxa 2025-08-15 20:43:31 +02:00 committed by GitHub
parent df2ccd9840
commit 610e6dcb91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 22 deletions

48
components/favicon.js Normal file
View File

@ -0,0 +1,48 @@
import { createContext, useContext, useMemo, useState } from 'react'
import { useHasNewNotes } from './use-has-new-notes'
import Head from 'next/head'
const FAVICONS = {
default: '/favicon.png',
notify: '/favicon-notify.png',
comments: '/favicon-comments.png',
notifyWithComments: '/favicon-notify-with-comments.png'
}
const getFavicon = (hasNewNotes, hasNewComments) => {
if (hasNewNotes && hasNewComments) return FAVICONS.notifyWithComments
if (hasNewNotes) return FAVICONS.notify
if (hasNewComments) return FAVICONS.comments
return FAVICONS.default
}
export const FaviconContext = createContext()
export default function FaviconProvider ({ children }) {
const hasNewNotes = useHasNewNotes()
const [hasNewComments, setHasNewComments] = useState(false)
const favicon = useMemo(() =>
getFavicon(hasNewNotes, hasNewComments),
[hasNewNotes, hasNewComments])
const contextValue = useMemo(() => ({
favicon,
hasNewNotes,
hasNewComments,
setHasNewComments
}), [favicon, hasNewNotes, hasNewComments, setHasNewComments])
return (
<FaviconContext.Provider value={contextValue}>
<Head>
<link rel='shortcut icon' href={favicon} />
</Head>
{children}
</FaviconContext.Provider>
)
}
export function useFavicon () {
return useContext(FaviconContext)
}

View File

@ -23,7 +23,6 @@ import { useWalletIndicator } from '@/wallets/client/hooks'
import SwitchAccountList, { nextAccount, useAccounts } from '@/components/account'
import { useShowModal } from '@/components/modal'
import { numWithUnits } from '@/lib/format'
import Head from 'next/head'
export function Brand ({ className }) {
return (
@ -121,9 +120,6 @@ export function NavNotifications ({ className }) {
return (
<>
<Head>
<link rel='shortcut icon' href={hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
</Head>
<Link href='/notifications' passHref legacyBehavior>
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
<NoteIcon height={28} width={20} className='theme' />

View File

@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState, startTransition, createContex
import styles from './comment.module.css'
import { useRouter } from 'next/router'
import LongPressable from './long-pressable'
import { useFavicon } from './favicon'
const CommentsNavigatorContext = createContext({
navigator: {
@ -28,6 +29,7 @@ export function useCommentsNavigatorContext () {
export function useCommentsNavigator () {
const router = useRouter()
const { setHasNewComments } = useFavicon()
const [commentCount, setCommentCount] = useState(0)
// refs in ref to not re-render on tracking
const commentRefs = useRef([])
@ -52,10 +54,12 @@ export function useCommentsNavigator () {
const clearCommentRefs = useCallback(() => {
commentRefs.current = []
startTransition?.(() => setCommentCount(0))
setHasNewComments(false)
}, [])
// track a new comment
const trackNewComment = useCallback((commentRef, createdAt) => {
setHasNewComments(true)
try {
window.requestAnimationFrame(() => {
if (!commentRef?.current || !commentRef.current.isConnected) return
@ -89,6 +93,9 @@ export function useCommentsNavigator () {
// remove a comment ref from the list
const untrackNewComment = useCallback((commentRef, options = {}) => {
// we just need to read a single comment to clear the favicon
setHasNewComments(false)
const { includeDescendants = false, clearOutline = false } = options
const refNode = commentRef.current

View File

@ -20,6 +20,7 @@ import { ChainFeeProvider } from '@/components/chain-fee.js'
import dynamic from 'next/dynamic'
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
import WalletsProvider from '@/wallets/client/context'
import FaviconProvider from '@/components/favicon'
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
@ -121,24 +122,26 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
<MeProvider me={me}>
<WalletsProvider>
<HasNewNotesProvider>
<ServiceWorkerProvider>
<PriceProvider price={price}>
<AnimationProvider>
<ToastProvider>
<ShowModalProvider>
<BlockHeightProvider blockHeight={blockHeight}>
<ChainFeeProvider chainFee={chainFee}>
<ErrorBoundary>
<Component ssrData={ssrData} {...otherProps} />
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
</ErrorBoundary>
</ChainFeeProvider>
</BlockHeightProvider>
</ShowModalProvider>
</ToastProvider>
</AnimationProvider>
</PriceProvider>
</ServiceWorkerProvider>
<FaviconProvider>
<ServiceWorkerProvider>
<PriceProvider price={price}>
<AnimationProvider>
<ToastProvider>
<ShowModalProvider>
<BlockHeightProvider blockHeight={blockHeight}>
<ChainFeeProvider chainFee={chainFee}>
<ErrorBoundary>
<Component ssrData={ssrData} {...otherProps} />
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
</ErrorBoundary>
</ChainFeeProvider>
</BlockHeightProvider>
</ShowModalProvider>
</ToastProvider>
</AnimationProvider>
</PriceProvider>
</ServiceWorkerProvider>
</FaviconProvider>
</HasNewNotesProvider>
</WalletsProvider>
</MeProvider>

BIN
public/favicon-comments.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB