live comments: toggle (#2421)
* enhance: toggle live comments on posts, default status set by user settings * wip: toggle via mutation, footer placement * chat icon on footer, consistent naming, perf tweaks * update all tabs on toggle by dispatching events, correct icon, cleanup cleanup: - remove useless window checks - use skip instead of conditional options - correct naming * update localstorage on user setting change * revert disableLiveComments user setting * avoid redundant setState and usage of maybe stale state --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									e46f4f01b2
								
							
						
					
					
						commit
						8517e7277c
					
				@ -12,10 +12,13 @@ import No from '@/svgs/no.svg'
 | 
			
		||||
import Bolt from '@/svgs/bolt.svg'
 | 
			
		||||
import Amboss from '@/svgs/amboss.svg'
 | 
			
		||||
import Mempool from '@/svgs/bimi.svg'
 | 
			
		||||
import Live from '@/svgs/chat-unread-fill.svg'
 | 
			
		||||
import NoLive from '@/svgs/chat-off-fill.svg'
 | 
			
		||||
import Rewards from './footer-rewards'
 | 
			
		||||
import useDarkMode from './dark-mode'
 | 
			
		||||
import ActionTooltip from './action-tooltip'
 | 
			
		||||
import { useAnimationEnabled } from '@/components/animation'
 | 
			
		||||
import { useLiveCommentsToggle } from './use-live-comments'
 | 
			
		||||
 | 
			
		||||
const RssPopover = (
 | 
			
		||||
  <Popover>
 | 
			
		||||
@ -147,8 +150,11 @@ export default function Footer ({ links = true }) {
 | 
			
		||||
 | 
			
		||||
  const [animationEnabled, toggleAnimation] = useAnimationEnabled()
 | 
			
		||||
 | 
			
		||||
  const [disableLiveComments, toggleLiveComments] = useLiveCommentsToggle()
 | 
			
		||||
 | 
			
		||||
  const DarkModeIcon = darkMode ? Sun : Moon
 | 
			
		||||
  const LnIcon = animationEnabled ? No : Bolt
 | 
			
		||||
  const LiveIcon = disableLiveComments ? Live : NoLive
 | 
			
		||||
 | 
			
		||||
  const version = process.env.NEXT_PUBLIC_COMMIT_HASH
 | 
			
		||||
 | 
			
		||||
@ -164,6 +170,9 @@ export default function Footer ({ links = true }) {
 | 
			
		||||
              <ActionTooltip notForm overlayText={`${animationEnabled ? 'disable' : 'enable'} lightning animations`}>
 | 
			
		||||
                <LnIcon onClick={toggleAnimation} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
 | 
			
		||||
              </ActionTooltip>
 | 
			
		||||
              <ActionTooltip notForm overlayText={`${disableLiveComments ? 'enable' : 'disable'} live comments`}>
 | 
			
		||||
                <LiveIcon onClick={toggleLiveComments} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
 | 
			
		||||
              </ActionTooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className='mb-0' style={{ fontWeight: 500 }}>
 | 
			
		||||
              <Rewards />
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import preserveScroll from './preserve-scroll'
 | 
			
		||||
import { GET_NEW_COMMENTS } from '../fragments/comments'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { useEffect, useState, useCallback } from 'react'
 | 
			
		||||
import { SSR, COMMENT_DEPTH_LIMIT } from '../lib/constants'
 | 
			
		||||
import { useQuery, useApolloClient } from '@apollo/client'
 | 
			
		||||
import { commentsViewedAfterComment } from '../lib/new-comments'
 | 
			
		||||
@ -81,17 +81,16 @@ function cacheNewComments (cache, rootId, newComments, sort) {
 | 
			
		||||
export default function useLiveComments (rootId, after, sort) {
 | 
			
		||||
  const latestKey = `liveCommentsLatest:${rootId}`
 | 
			
		||||
  const { cache } = useApolloClient()
 | 
			
		||||
  const [disableLiveComments] = useLiveCommentsToggle()
 | 
			
		||||
  const [latest, setLatest] = useState(after)
 | 
			
		||||
  const [initialized, setInitialized] = useState(false)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (typeof window !== 'undefined') {
 | 
			
		||||
      const storedLatest = window.sessionStorage.getItem(latestKey)
 | 
			
		||||
      if (storedLatest && storedLatest > after) {
 | 
			
		||||
        setLatest(storedLatest)
 | 
			
		||||
      } else {
 | 
			
		||||
        setLatest(after)
 | 
			
		||||
      }
 | 
			
		||||
    const storedLatest = window.sessionStorage.getItem(latestKey)
 | 
			
		||||
    if (storedLatest && storedLatest > after) {
 | 
			
		||||
      setLatest(storedLatest)
 | 
			
		||||
    } else {
 | 
			
		||||
      setLatest(after)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Apollo might update the cache before the page has fully rendered, causing reads of stale cached data
 | 
			
		||||
@ -99,14 +98,13 @@ export default function useLiveComments (rootId, after, sort) {
 | 
			
		||||
    setInitialized(true)
 | 
			
		||||
  }, [after])
 | 
			
		||||
 | 
			
		||||
  const { data } = useQuery(GET_NEW_COMMENTS, SSR || !initialized
 | 
			
		||||
    ? {}
 | 
			
		||||
    : {
 | 
			
		||||
        pollInterval: POLL_INTERVAL,
 | 
			
		||||
        // only get comments newer than the passed latest timestamp
 | 
			
		||||
        variables: { rootId, after: latest },
 | 
			
		||||
        nextFetchPolicy: 'cache-and-network'
 | 
			
		||||
      })
 | 
			
		||||
  const { data } = useQuery(GET_NEW_COMMENTS, {
 | 
			
		||||
    pollInterval: POLL_INTERVAL,
 | 
			
		||||
    // only get comments newer than the passed latest timestamp
 | 
			
		||||
    variables: { rootId, after: latest },
 | 
			
		||||
    nextFetchPolicy: 'cache-and-network',
 | 
			
		||||
    skip: SSR || !initialized || disableLiveComments
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!data?.newComments?.comments?.length) return
 | 
			
		||||
@ -119,8 +117,41 @@ export default function useLiveComments (rootId, after, sort) {
 | 
			
		||||
    // save it to session storage, to persist between client-side navigations
 | 
			
		||||
    const newLatest = getLatestCommentCreatedAt(data.newComments.comments, latest)
 | 
			
		||||
    setLatest(newLatest)
 | 
			
		||||
    if (typeof window !== 'undefined') {
 | 
			
		||||
      window.sessionStorage.setItem(latestKey, newLatest)
 | 
			
		||||
    }
 | 
			
		||||
    window.sessionStorage.setItem(latestKey, newLatest)
 | 
			
		||||
  }, [data, cache, rootId, sort, latest])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const STORAGE_KEY = 'disableLiveComments'
 | 
			
		||||
const TOGGLE_EVENT = 'liveComments:toggle'
 | 
			
		||||
 | 
			
		||||
export function useLiveCommentsToggle () {
 | 
			
		||||
  const [disableLiveComments, setDisableLiveComments] = useState(false)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // preference: local storage
 | 
			
		||||
    const read = () => setDisableLiveComments(window.localStorage.getItem(STORAGE_KEY) === 'true')
 | 
			
		||||
    read()
 | 
			
		||||
 | 
			
		||||
    // update across tabs
 | 
			
		||||
    const onStorage = e => { if (e.key === STORAGE_KEY) read() }
 | 
			
		||||
    // update this tab
 | 
			
		||||
    const onToggle = () => read()
 | 
			
		||||
 | 
			
		||||
    window.addEventListener('storage', onStorage)
 | 
			
		||||
    window.addEventListener(TOGGLE_EVENT, onToggle)
 | 
			
		||||
    return () => {
 | 
			
		||||
      window.removeEventListener('storage', onStorage)
 | 
			
		||||
      window.removeEventListener(TOGGLE_EVENT, onToggle)
 | 
			
		||||
    }
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const toggle = useCallback(() => {
 | 
			
		||||
    const current = window.localStorage.getItem(STORAGE_KEY) === 'true'
 | 
			
		||||
 | 
			
		||||
    window.localStorage.setItem(STORAGE_KEY, !current)
 | 
			
		||||
    // trigger local event to update this tab
 | 
			
		||||
    window.dispatchEvent(new Event(TOGGLE_EVENT))
 | 
			
		||||
  }, [disableLiveComments])
 | 
			
		||||
 | 
			
		||||
  return [disableLiveComments, toggle]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								svgs/chat-off-fill.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								svgs/chat-off-fill.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2.80777 1.39337L22.6068 21.1924L21.1925 22.6066L17.5846 18.9994L6.45516 19L2.00016 22.5V3.99997C2.00016 3.83067 2.04223 3.6712 2.11649 3.53143L1.39355 2.80759L2.80777 1.39337ZM21.0002 2.99997C21.5524 2.99997 22.0002 3.44769 22.0002 3.99997V17.785L7.21416 2.99997H21.0002Z"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 376 B  | 
							
								
								
									
										1
									
								
								svgs/chat-unread-fill.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								svgs/chat-unread-fill.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M21 7C22.6569 7 24 5.65685 24 4C24 2.34315 22.6569 1 21 1C19.3431 1 18 2.34315 18 4C18 5.65685 19.3431 7 21 7ZM21 9C21.3425 9 21.6769 8.96557 22 8.89998V18C22 18.5523 21.5523 19 21 19H6.45455L2 22.5V4C2 3.44772 2.44772 3 3 3H16.1C16.0344 3.32311 16 3.65753 16 4C16 6.76142 18.2386 9 21 9Z"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 392 B  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user