From 8517e7277cf176be4bceb0614ba2662c7bb7632e Mon Sep 17 00:00:00 2001 From: soxa <6390896+Soxasora@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:39:09 +0200 Subject: [PATCH] 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> --- components/footer.js | 9 +++++ components/use-live-comments.js | 69 ++++++++++++++++++++++++--------- svgs/chat-off-fill.svg | 1 + svgs/chat-unread-fill.svg | 1 + 4 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 svgs/chat-off-fill.svg create mode 100644 svgs/chat-unread-fill.svg diff --git a/components/footer.js b/components/footer.js index 7d1b0fa0..6caca0b0 100644 --- a/components/footer.js +++ b/components/footer.js @@ -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 = ( @@ -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 }) { + + +
diff --git a/components/use-live-comments.js b/components/use-live-comments.js index 59fa449c..e1ba735a 100644 --- a/components/use-live-comments.js +++ b/components/use-live-comments.js @@ -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] +} diff --git a/svgs/chat-off-fill.svg b/svgs/chat-off-fill.svg new file mode 100644 index 00000000..3bc60594 --- /dev/null +++ b/svgs/chat-off-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/svgs/chat-unread-fill.svg b/svgs/chat-unread-fill.svg new file mode 100644 index 00000000..0d9e1253 --- /dev/null +++ b/svgs/chat-unread-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file