From 2fa5dd6d1ff60e9f5250c96d10ed3f1dddca7e3f Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 23 Oct 2025 11:59:43 +0200 Subject: [PATCH] Add UI support for disabled live feeds (#36577) Co-authored-by: diondiondion --- .../features/community_timeline/index.jsx | 18 ++++++++++++++++-- .../mastodon/features/firehose/index.jsx | 18 ++++++++++++++---- .../features/navigation_panel/index.tsx | 10 +++++----- .../features/public_timeline/index.jsx | 18 ++++++++++++++++-- app/javascript/mastodon/initial_state.ts | 8 ++++---- app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/permissions.ts | 17 +++++++++++++++++ 7 files changed, 73 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/features/community_timeline/index.jsx b/app/javascript/mastodon/features/community_timeline/index.jsx index 5652ea532..b33fdd26b 100644 --- a/app/javascript/mastodon/features/community_timeline/index.jsx +++ b/app/javascript/mastodon/features/community_timeline/index.jsx @@ -10,7 +10,8 @@ import { connect } from 'react-redux'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { domain } from 'mastodon/initial_state'; +import { domain, localLiveFeedAccess } from 'mastodon/initial_state'; +import { canViewFeed } from 'mastodon/permissions'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { connectCommunityStream } from '../../actions/streaming'; @@ -120,8 +121,21 @@ class CommunityTimeline extends PureComponent { render () { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; + const { signedIn, permissions } = this.props.identity; const pinned = !!columnId; + const emptyMessage = canViewFeed(signedIn, permissions, localLiveFeedAccess) ? ( + + ) : ( + + ); + return ( } + emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/firehose/index.jsx b/app/javascript/mastodon/features/firehose/index.jsx index 69db171cd..91704f123 100644 --- a/app/javascript/mastodon/features/firehose/index.jsx +++ b/app/javascript/mastodon/features/firehose/index.jsx @@ -13,7 +13,8 @@ import { changeSetting } from 'mastodon/actions/settings'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; -import { localLiveFeedAccess, remoteLiveFeedAccess, me, domain } from 'mastodon/initial_state'; +import { localLiveFeedAccess, remoteLiveFeedAccess, domain } from 'mastodon/initial_state'; +import { canViewFeed } from 'mastodon/permissions'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import Column from '../../components/column'; @@ -52,7 +53,7 @@ const ColumnSettings = () => { const Firehose = ({ feedType, multiColumn }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { signedIn } = useIdentity(); + const { signedIn, permissions } = useIdentity(); const columnRef = useRef(null); const onlyMedia = useAppSelector((state) => state.getIn(['settings', 'firehose', 'onlyMedia'], false)); @@ -151,6 +152,15 @@ const Firehose = ({ feedType, multiColumn }) => { /> ); + const canViewSelectedFeed = canViewFeed(signedIn, permissions, feedType === 'community' ? localLiveFeedAccess : remoteLiveFeedAccess); + + const disabledTimelineMessage = ( + + ); + return ( { - {(signedIn || (localLiveFeedAccess === 'public' && remoteLiveFeedAccess === 'public')) && ( + {(canViewFeed(signedIn, permissions, localLiveFeedAccess) && canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && (
@@ -187,7 +197,7 @@ const Firehose = ({ feedType, multiColumn }) => { onLoadMore={handleLoadMore} trackScroll scrollKey='firehose' - emptyMessage={emptyMessage} + emptyMessage={canViewSelectedFeed ? emptyMessage : disabledTimelineMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/navigation_panel/index.tsx b/app/javascript/mastodon/features/navigation_panel/index.tsx index d509bfb6c..446deb1dd 100644 --- a/app/javascript/mastodon/features/navigation_panel/index.tsx +++ b/app/javascript/mastodon/features/navigation_panel/index.tsx @@ -42,6 +42,7 @@ import { me, } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; +import { canViewFeed } from 'mastodon/permissions'; import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -194,7 +195,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ multiColumn = false, }) => { const intl = useIntl(); - const { signedIn, disabledAccountId } = useIdentity(); + const { signedIn, permissions, disabledAccountId } = useIdentity(); const location = useLocation(); const showSearch = useBreakpoint('full') && !multiColumn; @@ -262,13 +263,12 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ /> )} - {(signedIn || - localLiveFeedAccess === 'public' || - remoteLiveFeedAccess === 'public') && ( + {(canViewFeed(signedIn, permissions, localLiveFeedAccess) || + canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && ( + ) : ( + + ); + return ( } + emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/initial_state.ts b/app/javascript/mastodon/initial_state.ts index 324c093b4..c1cd37753 100644 --- a/app/javascript/mastodon/initial_state.ts +++ b/app/javascript/mastodon/initial_state.ts @@ -33,10 +33,10 @@ interface InitialStateMeta { single_user_mode: boolean; source_url: string; streaming_api_base_url: string; - local_live_feed_access: 'public' | 'authenticated'; - remote_live_feed_access: 'public' | 'authenticated'; - local_topic_feed_access: 'public' | 'authenticated'; - remote_topic_feed_access: 'public' | 'authenticated'; + local_live_feed_access: 'public' | 'authenticated' | 'disabled'; + remote_live_feed_access: 'public' | 'authenticated' | 'disabled'; + local_topic_feed_access: 'public' | 'authenticated' | 'disabled'; + remote_topic_feed_access: 'public' | 'authenticated' | 'disabled'; title: string; show_trends: boolean; trends_as_landing_page: boolean; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 6917bfef3..12fb8f434 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -333,6 +333,7 @@ "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.", + "empty_column.disabled_feed": "This feed has been disabled by your server administrators.", "empty_column.domain_blocks": "There are no blocked domains yet.", "empty_column.explore_statuses": "Nothing is trending right now. Check back later!", "empty_column.favourited_statuses": "You don't have any favorite posts yet. When you favorite one, it will show up here.", diff --git a/app/javascript/mastodon/permissions.ts b/app/javascript/mastodon/permissions.ts index d7695d2f5..a83e1d77a 100644 --- a/app/javascript/mastodon/permissions.ts +++ b/app/javascript/mastodon/permissions.ts @@ -1,3 +1,4 @@ +export const PEMRISSION_VIEW_FEEDS = 0x0000000000100000; export const PERMISSION_INVITE_USERS = 0x0000000000010000; export const PERMISSION_MANAGE_USERS = 0x0000000000000400; export const PERMISSION_MANAGE_TAXONOMIES = 0x0000000000000100; @@ -22,3 +23,19 @@ export function canManageReports(permissions: number) { (permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS ); } + +export const canViewFeed = ( + signedIn: boolean, + permissions: number, + setting: 'public' | 'authenticated' | 'disabled' | undefined, +) => { + switch (setting) { + case 'public': + return true; + case 'authenticated': + return signedIn; + case 'disabled': + default: + return (permissions & PEMRISSION_VIEW_FEEDS) === PEMRISSION_VIEW_FEEDS; + } +};