From 3db6c890c26ec06e03e48e36b79d5623870d5303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 12 Sep 2025 14:12:10 +0200 Subject: [PATCH] pl-fe: add 'expand all posts' button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../pl-fe/src/components/status-content.tsx | 6 +-- packages/pl-fe/src/components/status.tsx | 4 +- .../statuses/sensitive-content-overlay.tsx | 6 +-- .../notifications/components/notification.tsx | 4 +- .../src/features/status/components/thread.tsx | 16 ++++-- packages/pl-fe/src/locales/en.json | 1 + packages/pl-fe/src/pages/statuses/status.tsx | 52 ++++++++++++------- packages/pl-fe/src/stores/status-meta.ts | 50 +++++++++++------- 8 files changed, 89 insertions(+), 50 deletions(-) diff --git a/packages/pl-fe/src/components/status-content.tsx b/packages/pl-fe/src/components/status-content.tsx index c5eee8800..ba0886a33 100644 --- a/packages/pl-fe/src/components/status-content.tsx +++ b/packages/pl-fe/src/components/status-content.tsx @@ -93,7 +93,7 @@ const StatusContent: React.FC = React.memo(({ const node = useRef(null); const spoilerNode = useRef(null); - const { statuses: statusesMeta, collapseStatus, expandStatus } = useStatusMetaStore(); + const { statuses: statusesMeta, collapseStatuses, expandStatuses } = useStatusMetaStore(); const statusMeta = statusesMeta[status.id] || {}; const { data: translation } = useStatusTranslation(status.id, statusMeta.targetLanguage); @@ -123,9 +123,9 @@ const StatusContent: React.FC = React.memo(({ e.stopPropagation(); if (expanded) { - collapseStatus(status.id); + collapseStatuses([status.id]); setCollapsed(null); - } else expandStatus(status.id); + } else expandStatuses([status.id]); }; useLayoutEffect(() => { diff --git a/packages/pl-fe/src/components/status.tsx b/packages/pl-fe/src/components/status.tsx index cdf8b89f3..bcd873448 100644 --- a/packages/pl-fe/src/components/status.tsx +++ b/packages/pl-fe/src/components/status.tsx @@ -79,7 +79,7 @@ const Status: React.FC = (props) => { const history = useHistory(); const dispatch = useAppDispatch(); - const { toggleStatusMediaHidden } = useStatusMetaStore(); + const { toggleStatusesMediaHidden } = useStatusMetaStore(); const { openModal } = useModalsStore(); const { boostModal } = useSettings(); const didShowCard = useRef(false); @@ -188,7 +188,7 @@ const Status: React.FC = (props) => { }; const handleHotkeyToggleSensitive = () => { - toggleStatusMediaHidden(actualStatus.id); + toggleStatusesMediaHidden([actualStatus.id]); }; const handleHotkeyReact = () => { diff --git a/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx b/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx index a890060c8..a398c0266 100644 --- a/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx +++ b/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx @@ -64,13 +64,13 @@ const SensitiveContentOverlay = React.forwardRef filters.map(({ filter }) => filter.title), [filters]); - const { hideStatusMedia, revealStatusMedia } = useStatusMetaStore(); + const { hideStatusesMedia, revealStatusesMedia } = useStatusMetaStore(); const toggleVisibility = (event: React.MouseEvent) => { event.stopPropagation(); - if (visible) hideStatusMedia(status.id); - else revealStatusMedia(status.id); + if (visible) hideStatusesMedia([status.id]); + else revealStatusesMedia([status.id]); }; if (!useShowOverlay(status, displayMedia)) return null; diff --git a/packages/pl-fe/src/features/notifications/components/notification.tsx b/packages/pl-fe/src/features/notifications/components/notification.tsx index 9c4c2aa3d..368bc5267 100644 --- a/packages/pl-fe/src/features/notifications/components/notification.tsx +++ b/packages/pl-fe/src/features/notifications/components/notification.tsx @@ -204,7 +204,7 @@ const Notification: React.FC = (props) => { const getNotification = useCallback(makeGetNotification(), []); const { me } = useLoggedIn(); - const { toggleStatusMediaHidden } = useStatusMetaStore(); + const { toggleStatusesMediaHidden } = useStatusMetaStore(); const { openModal } = useModalsStore(); const { settings } = useSettingsStore(); @@ -288,7 +288,7 @@ const Notification: React.FC = (props) => { const handleHotkeyToggleSensitive = useCallback(() => { if (status && typeof status === 'object') { - toggleStatusMediaHidden(status.id); + toggleStatusesMediaHidden([status.id]); } }, [status]); diff --git a/packages/pl-fe/src/features/status/components/thread.tsx b/packages/pl-fe/src/features/status/components/thread.tsx index 66eae51cf..68bbbf6aa 100644 --- a/packages/pl-fe/src/features/status/components/thread.tsx +++ b/packages/pl-fe/src/features/status/components/thread.tsx @@ -125,19 +125,21 @@ interface IThread { withMedia?: boolean; isModal?: boolean; itemClassName?: string; + setExpandAllStatuses?: (fn: () => void) => void; } -const Thread: React.FC = ({ +const Thread = ({ itemClassName, status, isModal, withMedia = true, -}) => { + setExpandAllStatuses, +}: IThread) => { const dispatch = useAppDispatch(); const history = useHistory(); const intl = useIntl(); - const { toggleStatusMediaHidden } = useStatusMetaStore(); + const { expandStatuses, revealStatusesMedia, toggleStatusesMediaHidden } = useStatusMetaStore(); const { openModal } = useModalsStore(); const { settings: { boostModal, threads: { displayMode } } } = useSettingsStore(); @@ -235,7 +237,7 @@ const Thread: React.FC = ({ }; const handleHotkeyToggleSensitive = () => { - toggleStatusMediaHidden(status.id); + toggleStatusesMediaHidden([status.id]); }; const handleMoveUp = (id: string) => { @@ -413,6 +415,12 @@ const Thread: React.FC = ({ const children = useMemo(() => renderChildren(thread), [thread, linear]); if (isModal) children.unshift(
); + useEffect(() => { + setExpandAllStatuses?.(() => { + expandStatuses(thread); + revealStatusesMedia(thread); + }); + }, [thread]); return ( = (props) => { const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector((state) => getStatus(state, { id: props.params.statusId })); + const [expandAllStatuses, setExpandAllStatuses] = useState<() => void>(); const [isLoaded, setIsLoaded] = useState(!!status); - const { settings: { threads: { displayMode } } } = useSettingsStore(); + const { settings: { displaySpoilers, threads: { displayMode } } } = useSettingsStore(); /** Fetch the status (and context) from the API. */ const fetchData = () => { @@ -77,22 +79,36 @@ const StatusPage: React.FC = (props) => { const handleRefresh = () => fetchData(); - const items: Menu = useMemo(() => [ - { - text: intl.formatMessage(messages.treeView), - action: () => dispatch(changeSetting(['threads', 'displayMode'], 'tree')), - icon: require('@tabler/icons/outline/list-tree.svg'), - type: 'radio', - checked: displayMode === 'tree', - }, - { - text: intl.formatMessage(messages.linearView), - action: () => dispatch(changeSetting(['threads', 'displayMode'], 'linear')), - icon: require('@tabler/icons/outline/list.svg'), - type: 'radio', - checked: displayMode === 'linear', - }, - ], [displayMode]); + const items = useMemo(() => { + const menu: Menu = [ + { + text: intl.formatMessage(messages.treeView), + action: () => dispatch(changeSetting(['threads', 'displayMode'], 'tree')), + icon: require('@tabler/icons/outline/list-tree.svg'), + type: 'radio', + checked: displayMode === 'tree', + }, + { + text: intl.formatMessage(messages.linearView), + action: () => dispatch(changeSetting(['threads', 'displayMode'], 'linear')), + icon: require('@tabler/icons/outline/list.svg'), + type: 'radio', + checked: displayMode === 'linear', + }, + ]; + + if (!displaySpoilers && expandAllStatuses) { + menu.push( + null, + { + text: intl.formatMessage(messages.expandAll), + action: expandAllStatuses, + icon: require('@tabler/icons/outline/chevron-down.svg'), + }, + ); + } + return menu; + }, [displayMode, expandAllStatuses]); if (status?.event) { return ( @@ -130,7 +146,7 @@ const StatusPage: React.FC = (props) => { action={} > - + setExpandAllStatuses(() => fn)} /> diff --git a/packages/pl-fe/src/stores/status-meta.ts b/packages/pl-fe/src/stores/status-meta.ts index 244dacd04..009b41368 100644 --- a/packages/pl-fe/src/stores/status-meta.ts +++ b/packages/pl-fe/src/stores/status-meta.ts @@ -3,11 +3,11 @@ import { mutative } from 'zustand-mutative'; type State = { statuses: Record; - expandStatus: (statusId: string) => void; - collapseStatus: (statusId: string) => void; - revealStatusMedia: (statusId: string) => void; - hideStatusMedia: (statusId: string) => void; - toggleStatusMediaHidden: (statusId: string) => void; + expandStatuses: (statusIds: Array) => void; + collapseStatuses: (statusIds: Array) => void; + revealStatusesMedia: (statusIds: Array) => void; + hideStatusesMedia: (statusIds: Array) => void; + toggleStatusesMediaHidden: (statusIds: Array) => void; fetchTranslation: (statusId: string, targetLanguage: string) => void; hideTranslation: (statusId: string) => void; setStatusLanguage: (statusId: string, language: string) => void; @@ -15,27 +15,41 @@ type State = { const useStatusMetaStore = create()(mutative((set) => ({ statuses: {}, - expandStatus: (statusId) => set((state: State) => { - if (!state.statuses[statusId]) state.statuses[statusId] = {}; + expandStatuses: (statusIds) => set((state: State) => { + for (const statusId of statusIds) { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; - state.statuses[statusId].expanded = true; + state.statuses[statusId].expanded = true; + } }), - collapseStatus: (statusId) => set((state: State) => { - if (!state.statuses[statusId]) state.statuses[statusId] = {}; + collapseStatuses: (statusIds) => set((state: State) => { + for (const statusId of statusIds) { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; - state.statuses[statusId].expanded = false; + state.statuses[statusId].expanded = false; + } }), - revealStatusMedia: (statusId) => set((state: State) => { - if (!state.statuses[statusId]) state.statuses[statusId] = {}; + revealStatusesMedia: (statusIds) => set((state: State) => { + for (const statusId of statusIds) { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; - state.statuses[statusId].mediaVisible = true; + state.statuses[statusId].mediaVisible = true; + } }), - hideStatusMedia: (statusId) => set((state: State) => { - if (!state.statuses[statusId]) state.statuses[statusId] = {}; + hideStatusesMedia: (statusIds) => set((state: State) => { + for (const statusId of statusIds) { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; - state.statuses[statusId].mediaVisible = false; + state.statuses[statusId].mediaVisible = false; + } }), - toggleStatusMediaHidden: (statusId) => (state: State) => state[state.statuses[statusId].mediaVisible ? 'hideStatusMedia' : 'revealStatusMedia'](statusId), + toggleStatusesMediaHidden: (statusIds) => (state: State) => { + for (const statusId of statusIds) { + if (!state.statuses[statusId]) state.statuses[statusId] = {}; + + state.statuses[statusId].mediaVisible = !state.statuses[statusId].mediaVisible; + } + }, fetchTranslation: (statusId, targetLanguage) => set((state: State) => { if (!state.statuses[statusId]) state.statuses[statusId] = {};