nicolium: use experimental timeline on all timelines (when enabled)
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -7,7 +7,17 @@ import Status from '@/components/statuses/status';
|
||||
import Tombstone from '@/components/statuses/tombstone';
|
||||
import PlaceholderStatus from '@/features/placeholder/components/placeholder-status';
|
||||
import { useStatus } from '@/queries/statuses/use-status';
|
||||
import { useHomeTimeline } from '@/queries/timelines/use-timelines';
|
||||
import {
|
||||
useAntennaTimeline,
|
||||
useBubbleTimeline,
|
||||
useGroupTimeline,
|
||||
useHashtagTimeline,
|
||||
useHomeTimeline,
|
||||
useLinkTimeline,
|
||||
useListTimeline,
|
||||
usePublicTimeline,
|
||||
useWrenchedTimeline,
|
||||
} from '@/queries/timelines/use-timelines';
|
||||
|
||||
import type { FilterContextType } from '@/queries/settings/use-filters';
|
||||
import type { TimelineEntry } from '@/queries/timelines/use-timeline';
|
||||
@ -70,8 +80,13 @@ const TimelineStatus: React.FC<ITimelineStatus> = (props): React.JSX.Element =>
|
||||
);
|
||||
};
|
||||
|
||||
const NewTimelineColumn = () => {
|
||||
const { data, handleLoadMore, isLoading } = useHomeTimeline();
|
||||
interface ITimeline {
|
||||
query: ReturnType<typeof useHomeTimeline>;
|
||||
contextType?: FilterContextType;
|
||||
}
|
||||
|
||||
const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public' }) => {
|
||||
const { data, handleLoadMore, isLoading } = query;
|
||||
|
||||
const renderEntry = (entry: TimelineEntry) => {
|
||||
if (entry.type === 'status') {
|
||||
@ -81,7 +96,7 @@ const NewTimelineColumn = () => {
|
||||
id={entry.id}
|
||||
isConnectedTop={entry.isConnectedTop}
|
||||
isConnectedBottom={entry.isConnectedBottom}
|
||||
contextType='home'
|
||||
contextType={contextType}
|
||||
// onMoveUp={handleMoveUp}
|
||||
// onMoveDown={handleMoveDown}
|
||||
// contextType={timelineId}
|
||||
@ -122,4 +137,94 @@ const NewTimelineColumn = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { NewTimelineColumn };
|
||||
const HomeTimelineColumn = () => {
|
||||
const timelineQuery = useHomeTimeline();
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='home' />;
|
||||
};
|
||||
|
||||
interface IPublicTimelineColumn {
|
||||
local?: boolean;
|
||||
remote?: boolean;
|
||||
instance?: string;
|
||||
}
|
||||
|
||||
const PublicTimelineColumn: React.FC<IPublicTimelineColumn> = (params) => {
|
||||
const timelineQuery = usePublicTimeline(params);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
interface IHashtagTimelineColumn {
|
||||
hashtag: string;
|
||||
}
|
||||
|
||||
const HashtagTimelineColumn: React.FC<IHashtagTimelineColumn> = ({ hashtag }) => {
|
||||
const timelineQuery = useHashtagTimeline(hashtag);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
interface ILinkTimelineColumn {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const LinkTimelineColumn: React.FC<ILinkTimelineColumn> = ({ url }) => {
|
||||
const timelineQuery = useLinkTimeline(url);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
interface IListTimelineColumn {
|
||||
listId: string;
|
||||
}
|
||||
|
||||
const ListTimelineColumn: React.FC<IListTimelineColumn> = ({ listId }) => {
|
||||
const timelineQuery = useListTimeline(listId);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='home' />;
|
||||
};
|
||||
|
||||
interface IGroupTimelineColumn {
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
const GroupTimelineColumn: React.FC<IGroupTimelineColumn> = ({ groupId }) => {
|
||||
const timelineQuery = useGroupTimeline(groupId);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
const BubbleTimelineColumn = () => {
|
||||
const timelineQuery = useBubbleTimeline();
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
interface IAntennaTimelineColumn {
|
||||
antennaId: string;
|
||||
}
|
||||
|
||||
const AntennaTimelineColumn: React.FC<IAntennaTimelineColumn> = ({ antennaId }) => {
|
||||
const timelineQuery = useAntennaTimeline(antennaId);
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
const WrenchedTimelineColumn = () => {
|
||||
const timelineQuery = useWrenchedTimeline();
|
||||
|
||||
return <Timeline query={timelineQuery} contextType='public' />;
|
||||
};
|
||||
|
||||
export {
|
||||
HomeTimelineColumn,
|
||||
PublicTimelineColumn,
|
||||
HashtagTimelineColumn,
|
||||
LinkTimelineColumn,
|
||||
ListTimelineColumn,
|
||||
GroupTimelineColumn,
|
||||
BubbleTimelineColumn,
|
||||
AntennaTimelineColumn,
|
||||
WrenchedTimelineColumn,
|
||||
};
|
||||
|
||||
@ -35,7 +35,7 @@ const AnnouncementContent: React.FC<IAnnouncementContent> = ({ announcement }) =
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate({ to: '/tags/$id', params: { id: hashtag } });
|
||||
navigate({ to: '/tags/$hashtag', params: { hashtag } });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ interface IHashtagLink {
|
||||
}
|
||||
|
||||
const HashtagLink: React.FC<IHashtagLink> = ({ hashtag }) => (
|
||||
<Link to='/tags/$id' params={{ id: hashtag }} /* onClick={(e) => e.stopPropagation()} */>
|
||||
<Link to='/tags/$hashtag' params={{ hashtag }} /* onClick={(e) => e.stopPropagation()} */>
|
||||
#{hashtag}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@ -34,7 +34,7 @@ const Hashtag: React.FC<IHashtag> = ({ hashtag }) => {
|
||||
return (
|
||||
<HStack alignItems='center' justifyContent='between' data-testid='hashtag'>
|
||||
<Stack>
|
||||
<Link to='/tags/$id' params={{ id: hashtag.name }} className='hover:underline'>
|
||||
<Link to='/tags/$hashtag' params={{ hashtag: hashtag.name }} className='hover:underline'>
|
||||
<Text tag='span' size='sm' weight='semibold'>
|
||||
#{hashtag.name}
|
||||
</Text>
|
||||
|
||||
@ -32,8 +32,8 @@ const HashtagsBar: React.FC<IHashtagsBar> = ({ hashtags }) => {
|
||||
{revealedHashtags.map((hashtag) => (
|
||||
<Link
|
||||
key={hashtag}
|
||||
to='/tags/$id'
|
||||
params={{ id: hashtag }}
|
||||
to='/tags/$hashtag'
|
||||
params={{ hashtag }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
|
||||
@ -438,7 +438,7 @@ export const conversationsRoute = createRoute({
|
||||
// Tags and links
|
||||
export const hashtagTimelineRoute = createRoute({
|
||||
getParentRoute: () => layouts.default,
|
||||
path: '/tags/$id',
|
||||
path: '/tags/$hashtag',
|
||||
component: HashtagTimeline,
|
||||
beforeLoad: (options) => {
|
||||
const {
|
||||
@ -1271,10 +1271,10 @@ const redirectPlFeRoute = createRoute({
|
||||
});
|
||||
const redirectTagRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/tag/$id',
|
||||
path: '/tag/$hashtag',
|
||||
component: () => {
|
||||
const { id } = redirectTagRoute.useParams();
|
||||
return <Navigate to='/tags/$id' params={{ id }} replace />;
|
||||
const { hashtag } = redirectTagRoute.useParams();
|
||||
return <Navigate to='/tags/$hashtag' params={{ hashtag }} replace />;
|
||||
},
|
||||
});
|
||||
const redirectNoticeStatusRoute = createRoute({
|
||||
|
||||
@ -97,7 +97,7 @@ const EventDiscussionPage: React.FC = () => {
|
||||
<Stack space={2}>
|
||||
{me && (
|
||||
<div className='border-b border-solid border-gray-200 p-2 pt-0 dark:border-gray-800'>
|
||||
<ComposeForm id={`reply:${status.id}`} autoFocus={false} event={status.id} transparent />
|
||||
<ComposeForm id={`reply:${status.id}`} event={status.id} transparent />
|
||||
</div>
|
||||
)}
|
||||
<div ref={node} className='thread p-0 shadow-none sm:p-2'>
|
||||
|
||||
@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchAntennaTimeline } from '@/actions/timelines';
|
||||
import { AntennaTimelineColumn } from '@/columns/timeline';
|
||||
import DropdownMenu from '@/components/dropdown-menu';
|
||||
import MissingIndicator from '@/components/missing-indicator';
|
||||
// import Button from '@/components/ui/button';
|
||||
@ -13,6 +14,7 @@ import { antennaTimelineRoute } from '@/features/ui/router';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useAntenna, useDeleteAntenna } from '@/queries/accounts/use-antennas';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteHeading: { id: 'confirmations.delete_antenna.heading', defaultMessage: 'Delete antenna' },
|
||||
@ -25,16 +27,12 @@ const messages = defineMessages({
|
||||
deleteAntenna: { id: 'antennas.delete', defaultMessage: 'Delete antenna' },
|
||||
});
|
||||
|
||||
const AntennaTimelinePage: React.FC = () => {
|
||||
const { antennaId } = antennaTimelineRoute.useParams();
|
||||
interface IAntennaTimeline {
|
||||
antennaId: string;
|
||||
}
|
||||
|
||||
const intl = useIntl();
|
||||
const AntennaTimeline: React.FC<IAntennaTimeline> = ({ antennaId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { openModal } = useModalsActions();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: antenna, isFetching } = useAntenna(antennaId);
|
||||
const { mutate: deleteAntenna } = useDeleteAntenna();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchAntennaTimeline(antennaId));
|
||||
@ -44,6 +42,40 @@ const AntennaTimelinePage: React.FC = () => {
|
||||
dispatch(fetchAntennaTimeline(antennaId, true));
|
||||
};
|
||||
|
||||
const emptyMessage = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='empty_column.antenna'
|
||||
defaultMessage='There is nothing in this antenna yet. When posts matching the criteria will be created, they will appear here.'
|
||||
/>
|
||||
{/* <br /><br />
|
||||
<Button onClick={handleEditClick}><FormattedMessage id='circle.click_to_add' defaultMessage='Click here to add people' /></Button> */}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='antenna_timeline'
|
||||
timelineId={`antenna:${antennaId}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={emptyMessage}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AntennaTimelinePage: React.FC = () => {
|
||||
const { antennaId } = antennaTimelineRoute.useParams();
|
||||
|
||||
const intl = useIntl();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
const { openModal } = useModalsActions();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: antenna, isFetching } = useAntenna(antennaId);
|
||||
const { mutate: deleteAntenna } = useDeleteAntenna();
|
||||
|
||||
const handleEditClick = () => {
|
||||
openModal('ANTENNA_EDITOR', { antennaId });
|
||||
};
|
||||
@ -79,17 +111,6 @@ const AntennaTimelinePage: React.FC = () => {
|
||||
return <MissingIndicator />;
|
||||
}
|
||||
|
||||
const emptyMessage = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='empty_column.antenna'
|
||||
defaultMessage='There is nothing in this antenna yet. When posts matching the criteria will be created, they will appear here.'
|
||||
/>
|
||||
{/* <br /><br />
|
||||
<Button onClick={handleEditClick}><FormattedMessage id='circle.click_to_add' defaultMessage='Click here to add people' /></Button> */}
|
||||
</div>
|
||||
);
|
||||
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.editAntenna),
|
||||
@ -113,14 +134,11 @@ const AntennaTimelinePage: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='antenna_timeline'
|
||||
timelineId={`antenna:${antennaId}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={emptyMessage}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<AntennaTimelineColumn antennaId={antennaId} />
|
||||
) : (
|
||||
<AntennaTimeline antennaId={antennaId} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchBubbleTimeline } from '@/actions/timelines';
|
||||
import { useBubbleStream } from '@/api/hooks/streaming/use-bubble-stream';
|
||||
import { BubbleTimelineColumn } from '@/columns/timeline';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Column from '@/components/ui/column';
|
||||
import Timeline from '@/features/ui/components/timeline';
|
||||
@ -14,12 +15,11 @@ const messages = defineMessages({
|
||||
title: { id: 'column.bubble', defaultMessage: 'Bubble timeline' },
|
||||
});
|
||||
|
||||
const BubbleTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
const BubbleTimeline = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
|
||||
const onlyMedia = settings.timelines.bubble?.other.onlyMedia ?? false;
|
||||
|
||||
const timelineId = 'bubble';
|
||||
@ -36,24 +36,35 @@ const BubbleTimelinePage = () => {
|
||||
dispatch(fetchBubbleTimeline({ onlyMedia }));
|
||||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const BubbleTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const settings = useSettings();
|
||||
const { experimentalTimeline } = settings;
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
{experimentalTimeline ? <BubbleTimelineColumn /> : <BubbleTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchPublicTimeline } from '@/actions/timelines';
|
||||
import { useCommunityStream } from '@/api/hooks/streaming/use-community-stream';
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Column from '@/components/ui/column';
|
||||
import Timeline from '@/features/ui/components/timeline';
|
||||
@ -13,20 +14,27 @@ const messages = defineMessages({
|
||||
title: { id: 'column.community', defaultMessage: 'Local timeline' },
|
||||
});
|
||||
|
||||
const CommunityTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
interface ICommunityTimeline {
|
||||
onTimelineFailed?: () => void;
|
||||
}
|
||||
|
||||
const CommunityTimeline: React.FC<ICommunityTimeline> = ({ onTimelineFailed }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const settings = useSettings();
|
||||
|
||||
const onlyMedia = settings.timelines['public:local']?.other.onlyMedia ?? false;
|
||||
|
||||
const timelineId = 'public:local';
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia, local: true }, true));
|
||||
dispatch(
|
||||
fetchPublicTimeline({ onlyMedia, local: true }, true, undefined, () => {
|
||||
onTimelineFailed?.();
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const handleRefresh = () => dispatch(fetchPublicTimeline({ onlyMedia, local: true }));
|
||||
const handleRefresh = () => dispatch(fetchPublicTimeline({ onlyMedia, local: true }, true));
|
||||
|
||||
useCommunityStream({ onlyMedia });
|
||||
|
||||
@ -34,26 +42,36 @@ const CommunityTimelinePage = () => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia, local: true }));
|
||||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const CommunityTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { experimentalTimeline } = useSettings();
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
{experimentalTimeline ? <PublicTimelineColumn local /> : <CommunityTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { CommunityTimelinePage as default };
|
||||
export { CommunityTimeline, CommunityTimelinePage as default };
|
||||
|
||||
@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchGroupTimeline } from '@/actions/timelines';
|
||||
import { useGroupStream } from '@/api/hooks/streaming/use-group-stream';
|
||||
import { GroupTimelineColumn } from '@/columns/timeline';
|
||||
import Avatar from '@/components/ui/avatar';
|
||||
import HStack from '@/components/ui/hstack';
|
||||
import Stack from '@/components/ui/stack';
|
||||
@ -18,31 +19,21 @@ import { useOwnAccount } from '@/hooks/use-own-account';
|
||||
import { useGroupQuery } from '@/queries/groups/use-group';
|
||||
import { makeGetStatusIds } from '@/selectors';
|
||||
import { useComposeActions, useUploadCompose } from '@/stores/compose';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const getStatusIds = makeGetStatusIds();
|
||||
|
||||
const GroupTimelinePage: React.FC = () => {
|
||||
const { groupId } = groupTimelineRoute.useParams();
|
||||
interface IGroupTimeline {
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
const composeId = `group:${groupId}`;
|
||||
|
||||
const { data: account } = useOwnAccount();
|
||||
const GroupTimeline: React.FC<IGroupTimeline> = ({ groupId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const uploadCompose = useUploadCompose(composeId);
|
||||
const { updateCompose } = useComposeActions();
|
||||
const composer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data: group } = useGroupQuery(groupId);
|
||||
|
||||
const canComposeGroupStatus = !!account && group?.relationship?.member;
|
||||
const featuredStatusIds = useAppSelector((state) =>
|
||||
getStatusIds(state, { type: `group:${group?.id}:pinned` }),
|
||||
getStatusIds(state, { type: `group:${groupId}:pinned` }),
|
||||
);
|
||||
|
||||
const { isDragging, isDraggedOver } = useDraggedFiles(composer, (files) => {
|
||||
uploadCompose(files);
|
||||
});
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchGroupTimeline(groupId, {}, true));
|
||||
};
|
||||
@ -51,7 +42,46 @@ const GroupTimelinePage: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchGroupTimeline(groupId, {}));
|
||||
// dispatch(fetchGroupTimeline(groupId, { pinned: true }));
|
||||
}, [groupId]);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
scrollKey='group_timeline'
|
||||
timelineId={`group:${groupId}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GroupTimelinePage: React.FC = () => {
|
||||
const { groupId } = groupTimelineRoute.useParams();
|
||||
|
||||
const composeId = `group:${groupId}`;
|
||||
|
||||
const { data: account } = useOwnAccount();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
const uploadCompose = useUploadCompose(composeId);
|
||||
const { updateCompose } = useComposeActions();
|
||||
const composer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data: group } = useGroupQuery(groupId);
|
||||
|
||||
const canComposeGroupStatus = !!account && group?.relationship?.member;
|
||||
|
||||
const { isDragging, isDraggedOver } = useDraggedFiles(composer, (files) => {
|
||||
uploadCompose(files);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCompose(composeId, (draft) => {
|
||||
draft.visibility = 'group';
|
||||
draft.groupId = groupId;
|
||||
@ -85,32 +115,16 @@ const GroupTimelinePage: React.FC = () => {
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<ComposeForm
|
||||
id={composeId}
|
||||
shouldCondense
|
||||
autoFocus={false}
|
||||
group={groupId}
|
||||
withAvatar
|
||||
transparent
|
||||
/>
|
||||
<ComposeForm id={composeId} shouldCondense group={groupId} withAvatar transparent />
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Timeline
|
||||
scrollKey='group_timeline'
|
||||
timelineId={composeId}
|
||||
onLoadMore={handleLoadMore}
|
||||
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}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<GroupTimelineColumn groupId={groupId} />
|
||||
) : (
|
||||
<GroupTimeline groupId={groupId} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchHashtagTimeline, clearTimeline } from '@/actions/timelines';
|
||||
import { useHashtagStream } from '@/api/hooks/streaming/use-hashtag-stream';
|
||||
import { HashtagTimelineColumn } from '@/columns/timeline';
|
||||
import List, { ListItem } from '@/components/list';
|
||||
import Column from '@/components/ui/column';
|
||||
import Toggle from '@/components/ui/toggle';
|
||||
@ -16,22 +17,53 @@ import {
|
||||
useUnfollowHashtagMutation,
|
||||
} from '@/queries/hashtags/use-followed-tags';
|
||||
import { useHashtag } from '@/queries/hashtags/use-hashtag';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const HashtagTimelinePage: React.FC = () => {
|
||||
const { id: tagId } = hashtagTimelineRoute.useParams();
|
||||
interface IHashtagTimeline {
|
||||
hashtag: string;
|
||||
}
|
||||
|
||||
const features = useFeatures();
|
||||
const HashtagTimeline: React.FC<IHashtagTimeline> = ({ hashtag }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: tag } = useHashtag(tagId);
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
|
||||
const { mutate: followHashtag } = useFollowHashtagMutation(tagId);
|
||||
const { mutate: unfollowHashtag } = useUnfollowHashtagMutation(tagId);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchHashtagTimeline(tagId, {}, true));
|
||||
dispatch(fetchHashtagTimeline(hashtag, {}, true));
|
||||
};
|
||||
|
||||
useHashtagStream(hashtag);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(clearTimeline(`hashtag:${hashtag}`));
|
||||
dispatch(fetchHashtagTimeline(hashtag));
|
||||
}, [hashtag]);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='hashtag_timeline'
|
||||
timelineId={`hashtag:${hashtag}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={
|
||||
<FormattedMessage
|
||||
id='empty_column.hashtag'
|
||||
defaultMessage='There is nothing in this hashtag yet.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const HashtagTimelinePage: React.FC = () => {
|
||||
const { hashtag } = hashtagTimelineRoute.useParams();
|
||||
|
||||
const features = useFeatures();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
const { data: tag } = useHashtag(hashtag);
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
|
||||
const { mutate: followHashtag } = useFollowHashtagMutation(hashtag);
|
||||
const { mutate: unfollowHashtag } = useUnfollowHashtagMutation(hashtag);
|
||||
|
||||
const handleFollow = () => {
|
||||
if (tag?.following) {
|
||||
unfollowHashtag();
|
||||
@ -40,15 +72,8 @@ const HashtagTimelinePage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useHashtagStream(tagId);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(clearTimeline(`hashtag:${tagId}`));
|
||||
dispatch(fetchHashtagTimeline(tagId));
|
||||
}, [tagId]);
|
||||
|
||||
return (
|
||||
<Column label={`#${tagId}`}>
|
||||
<Column label={`#${hashtag}`}>
|
||||
{features.followHashtags && isLoggedIn && (
|
||||
<List>
|
||||
<ListItem
|
||||
@ -59,18 +84,11 @@ const HashtagTimelinePage: React.FC = () => {
|
||||
</ListItem>
|
||||
</List>
|
||||
)}
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='hashtag_timeline'
|
||||
timelineId={`hashtag:${tagId}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={
|
||||
<FormattedMessage
|
||||
id='empty_column.hashtag'
|
||||
defaultMessage='There is nothing in this hashtag yet.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<HashtagTimelineColumn hashtag={hashtag} />
|
||||
) : (
|
||||
<HashtagTimeline hashtag={hashtag} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchHomeTimeline } from '@/actions/timelines';
|
||||
import { NewTimelineColumn } from '@/columns/timeline';
|
||||
import { HomeTimelineColumn } from '@/columns/timeline';
|
||||
import { Link } from '@/components/link';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Column from '@/components/ui/column';
|
||||
@ -110,7 +110,7 @@ const HomeTimelinePage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||
{experimentalTimeline ? <NewTimelineColumn /> : <HomeTimeline />}
|
||||
{experimentalTimeline ? <HomeTimelineColumn /> : <HomeTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchPublicTimeline } from '@/actions/timelines';
|
||||
import { useCommunityStream } from '@/api/hooks/streaming/use-community-stream';
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import Markup from '@/components/markup';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import { ParsedContent } from '@/components/statuses/parsed-content';
|
||||
import Button from '@/components/ui/button';
|
||||
import Column from '@/components/ui/column';
|
||||
import HStack from '@/components/ui/hstack';
|
||||
import Stack from '@/components/ui/stack';
|
||||
import Timeline from '@/features/ui/components/timeline';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useInstance } from '@/hooks/use-instance';
|
||||
import { useRegistrationStatus } from '@/hooks/use-registration-status';
|
||||
import { About } from '@/pages/utils/about';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
import { getTextDirection } from '@/utils/rtl';
|
||||
|
||||
import { CommunityTimeline } from './community-timeline';
|
||||
|
||||
interface ILogoText extends Pick<React.HTMLAttributes<HTMLHeadingElement>, 'className' | 'dir'> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
@ -54,34 +53,14 @@ const SiteBanner: React.FC = () => {
|
||||
};
|
||||
|
||||
const LandingTimelinePage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
const { isOpen } = useRegistrationStatus();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
|
||||
const [timelineFailed, setTimelineFailed] = useState(false);
|
||||
|
||||
const timelineEnabled = !instance.pleroma.metadata.restrict_unauthenticated.timelines.local;
|
||||
|
||||
const timelineId = 'public:local';
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchPublicTimeline({ local: true }, true));
|
||||
};
|
||||
|
||||
const handleRefresh = () => dispatch(fetchPublicTimeline({ local: true }));
|
||||
|
||||
useCommunityStream({ enabled: timelineEnabled });
|
||||
|
||||
useEffect(() => {
|
||||
if (timelineEnabled) {
|
||||
dispatch(
|
||||
fetchPublicTimeline({ local: true }, false, undefined, () => {
|
||||
setTimelineFailed(true);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Column withHeader={false}>
|
||||
<div className='mb-4 mt-12 px-4 lg:mb-12'>
|
||||
@ -100,22 +79,11 @@ const LandingTimelinePage = () => {
|
||||
</HStack>
|
||||
|
||||
{timelineEnabled && !timelineFailed ? (
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={timelineId}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
experimentalTimeline ? (
|
||||
<PublicTimelineColumn local />
|
||||
) : (
|
||||
<CommunityTimeline onTimelineFailed={() => setTimelineFailed(true)} />
|
||||
)
|
||||
) : (
|
||||
<About slug='index' />
|
||||
)}
|
||||
|
||||
@ -2,48 +2,66 @@ import React, { useEffect } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { clearTimeline, fetchLinkTimeline } from '@/actions/timelines';
|
||||
import { LinkTimelineColumn } from '@/columns/timeline';
|
||||
import Column from '@/components/ui/column';
|
||||
import Timeline from '@/features/ui/components/timeline';
|
||||
import { linkTimelineRoute } from '@/features/ui/router';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const messages = defineMessages({
|
||||
header: { id: 'column.link_timeline', defaultMessage: 'Posts linking to {url}' },
|
||||
});
|
||||
|
||||
interface ILinkTimeline {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const LinkTimeline: React.FC<ILinkTimeline> = ({ url }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchLinkTimeline(url, true));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(clearTimeline(`link:${url}`));
|
||||
dispatch(fetchLinkTimeline(url));
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='link_timeline'
|
||||
timelineId={`link:${url}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkTimelinePage: React.FC = () => {
|
||||
const { url } = linkTimelineRoute.useParams();
|
||||
const decodedUrl = decodeURIComponent(url || '');
|
||||
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchLinkTimeline(decodedUrl, true));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(clearTimeline(`link:${decodedUrl}`));
|
||||
dispatch(fetchLinkTimeline(decodedUrl));
|
||||
}, [decodedUrl]);
|
||||
const { experimentalTimeline } = useSettings();
|
||||
|
||||
return (
|
||||
<Column
|
||||
label={intl.formatMessage(messages.header, { url: decodedUrl.replace(/^https?:\/\//, '') })}
|
||||
>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='link_timeline'
|
||||
timelineId={`link:${decodedUrl}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
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')}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<LinkTimelineColumn url={decodedUrl} />
|
||||
) : (
|
||||
<LinkTimeline url={decodedUrl} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchListTimeline } from '@/actions/timelines';
|
||||
import { useListStream } from '@/api/hooks/streaming/use-list-stream';
|
||||
import { ListTimelineColumn } from '@/columns/timeline';
|
||||
import DropdownMenu from '@/components/dropdown-menu';
|
||||
import MissingIndicator from '@/components/missing-indicator';
|
||||
import Button from '@/components/ui/button';
|
||||
@ -14,6 +15,7 @@ import { listTimelineRoute } from '@/features/ui/router';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useDeleteList, useList } from '@/queries/accounts/use-lists';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' },
|
||||
@ -26,27 +28,66 @@ const messages = defineMessages({
|
||||
deleteList: { id: 'lists.delete', defaultMessage: 'Delete list' },
|
||||
});
|
||||
|
||||
interface IListTimeline {
|
||||
listId: string;
|
||||
}
|
||||
|
||||
const ListTimeline: React.FC<IListTimeline> = ({ listId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const settings = useSettings();
|
||||
const { openModal } = useModalsActions();
|
||||
|
||||
const onlyMedia = settings.timelines[`list:${listId}`]?.other.onlyMedia ?? false;
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchListTimeline(listId, true));
|
||||
};
|
||||
|
||||
useListStream(listId);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchListTimeline(listId, false));
|
||||
}, [listId, onlyMedia]);
|
||||
|
||||
const handleEditClick = () => {
|
||||
openModal('LIST_EDITOR', { listId });
|
||||
};
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`list_${listId}_timeline`}
|
||||
timelineId={`list:${listId}${onlyMedia ? ':media' : ''}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='empty_column.list'
|
||||
defaultMessage='There is nothing in this list yet. When members of this list create new posts, they will appear here.'
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={handleEditClick}>
|
||||
<FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people' />
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/list-bullets.svg')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ListTimelinePage: React.FC = () => {
|
||||
const { listId } = listTimelineRoute.useParams();
|
||||
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
const { openModal } = useModalsActions();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: list, isFetching } = useList(listId);
|
||||
const { mutate: deleteList } = useDeleteList();
|
||||
|
||||
useListStream(listId);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchListTimeline(listId));
|
||||
}, [listId]);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchListTimeline(listId, true));
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
openModal('LIST_EDITOR', { listId });
|
||||
};
|
||||
@ -82,20 +123,6 @@ const ListTimelinePage: React.FC = () => {
|
||||
return <MissingIndicator />;
|
||||
}
|
||||
|
||||
const emptyMessage = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='empty_column.list'
|
||||
defaultMessage='There is nothing in this list yet. When members of this list create new posts, they will appear here.'
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={handleEditClick}>
|
||||
<FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people' />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.editList),
|
||||
@ -119,14 +146,11 @@ const ListTimelinePage: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='list_timeline'
|
||||
timelineId={`list:${listId}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={emptyMessage}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/list-bullets.svg')}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<ListTimelineColumn listId={listId} />
|
||||
) : (
|
||||
<ListTimeline listId={listId} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { changeSetting } from '@/actions/settings';
|
||||
import { fetchPublicTimeline } from '@/actions/timelines';
|
||||
import { usePublicStream } from '@/api/hooks/streaming/use-public-stream';
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Accordion from '@/components/ui/accordion';
|
||||
import Column from '@/components/ui/column';
|
||||
@ -19,27 +20,13 @@ const messages = defineMessages({
|
||||
dismiss: { id: 'fediverse_tab.explanation_box.dismiss', defaultMessage: "Don't show again" },
|
||||
});
|
||||
|
||||
const PublicTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
const PublicTimeline = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const instance = useInstance();
|
||||
const settings = useSettings();
|
||||
const onlyMedia = settings.timelines.public?.other.onlyMedia ?? false;
|
||||
|
||||
const timelineId = 'public';
|
||||
|
||||
const explanationBoxExpanded = settings.explanationBox;
|
||||
const showExplanationBox = settings.showExplanationBox;
|
||||
|
||||
const dismissExplanationBox = () => {
|
||||
dispatch(changeSetting(['showExplanationBox'], false));
|
||||
};
|
||||
|
||||
const toggleExplanationBox = (setting: boolean) => {
|
||||
dispatch(changeSetting(['explanationBox'], setting));
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia }, true));
|
||||
};
|
||||
@ -52,6 +39,45 @@ const PublicTimelinePage = () => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia }, true));
|
||||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const PublicTimelinePage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const instance = useInstance();
|
||||
const settings = useSettings();
|
||||
const { experimentalTimeline } = settings;
|
||||
|
||||
const explanationBoxExpanded = settings.explanationBox;
|
||||
const showExplanationBox = settings.showExplanationBox;
|
||||
|
||||
const dismissExplanationBox = () => {
|
||||
dispatch(changeSetting(['showExplanationBox'], false));
|
||||
};
|
||||
|
||||
const toggleExplanationBox = (setting: boolean) => {
|
||||
dispatch(changeSetting(['explanationBox'], setting));
|
||||
};
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<PinnedHostsPicker />
|
||||
@ -89,22 +115,7 @@ const PublicTimelinePage = () => {
|
||||
/>
|
||||
</Accordion>
|
||||
)}
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
{experimentalTimeline ? <PublicTimelineColumn /> : <PublicTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchPublicTimeline } from '@/actions/timelines';
|
||||
import { useRemoteStream } from '@/api/hooks/streaming/use-remote-stream';
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import Column from '@/components/ui/column';
|
||||
import HStack from '@/components/ui/hstack';
|
||||
import IconButton from '@/components/ui/icon-button';
|
||||
@ -18,25 +19,17 @@ const messages = defineMessages({
|
||||
close: { id: 'remote_timeline.close', defaultMessage: 'Close remote timeline' },
|
||||
});
|
||||
|
||||
/** View statuses from a remote instance. */
|
||||
const RemoteTimelinePage: React.FC = () => {
|
||||
const { instance } = remoteTimelineRoute.useParams();
|
||||
interface IRemoteTimeline {
|
||||
instance: string;
|
||||
}
|
||||
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const RemoteTimeline: React.FC<IRemoteTimeline> = ({ instance }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
const timelineId = 'remote';
|
||||
const onlyMedia = settings.timelines.remote?.other.onlyMedia ?? false;
|
||||
|
||||
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
|
||||
|
||||
const handleCloseClick: React.MouseEventHandler = () => {
|
||||
navigate({ to: '/timeline/fediverse' });
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia, instance }, true));
|
||||
};
|
||||
@ -45,7 +38,41 @@ const RemoteTimelinePage: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchPublicTimeline({ onlyMedia, instance }));
|
||||
}, [onlyMedia]);
|
||||
}, [onlyMedia, instance]);
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_${instance}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}:${instance}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** View statuses from a remote instance. */
|
||||
const RemoteTimelinePage: React.FC = () => {
|
||||
const { instance } = remoteTimelineRoute.useParams();
|
||||
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const settings = useSettings();
|
||||
const { experimentalTimeline } = settings;
|
||||
|
||||
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
|
||||
|
||||
const handleCloseClick: React.MouseEventHandler = () => {
|
||||
navigate({ to: '/timeline/fediverse' });
|
||||
};
|
||||
|
||||
return (
|
||||
<Column label={instance}>
|
||||
@ -70,20 +97,11 @@ const RemoteTimelinePage: React.FC = () => {
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_${instance}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}:${instance}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
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')}
|
||||
/>
|
||||
{experimentalTimeline ? (
|
||||
<PublicTimelineColumn instance={instance} />
|
||||
) : (
|
||||
<RemoteTimeline instance={instance} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchWrenchedTimeline } from '@/actions/timelines';
|
||||
import { WrenchedTimelineColumn } from '@/columns/timeline';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Column from '@/components/ui/column';
|
||||
import Timeline from '@/features/ui/components/timeline';
|
||||
@ -12,11 +13,10 @@ const messages = defineMessages({
|
||||
title: { id: 'column.wrenched', defaultMessage: 'Recent wrenches timeline' },
|
||||
});
|
||||
|
||||
const WrenchedTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
const WrenchedTimeline = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
const onlyMedia = settings.timelines.wrenched?.other.onlyMedia ?? false;
|
||||
|
||||
const timelineId = 'wrenched';
|
||||
@ -31,24 +31,35 @@ const WrenchedTimelinePage = () => {
|
||||
dispatch(fetchWrenchedTimeline({ onlyMedia }));
|
||||
}, [onlyMedia]);
|
||||
|
||||
return (
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const WrenchedTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const settings = useSettings();
|
||||
const { experimentalTimeline } = settings;
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
prefix='home'
|
||||
onLoadMore={handleLoadMore}
|
||||
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>
|
||||
{experimentalTimeline ? <WrenchedTimelineColumn /> : <WrenchedTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user