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 Bolt from '@/svgs/bolt.svg'
|
||||||
import Amboss from '@/svgs/amboss.svg'
|
import Amboss from '@/svgs/amboss.svg'
|
||||||
import Mempool from '@/svgs/bimi.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 Rewards from './footer-rewards'
|
||||||
import useDarkMode from './dark-mode'
|
import useDarkMode from './dark-mode'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import { useAnimationEnabled } from '@/components/animation'
|
import { useAnimationEnabled } from '@/components/animation'
|
||||||
|
import { useLiveCommentsToggle } from './use-live-comments'
|
||||||
|
|
||||||
const RssPopover = (
|
const RssPopover = (
|
||||||
<Popover>
|
<Popover>
|
||||||
@ -147,8 +150,11 @@ export default function Footer ({ links = true }) {
|
|||||||
|
|
||||||
const [animationEnabled, toggleAnimation] = useAnimationEnabled()
|
const [animationEnabled, toggleAnimation] = useAnimationEnabled()
|
||||||
|
|
||||||
|
const [disableLiveComments, toggleLiveComments] = useLiveCommentsToggle()
|
||||||
|
|
||||||
const DarkModeIcon = darkMode ? Sun : Moon
|
const DarkModeIcon = darkMode ? Sun : Moon
|
||||||
const LnIcon = animationEnabled ? No : Bolt
|
const LnIcon = animationEnabled ? No : Bolt
|
||||||
|
const LiveIcon = disableLiveComments ? Live : NoLive
|
||||||
|
|
||||||
const version = process.env.NEXT_PUBLIC_COMMIT_HASH
|
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`}>
|
<ActionTooltip notForm overlayText={`${animationEnabled ? 'disable' : 'enable'} lightning animations`}>
|
||||||
<LnIcon onClick={toggleAnimation} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
|
<LnIcon onClick={toggleAnimation} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
|
||||||
</ActionTooltip>
|
</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>
|
||||||
<div className='mb-0' style={{ fontWeight: 500 }}>
|
<div className='mb-0' style={{ fontWeight: 500 }}>
|
||||||
<Rewards />
|
<Rewards />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import preserveScroll from './preserve-scroll'
|
import preserveScroll from './preserve-scroll'
|
||||||
import { GET_NEW_COMMENTS } from '../fragments/comments'
|
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 { SSR, COMMENT_DEPTH_LIMIT } from '../lib/constants'
|
||||||
import { useQuery, useApolloClient } from '@apollo/client'
|
import { useQuery, useApolloClient } from '@apollo/client'
|
||||||
import { commentsViewedAfterComment } from '../lib/new-comments'
|
import { commentsViewedAfterComment } from '../lib/new-comments'
|
||||||
@ -81,31 +81,29 @@ function cacheNewComments (cache, rootId, newComments, sort) {
|
|||||||
export default function useLiveComments (rootId, after, sort) {
|
export default function useLiveComments (rootId, after, sort) {
|
||||||
const latestKey = `liveCommentsLatest:${rootId}`
|
const latestKey = `liveCommentsLatest:${rootId}`
|
||||||
const { cache } = useApolloClient()
|
const { cache } = useApolloClient()
|
||||||
|
const [disableLiveComments] = useLiveCommentsToggle()
|
||||||
const [latest, setLatest] = useState(after)
|
const [latest, setLatest] = useState(after)
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const storedLatest = window.sessionStorage.getItem(latestKey)
|
const storedLatest = window.sessionStorage.getItem(latestKey)
|
||||||
if (storedLatest && storedLatest > after) {
|
if (storedLatest && storedLatest > after) {
|
||||||
setLatest(storedLatest)
|
setLatest(storedLatest)
|
||||||
} else {
|
} else {
|
||||||
setLatest(after)
|
setLatest(after)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Apollo might update the cache before the page has fully rendered, causing reads of stale cached data
|
// Apollo might update the cache before the page has fully rendered, causing reads of stale cached data
|
||||||
// this prevents GET_NEW_COMMENTS from producing results before the page has fully rendered
|
// this prevents GET_NEW_COMMENTS from producing results before the page has fully rendered
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}, [after])
|
}, [after])
|
||||||
|
|
||||||
const { data } = useQuery(GET_NEW_COMMENTS, SSR || !initialized
|
const { data } = useQuery(GET_NEW_COMMENTS, {
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
pollInterval: POLL_INTERVAL,
|
pollInterval: POLL_INTERVAL,
|
||||||
// only get comments newer than the passed latest timestamp
|
// only get comments newer than the passed latest timestamp
|
||||||
variables: { rootId, after: latest },
|
variables: { rootId, after: latest },
|
||||||
nextFetchPolicy: 'cache-and-network'
|
nextFetchPolicy: 'cache-and-network',
|
||||||
|
skip: SSR || !initialized || disableLiveComments
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -119,8 +117,41 @@ export default function useLiveComments (rootId, after, sort) {
|
|||||||
// save it to session storage, to persist between client-side navigations
|
// save it to session storage, to persist between client-side navigations
|
||||||
const newLatest = getLatestCommentCreatedAt(data.newComments.comments, latest)
|
const newLatest = getLatestCommentCreatedAt(data.newComments.comments, latest)
|
||||||
setLatest(newLatest)
|
setLatest(newLatest)
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.sessionStorage.setItem(latestKey, newLatest)
|
window.sessionStorage.setItem(latestKey, newLatest)
|
||||||
}
|
|
||||||
}, [data, cache, rootId, sort, latest])
|
}, [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