pl-fe: improve empty column messages

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-10-13 14:33:27 +02:00
parent 7e17ec3ac0
commit 8cfda4b38d
58 changed files with 119 additions and 128 deletions

View File

@@ -249,7 +249,7 @@ const NotificationsColumn = () => {
isLoading={isLoading}
showLoading={isLoading && displayedNotifications.length === 0}
hasMore={hasMore}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
placeholderComponent={PlaceholderNotification}
placeholderCount={20}
onLoadMore={handleLoadOlder}

View File

@@ -0,0 +1,24 @@
import React from 'react';
import Icon from './ui/icon';
import Stack from './ui/stack';
import Text from './ui/text';
interface IEmptyMessage {
text: React.ReactNode;
icon?: string;
}
const EmptyMessage: React.FC<IEmptyMessage> = ({ text, icon = require('@phosphor-icons/core/regular/empty.svg') }) => (
<Stack space={4} className='⁂-empty-message py-6' justifyContent='center' alignItems='center'>
<div className='rounded-full bg-gray-200 p-4 dark:bg-gray-800'>
<Icon src={icon} className='size-6 text-gray-600' />
</div>
<Text theme='muted' align='center'>
{text}
</Text>
</Stack>
);
export { EmptyMessage };

View File

@@ -4,10 +4,11 @@ import { useHistory } from 'react-router-dom';
import { Virtuoso, Components, VirtuosoProps, VirtuosoHandle, ListRange, IndexLocationWithAlign } from 'react-virtuoso';
import LoadMore from 'pl-fe/components/load-more';
import Card from 'pl-fe/components/ui/card';
import Spinner from 'pl-fe/components/ui/spinner';
import { useSettings } from 'pl-fe/hooks/use-settings';
import { EmptyMessage } from './empty-message';
/** Custom Viruoso component context. */
type Context = {
itemClassName?: string;
@@ -48,12 +49,12 @@ interface IScrollableList extends VirtuosoProps<any, any> {
hasMore?: boolean;
/** Additional element to display at the top of the list. */
prepend?: React.ReactNode;
/** Whether to display the prepended element. */
alwaysPrepend?: boolean;
/** Message to display when the list is loaded but empty. */
emptyMessage?: React.ReactNode;
/** Should the empty message be displayed in a Card */
emptyMessageCard?: boolean;
/** Message to display when the list is loaded but empty. */
emptyMessageText?: React.ReactNode;
/** Message to display next to the emptyMessage text. */
emptyMessageIcon?: string;
/** Scrollable content. */
children: Iterable<React.ReactNode>;
/** Callback when the list is scrolled to the top. */
@@ -83,11 +84,11 @@ interface IScrollableList extends VirtuosoProps<any, any> {
const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
scrollKey,
prepend = null,
alwaysPrepend,
children,
isLoading,
emptyMessage,
emptyMessageCard = true,
emptyMessageText,
emptyMessageIcon,
showLoading,
onScroll,
onScrollToTop,
@@ -153,23 +154,13 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
}, []);
/* Render an empty state instead of the scrollable list. */
const renderEmpty = (): JSX.Element => (
<div className='mt-2'>
{alwaysPrepend && prepend}
{isLoading ? (
<Spinner />
) : (
<>
{emptyMessageCard ? (
<Card variant='rounded' size='lg'>
{emptyMessage}
</Card>
) : emptyMessage}
</>
)}
</div>
);
const renderEmpty = (): JSX.Element => {
return isLoading ? (
<Spinner />
) : emptyMessageText ? (
<EmptyMessage text={emptyMessageText} icon={emptyMessageIcon} />
) : <>{emptyMessage}</>;
};
/** Render a single item. */
const renderItem = (_i: number, element: JSX.Element): JSX.Element => {
@@ -250,9 +241,9 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
itemClassName,
}}
components={{
Header: () => <>{prepend}</>,
Header: prepend ? () => <>{prepend}</> : undefined,
ScrollSeekPlaceholder: Placeholder as any,
EmptyPlaceholder: () => renderEmpty(),
EmptyPlaceholder: renderEmpty,
List,
Item,
Footer: loadMore,

View File

@@ -1,18 +1,12 @@
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import ScrollableList from 'pl-fe/components/scrollable-list';
import { useAdminAccounts } from 'pl-fe/queries/admin/use-accounts';
import UnapprovedAccount from '../components/unapproved-account';
const messages = defineMessages({
heading: { id: 'column.admin.awaiting_approval', defaultMessage: 'Awaiting Approval' },
emptyMessage: { id: 'admin.awaiting_approval.empty_message', defaultMessage: 'There is nobody waiting for approval. When a new user signs up, you can review them here.' },
});
const AwaitingApproval: React.FC = () => {
const intl = useIntl();
const { data, isPending, isFetching } = useAdminAccounts({
origin: 'local',
status: 'pending',
@@ -29,7 +23,7 @@ const AwaitingApproval: React.FC = () => {
scrollKey='awaitingApproval'
isLoading={isFetching}
showLoading={isPending}
emptyMessage={intl.formatMessage(messages.emptyMessage)}
emptyMessageText={<FormattedMessage id='admin.awaiting_approval.empty_message' defaultMessage='There is nobody waiting for approval. When a new user signs up, you can review them here.' />}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
>
{accountIds.map(id => (

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl';
import { FormattedList, FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
@@ -11,14 +11,7 @@ import { useReports } from 'pl-fe/queries/admin/use-reports';
import Report from '../components/report';
const messages = defineMessages({
heading: { id: 'column.admin.reports', defaultMessage: 'Reports' },
modlog: { id: 'column.admin.reports.menu.moderation_log', defaultMessage: 'Moderation log' },
emptyMessage: { id: 'admin.reports.empty_message', defaultMessage: 'There are no open reports. If a user gets reported, they will show up here.' },
});
const Reports: React.FC = () => {
const intl = useIntl();
const [params, setParams] = useSearchParams();
const resolved = params.get('resolved') as any as boolean || undefined;
@@ -70,7 +63,7 @@ const Reports: React.FC = () => {
scrollKey='adminReports'
isLoading={isPending}
showLoading={isPending}
emptyMessage={intl.formatMessage(messages.emptyMessage)}
emptyMessageText={<FormattedMessage id='admin.reports.empty_message' defaultMessage='There are no open reports. If a user gets reported, they will show up here.' />}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
itemClassName='pt-4'

View File

@@ -61,7 +61,7 @@ const ManagePendingParticipants: React.FC<IManagePendingParticipants> = ({ statu
<Stack space={3}>
<ScrollableList
scrollKey={`eventPendingParticipants:${statusId}`}
emptyMessage={<FormattedMessage id='empty_column.event_participant_requests' defaultMessage='There are no pending event participation requests.' />}
emptyMessageText={<FormattedMessage id='empty_column.event_participant_requests' defaultMessage='There are no pending event participation requests.' />}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}

View File

@@ -45,7 +45,7 @@ const ConversationsList: React.FC = () => {
id='direct-list'
isLoading={isLoading}
showLoading={isLoading && conversations.length === 0}
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
emptyMessageText={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
>
{conversations.map((item: any) => (
<Conversation

View File

@@ -217,7 +217,6 @@
"admin.statuses.status_marked_message_sensitive": "Post by @{acct} was marked sensitive",
"admin.theme.title": "Theme",
"admin.user_index.empty": "No users found.",
"admin.user_index.search_input_placeholder": "Who are you looking for?",
"admin.users.actions.deactivate_user": "Deactivate @{name}",
"admin.users.actions.delete_user": "Delete @{name}",
"admin.users.actions.demote_to_moderator_message": "@{acct} was demoted to a moderator",
@@ -363,7 +362,6 @@
"circles.subheading": "Your circles",
"column.admin.account": "Moderate @{acct}",
"column.admin.announcements": "Announcements",
"column.admin.awaiting_approval": "Awaiting Approval",
"column.admin.create_announcement": "Create announcement",
"column.admin.create_domain": "Create domain",
"column.admin.create_rule": "Create rule",
@@ -374,11 +372,9 @@
"column.admin.edit_rule": "Edit rule",
"column.admin.moderation_log": "Moderation log",
"column.admin.relays": "Instance relays",
"column.admin.reports": "Reports",
"column.admin.reports.filter_message": "You are displaying reports {query}.",
"column.admin.reports.filter_message.account": "from @{acct}",
"column.admin.reports.filter_message.target_account": "targeting @{acct}",
"column.admin.reports.menu.moderation_log": "Moderation log",
"column.admin.rules": "Instance rules",
"column.admin.users": "Users",
"column.aliases": "Account aliases",

View File

@@ -27,7 +27,7 @@ const BirthdaysModal = ({ onClose }: BaseModalProps) => {
body = (
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
useWindowScroll={false}

View File

@@ -31,7 +31,7 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
body = (
<PullToRefresh onRefresh={refetch}>
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}

View File

@@ -31,7 +31,7 @@ const EventParticipantsModal: React.FC<BaseModalProps & EventParticipantsModalPr
body = (
<PullToRefresh onRefresh={refetch}>
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
hasMore={hasNextPage}

View File

@@ -41,7 +41,7 @@ const FamiliarFollowersModal: React.FC<BaseModalProps & FamiliarFollowersModalPr
body = (
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
useWindowScroll={false}

View File

@@ -31,7 +31,7 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
body = (
<PullToRefresh onRefresh={refetch}>
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}

View File

@@ -85,7 +85,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
{reactions.length > 0 && renderFilterBar()}
<PullToRefresh onRefresh={refetch}>
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName={clsx('max-w-full', {
'!mt-4': reactions.length > 0,
})}

View File

@@ -31,7 +31,7 @@ const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, s
body = (
<PullToRefresh onRefresh={refetch}>
<ScrollableList
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}

View File

@@ -82,7 +82,7 @@ const FollowRequestsPage: React.FC = () => {
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
emptyMessageText={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
>
{accountIds.map(id =>
<AccountAuthorize key={id} id={id} />,

View File

@@ -59,7 +59,7 @@ const FollowersPage: React.FC<IFollowersPage> = ({ params }) => {
scrollKey='followers'
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
emptyMessage={<FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />}
emptyMessageText={<FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />}
itemClassName='pb-4'
isLoading={isFetching}
>

View File

@@ -59,7 +59,7 @@ const FollowingPage: React.FC<IFollowingPage> = ({ params }) => {
scrollKey='following'
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
emptyMessage={<FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />}
emptyMessageText={<FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />}
itemClassName='pb-4'
isLoading={isFetching}
>

View File

@@ -24,7 +24,7 @@ const OutgoingFollowRequestsPage: React.FC = () => {
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={<FormattedMessage id='empty_column.outgoing_follow_requests' defaultMessage="You don't have any outgoing follow requests yet. When you try to follow a user, it will show up here." />}
emptyMessageText={<FormattedMessage id='empty_column.outgoing_follow_requests' defaultMessage="You don't have any outgoing follow requests yet. When you try to follow a user, it will show up here." />}
itemClassName='p-2.5'
>
{accountIds.map(id =>

View File

@@ -98,7 +98,7 @@ const AccountTimelinePage: React.FC<IAccountTimelinePage> = ({ params, withRepli
isLoading={isLoading}
hasMore={hasMore}
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts here!' />}
emptyMessageText={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts here!' />}
/>
);
};

View File

@@ -118,7 +118,7 @@ const AdminAnnouncementsPage: React.FC = () => {
</Button>
<ScrollableList
scrollKey='announcements'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='py-3 first:pt-0 last:pb-0'
isLoading={isLoading}
showLoading={isLoading && isPending}

View File

@@ -132,7 +132,7 @@ const AdminDomainsPage: React.FC = () => {
{domains && (
<ScrollableList
scrollKey='domains'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='py-3 first:pt-0 last:pb-0'
isLoading={isFetching}
showLoading={isFetching && !domains?.length}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { defineMessages, FormattedDate, useIntl } from 'react-intl';
import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
@@ -11,7 +11,6 @@ import type { AdminModerationLogEntry } from 'pl-api';
const messages = defineMessages({
heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation log' },
emptyMessage: { id: 'admin.moderation_log.empty_message', defaultMessage: 'You have not performed any moderation actions yet. When you do, a history will be shown here.' },
});
const ModerationLogPage = () => {
@@ -36,7 +35,7 @@ const ModerationLogPage = () => {
scrollKey='moderationLog'
isLoading={isLoading}
showLoading={showLoading}
emptyMessage={intl.formatMessage(messages.emptyMessage)}
emptyMessageText={<FormattedMessage id='admin.moderation_log.empty_message' defaultMessage='You have not performed any moderation actions yet. When you do, a history will be shown here.' />}
hasMore={hasNextPage}
onLoadMore={handleLoadMore}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'

View File

@@ -127,7 +127,7 @@ const RelaysPage: React.FC = () => {
{relays && (
<ScrollableList
scrollKey='relays'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='py-3 first:pt-0 last:pb-0'
isLoading={isFetching}
showLoading={isFetching && !relays?.length}

View File

@@ -98,7 +98,7 @@ const RulesPage: React.FC = () => {
</Button>
<ScrollableList
scrollKey='rules'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='py-3 first:pt-0 last:pb-0'
isLoading={isLoading}
showLoading={isLoading}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useSearchParams } from 'react-router-dom-v5-compat';
import ScrollableList from 'pl-fe/components/scrollable-list';
@@ -11,8 +11,6 @@ import { SearchInput } from '../search/search';
const messages = defineMessages({
heading: { id: 'column.admin.users', defaultMessage: 'Users' },
empty: { id: 'admin.user_index.empty', defaultMessage: 'No users found.' },
searchPlaceholder: { id: 'admin.user_index.search_input_placeholder', defaultMessage: 'Who are you looking for?' },
});
@@ -37,7 +35,7 @@ const UserIndexPage: React.FC = () => {
isLoading={isFetching}
showLoading={isPending}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={intl.formatMessage(messages.empty)}
emptyMessageText={<FormattedMessage id='admin.user_index.empty' defaultMessage='No users found.' />}
itemClassName='pb-4'
>
{(accountIds || []).map(id =>

View File

@@ -84,8 +84,7 @@ const GroupBlockedMembers: React.FC<IGroupBlockedMembers> = ({ params }) => {
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${group.id}/manage`}>
<ScrollableList
scrollKey={`groupBlockedMembers:${groupId}`}
emptyMessage={emptyMessage}
emptyMessageCard={false}
emptyMessageText={emptyMessage}
>
{accountIds.map((accountId) =>
<BlockedMember key={accountId} accountId={accountId} groupId={groupId} />,

View File

@@ -108,7 +108,7 @@ const GroupMembershipRequests: React.FC<IGroupMembershipRequests> = ({ params })
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey={`groupMembershipRequests:${groupId}`}
emptyMessage={<FormattedMessage id='empty_column.group_membership_requests' defaultMessage='There are no pending membership requests for this group.' />}
emptyMessageText={<FormattedMessage id='empty_column.group_membership_requests' defaultMessage='There are no pending membership requests for this group.' />}
>
{accounts.map((account) => (
<MembershipRequest

View File

@@ -68,8 +68,7 @@ const Groups: React.FC = () => {
<ScrollableList
scrollKey='groups'
emptyMessage={renderBlankslate()}
emptyMessageCard={false}
emptyMessageText={renderBlankslate()}
itemClassName='pb-4 last:pb-0'
isLoading={isLoading}
showLoading={isLoading && groups.length === 0}

View File

@@ -159,7 +159,7 @@ const AliasesPage = () => {
<div className='flex-1'>
<ScrollableList
scrollKey='aliases'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
>
{aliases.map((alias, i) => (
<HStack alignItems='center' justifyContent='between' space={1} key={i} className='p-2'>

View File

@@ -39,8 +39,7 @@ const BlocksPage: React.FC = () => {
scrollKey='blocks'
onLoadMore={fetchNextPage}
hasMore={hasNextPage}
emptyMessage={emptyMessage}
emptyMessageCard={false}
emptyMessageText={emptyMessage}
itemClassName={clsx('pb-4', { 'last:pb-0': !hasNextPage })}
isLoading={isFetching}
>

View File

@@ -40,7 +40,7 @@ const DomainBlocksPage: React.FC = () => {
scrollKey='domainBlocks'
onLoadMore={handleLoadMore}
hasMore={hasNextPage}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='divide-y divide-gray-200 dark:divide-gray-800'
>
{domains.map((domain) =>

View File

@@ -71,7 +71,7 @@ const FiltersPage = () => {
<ScrollableList
scrollKey='filters'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
itemClassName='pb-4 last:pb-0'
>
{filters.map((filter) => (

View File

@@ -22,7 +22,7 @@ const FollowedTagsPage = () => {
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='followedTags'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
isLoading={isLoading}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}

View File

@@ -31,10 +31,9 @@ const MutesPage: React.FC = () => {
isLoading={isFetching}
onLoadMore={fetchNextPage}
hasMore={hasNextPage}
emptyMessage={
emptyMessageText={
<FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />
}
emptyMessageCard={false}
>
{data.map((accountId) =>
<AccountContainer key={accountId} id={accountId} actionType='muting' />,

View File

@@ -96,7 +96,7 @@ const BookmarksPage: React.FC<IBookmarks> = ({ params }) => {
hasMore={hasNextPage}
isLoading={isFetching}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
/>
</PullToRefresh>
</Column>

View File

@@ -21,7 +21,7 @@ const DraftStatusesPage = () => {
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='draftStatuses'
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
>
{drafts.toReversed().map((draft) => <DraftStatus key={draft.draft_id} draftStatus={draft} />)}

View File

@@ -58,7 +58,7 @@ const FavouritedStatusesPage: React.FC<IFavourites> = ({ params }) => {
hasMore={hasNextPage}
isLoading={isFetching}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
/>
</Column>
);

View File

@@ -244,7 +244,7 @@ const InteractionRequestsPage = () => {
isLoading={isFetching}
showLoading={isLoading}
hasMore={hasNextPage}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
onLoadMore={() => fetchNextPage()}
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', {
'animate-pulse': data?.length === 0,

View File

@@ -33,7 +33,7 @@ const PinnedStatusesPage = () => {
scrollKey='pinned_statuses'
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={<FormattedMessage id='pinned_statuses.none' defaultMessage='No pins to show.' />}
emptyMessageText={<FormattedMessage id='pinned_statuses.none' defaultMessage='No pins to show.' />}
/>
</Column>
);

View File

@@ -37,7 +37,7 @@ const QuotesPage: React.FC = () => {
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={handleLoadMore}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
/>
</PullToRefresh>
</Column>

View File

@@ -24,7 +24,7 @@ const ScheduledStatusesPage = () => {
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
>
{scheduledStatuses.map((status) => <ScheduledStatus key={status.id} scheduledStatus={status} />)}

View File

@@ -141,7 +141,7 @@ const EventDiscussionPage: React.FC<IEventDiscussion> = ({ params: { statusId: s
id='thread'
placeholderComponent={() => <PlaceholderStatus variant='slim' />}
initialTopMostItemIndex={0}
emptyMessage={<FormattedMessage id='event.discussion.empty' defaultMessage='No one has commented this event yet. When someone does, they will appear here.' />}
emptyMessageText={<FormattedMessage id='event.discussion.empty' defaultMessage='No one has commented this event yet. When someone does, they will appear here.' />}
>
{children}
</ScrollableList>

View File

@@ -45,7 +45,8 @@ const BubbleTimelinePage = () => {
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
prefix='home'
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.bubble' defaultMessage='There is nothing here! Write something publicly to fill it up' />}
emptyMessageText={<FormattedMessage id='empty_column.bubble' defaultMessage='There is nothing here! Write something publicly to fill it up' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</PullToRefresh>
</Column>

View File

@@ -102,7 +102,8 @@ const CircleTimelinePage: React.FC = () => {
scrollKey='circle_timeline'
timelineId={`circle:${id}`}
onLoadMore={handleLoadMore}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</Column>
);

View File

@@ -43,7 +43,8 @@ const CommunityTimelinePage = () => {
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
prefix='home'
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
emptyMessageText={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</PullToRefresh>
</Column>

View File

@@ -9,9 +9,7 @@ import { useGroup } from 'pl-fe/api/hooks/groups/use-group';
import { useGroupStream } from 'pl-fe/api/hooks/streaming/use-group-stream';
import Avatar from 'pl-fe/components/ui/avatar';
import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import Timeline from 'pl-fe/features/ui/components/timeline';
import { ComposeForm } from 'pl-fe/features/ui/util/async-components';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
@@ -95,21 +93,8 @@ const GroupTimelinePage: React.FC<IGroupTimelinePage> = (props) => {
scrollKey='group_timeline'
timelineId={composeId}
onLoadMore={handleLoadMore}
emptyMessage={
<Stack space={4} className='py-6' justifyContent='center' alignItems='center'>
<div className='rounded-full bg-gray-200 p-4 dark:bg-gray-800'>
<Icon
src={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
className='size-6 text-gray-600'
/>
</div>
<Text theme='muted'>
<FormattedMessage id='empty_column.group' defaultMessage='There are no posts in this group yet.' />
</Text>
</Stack>
}
emptyMessageCard={false}
emptyMessageText={<FormattedMessage id='empty_column.group' defaultMessage='There are no posts in this group yet.' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
showGroup={false}
featuredStatusIds={featuredStatusIds}
/>

View File

@@ -69,7 +69,7 @@ const HashtagTimelinePage: React.FC<IHashtagTimelinePage> = ({ params }) => {
scrollKey='hashtag_timeline'
timelineId={`hashtag:${tagId}`}
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
emptyMessageText={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
/>
</Column>
);

View File

@@ -63,7 +63,7 @@ const HomeTimelinePage: React.FC = () => {
scrollKey='home_timeline'
onLoadMore={handleLoadMore}
timelineId='home'
emptyMessage={
emptyMessageText={
<Stack space={1}>
<Text size='xl' weight='medium' align='center'>
<FormattedMessage
@@ -97,6 +97,7 @@ const HomeTimelinePage: React.FC = () => {
)}
</Stack>
}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</PullToRefresh>
</Column>

View File

@@ -105,7 +105,8 @@ const LandingTimelinePage = () => {
timelineId={timelineId}
prefix='home'
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
emptyMessageText={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</PullToRefresh>
) : (

View File

@@ -40,7 +40,8 @@ const LinkTimelinePage: React.FC<ILinkTimelinePage> = ({ params }) => {
scrollKey='link_timeline'
timelineId={`link:${url}`}
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.link_timeline' defaultMessage='There are no posts with this link yet.' />}
emptyMessageText={<FormattedMessage id='empty_column.link_timeline' defaultMessage='There are no posts with this link yet.' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</Column>
);

View File

@@ -105,7 +105,8 @@ const ListTimelinePage: React.FC = () => {
scrollKey='list_timeline'
timelineId={`list:${id}`}
onLoadMore={handleLoadMore}
emptyMessage={emptyMessage}
emptyMessageText={emptyMessage}
emptyMessageIcon={require('@phosphor-icons/core/regular/list-bullets.svg')}
/>
</Column>
);

View File

@@ -90,7 +90,8 @@ const PublicTimelinePage = () => {
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
prefix='home'
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
emptyMessageText={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</PullToRefresh>
</Column>

View File

@@ -68,13 +68,14 @@ const RemoteTimelinePage: React.FC<IRemoteTimelinePage> = ({ params }) => {
scrollKey={`${timelineId}_${instance}_timeline`}
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}:${instance}`}
onLoadMore={handleLoadMore}
emptyMessage={
emptyMessageText={
<FormattedMessage
id='empty_column.remote'
defaultMessage='There is nothing here! Manually follow users from {instance} to fill it up.'
values={{ instance }}
/>
}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</Column>
);

View File

@@ -40,7 +40,8 @@ const TestTimelinePage: React.FC = () => {
<Timeline
scrollKey={`${timelineId}_timeline`}
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
emptyMessage={<FormattedMessage id='empty_column.test' defaultMessage='The test timeline is empty.' />}
emptyMessageText={<FormattedMessage id='empty_column.test' defaultMessage='The test timeline is empty.' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
/>
</Column>
);

View File

@@ -41,7 +41,8 @@ const WrenchedTimelinePage = () => {
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
prefix='home'
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.wrenched' defaultMessage='There is nothing here! 🔧 a public post to fill it up' />}
emptyMessageText={<FormattedMessage id='empty_column.wrenched' defaultMessage='There is nothing here! 🔧 a public post to fill it up' />}
emptyMessageIcon={require('@phosphor-icons/core/regular/wrench.svg')}
/>
</PullToRefresh>
</Column>

View File

@@ -1,5 +1,5 @@
import React, { useState, useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Accordion from 'pl-fe/components/ui/accordion';
@@ -14,8 +14,6 @@ const messages = defineMessages({
heading: { id: 'column.federation_restrictions', defaultMessage: 'Federation restrictions' },
boxTitle: { id: 'federation_restrictions.explanation_box.title', defaultMessage: 'Instance-specific policies' },
boxMessage: { id: 'federation_restrictions.explanation_box.message', defaultMessage: 'Normally servers on the Fediverse can communicate freely. {siteTitle} has imposed restrictions on the following servers.' },
emptyMessage: { id: 'federation_restrictions.empty_message', defaultMessage: '{siteTitle} has not restricted any instances.' },
notDisclosed: { id: 'federation_restrictions.not_disclosed_message', defaultMessage: '{siteTitle} does not disclose federation restrictions through the API.' },
});
const FederationRestrictionsPage = () => {
@@ -33,7 +31,9 @@ const FederationRestrictionsPage = () => {
setExplanationBoxExpanded(setting);
};
const emptyMessage = disclosed ? messages.emptyMessage : messages.notDisclosed;
const emptyMessage = disclosed
? <FormattedMessage id='federation_restrictions.empty_message' defaultMessage='{siteTitle} has not restricted any instances.' values={{ siteTitle: instance.title }} />
: <FormattedMessage id='federation_restrictions.not_disclosed_message' defaultMessage='{siteTitle} does not disclose federation restrictions through the API.' values={{ siteTitle: instance.title }} />;
return (
<Column label={intl.formatMessage(messages.heading)}>
@@ -46,7 +46,7 @@ const FederationRestrictionsPage = () => {
</Accordion>
<div className='pt-4'>
<ScrollableList emptyMessage={intl.formatMessage(emptyMessage, { siteTitle: instance.title })}>
<ScrollableList emptyMessageText={emptyMessage}>
{hosts.map((host) => <RestrictedInstance key={host} host={host} />)}
</ScrollableList>
</div>

View File

@@ -171,3 +171,7 @@ a.⁂-list-item,
align-items: stretch;
overflow: hidden;
}
div[data-viewport-type="window"]:has(.-empty-message) {
position: initial!important;
}