pl-fe: support mastodon link timeline

Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
Nicole Mikołajczyk
2025-04-09 16:30:21 +02:00
parent 7517dfb62f
commit 1eaa5f0ded
6 changed files with 85 additions and 1 deletions

View File

@ -16,6 +16,7 @@ import type {
PaginatedResponse,
PublicTimelineParams,
Status as BaseStatus,
LinkTimelineParams,
} from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -293,6 +294,22 @@ const fetchHashtagTimeline = (hashtag: string, { tags }: Record<string, any> = {
return dispatch(handleTimelineExpand(timelineId, fn, false, done));
};
const fetchLinkTimeline = (url: string, expand = false, done = noOp) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const timelineId = `link:${url}`;
const params: LinkTimelineParams = {};
if (expand && state.timelines[timelineId]?.isLoading) return;
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
const fn = (expand && state.timelines[timelineId]?.next?.()) || getClient(state).timelines.linkTimeline(url, params);
return dispatch(handleTimelineExpand(timelineId, fn, false, done));
};
const expandTimelineRequest = (timeline: string) => ({
type: TIMELINE_EXPAND_REQUEST,
timeline,
@ -361,6 +378,7 @@ export {
fetchListTimeline,
fetchGroupTimeline,
fetchHashtagTimeline,
fetchLinkTimeline,
expandTimelineSuccess,
scrollTopTimeline,
type TimelineAction,

View File

@ -1,5 +1,6 @@
import { TrendsLink } from 'pl-api';
import React from 'react';
import { Link } from 'react-router-dom';
import { getTextDirection } from 'pl-fe/utils/rtl';
@ -56,7 +57,11 @@ const TrendingLink: React.FC<ITrendingLink> = ({ trendingLink }) => {
</Text>
</HStack>
{!!count && accountsCountRenderer(count)}
{!!count && (
<Link to={`/links/${encodeURIComponent(trendingLink.url)}`} className='hover:underline'>
{accountsCountRenderer(count)}
</Link>
)}
</HStack>
</Stack>
</a>

View File

@ -0,0 +1,56 @@
import React, { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { clearTimeline, fetchLinkTimeline } from 'pl-fe/actions/timelines';
import Column from 'pl-fe/components/ui/column';
import Timeline from 'pl-fe/features/ui/components/timeline';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useIsMobile } from 'pl-fe/hooks/use-is-mobile';
import { useTheme } from 'pl-fe/hooks/use-theme';
const messages = defineMessages({
header: { id: 'column.link_timeline', defaultMessage: 'Posts linking to {url}' },
});
interface ILinkTimeline {
params?: {
url?: string;
};
}
const HashtagTimeline: React.FC<ILinkTimeline> = ({ params }) => {
const url = decodeURIComponent(params?.url || '');
const intl = useIntl();
const dispatch = useAppDispatch();
const theme = useTheme();
const isMobile = useIsMobile();
const handleLoadMore = () => {
dispatch(fetchLinkTimeline(url, true));
};
useEffect(() => {
dispatch(clearTimeline(`link:${url}`));
dispatch(fetchLinkTimeline(url));
}, [url]);
return (
<Column
label={intl.formatMessage(messages.header, { url: url.replace(/^https?:\/\//, '') })}
transparent={!isMobile}
>
<Timeline
className='black:p-0 black:sm:p-4 black:sm:pt-0'
loadMoreClassName='black:sm:mx-4'
scrollKey='link_timeline'
timelineId={`link:${url}`}
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.link_timeline' defaultMessage='There are no posts with this link yet.' />}
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/>
</Column>
);
};
export { HashtagTimeline as default };

View File

@ -108,6 +108,7 @@ import {
InteractionPolicies,
InteractionRequests,
LandingTimeline,
LinkTimeline,
ListTimeline,
Lists,
LoginPage,
@ -234,6 +235,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = React.memo(({ chil
<Redirect from='/registration/:token' to='/invite/:token' />
<WrappedRoute path='/tags/:id' publicRoute layout={DefaultLayout} component={HashtagTimeline} content={children} />
<WrappedRoute path='/links/:url' publicRoute layout={DefaultLayout} component={LinkTimeline} content={children} />
{features.lists && <WrappedRoute path='/lists' layout={DefaultLayout} component={Lists} content={children} />}
{features.lists && <WrappedRoute path='/list/:id' layout={DefaultLayout} component={ListTimeline} content={children} />}

View File

@ -59,6 +59,7 @@ export const IntentionalError = lazy(() => import('pl-fe/features/intentional-er
export const InteractionPolicies = lazy(() => import('pl-fe/features/interaction-policies'));
export const InteractionRequests = lazy(() => import('pl-fe/features/interaction-requests'));
export const LandingTimeline = lazy(() => import('pl-fe/features/landing-timeline'));
export const LinkTimeline = lazy(() => import('pl-fe/features/link-timeline'));
export const Lists = lazy(() => import('pl-fe/features/lists'));
export const ListTimeline = lazy(() => import('pl-fe/features/list-timeline'));
export const LoginPage = lazy(() => import('pl-fe/features/auth-login/components/login-page'));

View File

@ -402,6 +402,7 @@
"column.info": "Server information",
"column.interaction_policies": "Interaction policies",
"column.interaction_requests": "Interaction requests",
"column.link_timeline": "Posts linking to {url}",
"column.lists": "Lists",
"column.manage_group": "Manage group",
"column.mentions": "Mentions",
@ -741,6 +742,7 @@
"empty_column.home.subtitle": "{siteTitle} gets more interesting once you follow other users.",
"empty_column.home.title": "You're not following anyone yet",
"empty_column.interaction_requests": "There are no pending interaction requests.",
"empty_column.link_timeline": "There are no posts with this link yet.",
"empty_column.list": "There is nothing in this list yet. When members of this list create new posts, they will appear here.",
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
"empty_column.mutes": "You haven't muted any users yet.",