nicolium: experimental timeline: display reblog/hashtag follow information

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-05 13:41:42 +01:00
parent 00003c4d79
commit 681285e42d
3 changed files with 113 additions and 10 deletions

View File

@ -1,15 +1,21 @@
import { Link } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { useRef } from 'react';
import { defineMessages } from 'react-intl';
import { defineMessages, FormattedList, FormattedMessage } from 'react-intl';
import LoadMore from '@/components/load-more';
import ScrollTopButton from '@/components/scroll-top-button';
import ScrollableList from '@/components/scrollable-list';
import Status from '@/components/statuses/status';
import Status, { StatusFollowedTagInfo } from '@/components/statuses/status';
import StatusInfo from '@/components/statuses/status-info';
import Tombstone from '@/components/statuses/tombstone';
import Icon from '@/components/ui/icon';
import Portal from '@/components/ui/portal';
import Emojify from '@/features/emoji/emojify';
import PlaceholderStatus from '@/features/placeholder/components/placeholder-status';
import { useStatus } from '@/queries/statuses/use-status';
import { useFeatures } from '@/hooks/use-features';
import { useAccounts } from '@/queries/accounts/use-accounts';
import { type SelectedStatus, useStatus } from '@/queries/statuses/use-status';
import {
useAntennaTimeline,
useBubbleTimeline,
@ -44,8 +50,90 @@ const PlaceholderTimelineStatus = () => (
</div>
);
interface ITimelineStatusInfo {
status: SelectedStatus;
rebloggedBy: Array<string>;
timelineId: string;
}
const TimelineStatusInfo: React.FC<ITimelineStatusInfo> = ({ status, rebloggedBy, timelineId }) => {
const features = useFeatures();
const isReblogged = rebloggedBy.length > 0;
const { data: accounts } = useAccounts(rebloggedBy);
if (isReblogged) {
const renderedAccounts = accounts.slice(0, 2).map(
(account) =>
!!account && (
<Link
key={account.acct}
to='/@{$username}'
params={{ username: account.acct }}
className='hover:underline'
>
<bdi className='truncate'>
<strong className='text-gray-800 dark:text-gray-200'>
<Emojify text={account.display_name} emojis={account.emojis} />
</strong>
</bdi>
</Link>
),
);
if (accounts.length > 2) {
renderedAccounts.push(
<FormattedMessage
id='notification.more'
defaultMessage='{count, plural, one {# other} other {# others}}'
values={{ count: accounts.length - renderedAccounts.length }}
/>,
);
}
const values = {
name: <FormattedList type='conjunction' value={renderedAccounts} />,
count: accounts.length,
};
return (
<StatusInfo
className='mt-4'
avatarSize={42}
icon={
<Icon
src={require('@phosphor-icons/core/regular/repeat.svg')}
className='size-4 text-green-600'
aria-hidden
/>
}
text={
// status.visibility === 'private' ? (
// <FormattedMessage
// id='status.reblogged_by_private'
// defaultMessage='{name} reposted to followers'
// values={values}
// />
// ) : (
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={values}
/>
// )
}
/>
);
}
if (timelineId.split(':')[0] === 'home' && features.followHashtags) {
return <StatusFollowedTagInfo className='mt-4' status={status} avatarSize={42} />;
}
};
interface ITimelineStatus {
id: string;
rebloggedBy: Array<string>;
timelineId: string;
contextType?: FilterContextType;
isConnectedTop?: boolean;
isConnectedBottom?: boolean;
@ -92,6 +180,13 @@ const TimelineStatus: React.FC<ITimelineStatus> = (props): React.JSX.Element =>
'⁂-timeline-status--connected-top': isConnectedTop,
})}
>
{statusQuery.data && (
<TimelineStatusInfo
status={statusQuery.data!}
rebloggedBy={props.rebloggedBy}
timelineId={props.timelineId}
/>
)}
{renderConnector()}
{statusQuery.isPending ? (
<PlaceholderStatus variant='slim' />
@ -110,7 +205,8 @@ interface ITimeline {
const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public' }) => {
const node = useRef<VirtuosoHandle | null>(null);
const { entries, queuedCount, fetchNextPage, dequeueEntries, isFetching, isPending } = query;
const { timelineId, entries, queuedCount, fetchNextPage, dequeueEntries, isFetching, isPending } =
query;
const handleMoveUp = (index: number) =>
selectChild(index - 1, node, document.getElementById('status-list') ?? undefined);
@ -134,6 +230,8 @@ const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public' }) => {
contextType={contextType}
onMoveUp={() => handleMoveUp(index)}
onMoveDown={() => handleMoveDown(index)}
rebloggedBy={entry.rebloggedBy}
timelineId={timelineId}
// contextType={timelineId}
// showGroup={showGroup}
// variant={divideType === 'border' ? 'slim' : 'rounded'}

View File

@ -98,11 +98,16 @@ const AccountInfo: React.FC<IAccountInfo> = React.memo(({ status }) => {
AccountInfo.displayName = 'AccountInfo';
interface IStatusFollowedTagInfo {
className?: string;
status: SelectedStatus;
avatarSize: number;
}
const StatusFollowedTagInfo: React.FC<IStatusFollowedTagInfo> = ({ status, avatarSize }) => {
const StatusFollowedTagInfo: React.FC<IStatusFollowedTagInfo> = ({
className,
status,
avatarSize,
}) => {
const { data: followedTags } = useFollowedTags();
const filteredTags = status.tags.filter((tag) =>
@ -130,7 +135,7 @@ const StatusFollowedTagInfo: React.FC<IStatusFollowedTagInfo> = ({ status, avata
return (
<StatusInfo
className='-mb-1'
className={className}
avatarSize={avatarSize}
icon={
<Icon
@ -526,7 +531,7 @@ const Status: React.FC<IStatus> = React.memo((props) => {
} else if (fromHomeTimeline) {
return (
features.followHashtags && (
<StatusFollowedTagInfo status={actualStatus} avatarSize={avatarSize} />
<StatusFollowedTagInfo className='-mb-1' status={actualStatus} avatarSize={avatarSize} />
)
);
}
@ -691,4 +696,4 @@ const Status: React.FC<IStatus> = React.memo((props) => {
Status.displayName = 'Status';
export { type IStatus, Status as default };
export { type IStatus, Status as default, StatusFollowedTagInfo };

View File

@ -57,8 +57,8 @@ const useTimeline = (timelineId: string, fetcher: TimelineFetcher, streamConfig?
}, [timelineId]);
return useMemo(
() => ({ ...timeline, fetchNextPage, dequeueEntries }),
[timeline, fetchNextPage, dequeueEntries],
() => ({ ...timeline, timelineId, fetchNextPage, dequeueEntries }),
[timeline, timelineId, fetchNextPage, dequeueEntries],
);
};