Merge branch 'main' into create-wallet

merge-requests/3333/head
danidfra 2025-03-05 01:05:21 -03:00
commit 33612d77c3
20 zmienionych plików z 251 dodań i 203 usunięć

Wyświetl plik

@ -100,7 +100,7 @@ const dequeueTimeline = (timelineId: string, expandFunc?: (lastStatusId: string)
} else {
if (timelineId === 'home') {
dispatch(clearTimeline(timelineId));
dispatch(expandHomeTimeline(optionalExpandArgs));
dispatch(expandFollowsTimeline(optionalExpandArgs));
} else if (timelineId === 'community') {
dispatch(clearTimeline(timelineId));
dispatch(expandCommunityTimeline(optionalExpandArgs));
@ -194,20 +194,20 @@ const expandTimeline = (timelineId: string, path: string, params: Record<string,
});
};
interface ExpandHomeTimelineOpts {
interface ExpandFollowsTimelineOpts {
maxId?: string;
url?: string;
}
interface HomeTimelineParams {
interface FollowsTimelineParams {
max_id?: string;
exclude_replies?: boolean;
with_muted?: boolean;
}
const expandHomeTimeline = ({ url, maxId }: ExpandHomeTimelineOpts = {}, done = noOp) => {
const expandFollowsTimeline = ({ url, maxId }: ExpandFollowsTimelineOpts = {}, done = noOp) => {
const endpoint = url || '/api/v1/timelines/home';
const params: HomeTimelineParams = {};
const params: FollowsTimelineParams = {};
if (!url && maxId) {
params.max_id = maxId;
@ -337,7 +337,7 @@ export {
deleteFromTimelines,
clearTimeline,
expandTimeline,
expandHomeTimeline,
expandFollowsTimeline,
expandPublicTimeline,
expandRemoteTimeline,
expandCommunityTimeline,

Wyświetl plik

@ -6,15 +6,14 @@ import { closeModal } from 'soapbox/actions/modals.ts';
import { HTTPError } from 'soapbox/api/HTTPError.ts';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
import { captchaSchema, type CaptchaData } from 'soapbox/schemas/captcha.ts';
import toast from 'soapbox/toast.tsx';
const messages = defineMessages({
sucessMessage: { id: 'nostr_signup.captcha_message.sucess', defaultMessage: 'Incredible! You\'ve successfully completed the captcha.' },
wrongMessage: { id: 'nostr_signup.captcha_message.wrong', defaultMessage: 'Oops! It looks like your captcha response was incorrect. Please try again.' },
errorMessage: { id: 'nostr_signup.captcha_message.error', defaultMessage: 'It seems an error has occurred. Please try again. If the problem persists, please contact us.' },
misbehavingMessage: { id: 'nostr_signup.captcha_message.misbehaving', defaultMessage: 'It looks like we\'re experiencing issues with the {instance}. Please try again. If the error persists, try again later.' },
success: { id: 'nostr_signup.captcha_message.sucess', defaultMessage: 'Incredible! You\'ve successfully completed the captcha.' },
wrong: { id: 'nostr_signup.captcha_message.wrong', defaultMessage: 'Oops! It looks like your captcha response was incorrect. Please try again.' },
expired: { id: 'nostr_signup.captcha_message.expired', defaultMessage: 'The captcha has expired. Please start over.' },
error: { id: 'nostr_signup.captcha_message.error', defaultMessage: 'It seems an error has occurred. Please try again. If the problem persists, please contact us.' },
});
function getRandomNumber(min: number, max: number): number {
@ -23,7 +22,6 @@ function getRandomNumber(min: number, max: number): number {
const useCaptcha = () => {
const api = useApi();
const { instance } = useInstance();
const dispatch = useAppDispatch();
const intl = useIntl();
const [captcha, setCaptcha] = useState<CaptchaData>();
@ -68,7 +66,7 @@ const useCaptcha = () => {
await api.post(`/api/v1/ditto/captcha/${captcha.id}/verify`, result);
dispatch(closeModal('CAPTCHA'));
await dispatch(fetchMe()); // refetch account so `captcha_solved` changes.
toast.success(messages.sucessMessage);
toast.success(messages.success);
} catch (error) {
setTryAgain(true);
@ -77,13 +75,13 @@ const useCaptcha = () => {
switch (status) {
case 400:
message = intl.formatMessage(messages.wrongMessage);
message = intl.formatMessage(messages.wrong);
break;
case 422:
message = intl.formatMessage(messages.misbehavingMessage, { instance: instance.title });
case 410:
message = intl.formatMessage(messages.expired);
break;
default:
message = intl.formatMessage(messages.errorMessage);
message = intl.formatMessage(messages.error);
console.error(error);
break;
}

Wyświetl plik

@ -140,7 +140,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
<Avatar src={account.avatar} size={80} className='size-20 overflow-hidden bg-gray-50 ring-2 ring-white' />
</Link>
<div className='mt-2'>
<div className='relative z-50 mt-2'>
<ActionButton account={account} small />
</div>
</HStack>

Wyświetl plik

@ -1,4 +1,3 @@
import atIcon from '@tabler/icons/outline/at.svg';
import banIcon from '@tabler/icons/outline/ban.svg';
import bookmarkIcon from '@tabler/icons/outline/bookmark.svg';
import calendarEventIcon from '@tabler/icons/outline/calendar-event.svg';
@ -36,7 +35,6 @@ import ProfileStats from 'soapbox/features/ui/components/profile-stats.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
import { useSettingsNotifications } from 'soapbox/hooks/useSettingsNotifications.ts';
import { makeGetOtherAccounts } from 'soapbox/selectors/index.ts';
@ -114,7 +112,6 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const sidebarOpen = useAppSelector((state) => state.sidebar.sidebarOpen);
const settings = useAppSelector((state) => getSettings(state));
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
const { instance } = useInstance();
const settingsNotifications = useSettingsNotifications();
const closeButtonRef = useRef(null);
@ -271,25 +268,14 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
/>
)}
{features.publicTimeline && <>
<Divider />
{features.publicTimeline && features.federating && (
<SidebarLink
to='/timeline/local'
icon={features.federating ? atIcon : worldIcon}
text={features.federating ? instance.domain : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
to='/timeline/global'
icon={worldIcon}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/timeline/global'
icon={worldIcon}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
onClick={onClose}
/>
)}
</>}
)}
<Divider />

Wyświetl plik

@ -3,7 +3,6 @@ import circlesFilledIcon from '@tabler/icons/filled/circles.svg';
import homeFilledIcon from '@tabler/icons/filled/home.svg';
import settingsFilledIcon from '@tabler/icons/filled/settings.svg';
import userFilledIcon from '@tabler/icons/filled/user.svg';
import atIcon from '@tabler/icons/outline/at.svg';
import bellIcon from '@tabler/icons/outline/bell.svg';
import bookmarkIcon from '@tabler/icons/outline/bookmark.svg';
import calendarEventIcon from '@tabler/icons/outline/calendar-event.svg';
@ -162,6 +161,16 @@ const SidebarNavigation = () => {
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
/>
{account && (
<SidebarNavigationLink
to='/notifications'
icon={bellIcon}
activeIcon={bellFilledIcon}
count={notificationCount}
text={<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />}
/>
)}
<SidebarNavigationLink
to='/search'
icon={searchIcon}
@ -170,13 +179,6 @@ const SidebarNavigation = () => {
{account && (
<>
<SidebarNavigationLink
to='/notifications'
icon={bellIcon}
activeIcon={bellFilledIcon}
count={notificationCount}
text={<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />}
/>
{renderMessagesLink()}
@ -188,6 +190,10 @@ const SidebarNavigation = () => {
text={<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />}
/>
)}
</>)}
{account && (
<>
<SidebarNavigationLink
to={`/@${account.acct}`}
@ -223,23 +229,13 @@ const SidebarNavigation = () => {
)}
{(features.publicTimeline) && (
<>
{(account || !restrictUnauth.timelines.local) && (
<SidebarNavigationLink
to='/timeline/local'
icon={features.federating ? atIcon : worldIcon}
text={features.federating ? instance.domain : <FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
/>
)}
features.federating && (account || !restrictUnauth.timelines.federated)) && (
<SidebarNavigationLink
to='/timeline/global'
icon={worldIcon}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
/>
{(features.federating && (account || !restrictUnauth.timelines.federated)) && (
<SidebarNavigationLink
to='/timeline/global'
icon={worldIcon}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
/>
)}
</>
)}
{menu.length > 0 && (

Wyświetl plik

@ -251,8 +251,8 @@ const Upload: React.FC<IUpload> = ({
<div className='absolute inset-0 z-[-1] size-full'>
{mediaType === 'video' && (
<video className='size-full object-cover' autoPlay playsInline muted loop>
<source src={media.preview_url} />
<video className='size-full object-cover' poster={media.preview_url} autoPlay playsInline muted loop>
<source src={media.url} />
</video>
)}
{uploadIcon}

Wyświetl plik

@ -5,6 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { patchMe } from 'soapbox/actions/me.ts';
import { changeSetting } from 'soapbox/actions/settings.ts';
import { HTTPError } from 'soapbox/api/HTTPError.ts';
import List, { ListItem } from 'soapbox/components/list.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import { CardHeader, CardTitle } from 'soapbox/components/ui/card.tsx';
@ -90,6 +91,11 @@ const EditIdentity: React.FC<IEditIdentity> = () => {
setReason('');
setSubmitted(true);
},
onError(error) {
if (error instanceof HTTPError) {
toast.showAlertForError(error);
}
},
});
};
@ -185,6 +191,10 @@ export const UsernameInput: React.FC<React.ComponentProps<typeof Input>> = (prop
return (
<Input
placeholder={intl.formatMessage(messages.username)}
autoComplete='off'
autoCorrect='off'
autoCapitalize='off'
pattern='^[\w.]+$'
append={(
<HStack alignItems='center' space={1} className='rounded p-1 text-sm backdrop-blur'>
<Icon className='size-4' src={atIcon} />

Wyświetl plik

@ -0,0 +1,114 @@
import { useEffect, useRef } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { expandFollowsTimeline } from 'soapbox/actions/timelines.ts';
import PullToRefresh from 'soapbox/components/pull-to-refresh.tsx';
import { Column } from 'soapbox/components/ui/column.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import Timeline from 'soapbox/features/ui/components/timeline.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
});
const FollowsTimeline: React.FC = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const features = useFeatures();
const { instance } = useInstance();
const polling = useRef<NodeJS.Timeout | null>(null);
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
const next = useAppSelector(state => state.timelines.get('home')?.next);
const handleLoadMore = (maxId: string) => {
dispatch(expandFollowsTimeline({ url: next, maxId }));
};
// Mastodon generates the feed in Redis, and can return a partial timeline
// (HTTP 206) for new users. Poll until we get a full page of results.
const checkIfReloadNeeded = () => {
if (isPartial) {
polling.current = setInterval(() => {
dispatch(expandFollowsTimeline());
}, 3000);
} else {
stopPolling();
}
};
const stopPolling = () => {
if (polling.current) {
clearInterval(polling.current);
polling.current = null;
}
};
const handleRefresh = () => {
return dispatch(expandFollowsTimeline());
};
useEffect(() => {
checkIfReloadNeeded();
return () => {
stopPolling();
};
}, [isPartial]);
return (
<Column label={intl.formatMessage(messages.title)} withHeader={false} slim>
<PullToRefresh onRefresh={handleRefresh}>
<Timeline
scrollKey='home_timeline'
onLoadMore={handleLoadMore}
timelineId='home'
emptyMessage={
<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'
defaultMessage='Or you can visit {public} to get started and meet other users.'
values={{
public: (
<Link to='/timeline/local' className='text-primary-600 hover:underline dark:text-primary-400'>
<FormattedMessage id='empty_column.home.local_tab' defaultMessage='the {site_title} tab' values={{ site_title: instance.title }} />
</Link>
),
}}
/>
</Text>
)}
</Stack>
}
/>
</PullToRefresh>
</Column>
);
};
export default FollowsTimeline;

Wyświetl plik

@ -1,112 +1,47 @@
import { useEffect, useRef } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { Suspense } from 'react';
import { FormattedMessage } from 'react-intl';
import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { expandHomeTimeline } from 'soapbox/actions/timelines.ts';
import PullToRefresh from 'soapbox/components/pull-to-refresh.tsx';
import { Column } from 'soapbox/components/ui/column.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import Timeline from 'soapbox/features/ui/components/timeline.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import Tabs from 'soapbox/components/ui/tabs.tsx';
import { CommunityTimeline, FollowsTimeline } from 'soapbox/features/ui/util/async-components.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
});
const HomeTimeline: React.FC = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const features = useFeatures();
const HomeTimeline = () => {
const { instance } = useInstance();
const polling = useRef<NodeJS.Timeout | null>(null);
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
const next = useAppSelector(state => state.timelines.get('home')?.next);
const handleLoadMore = (maxId: string) => {
dispatch(expandHomeTimeline({ url: next, maxId }));
};
// Mastodon generates the feed in Redis, and can return a partial timeline
// (HTTP 206) for new users. Poll until we get a full page of results.
const checkIfReloadNeeded = () => {
if (isPartial) {
polling.current = setInterval(() => {
dispatch(expandHomeTimeline());
}, 3000);
} else {
stopPolling();
}
};
const stopPolling = () => {
if (polling.current) {
clearInterval(polling.current);
polling.current = null;
}
};
const handleRefresh = () => {
return dispatch(expandHomeTimeline());
};
useEffect(() => {
checkIfReloadNeeded();
return () => {
stopPolling();
};
}, [isPartial]);
const match = useRouteMatch();
const notifications = useAppSelector((state) => state.notificationsTab);
return (
<Column label={intl.formatMessage(messages.title)} withHeader={false} slim>
<PullToRefresh onRefresh={handleRefresh}>
<Timeline
scrollKey='home_timeline'
onLoadMore={handleLoadMore}
timelineId='home'
emptyMessage={
<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'
defaultMessage='Or you can visit {public} to get started and meet other users.'
values={{
public: (
<Link to='/timeline/local' className='text-primary-600 hover:underline dark:text-primary-400'>
<FormattedMessage id='empty_column.home.local_tab' defaultMessage='the {site_title} tab' values={{ site_title: instance.title }} />
</Link>
),
}}
/>
</Text>
)}
</Stack>
}
<>
<div className='sticky top-11 z-50 bg-white black:bg-black dark:bg-primary-900 lg:top-0'>
<Tabs
items={[
{
to: '/',
name: '/',
text: <FormattedMessage id='tabs_bar.follows' defaultMessage='Follows' />,
notification: notifications.home,
},
{
to: '/timeline/local',
name: '/timeline/local',
text: <div className='block max-w-xs truncate'>{instance.title}</div>,
notification: notifications.instance,
},
]}
activeItem={match.path}
/>
</PullToRefresh>
</Column>
</div>
<Suspense fallback={<div className='p-4 text-center'><FormattedMessage id='loading_indicator.label' defaultMessage='Loading…' /></div>}>
<Switch>
<Route path='/' exact component={FollowsTimeline} />
<Route path='/timeline/local' exact component={CommunityTimeline} />
</Switch>
</Suspense>
</>
);
};

Wyświetl plik

@ -128,7 +128,8 @@ const SoapboxConfig: React.FC = () => {
if (file) {
data.append('file', file);
dispatch(uploadMedia(data)).then(({ data }: any) => {
dispatch(uploadMedia(data)).then(async (response) => {
const data = await response.json();
handleChange(path, () => data.url)(e);
}).catch(console.error);
}

Wyświetl plik

@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { revokeName, setBadges as saveBadges } from 'soapbox/actions/admin.ts';
import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation.tsx';
import { HTTPError } from 'soapbox/api/HTTPError.ts';
import { useSuggest, useVerify } from 'soapbox/api/hooks/admin/index.ts';
import { useAccount } from 'soapbox/api/hooks/index.ts';
import Account from 'soapbox/components/account.tsx';
@ -100,7 +101,11 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
const handleRevokeName = () => {
dispatch(revokeName(account.id))
.then(() => toast.success(intl.formatMessage(messages.revokedName)))
.catch(() => {});
.catch((error) => {
if (error instanceof HTTPError) {
toast.showAlertForError(error);
}
});
};
const handleDelete = () => {

Wyświetl plik

@ -34,13 +34,13 @@ const StreakModal: React.FC<IStreakModal> = ({ onClose }) => {
<Modal
title={
<HStack alignItems='center' justifyContent='center' space={1} className='my-6 -mr-8'>
<Text weight='bold' size='2xl' className='text-black'>
<FormattedMessage id='streak_modal.title' defaultMessage="You've unlocked a" />
<Text weight='bold' size='xl' className='text-black'>
<FormattedMessage id='streak_modal.title' defaultMessage='You unlocked a' />
</Text>
<Text theme='primary'>
<Icon src={flameIcon} className='size-6' />
</Text>
<Text weight='bold' size='2xl' className='text-black'>
<Text weight='bold' size='xl' className='text-black'>
<FormattedMessage id='streak_modal.sub' defaultMessage='streak!' />
</Text>
</HStack>

Wyświetl plik

@ -2,6 +2,7 @@ import { FormattedMessage } from 'react-intl';
import { openModal } from 'soapbox/actions/modals.ts';
import Button from 'soapbox/components/ui/button.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
@ -26,19 +27,32 @@ const SignUpPanel = () => {
<FormattedMessage id='signup_panel.title' defaultMessage='New to {site_title}?' values={{ site_title: instance.title }} />
</Text>
<Text theme='muted' size='sm'>
<Text size='sm' theme='muted'>
<FormattedMessage id='signup_panel.subtitle' defaultMessage="Sign up now to discuss what's happening." />
</Text>
</Stack>
<Button
theme='primary'
onClick={nostrSignup ? () => dispatch(openModal('NOSTR_SIGNUP')) : undefined}
to={nostrSignup ? undefined : '/signup'}
block
>
<FormattedMessage id='account.register' defaultMessage='Sign up' />
</Button>
<HStack space={2}>
<Button
theme='tertiary'
onClick={nostrSignup ? () => dispatch(openModal('NOSTR_LOGIN')) : undefined}
to={nostrSignup ? undefined : '/login'}
block
>
<FormattedMessage id='account.login' defaultMessage='Log in' />
</Button>
<Button
theme='primary'
onClick={nostrSignup ? () => dispatch(openModal('NOSTR_SIGNUP')) : undefined}
to={nostrSignup ? undefined : '/signup'}
block
>
<FormattedMessage id='account.register' defaultMessage='Sign up' />
</Button>
</HStack>
</Stack>
);
};

Wyświetl plik

@ -10,7 +10,7 @@ import { expandNotifications } from 'soapbox/actions/notifications.ts';
import { registerPushNotifications } from 'soapbox/actions/push-notifications/registerer.ts';
import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses.ts';
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions.ts';
import { expandHomeTimeline } from 'soapbox/actions/timelines.ts';
import { expandFollowsTimeline } from 'soapbox/actions/timelines.ts';
import { useUserStream } from 'soapbox/api/hooks/index.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import SidebarNavigation from 'soapbox/components/sidebar-navigation.tsx';
@ -47,7 +47,6 @@ import FloatingActionButton from './components/floating-action-button.tsx';
import Navbar from './components/navbar.tsx';
import {
Status,
CommunityTimeline,
PublicTimeline,
RemoteTimeline,
AccountTimeline,
@ -196,7 +195,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
NOTE: we cannot nest routes in a fragment
https://stackoverflow.com/a/68637108
*/}
{features.federating && <WrappedRoute path='/timeline/local' exact page={HomePage} component={CommunityTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/local' exact page={HomePage} component={HomeTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/global' exact page={HomePage} component={PublicTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/:instance' exact page={RemoteInstancePage} component={RemoteTimeline} content={children} publicRoute />}
@ -426,7 +425,7 @@ const UI: React.FC<IUI> = ({ children }) => {
const loadAccountData = () => {
if (!account) return;
dispatch(expandHomeTimeline({}, () => {
dispatch(expandFollowsTimeline({}, () => {
dispatch(fetchSuggestionsForTimeline());
}));

Wyświetl plik

@ -8,7 +8,6 @@ export const LandingTimeline = lazy(() => import('soapbox/features/landing-timel
export const HomeTimeline = lazy(() => import('soapbox/features/home-timeline/index.tsx'));
export const PublicTimeline = lazy(() => import('soapbox/features/public-timeline/index.tsx'));
export const RemoteTimeline = lazy(() => import('soapbox/features/remote-timeline/index.tsx'));
export const CommunityTimeline = lazy(() => import('soapbox/features/community-timeline/index.tsx'));
export const HashtagTimeline = lazy(() => import('soapbox/features/hashtag-timeline/index.tsx'));
export const DirectTimeline = lazy(() => import('soapbox/features/direct-timeline/index.tsx'));
export const Conversations = lazy(() => import('soapbox/features/conversations/index.tsx'));
@ -185,3 +184,5 @@ export const MyWallet = lazy(() => import('soapbox/features/my-wallet/index.tsx'
export const MyWalletRelays = lazy(() => import('soapbox/features/my-wallet/components/wallet-relays.tsx'));
export const MyWalletMints = lazy(() => import('soapbox/features/my-wallet/components/wallet-mints.tsx'));
export const StreakModal = lazy(() => import('soapbox/features/ui/components/modals/streak-modal.tsx'));
export const FollowsTimeline = lazy(() => import('soapbox/features/home-timeline/follows-timeline.tsx'));
export const CommunityTimeline = lazy(() => import('soapbox/features/home-timeline/community-timeline.tsx'));

Wyświetl plik

@ -133,6 +133,7 @@ const Video: React.FC<IVideo> = ({
aspectRatio = 16 / 9,
link,
blurhash,
preview,
}) => {
const intl = useIntl();
const isMobile = useIsMobile();
@ -571,6 +572,7 @@ const Video: React.FC<IVideo> = ({
onProgress={handleProgress}
onVolumeChange={handleVolumeChange}
muted={muted}
poster={preview}
/>
<div

Wyświetl plik

@ -1462,6 +1462,7 @@
"sw.url": "رابط الإسكربت",
"tabs_bar.all": "الكل",
"tabs_bar.dashboard": "لوحة التحكم",
"tabs_bar.global": "عالمي",
"tabs_bar.groups": "المجموعات",
"tabs_bar.home": "الرئيسية",
"tabs_bar.more": "المزيد",

Wyświetl plik

@ -1190,7 +1190,7 @@
"nostr_signup.captcha_check_button.checking": "Checking…",
"nostr_signup.captcha_instruction": "Complete the puzzle by dragging the puzzle piece to the correct position.",
"nostr_signup.captcha_message.error": "It seems an error has occurred. Please try again. If the problem persists, please contact us.",
"nostr_signup.captcha_message.misbehaving": "It looks like we're experiencing issues with the {instance}. Please try again. If the error persists, try again later.",
"nostr_signup.captcha_message.expired": "The captcha has expired. Please start over.",
"nostr_signup.captcha_message.sucess": "Incredible! You've successfully completed the captcha.",
"nostr_signup.captcha_message.wrong": "Oops! It looks like your captcha response was incorrect. Please try again.",
"nostr_signup.captcha_reset_button": "Reset puzzle",
@ -1614,7 +1614,7 @@
"streak_modal.message": "Post every day to keep it going.",
"streak_modal.streak_count": "1",
"streak_modal.sub": "streak!",
"streak_modal.title": "You've unlocked a",
"streak_modal.title": "You unlocked a",
"streamfield.add": "Add",
"streamfield.remove": "Remove",
"suggestions.dismiss": "Dismiss suggestion",
@ -1626,8 +1626,8 @@
"sw.state.waiting": "Waiting",
"sw.status": "Status",
"sw.url": "Script URL",
"tabs_bar.all": "All",
"tabs_bar.dashboard": "Dashboard",
"tabs_bar.follows": "Follows",
"tabs_bar.global": "Global",
"tabs_bar.groups": "Groups",
"tabs_bar.home": "Home",

Wyświetl plik

@ -1,7 +1,6 @@
import clsx from 'clsx';
import { useRef } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useSelector } from 'react-redux';
import { useIntl } from 'react-intl';
import { Link, useLocation } from 'react-router-dom';
import { uploadCompose } from 'soapbox/actions/compose.ts';
@ -9,7 +8,6 @@ import Avatar from 'soapbox/components/ui/avatar.tsx';
import { Card, CardBody } from 'soapbox/components/ui/card.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Layout from 'soapbox/components/ui/layout.tsx';
import Tabs from 'soapbox/components/ui/tabs.tsx';
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
import {
WhoToFollowPanel,
@ -27,11 +25,9 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useDraggedFiles } from 'soapbox/hooks/useDraggedFiles.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
import { useIsMobile } from 'soapbox/hooks/useIsMobile.ts';
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { RootState } from 'soapbox/store.ts';
import ComposeForm from '../features/compose/components/compose-form.tsx';
@ -43,17 +39,16 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const { pathname } = useLocation();
const notifications = useSelector((state: RootState) => state.notificationsTab);
const me = useAppSelector(state => state.me);
const { account } = useOwnAccount();
const features = useFeatures();
const soapboxConfig = useSoapboxConfig();
const { instance } = useInstance();
const composeId = 'home';
const composeBlock = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile();
const isGlobalPage = pathname === '/timeline/global';
const hasPatron = soapboxConfig.extensions.getIn(['patron', 'enabled']) === true;
const hasCrypto = typeof soapboxConfig.cryptoAddresses.getIn([0, 'ticker']) === 'string';
@ -67,7 +62,7 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
const avatar = account ? account.avatar : '';
const renderSuggestions = () => {
if (features.suggestionsLocal && pathname !== '/timeline/global') {
if (features.suggestionsLocal && !isGlobalPage) {
return <LatestAccountsPanel limit={3} />;
} else if (features.suggestions) {
return <WhoToFollowPanel limit={3} />;
@ -105,15 +100,6 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
</Card>
)}
<div className='sticky top-12 z-20 bg-white/90 backdrop-blur black:bg-black/90 dark:bg-primary-900/90 lg:top-0'>
<Tabs
items={[
{ name: 'home', text: <FormattedMessage id='tabs_bar.home' defaultMessage='Home' />, to: '/', notification: notifications.home },
{ name: 'local', text: <div className='block max-w-xs truncate'>{instance.domain}</div>, to: '/timeline/local', notification: notifications.instance },
]}
activeItem={pathname === '/timeline/local' ? 'local' : 'home'}
/>
</div>
{children}