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