nicolium: add a way to use the new timeline
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
123
packages/nicolium/src/columns/timeline.tsx
Normal file
123
packages/nicolium/src/columns/timeline.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import LoadMore from '@/components/load-more';
|
||||
import ScrollableList from '@/components/scrollable-list';
|
||||
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 { type TimelineEntry, useHomeTimeline } from '@/queries/timelines/use-home-timeline';
|
||||
|
||||
import type { FilterContextType } from '@/queries/settings/use-filters';
|
||||
|
||||
interface ITimelineStatus {
|
||||
id: string;
|
||||
contextType?: FilterContextType;
|
||||
isConnectedTop?: boolean;
|
||||
isConnectedBottom?: boolean;
|
||||
onMoveUp?: (id: string) => void;
|
||||
onMoveDown?: (id: string) => void;
|
||||
}
|
||||
|
||||
/** Status with reply-connector in threads. */
|
||||
const TimelineStatus: React.FC<ITimelineStatus> = (props): React.JSX.Element => {
|
||||
const { id, isConnectedTop, isConnectedBottom } = props;
|
||||
|
||||
const statusQuery = useStatus(id, { withFilteredResults: true });
|
||||
|
||||
if (statusQuery.data?.deleted) {
|
||||
return (
|
||||
<div className='py-4 pb-8'>
|
||||
<Tombstone id={id} onMoveUp={props.onMoveUp} onMoveDown={props.onMoveDown} deleted />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderConnector = (): React.JSX.Element | null => {
|
||||
const isConnected = isConnectedTop || isConnectedBottom;
|
||||
|
||||
if (!isConnected) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute left-10 z-[1] hidden w-0.5 bg-gray-200 black:bg-gray-800 dark:bg-primary-800 rtl:left-auto rtl:right-5',
|
||||
{
|
||||
'top-20 !block h-[calc(100%-42px-8px-1rem)]': isConnectedBottom,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx('relative', {
|
||||
'timeline-status-connected': isConnectedBottom,
|
||||
'border-b border-solid border-gray-200 dark:border-gray-800': !isConnectedBottom,
|
||||
})}
|
||||
>
|
||||
{renderConnector()}
|
||||
{statusQuery.isPending ? (
|
||||
<PlaceholderStatus variant='default' />
|
||||
) : statusQuery.data ? (
|
||||
<Status status={statusQuery.data!} {...props} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NewTimelineColumn = () => {
|
||||
const { data, handleLoadMore, isLoading } = useHomeTimeline();
|
||||
|
||||
const renderEntry = (entry: TimelineEntry) => {
|
||||
if (entry.type === 'status') {
|
||||
return (
|
||||
<TimelineStatus
|
||||
key={entry.id}
|
||||
id={entry.id}
|
||||
isConnectedTop={entry.isConnectedTop}
|
||||
isConnectedBottom={entry.isConnectedBottom}
|
||||
contextType='home'
|
||||
// onMoveUp={handleMoveUp}
|
||||
// onMoveDown={handleMoveDown}
|
||||
// contextType={timelineId}
|
||||
// showGroup={showGroup}
|
||||
// variant={divideType === 'border' ? 'slim' : 'rounded'}
|
||||
// fromBookmarks={other.scrollKey === 'bookmarked_statuses'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (entry.type === 'page-end' || entry.type === 'page-start') {
|
||||
return (
|
||||
<div className='m-4'>
|
||||
<LoadMore key='load-more' onClick={() => handleLoadMore(entry)} disabled={isLoading} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollableList
|
||||
id='status-list'
|
||||
key='scrollable-list'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && !data}
|
||||
placeholderComponent={() => <PlaceholderStatus variant={'slim'} />}
|
||||
placeholderCount={20}
|
||||
// className={className}
|
||||
// listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
|
||||
// 'divide-none': divideType !== 'border',
|
||||
// })}
|
||||
// itemClassName={clsx({
|
||||
// 'pb-3': divideType !== 'border',
|
||||
// })}
|
||||
// {...other}
|
||||
>
|
||||
{(data || []).map(renderEntry)}
|
||||
</ScrollableList>
|
||||
);
|
||||
};
|
||||
|
||||
export { NewTimelineColumn };
|
||||
@ -902,6 +902,29 @@ const Preferences = () => {
|
||||
</ListItem>
|
||||
</List>
|
||||
)}
|
||||
|
||||
<List>
|
||||
<ListItem
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='preferences.fields.experimental_timeline_label'
|
||||
defaultMessage='Enable experimental timeline'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='preferences.fields.experimental_timeline_hint'
|
||||
defaultMessage='It replaces the stable timeline experience and might not offer all features.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingToggle
|
||||
settings={settings}
|
||||
settingPath={['experimentalTimeline']}
|
||||
onChange={onToggleChange}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1556,6 +1556,8 @@
|
||||
"preferences.fields.display_media.default": "Hide posts marked as sensitive",
|
||||
"preferences.fields.display_media.hide_all": "Always hide media posts",
|
||||
"preferences.fields.display_media.show_all": "Always show posts",
|
||||
"preferences.fields.experimental_timeline_hint": "It replaces the stable timeline experience and might not offer all features.",
|
||||
"preferences.fields.experimental_timeline_label": "Enable experimental timeline",
|
||||
"preferences.fields.implicit_addressing_label": "Include mentions in post content when replying",
|
||||
"preferences.fields.interface_size": "Interface size",
|
||||
"preferences.fields.known_languages_label": "Languages you know",
|
||||
|
||||
@ -2,6 +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 { Link } from '@/components/link';
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
import Column from '@/components/ui/column';
|
||||
@ -12,13 +13,13 @@ import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from '@/hooks/use-app-selector';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useInstance } from '@/hooks/use-instance';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||
});
|
||||
|
||||
const HomeTimelinePage: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const HomeTimeline: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const instance = useInstance();
|
||||
@ -54,53 +55,62 @@ const HomeTimelinePage: React.FC = () => {
|
||||
useEffect(() => checkIfReloadNeeded(isPartial), [isPartial]);
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='home_timeline'
|
||||
onLoadMore={handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessageText={
|
||||
<Stack space={1}>
|
||||
<Text size='xl' weight='medium' align='center'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.title'
|
||||
defaultMessage="You're not following anyone yet"
|
||||
/>
|
||||
</Text>
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
<Timeline
|
||||
loadMoreClassName='sm:pb-4 black:sm:pb-0 black:sm:mx-4'
|
||||
scrollKey='home_timeline'
|
||||
onLoadMore={handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessageText={
|
||||
<Stack space={1}>
|
||||
<Text size='xl' weight='medium' align='center'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.title'
|
||||
defaultMessage="You're not following anyone yet"
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Text theme='muted' align='center'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.subtitle'
|
||||
defaultMessage='{siteTitle} gets more interesting once you follow other users.'
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
{features.federating && (
|
||||
<Text theme='muted' align='center'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.subtitle'
|
||||
defaultMessage='{siteTitle} gets more interesting once you follow other users.'
|
||||
values={{ siteTitle: instance.title }}
|
||||
id='empty_column.home'
|
||||
defaultMessage='Or you can visit {public} to get started and meet other users.'
|
||||
values={{
|
||||
public: (
|
||||
<Link to='/timeline/local'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.local_tab'
|
||||
defaultMessage='the Local tab'
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
);
|
||||
};
|
||||
|
||||
{features.federating && (
|
||||
<Text theme='muted' align='center'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home'
|
||||
defaultMessage='Or you can visit {public} to get started and meet other users.'
|
||||
values={{
|
||||
public: (
|
||||
<Link to='/timeline/local'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.local_tab'
|
||||
defaultMessage='the Local tab'
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
emptyMessageIcon={require('@phosphor-icons/core/regular/chat-centered-text.svg')}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
const HomeTimelinePage: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { experimentalTimeline } = useSettings();
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||
{experimentalTimeline ? <NewTimelineColumn /> : <HomeTimeline />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
@ -123,6 +123,8 @@ const settingsSchema = v.object({
|
||||
saved: v.fallback(v.boolean(), true),
|
||||
|
||||
demo: v.fallback(v.boolean(), false),
|
||||
|
||||
experimentalTimeline: v.fallback(v.boolean(), false),
|
||||
});
|
||||
|
||||
type Settings = v.InferOutput<typeof settingsSchema>;
|
||||
|
||||
Reference in New Issue
Block a user