diff --git a/packages/nicolium/src/columns/timeline.tsx b/packages/nicolium/src/columns/timeline.tsx index d490e5bd2..0404905f7 100644 --- a/packages/nicolium/src/columns/timeline.tsx +++ b/packages/nicolium/src/columns/timeline.tsx @@ -1,6 +1,6 @@ import { Link } from '@tanstack/react-router'; import clsx from 'clsx'; -import React, { useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl'; import ScrollTopButton from '@/components/scroll-top-button'; @@ -57,6 +57,18 @@ const messages = defineMessages({ }, }); +const SkipPinned: React.FC> = ({ onClick }) => { + return ( + + ); +}; + const PlaceholderTimelineStatus = () => (
@@ -284,6 +296,7 @@ interface ITimelineStatus { isConnectedBottom?: boolean; onMoveUp?: (id: string) => void | boolean; onMoveDown?: (id: string) => void | boolean; + featured?: boolean; } /** Status with reply-connector in threads. */ @@ -354,14 +367,21 @@ const TimelineStatus: React.FC = (props): React.JSX.Element => type IBaseTimeline = Pick< IScrollableList, 'emptyMessageIcon' | 'emptyMessageText' | 'onTopItemChanged' ->; +> & { + featuredStatusIds?: Array; +}; interface ITimeline extends IBaseTimeline { query: ReturnType; contextType?: FilterContextType; } -const Timeline: React.FC = ({ query, contextType = 'public', ...props }) => { +const Timeline: React.FC = ({ + query, + contextType = 'public', + featuredStatusIds, + ...props +}) => { const node = useRef(null); const { @@ -376,16 +396,36 @@ const Timeline: React.FC = ({ query, contextType = 'public', ...props hasNextPage, } = query; - const handleMoveUp = (index: number) => + const handleMoveUp = (index: number) => { + console.log(index); selectChild(index - 1, node, document.getElementById('status-list') ?? undefined); + }; - const handleMoveDown = (index: number) => + const handleMoveDown = (index: number) => { + console.log(index); selectChild( index + 1, node, document.getElementById('status-list') ?? undefined, entries.length, ); + }; + + const handleSkipPinned = () => { + const skipPinned = () => { + selectChild( + featuredStatusIds?.length ?? 0, + node, + document.getElementById('status-list') ?? undefined, + (featuredStatusIds?.length ?? 0) + entries.length, + 'start', + ); + }; + + skipPinned(); + + setTimeout(() => skipPinned, 0); + }; const renderEntry = (entry: TimelineEntry, index: number) => { if (entry.type === 'status') { @@ -417,6 +457,34 @@ const Timeline: React.FC = ({ query, contextType = 'public', ...props } }; + const renderedEntries = useMemo(() => { + const rendered = []; + + if (featuredStatusIds && featuredStatusIds.length > 0) { + for (const id of featuredStatusIds) { + const index = rendered.length; + rendered.push( + handleMoveUp(index)} + onMoveDown={() => handleMoveDown(index)} + rebloggedBy={[]} + timelineId={timelineId} + featured + />, + ); + } + } + + for (const entry of entries) { + rendered.push(renderEntry(entry, rendered.length)); + } + + return rendered; + }, [entries, contextType, timelineId, featuredStatusIds]); + return ( <> @@ -427,6 +495,9 @@ const Timeline: React.FC = ({ query, contextType = 'public', ...props liveRegionMessage={messages.queueLiveRegion} /> + {featuredStatusIds && featuredStatusIds.length > 3 && entries?.length > 0 && ( + + )} = ({ query, contextType = 'public', ...props onLoadMore={fetchNextPage} {...props} > - {(entries || []).map(renderEntry)} + {renderedEntries} ); diff --git a/packages/nicolium/src/components/statuses/status-list.tsx b/packages/nicolium/src/components/statuses/status-list.tsx index 49a85f1f2..1e82ad3d4 100644 --- a/packages/nicolium/src/components/statuses/status-list.tsx +++ b/packages/nicolium/src/components/statuses/status-list.tsx @@ -11,22 +11,8 @@ import PendingStatus from '@/features/ui/components/pending-status'; import { timelineToFilterContextType } from '@/queries/settings/use-filters'; import { selectChild } from '@/utils/scroll-utils'; -import Icon from '../ui/icon'; - import type { VirtuosoHandle } from 'react-virtuoso'; -const SkipPinned: React.FC> = ({ onClick }) => { - return ( - - ); -}; - interface IStatusList extends Omit { /** Unique key to preserve the scroll position when navigating back. */ scrollKey: string; @@ -34,8 +20,6 @@ interface IStatusList extends Omit { statusIds: Array; /** Last _unfiltered_ status ID (maxId) for pagination. */ lastStatusId?: string; - /** Pinned statuses to show at the top of the feed. */ - featuredStatusIds?: Array; /** Pagination callback when the end of the list is reached. */ onLoadMore?: (lastStatusId: string) => void; /** Whether the data is currently being fetched. */ @@ -54,7 +38,6 @@ interface IStatusList extends Omit { const StatusList: React.FC = ({ statusIds, lastStatusId, - featuredStatusIds, onLoadMore, timelineId, isLoading, @@ -67,23 +50,17 @@ const StatusList: React.FC = ({ const contextType = timelineToFilterContextType(timelineId); - const getFeaturedStatusCount = () => featuredStatusIds?.length ?? 0; - - const getCurrentStatusIndex = (id: string, featured: boolean): number => { - if (featured) { - return featuredStatusIds?.findIndex((key) => key === id) ?? 0; - } else { - return statusIds.findIndex((key) => key === id) + getFeaturedStatusCount(); - } + const getCurrentStatusIndex = (id: string): number => { + return statusIds.findIndex((key) => key === id); }; - const handleMoveUp = (id: string, featured: boolean = false) => { - const elementIndex = getCurrentStatusIndex(id, featured) - 1; + const handleMoveUp = (id: string) => { + const elementIndex = getCurrentStatusIndex(id) - 1; selectChild(elementIndex, node, document.getElementById('status-list') ?? undefined); }; - const handleMoveDown = (id: string, featured: boolean = false) => { - const elementIndex = getCurrentStatusIndex(id, featured) + 1; + const handleMoveDown = (id: string) => { + const elementIndex = getCurrentStatusIndex(id) + 1; selectChild( elementIndex, node, @@ -106,22 +83,6 @@ const StatusList: React.FC = ({ [onLoadMore, lastStatusId, statusIds.at(-1)], ); - const handleSkipPinned = () => { - const skipPinned = () => { - selectChild( - getFeaturedStatusCount(), - node, - document.getElementById('status-list') ?? undefined, - scrollableContent.length, - 'start', - ); - }; - - skipPinned(); - - setTimeout(() => skipPinned, 0); - }; - const renderLoadGap = (index: number) => { const ids = statusIds; const nextId = ids[index + 1]; @@ -155,23 +116,6 @@ const StatusList: React.FC = ({ }; const scrollableContent = useMemo(() => { - const renderFeaturedStatuses = (): React.ReactNode[] => { - if (!featuredStatusIds) return []; - - return featuredStatusIds.map((statusId) => ( - - )); - }; - const renderStatuses = (): React.ReactNode[] => { if (isLoading || statusIds.length > 0) { return statusIds.reduce((acc, statusId, index) => { @@ -193,15 +137,10 @@ const StatusList: React.FC = ({ } }; - const featuredStatuses = renderFeaturedStatuses(); const statuses = renderStatuses(); - if (featuredStatuses && statuses) { - return featuredStatuses.concat(statuses); - } else { - return statuses; - } - }, [featuredStatusIds, statusIds, isLoading, timelineId, showGroup]); + return statuses; + }, [statusIds, isLoading, timelineId, showGroup]); if (isPartial) { return ( @@ -226,9 +165,6 @@ const StatusList: React.FC = ({ return ( <> - {featuredStatusIds && featuredStatusIds.length > 3 && statusIds.length > 0 && ( - - )} { const features = useFeatures(); const { data: account, isPending } = useAccountLookup(username); - - const { data: _featuredStatusIds } = usePinnedStatuses(account?.id || ''); + const { data: featuredStatusIds } = usePinnedStatuses(account?.id || ''); const isBlocked = account?.relationship?.blocked_by && !features.blockersVisible; @@ -51,7 +50,7 @@ const AccountTimelinePage: React.FC = () => { }