nicolium: restore pinned posts display
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -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<React.ComponentProps<'button'>> = ({ onClick }) => {
|
||||
return (
|
||||
<button className='⁂-skip-pinned' onClick={onClick}>
|
||||
<Icon src={require('@phosphor-icons/core/regular/arrow-line-down.svg')} />
|
||||
|
||||
<p>
|
||||
<FormattedMessage id='status.skip_pinned' defaultMessage='Skip pinned posts' />
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const PlaceholderTimelineStatus = () => (
|
||||
<div className='⁂-timeline-status relative border-b border-solid border-gray-200 dark:border-gray-800'>
|
||||
<PlaceholderStatus variant='slim' />
|
||||
@ -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<ITimelineStatus> = (props): React.JSX.Element =>
|
||||
type IBaseTimeline = Pick<
|
||||
IScrollableList,
|
||||
'emptyMessageIcon' | 'emptyMessageText' | 'onTopItemChanged'
|
||||
>;
|
||||
> & {
|
||||
featuredStatusIds?: Array<string>;
|
||||
};
|
||||
|
||||
interface ITimeline extends IBaseTimeline {
|
||||
query: ReturnType<typeof useHomeTimeline>;
|
||||
contextType?: FilterContextType;
|
||||
}
|
||||
|
||||
const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public', ...props }) => {
|
||||
const Timeline: React.FC<ITimeline> = ({
|
||||
query,
|
||||
contextType = 'public',
|
||||
featuredStatusIds,
|
||||
...props
|
||||
}) => {
|
||||
const node = useRef<VirtuosoHandle | null>(null);
|
||||
|
||||
const {
|
||||
@ -376,16 +396,36 @@ const Timeline: React.FC<ITimeline> = ({ 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<ITimeline> = ({ 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(
|
||||
<TimelineStatus
|
||||
key={id}
|
||||
id={id}
|
||||
contextType={contextType}
|
||||
onMoveUp={() => 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 (
|
||||
<>
|
||||
<Portal>
|
||||
@ -427,6 +495,9 @@ const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public', ...props
|
||||
liveRegionMessage={messages.queueLiveRegion}
|
||||
/>
|
||||
</Portal>
|
||||
{featuredStatusIds && featuredStatusIds.length > 3 && entries?.length > 0 && (
|
||||
<SkipPinned onClick={handleSkipPinned} />
|
||||
)}
|
||||
<ScrollableList
|
||||
id='status-list'
|
||||
key='scrollable-list'
|
||||
@ -440,7 +511,7 @@ const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public', ...props
|
||||
onLoadMore={fetchNextPage}
|
||||
{...props}
|
||||
>
|
||||
{(entries || []).map(renderEntry)}
|
||||
{renderedEntries}
|
||||
</ScrollableList>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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<React.ComponentProps<'button'>> = ({ onClick }) => {
|
||||
return (
|
||||
<button className='⁂-skip-pinned' onClick={onClick}>
|
||||
<Icon src={require('@phosphor-icons/core/regular/arrow-line-down.svg')} />
|
||||
|
||||
<p>
|
||||
<FormattedMessage id='status.skip_pinned' defaultMessage='Skip pinned posts' />
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
|
||||
/** Unique key to preserve the scroll position when navigating back. */
|
||||
scrollKey: string;
|
||||
@ -34,8 +20,6 @@ interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
|
||||
statusIds: Array<string>;
|
||||
/** Last _unfiltered_ status ID (maxId) for pagination. */
|
||||
lastStatusId?: string;
|
||||
/** Pinned statuses to show at the top of the feed. */
|
||||
featuredStatusIds?: Array<string>;
|
||||
/** 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<IScrollableList, 'onLoadMore' | 'children'> {
|
||||
const StatusList: React.FC<IStatusList> = ({
|
||||
statusIds,
|
||||
lastStatusId,
|
||||
featuredStatusIds,
|
||||
onLoadMore,
|
||||
timelineId,
|
||||
isLoading,
|
||||
@ -67,23 +50,17 @@ const StatusList: React.FC<IStatusList> = ({
|
||||
|
||||
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<IStatusList> = ({
|
||||
[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<IStatusList> = ({
|
||||
};
|
||||
|
||||
const scrollableContent = useMemo(() => {
|
||||
const renderFeaturedStatuses = (): React.ReactNode[] => {
|
||||
if (!featuredStatusIds) return [];
|
||||
|
||||
return featuredStatusIds.map((statusId) => (
|
||||
<StatusContainer
|
||||
key={`f-${statusId}`}
|
||||
id={statusId}
|
||||
featured
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
contextType={contextType}
|
||||
showGroup={showGroup}
|
||||
variant='slim'
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const renderStatuses = (): React.ReactNode[] => {
|
||||
if (isLoading || statusIds.length > 0) {
|
||||
return statusIds.reduce((acc, statusId, index) => {
|
||||
@ -193,15 +137,10 @@ const StatusList: React.FC<IStatusList> = ({
|
||||
}
|
||||
};
|
||||
|
||||
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<IStatusList> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{featuredStatusIds && featuredStatusIds.length > 3 && statusIds.length > 0 && (
|
||||
<SkipPinned onClick={handleSkipPinned} />
|
||||
)}
|
||||
<ScrollableList
|
||||
id='status-list'
|
||||
key='scrollable-list'
|
||||
|
||||
@ -18,8 +18,7 @@ const AccountTimelinePage: React.FC = () => {
|
||||
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 = () => {
|
||||
<AccountTimelineColumn
|
||||
accountId={account.id}
|
||||
excludeReplies={!withReplies}
|
||||
// featuredStatusIds={showPins ? featuredStatusIds : undefined}
|
||||
featuredStatusIds={!withReplies ? featuredStatusIds : undefined}
|
||||
emptyMessageText={
|
||||
<FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts here!' />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user