kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Mor component refactoring
rodzic
fe0b5d9109
commit
811063c283
|
@ -10,7 +10,7 @@ export const useAccount = (accountId: string, refetch = false) => {
|
|||
const { entity: account, ...result } = useEntity(
|
||||
[Entities.ACCOUNTS, accountId],
|
||||
() => api.get(`/api/v1/accounts/${accountId}`),
|
||||
{ schema: accountSchema, refetch },
|
||||
{ schema: accountSchema, refetch, enabled: !!accountId },
|
||||
);
|
||||
const { relationship } = useRelationship(accountId);
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ import RelativeTimestamp from './relative-timestamp';
|
|||
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
|
||||
|
||||
import type { StatusApprovalStatus } from 'soapbox/normalizers/status';
|
||||
import type { Account as AccountSchema } from 'soapbox/schemas';
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
import type { Account as AccountEntity } from 'soapbox/schemas';
|
||||
|
||||
interface IInstanceFavicon {
|
||||
account: AccountEntity | AccountSchema
|
||||
account: AccountEntity
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
|
@ -68,7 +67,7 @@ const ProfilePopper: React.FC<IProfilePopper> = ({ condition, wrapper, children
|
|||
};
|
||||
|
||||
export interface IAccount {
|
||||
account: AccountEntity | AccountSchema
|
||||
account: AccountEntity
|
||||
action?: React.ReactElement
|
||||
actionAlignment?: 'center' | 'top'
|
||||
actionIcon?: string
|
||||
|
|
|
@ -96,7 +96,7 @@ const SoapboxMount = () => {
|
|||
const features = useFeatures();
|
||||
const { pepeEnabled } = useRegistrationStatus();
|
||||
|
||||
const waitlisted = account && !account.source.get('approved', true);
|
||||
const waitlisted = account && !(account.source?.approved ?? true);
|
||||
const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding);
|
||||
const showOnboarding = account && !waitlisted && needsOnboarding;
|
||||
const { redirectRootNoLogin } = soapboxConfig;
|
||||
|
|
|
@ -19,7 +19,7 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
|
|||
const account = useOwnAccount();
|
||||
const history = useHistory();
|
||||
|
||||
const isOnboarded = account?.chats_onboarded;
|
||||
const isOnboarded = account?.source?.chats_onboarded !== false;
|
||||
|
||||
const path = history.location.pathname;
|
||||
const isSidebarHidden = matchPath(path, {
|
||||
|
|
|
@ -33,7 +33,7 @@ const ChatPageSettings = () => {
|
|||
|
||||
const [data, setData] = useState<FormData>({
|
||||
chats_onboarded: true,
|
||||
accepts_chat_messages: account?.accepts_chat_messages,
|
||||
accepts_chat_messages: account?.pleroma?.accepts_chat_messages,
|
||||
});
|
||||
|
||||
const onToggleChange = (key: string[], checked: boolean) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ const ChatWidget = () => {
|
|||
const path = history.location.pathname;
|
||||
const shouldHideWidget = Boolean(path.match(/^\/chats/));
|
||||
|
||||
if (!account?.chats_onboarded || shouldHideWidget) {
|
||||
if (account?.source?.chats_onboarded === false || shouldHideWidget) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { useAccount } from 'soapbox/api/hooks';
|
||||
import Account from 'soapbox/components/account';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
interface IAutosuggestAccount {
|
||||
id: string
|
||||
}
|
||||
|
||||
const AutosuggestAccount: React.FC<IAutosuggestAccount> = ({ id }) => {
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
const account = useAppSelector((state) => getAccount(state, id));
|
||||
|
||||
const { account } = useAccount(id);
|
||||
if (!account) return null;
|
||||
|
||||
return <Account account={account} hideActions showProfileHoverCard={false} />;
|
||||
|
||||
};
|
||||
|
||||
export default AutosuggestAccount;
|
||||
|
|
|
@ -46,7 +46,6 @@ const Conversation: React.FC<IConversation> = ({ conversationId, onMoveUp, onMov
|
|||
}
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
id={lastStatusId}
|
||||
unread={unread}
|
||||
|
|
|
@ -3,29 +3,27 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import { useAccount } from 'soapbox/api/hooks';
|
||||
import Account from 'soapbox/components/account';
|
||||
import Badge from 'soapbox/components/badge';
|
||||
import RelativeTimestamp from 'soapbox/components/relative-timestamp';
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
import ActionButton from 'soapbox/features/ui/components/action-button';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
interface IAccountCard {
|
||||
id: string
|
||||
}
|
||||
|
||||
const AccountCard: React.FC<IAccountCard> = ({ id }) => {
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const account = useAppSelector((state) => getAccount(state, id));
|
||||
const { account, relationship } = useAccount(id);
|
||||
const autoPlayGif = useAppSelector((state) => getSettings(state).get('autoPlayGif'));
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
const followedBy = me !== account.id && account.relationship?.followed_by;
|
||||
const followedBy = me !== account.id && relationship?.followed_by;
|
||||
|
||||
return (
|
||||
<div className='flex flex-col divide-y divide-gray-200 rounded-lg bg-white text-center shadow dark:divide-primary-700 dark:bg-primary-800'>
|
||||
|
@ -39,9 +37,11 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className='absolute bottom-2.5 right-2.5'>
|
||||
<ActionButton account={account} small />
|
||||
</div>
|
||||
{relationship && (
|
||||
<div className='absolute bottom-2.5 right-2.5'>
|
||||
<ActionButton account={account} relationship={relationship} small />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<img
|
||||
src={autoPlayGif ? account.header : account.header_static}
|
||||
|
@ -87,10 +87,10 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
|
|||
|
||||
<Stack>
|
||||
<Text theme='primary' size='md' weight='medium'>
|
||||
{account.last_status_at === null ? (
|
||||
<FormattedMessage id='account.never_active' defaultMessage='Never' />
|
||||
) : (
|
||||
{account.last_status_at ? (
|
||||
<RelativeTimestamp theme='inherit' timestamp={account.last_status_at} />
|
||||
) : (
|
||||
<FormattedMessage id='account.never_active' defaultMessage='Never' />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -20,7 +19,7 @@ import {
|
|||
Toggle,
|
||||
} from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import { accountSchema } from 'soapbox/schemas';
|
||||
import toast from 'soapbox/toast';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
|
@ -34,7 +33,8 @@ import type { Account } from 'soapbox/types/entities';
|
|||
* Pleroma's config is granular, but we simplify it into one setting.
|
||||
*/
|
||||
const hidesNetwork = (account: Account): boolean => {
|
||||
const { hide_followers, hide_follows, hide_followers_count, hide_follows_count } = account.pleroma.toJS();
|
||||
if (!account.pleroma) return false;
|
||||
const { hide_followers, hide_follows, hide_followers_count, hide_follows_count } = account.pleroma;
|
||||
return Boolean(hide_followers && hide_follows && hide_followers_count && hide_follows_count);
|
||||
};
|
||||
|
||||
|
@ -124,18 +124,18 @@ const accountToCredentials = (account: Account): AccountCredentials => {
|
|||
discoverable: account.discoverable,
|
||||
bot: account.bot,
|
||||
display_name: account.display_name,
|
||||
note: account.source.get('note', ''),
|
||||
note: account.source?.note || '',
|
||||
locked: account.locked,
|
||||
fields_attributes: [...account.source.get<Iterable<AccountCredentialsField>>('fields', ImmutableList()).toJS()],
|
||||
stranger_notifications: account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true,
|
||||
accepts_email_list: account.getIn(['pleroma', 'accepts_email_list']) === true,
|
||||
fields_attributes: account.source?.fields?.map(({ name, value }) => ({ name, value })) ?? [],
|
||||
stranger_notifications: account.pleroma?.notification_settings?.block_from_strangers === true,
|
||||
accepts_email_list: account.pleroma?.accepts_email_list === true,
|
||||
hide_followers: hideNetwork,
|
||||
hide_follows: hideNetwork,
|
||||
hide_followers_count: hideNetwork,
|
||||
hide_follows_count: hideNetwork,
|
||||
website: account.website,
|
||||
location: account.location,
|
||||
birthday: account.birthday,
|
||||
birthday: account.pleroma?.birthday || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -185,7 +185,7 @@ const EditProfile: React.FC = () => {
|
|||
useEffect(() => {
|
||||
if (account) {
|
||||
const credentials = accountToCredentials(account);
|
||||
const strangerNotifications = account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true;
|
||||
const strangerNotifications = account.pleroma?.notification_settings?.block_from_strangers === true;
|
||||
setData(credentials);
|
||||
setMuteStrangers(strangerNotifications);
|
||||
}
|
||||
|
@ -298,13 +298,13 @@ const EditProfile: React.FC = () => {
|
|||
}, [data.header, account?.header]);
|
||||
|
||||
/** Preview account data. */
|
||||
const previewAccount = useMemo(() => {
|
||||
return normalizeAccount({
|
||||
...account?.toJS(),
|
||||
const previewAccount = useMemo((): Account => {
|
||||
return accountSchema.parse({
|
||||
...account,
|
||||
...data,
|
||||
avatar: avatarUrl,
|
||||
header: headerUrl,
|
||||
}) as Account;
|
||||
});
|
||||
}, [account?.id, data.display_name, avatarUrl, headerUrl]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -275,7 +275,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
icon: require('@tabler/icons/at.svg'),
|
||||
});
|
||||
|
||||
if (status.getIn(['account', 'pleroma', 'accepts_chat_messages']) === true) {
|
||||
if (status.account?.pleroma?.accepts_chat_messages) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.chat, { name: username }),
|
||||
action: handleChatClick,
|
||||
|
@ -467,7 +467,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
<HStack alignItems='center' space={2}>
|
||||
<Icon src={require('@tabler/icons/map-pin.svg')} />
|
||||
<span>
|
||||
{event.location.get('name')}
|
||||
{event.location.name}
|
||||
</span>
|
||||
</HStack>
|
||||
)}
|
||||
|
|
|
@ -67,14 +67,14 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
<HStack space={2} alignItems='center'>
|
||||
<Icon src={require('@tabler/icons/map-pin.svg')} />
|
||||
<Text>
|
||||
{event.location.get('name')}
|
||||
{event.location.name}
|
||||
<br />
|
||||
{!!event.location.get('street')?.trim() && (<>
|
||||
{event.location.get('street')}
|
||||
{!!event.location.street?.trim() && (<>
|
||||
{event.location.street}
|
||||
<br />
|
||||
</>)}
|
||||
{[event.location.get('postalCode'), event.location.get('locality'), event.location.get('country')].filter(text => text).join(', ')}
|
||||
{tileServer && event.location.get('latitude') && (<>
|
||||
{[event.location.postal_code, event.location.locality, event.location.country].filter(text => text).join(', ')}
|
||||
{tileServer && event.location.latitude && (<>
|
||||
<br />
|
||||
<a href='#' className='text-primary-600 hover:underline dark:text-accent-blue' onClick={handleShowMap}>
|
||||
<FormattedMessage id='event.show_on_map' defaultMessage='Show on map' />
|
||||
|
@ -132,7 +132,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
}, [status]);
|
||||
|
||||
const renderLinks = useCallback(() => {
|
||||
if (!status.event?.links.size) return null;
|
||||
if (!status.event?.links?.length) return null;
|
||||
|
||||
return (
|
||||
<Stack space={1}>
|
||||
|
@ -178,8 +178,8 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
onToggleVisibility={handleToggleMediaVisibility}
|
||||
/>
|
||||
|
||||
{status.quote && status.pleroma.get('quote_visible', true) && (
|
||||
<QuotedStatus statusId={status.quote as string} />
|
||||
{status.quote && status.pleroma?.quote_visible !== false && (
|
||||
<QuotedStatus status={status.quote} />
|
||||
)}
|
||||
|
||||
{renderEventLocation()}
|
||||
|
|
|
@ -3,21 +3,23 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import VerificationBadge from 'soapbox/components/verification-badge';
|
||||
import { useAccount, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import { Card, CardBody, CardTitle, HStack, Stack, Text } from '../../components/ui';
|
||||
import ActionButton from '../ui/components/action-button';
|
||||
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
import type { Account } from 'soapbox/schemas';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'feed_suggestions.heading', defaultMessage: 'Suggested Profiles' },
|
||||
viewAll: { id: 'feed_suggestions.view_all', defaultMessage: 'View all' },
|
||||
});
|
||||
|
||||
const SuggestionItem = ({ accountId }: { accountId: string }) => {
|
||||
const account = useAccount(accountId) as Account;
|
||||
interface ISuggestionItem {
|
||||
account: Account
|
||||
}
|
||||
|
||||
const SuggestionItem: React.FC<ISuggestionItem> = ({ account }) => {
|
||||
return (
|
||||
<Stack space={3} className='w-52 shrink-0 rounded-md border border-solid border-gray-300 p-4 dark:border-gray-800 md:w-full md:shrink md:border-transparent md:p-0 dark:md:border-transparent'>
|
||||
<Link
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { authorizeFollowRequest, rejectFollowRequest } from 'soapbox/actions/accounts';
|
||||
import { useAccount } from 'soapbox/api/hooks';
|
||||
import Account from 'soapbox/components/account';
|
||||
import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
interface IAccountAuthorize {
|
||||
id: string
|
||||
|
@ -12,9 +12,7 @@ interface IAccountAuthorize {
|
|||
|
||||
const AccountAuthorize: React.FC<IAccountAuthorize> = ({ id }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
const account = useAppSelector((state) => getAccount(state, id));
|
||||
const { account } = useAccount(id);
|
||||
|
||||
const onAuthorize = () => dispatch(authorizeFollowRequest(id));
|
||||
const onReject = () => dispatch(rejectFollowRequest(id));
|
||||
|
|
|
@ -18,7 +18,7 @@ const BioStep = ({ onNext }: { onNext: () => void }) => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const account = useOwnAccount();
|
||||
const [value, setValue] = React.useState<string>(account?.source.get('note') || '');
|
||||
const [value, setValue] = React.useState<string>(account?.source?.note || '');
|
||||
const [isSubmitting, setSubmitting] = React.useState<boolean>(false);
|
||||
const [errors, setErrors] = React.useState<string[]>([]);
|
||||
|
||||
|
|
|
@ -53,14 +53,14 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
|
|||
collapsable
|
||||
/>
|
||||
|
||||
{status.media_attachments.size > 0 && (
|
||||
{status.media_attachments.length > 0 && (
|
||||
<AttachmentThumbs
|
||||
media={status.media_attachments}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
)}
|
||||
|
||||
{status.poll && <PollPreview pollId={status.poll as string} />}
|
||||
{status.poll && <PollPreview poll={status.poll} />}
|
||||
|
||||
<ScheduledStatusActionBar status={status} {...other} />
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@ const MessagesSettings = () => {
|
|||
label={intl.formatMessage(messages.label)}
|
||||
>
|
||||
<Toggle
|
||||
checked={account.accepts_chat_messages}
|
||||
checked={account.pleroma.accepts_chat_messages}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</ListItem>
|
||||
|
|
|
@ -95,14 +95,14 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
let quote;
|
||||
|
||||
if (actualStatus.quote) {
|
||||
if (actualStatus.pleroma.get('quote_visible', true) === false) {
|
||||
if (actualStatus.pleroma?.quote_visible === false) {
|
||||
quote = (
|
||||
<div className='quoted-actualStatus-tombstone'>
|
||||
<p><FormattedMessage id='actualStatuses.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
quote = <QuotedStatus statusId={actualStatus.quote as string} />;
|
||||
quote = <QuotedStatus status={actualStatus.quote} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
|
||||
<TranslateButton status={actualStatus} />
|
||||
|
||||
{(quote || actualStatus.card || actualStatus.media_attachments.size > 0) && (
|
||||
{(quote || actualStatus.card || actualStatus.media_attachments.length > 0) && (
|
||||
<Stack space={4}>
|
||||
<StatusMedia
|
||||
status={actualStatus}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
@ -62,10 +61,10 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
|
||||
const getNormalizedReacts = () => {
|
||||
return reduceEmoji(
|
||||
ImmutableList(status.pleroma.get('emoji_reactions') as any),
|
||||
status.pleroma?.emoji_reactions || [],
|
||||
status.favourites_count,
|
||||
status.favourited,
|
||||
allowedEmoji,
|
||||
allowedEmoji.toArray(),
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -95,7 +94,7 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
const navigateToQuotes: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status.id}/quotes`);
|
||||
history.push(`/@${status.account.acct}/posts/${status.id}/quotes`);
|
||||
};
|
||||
|
||||
const getQuotes = () => {
|
||||
|
@ -173,20 +172,20 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
const getEmojiReacts = () => {
|
||||
const emojiReacts = getNormalizedReacts();
|
||||
const count = emojiReacts.reduce((acc, cur) => (
|
||||
acc + cur.get('count')
|
||||
acc + (cur.count || 0)
|
||||
), 0);
|
||||
|
||||
if (count) {
|
||||
return (
|
||||
<InteractionCounter count={count} onClick={features.exposableReactions ? handleOpenReactionsModal : undefined}>
|
||||
<HStack space={0.5} alignItems='center'>
|
||||
{emojiReacts.take(3).map((e, i) => {
|
||||
{emojiReacts.slice(0, 3).map(({ name, url }, i) => {
|
||||
return (
|
||||
<Emoji
|
||||
key={i}
|
||||
className='h-4.5 w-4.5 flex-none'
|
||||
emoji={e.get('name')}
|
||||
src={e.get('url')}
|
||||
emoji={name}
|
||||
src={url}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -74,7 +74,7 @@ const CompareHistoryModal: React.FC<ICompareHistoryModal> = ({ onClose, statusId
|
|||
</div>
|
||||
)}
|
||||
|
||||
{version.media_attachments.size > 0 && (
|
||||
{version.media_attachments.length > 0 && (
|
||||
<AttachmentThumbs media={version.media_attachments} />
|
||||
)}
|
||||
|
||||
|
|
|
@ -31,12 +31,12 @@ const EventMapModal: React.FC<IEventMapModal> = ({ onClose, statusId }) => {
|
|||
const map = useRef<L.Map>();
|
||||
|
||||
useEffect(() => {
|
||||
const latlng: [number, number] = [+location.get('latitude'), +location.get('longitude')];
|
||||
const latlng: [number, number] = [+location.latitude, +location.longitude];
|
||||
|
||||
map.current = L.map('event-map').setView(latlng, 15);
|
||||
|
||||
L.marker(latlng, {
|
||||
title: location.get('name'),
|
||||
title: location.name,
|
||||
}).addTo(map.current);
|
||||
|
||||
L.tileLayer(tileServer, {
|
||||
|
@ -53,7 +53,7 @@ const EventMapModal: React.FC<IEventMapModal> = ({ onClose, statusId }) => {
|
|||
};
|
||||
|
||||
const onClickNavigate = () => {
|
||||
window.open(`https://www.openstreetmap.org/directions?from=&to=${location.get('latitude')},${location.get('longitude')}#map=14/${location.get('latitude')}/${location.get('longitude')}`, '_blank');
|
||||
window.open(`https://www.openstreetmap.org/directions?from=&to=${location.latitude},${location.longitude}#map=14/${location.latitude}/${location.longitude}`, '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -14,7 +14,7 @@ import { useAppDispatch } from 'soapbox/hooks';
|
|||
import ImageLoader from '../image-loader';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { Attachment, Status } from 'soapbox/types/entities';
|
||||
import type { Attachment, Status } from 'soapbox/schemas';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -161,9 +161,6 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
const isMultiMedia = media.map((image) => image.type !== 'image').toArray();
|
||||
|
||||
const content = media.map((attachment, i) => {
|
||||
const width = (attachment.meta.getIn(['original', 'width']) || undefined) as number | undefined;
|
||||
const height = (attachment.meta.getIn(['original', 'height']) || undefined) as number | undefined;
|
||||
|
||||
const link = (status && (
|
||||
<a href={status.url} onClick={handleStatusClick}>
|
||||
<FormattedMessage id='lightbox.view_context' defaultMessage='View context' />
|
||||
|
@ -175,8 +172,8 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
<ImageLoader
|
||||
previewSrc={attachment.preview_url}
|
||||
src={attachment.url}
|
||||
width={width}
|
||||
height={height}
|
||||
width={attachment.meta?.original?.width}
|
||||
height={attachment.meta?.original?.height}
|
||||
alt={attachment.description}
|
||||
key={attachment.url}
|
||||
onClick={toggleNavigation}
|
||||
|
@ -188,8 +185,8 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
preview={attachment.preview_url}
|
||||
blurhash={attachment.blurhash}
|
||||
src={attachment.url}
|
||||
width={width}
|
||||
height={height}
|
||||
width={attachment.meta?.original?.width}
|
||||
height={attachment.meta?.original?.height}
|
||||
startTime={time}
|
||||
detailed
|
||||
autoFocus={i === getIndex()}
|
||||
|
@ -204,11 +201,11 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
<Audio
|
||||
src={attachment.url}
|
||||
alt={attachment.description}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : (status?.getIn(['account', 'avatar_static'])) as string | undefined}
|
||||
backgroundColor={attachment.meta.getIn(['colors', 'background']) as string | undefined}
|
||||
foregroundColor={attachment.meta.getIn(['colors', 'foreground']) as string | undefined}
|
||||
accentColor={attachment.meta.getIn(['colors', 'accent']) as string | undefined}
|
||||
duration={attachment.meta.getIn(['original', 'duration'], 0) as number | undefined}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : status?.account.avatar_static}
|
||||
backgroundColor={attachment.meta?.colors?.background}
|
||||
foregroundColor={attachment.meta?.colors?.foreground}
|
||||
accentColor={attachment.meta?.colors?.accent}
|
||||
duration={attachment.meta?.duration || 0}
|
||||
key={attachment.url}
|
||||
/>
|
||||
);
|
||||
|
@ -218,8 +215,8 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
src={attachment.url}
|
||||
muted
|
||||
controls={false}
|
||||
width={width}
|
||||
height={height}
|
||||
width={attachment.meta?.original?.width}
|
||||
height={attachment.meta?.original?.height}
|
||||
key={attachment.preview_url}
|
||||
alt={attachment.description}
|
||||
onClick={toggleNavigation}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { makeGetAccount } from 'soapbox/selectors';
|
|||
|
||||
import ThemeToggle from './theme-toggle';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
import type { Account as AccountEntity } from 'soapbox/schemas';
|
||||
|
||||
const messages = defineMessages({
|
||||
add: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' },
|
||||
|
@ -71,7 +71,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
|||
|
||||
menu.push({ text: renderAccount(account), to: `/@${account.acct}` });
|
||||
|
||||
otherAccounts.forEach((otherAccount: AccountEntity) => {
|
||||
otherAccounts.forEach((otherAccount) => {
|
||||
if (otherAccount && otherAccount.id !== account.id) {
|
||||
menu.push({
|
||||
text: renderAccount(otherAccount),
|
||||
|
|
|
@ -13,8 +13,8 @@ const WaitlistPage = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const me = useOwnAccount();
|
||||
const isSmsVerified = me?.source.get('sms_verified');
|
||||
const account = useOwnAccount();
|
||||
const isSmsVerified = account?.source?.sms_verified;
|
||||
|
||||
const onClickLogOut: React.MouseEventHandler = (event) => {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1,21 +1,11 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { useAccount } from 'soapbox/api/hooks';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
|
||||
/** Get the logged-in account from the store, if any. */
|
||||
export const useOwnAccount = (): Account | null => {
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
|
||||
return useAppSelector((state) => {
|
||||
const { me } = state;
|
||||
|
||||
if (typeof me === 'string') {
|
||||
return getAccount(state, me);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
export const useOwnAccount = (): Account | undefined => {
|
||||
const accountId = useAppSelector((state) => state.me || '');
|
||||
const { account } = useAccount(accountId);
|
||||
return account;
|
||||
};
|
||||
|
|
|
@ -41,7 +41,7 @@ const useUpdateCredentials = () => {
|
|||
|
||||
return useMutation((data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', data), {
|
||||
onMutate(variables) {
|
||||
const cachedAccount = account?.toJS();
|
||||
const cachedAccount = account;
|
||||
dispatch(patchMeSuccess({ ...cachedAccount, ...variables }));
|
||||
|
||||
return { cachedAccount };
|
||||
|
|
|
@ -273,7 +273,7 @@ const deleteToken = (state: State, token: string) => {
|
|||
};
|
||||
|
||||
const deleteUser = (state: State, account: AccountEntity) => {
|
||||
const accountUrl = account.get('url');
|
||||
const accountUrl = account.url;
|
||||
|
||||
return state.withMutations(state => {
|
||||
state.update('users', users => users.delete(accountUrl));
|
||||
|
|
|
@ -128,7 +128,7 @@ export const statusToMentionsArray = (status: ImmutableMap<string, any>, account
|
|||
|
||||
return ImmutableOrderedSet([author])
|
||||
.concat(mentions)
|
||||
.delete(account.get('acct')) as ImmutableOrderedSet<string>;
|
||||
.delete(account.acct) as ImmutableOrderedSet<string>;
|
||||
};
|
||||
|
||||
export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: AccountEntity) => {
|
||||
|
|
|
@ -51,21 +51,33 @@ const baseAccountSchema = z.object({
|
|||
}).optional().catch(undefined),
|
||||
pleroma: z.object({
|
||||
accepts_chat_messages: z.boolean().catch(false),
|
||||
accepts_email_list: z.boolean().catch(false),
|
||||
birthday: birthdaySchema.nullish().catch(undefined),
|
||||
deactivated: z.boolean().catch(false),
|
||||
favicon: z.string().url().optional().catch(undefined),
|
||||
hide_favorites: z.boolean().catch(false),
|
||||
hide_followers: z.boolean().catch(false),
|
||||
hide_followers_count: z.boolean().catch(false),
|
||||
hide_follows: z.boolean().catch(false),
|
||||
hide_follows_count: z.boolean().catch(false),
|
||||
is_admin: z.boolean().catch(false),
|
||||
is_moderator: z.boolean().catch(false),
|
||||
is_suggested: z.boolean().catch(false),
|
||||
favicon: z.string().url().optional().catch(undefined),
|
||||
location: z.string().optional().catch(undefined),
|
||||
notification_settings: z.object({
|
||||
block_from_strangers: z.boolean().catch(false),
|
||||
}).optional().catch(undefined),
|
||||
tags: z.array(z.string()).catch([]),
|
||||
}).optional().catch(undefined),
|
||||
source: z.object({
|
||||
approved: z.boolean().catch(true),
|
||||
chats_onboarded: z.boolean().catch(true),
|
||||
fields: filteredArray(fieldSchema),
|
||||
note: z.string().catch(''),
|
||||
pleroma: z.object({
|
||||
discoverable: z.boolean().catch(true),
|
||||
}).optional().catch(undefined),
|
||||
sms_verified: z.boolean().catch(false),
|
||||
}).optional().catch(undefined),
|
||||
statuses_count: z.number().catch(0),
|
||||
suspended: z.boolean().catch(false),
|
||||
|
@ -79,6 +91,14 @@ const baseAccountSchema = z.object({
|
|||
type BaseAccount = z.infer<typeof baseAccountSchema>;
|
||||
type TransformableAccount = Omit<BaseAccount, 'moved'>;
|
||||
|
||||
const getDomain = (url: string) => {
|
||||
try {
|
||||
return new URL(url).host;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/** Add internal fields to the account. */
|
||||
const transformAccount = <T extends TransformableAccount>({ pleroma, other_settings, fields, ...account }: T) => {
|
||||
const customEmojiMap = makeCustomEmojiMap(account.emojis);
|
||||
|
@ -90,6 +110,12 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
|
|||
value_plain: unescapeHTML(field.value),
|
||||
}));
|
||||
|
||||
const domain = getDomain(account.url || account.uri);
|
||||
|
||||
if (pleroma) {
|
||||
pleroma.birthday = pleroma.birthday || other_settings?.birthday;
|
||||
}
|
||||
|
||||
return {
|
||||
...account,
|
||||
admin: pleroma?.is_admin || false,
|
||||
|
@ -97,19 +123,15 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
|
|||
discoverable: account.discoverable || account.source?.pleroma?.discoverable || false,
|
||||
display_name: account.display_name.trim().length === 0 ? account.username : account.display_name,
|
||||
display_name_html: emojify(escapeTextContentForBrowser(account.display_name), customEmojiMap),
|
||||
domain,
|
||||
fields: newFields,
|
||||
fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${new URL(account.url).host}`),
|
||||
fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`),
|
||||
header_static: account.header_static || account.header,
|
||||
moderator: pleroma?.is_moderator || false,
|
||||
location: account.location || pleroma?.location || other_settings?.location || '',
|
||||
note_emojified: emojify(account.note, customEmojiMap),
|
||||
pleroma: {
|
||||
accepts_chat_messages: pleroma?.accepts_chat_messages || false,
|
||||
birthday: pleroma?.birthday || other_settings?.birthday,
|
||||
hide_favorites: pleroma?.hide_favorites || false,
|
||||
is_suggested: pleroma?.is_suggested || false,
|
||||
tags: pleroma?.tags || [],
|
||||
},
|
||||
pleroma,
|
||||
staff: pleroma?.is_admin || pleroma?.is_moderator || false,
|
||||
suspended: account.suspended || pleroma?.deactivated || false,
|
||||
verified: account.verified || pleroma?.tags.includes('verified') || false,
|
||||
};
|
||||
|
|
|
@ -12,6 +12,9 @@ const locationSchema = z.object({
|
|||
origin_provider: z.string().catch(''),
|
||||
type: z.string().catch(''),
|
||||
timezone: z.string().catch(''),
|
||||
name: z.string().catch(''),
|
||||
latitude: z.number().catch(0),
|
||||
longitude: z.number().catch(0),
|
||||
geom: z.object({
|
||||
coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null),
|
||||
srid: z.string().catch(''),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
AdminAccountRecord,
|
||||
AdminReportRecord,
|
||||
AccountRecord,
|
||||
AnnouncementRecord,
|
||||
AnnouncementReactionRecord,
|
||||
AttachmentRecord,
|
||||
|
@ -19,7 +18,6 @@ import {
|
|||
MentionRecord,
|
||||
NotificationRecord,
|
||||
StatusEditRecord,
|
||||
StatusRecord,
|
||||
TagRecord,
|
||||
} from 'soapbox/normalizers';
|
||||
import { LogEntryRecord } from 'soapbox/reducers/admin-log';
|
||||
|
@ -48,18 +46,6 @@ type Notification = ReturnType<typeof NotificationRecord>;
|
|||
type StatusEdit = ReturnType<typeof StatusEditRecord>;
|
||||
type Tag = ReturnType<typeof TagRecord>;
|
||||
|
||||
interface Account extends ReturnType<typeof AccountRecord> {
|
||||
// HACK: we can't do a circular reference in the Record definition itself,
|
||||
// so do it here.
|
||||
moved: EmbeddedEntity<Account>
|
||||
}
|
||||
|
||||
interface Status extends ReturnType<typeof StatusRecord> {
|
||||
// HACK: same as above
|
||||
quote: EmbeddedEntity<Status>
|
||||
reblog: EmbeddedEntity<Status>
|
||||
}
|
||||
|
||||
// Utility types
|
||||
type APIEntity = Record<string, any>;
|
||||
type EmbeddedEntity<T extends object> = null | string | ReturnType<ImmutableRecord.Factory<T>>;
|
||||
|
@ -68,7 +54,6 @@ export {
|
|||
AdminAccount,
|
||||
AdminLog,
|
||||
AdminReport,
|
||||
Account,
|
||||
Announcement,
|
||||
AnnouncementReaction,
|
||||
Attachment,
|
||||
|
@ -85,7 +70,6 @@ export {
|
|||
Location,
|
||||
Mention,
|
||||
Notification,
|
||||
Status,
|
||||
StatusEdit,
|
||||
Tag,
|
||||
|
||||
|
@ -95,6 +79,7 @@ export {
|
|||
};
|
||||
|
||||
export type {
|
||||
Account,
|
||||
Card,
|
||||
EmojiReaction,
|
||||
Group,
|
||||
|
@ -103,4 +88,5 @@ export type {
|
|||
Poll,
|
||||
PollOption,
|
||||
Relationship,
|
||||
Status,
|
||||
} from 'soapbox/schemas';
|
|
@ -40,7 +40,7 @@ const filterBadges = (tags: string[]): Badge[] => {
|
|||
|
||||
/** Get badges from an account. */
|
||||
const getBadges = (account: Account): Badge[] => {
|
||||
return filterBadges(account.pleroma.tags);
|
||||
return filterBadges(account.pleroma?.tags ?? []);
|
||||
};
|
||||
|
||||
export {
|
||||
|
|
Ładowanie…
Reference in New Issue