Start replacing legacy statuses code

zod-statuses
Alex Gleason 2023-06-26 17:40:36 -05:00
rodzic 4c99889812
commit a3c1404eef
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
24 zmienionych plików z 146 dodań i 132 usunięć

Wyświetl plik

@ -22,9 +22,9 @@ import { createStatus } from './statuses';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
import type { Emoji } from 'soapbox/features/emoji';
import type { Account, Group } from 'soapbox/schemas';
import type { Account, Group, Status } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status, Tag } from 'soapbox/types/entities';
import type { APIEntity, Tag } from 'soapbox/types/entities';
import type { History } from 'soapbox/types/history';
const { CancelToken, isCancel } = axios;

Wyświetl plik

@ -10,8 +10,9 @@ import { importFetchedAccounts, importFetchedStatus } from './importer';
import { expandGroupFeaturedTimeline } from './timelines';
import type { AxiosError } from 'axios';
import type { Status as StatusEntity } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Group, Status as StatusEntity } from 'soapbox/types/entities';
import type { APIEntity, Group } from 'soapbox/types/entities';
const REBLOG_REQUEST = 'REBLOG_REQUEST';
const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
@ -85,7 +86,7 @@ const reblog = (status: StatusEntity) =>
dispatch(reblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function(response) {
api(getState).post(`/api/v1/statuses/${status.id}/reblog`).then(function(response) {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog));
@ -101,7 +102,7 @@ const unreblog = (status: StatusEntity) =>
dispatch(unreblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(() => {
api(getState).post(`/api/v1/statuses/${status.id}/unreblog`).then(() => {
dispatch(unreblogSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
@ -234,7 +235,7 @@ const dislike = (status: StatusEntity) =>
dispatch(dislikeRequest(status));
api(getState).post(`/api/friendica/statuses/${status.get('id')}/dislike`).then(function() {
api(getState).post(`/api/friendica/statuses/${status.id}/dislike`).then(function() {
dispatch(dislikeSuccess(status));
}).catch(function(error) {
dispatch(dislikeFail(status, error));
@ -247,7 +248,7 @@ const undislike = (status: StatusEntity) =>
dispatch(undislikeRequest(status));
api(getState).post(`/api/friendica/statuses/${status.get('id')}/undislike`).then(() => {
api(getState).post(`/api/friendica/statuses/${status.id}/undislike`).then(() => {
dispatch(undislikeSuccess(status));
}).catch(error => {
dispatch(undislikeFail(status, error));
@ -305,7 +306,7 @@ const bookmark = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(bookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function(response) {
api(getState).post(`/api/v1/statuses/${status.id}/bookmark`).then(function(response) {
dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status, response.data));
toast.success(messages.bookmarkAdded, {
@ -321,7 +322,7 @@ const unbookmark = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(unbookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
api(getState).post(`/api/v1/statuses/${status.id}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status, response.data));
toast.success(messages.bookmarkRemoved);
@ -504,7 +505,7 @@ const pin = (status: StatusEntity) =>
dispatch(pinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
api(getState).post(`/api/v1/statuses/${status.id}/pin`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(pinSuccess(status));
}).catch(error => {
@ -515,14 +516,14 @@ const pin = (status: StatusEntity) =>
const pinToGroup = (status: StatusEntity, group: Group) =>
(dispatch: AppDispatch, getState: () => RootState) => {
return api(getState)
.post(`/api/v1/groups/${group.id}/statuses/${status.get('id')}/pin`)
.post(`/api/v1/groups/${group.id}/statuses/${status.id}/pin`)
.then(() => dispatch(expandGroupFeaturedTimeline(group.id)));
};
const unpinFromGroup = (status: StatusEntity, group: Group) =>
(dispatch: AppDispatch, getState: () => RootState) => {
return api(getState)
.post(`/api/v1/groups/${group.id}/statuses/${status.get('id')}/unpin`)
.post(`/api/v1/groups/${group.id}/statuses/${status.id}/unpin`)
.then(() => dispatch(expandGroupFeaturedTimeline(group.id)));
};
@ -551,7 +552,7 @@ const unpin = (status: StatusEntity) =>
dispatch(unpinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
api(getState).post(`/api/v1/statuses/${status.id}/unpin`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unpinSuccess(status));
}).catch(error => {

Wyświetl plik

@ -3,9 +3,8 @@ import api from '../api';
import { openModal } from './modals';
import type { AxiosError } from 'axios';
import type { Account } from 'soapbox/schemas';
import type { Account, ChatMessage, Group, Status } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { ChatMessage, Group, Status } from 'soapbox/types/entities';
const REPORT_INIT = 'REPORT_INIT';
const REPORT_CANCEL = 'REPORT_CANCEL';

Wyświetl plik

@ -10,8 +10,9 @@ import { importFetchedStatus, importFetchedStatuses } from './importer';
import { openModal } from './modals';
import { deleteFromTimelines } from './timelines';
import type { Status } from 'soapbox/schemas';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status } from 'soapbox/types/entities';
import type { APIEntity } from 'soapbox/types/entities';
const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS';

Wyświetl plik

@ -12,6 +12,9 @@ export { useFollow } from './accounts/useFollow';
export { useRelationships } from './accounts/useRelationships';
export { usePatronUser } from './accounts/usePatronUser';
// Statuses
export { useStatus } from './statuses/useStatus';
// Groups
export { useBlockGroupMember } from './groups/useBlockGroupMember';
export { useCancelMembershipRequest } from './groups/useCancelMembershipRequest';

Wyświetl plik

@ -0,0 +1,18 @@
import { Entities } from 'soapbox/entity-store/entities';
import { useEntity } from 'soapbox/entity-store/hooks';
import { useApi } from 'soapbox/hooks/useApi';
import { type Status, statusSchema } from 'soapbox/schemas';
function useStatus(statusId: string | undefined) {
const api = useApi();
const { entity: status, ...rest } = useEntity<Status>(
[Entities.STATUSES, statusId!],
() => api.get(`/api/v1/statuses/${statusId}`),
{ schema: statusSchema, enabled: !!statusId },
);
return { status, ...rest };
}
export { useStatus };

Wyświetl plik

@ -6,10 +6,10 @@ import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch } from 'soapbox/hooks';
import type { List as ImmutableList } from 'immutable';
import type { Attachment } from 'soapbox/types/entities';
import type { Attachment } from 'soapbox/schemas';
interface IAttachmentThumbs {
media: ImmutableList<Attachment>
media: Attachment[]
onClick?(): void
sensitive?: boolean
}

Wyświetl plik

@ -13,7 +13,7 @@ import { IconButton, Portal } from '../ui';
import DropdownMenuItem, { MenuItem } from './dropdown-menu-item';
import type { Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/schemas';
export type Menu = Array<MenuItem | null>;

Wyświetl plik

@ -10,7 +10,7 @@ import Icon from './icon';
import { Button, HStack, Stack, Text } from './ui';
import VerificationBadge from './verification-badge';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
import type { Status, Event } from 'soapbox/schemas';
const messages = defineMessages({
eventBanner: { id: 'event.banner', defaultMessage: 'Event banner' },
@ -19,7 +19,7 @@ const messages = defineMessages({
});
interface IEventPreview {
status: StatusEntity
status: Status & { event: Event }
className?: string
hideAction?: boolean
floatingAction?: boolean
@ -30,7 +30,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
const me = useAppSelector((state) => state.me);
const account = status.account as AccountEntity;
const account = status.account;
const event = status.event!;
const banner = event.banner;
@ -74,13 +74,13 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
</HStack>
</HStack>
<EventDate status={status} />
<EventDate event={status.event} />
{event.location && (
<HStack alignItems='center' space={2}>
<Icon src={require('@tabler/icons/map-pin.svg')} />
<span>
{event.location.get('name')}
{event.location.name}
</span>
</HStack>
)}

Wyświetl plik

@ -1,4 +1,4 @@
import { List as ImmutableList } from 'immutable';
import { fromJS, List as ImmutableList } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
@ -30,7 +30,7 @@ import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts';
import GroupPopover from './groups/popover/group-popover';
import type { Menu } from 'soapbox/components/dropdown-menu';
import type { Account, Group, Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/schemas';
const messages = defineMessages({
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
@ -120,7 +120,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const features = useFeatures();
const settings = useSettings();
const soapboxConfig = useSoapboxConfig();
const deleteGroupStatus = useDeleteGroupStatus(status?.group as Group, status.id);
const deleteGroupStatus = useDeleteGroupStatus(status?.group!, status.id);
const { allowedEmoji } = soapboxConfig;
@ -237,7 +237,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleGroupPinClick: React.EventHandler<React.MouseEvent> = () => {
const group = status.group as Group;
const group = status.group!;
if (status.pinned) {
dispatch(unpinFromGroup(status, group));
@ -249,24 +249,24 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(mentionCompose(status.account as Account));
dispatch(mentionCompose(status.account));
};
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(directCompose(status.account as Account));
dispatch(directCompose(status.account));
};
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
const account = status.account as Account;
const account = status.account;
dispatch(launchChat(account.id, history));
};
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(initMuteModal(status.account as Account));
dispatch(initMuteModal(status.account));
};
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
const account = status.get('account') as Account;
const account = status.account;
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/ban.svg'),
@ -288,13 +288,13 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const handleEmbed = () => {
dispatch(openModal('EMBED', {
url: status.get('url'),
url: status.url,
onError: (error: any) => toast.showAlertForError(error),
}));
};
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(initReport(ReportableEntities.STATUS, status.account as Account, { status }));
dispatch(initReport(ReportableEntities.STATUS, status.account, { status }));
};
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
@ -308,7 +308,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const onModerate: React.MouseEventHandler = (e) => {
const account = status.account as Account;
const account = status.account;
dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id }));
};
@ -321,7 +321,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleDeleteFromGroup: React.EventHandler<React.MouseEvent> = () => {
const account = status.account as Account;
const account = status.account;
dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.deleteHeading),
@ -490,8 +490,8 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
}
if (isGroupStatus && !!status.group) {
const group = status.group as Group;
const account = status.account as Account;
const group = status.group;
const account = status.account;
const isGroupOwner = groupRelationship?.role === GroupRoles.OWNER;
const isGroupAdmin = groupRelationship?.role === GroupRoles.ADMIN;
const isStatusFromOwner = group.owner.id === account.id;
@ -551,7 +551,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const favouriteCount = status.favourites_count;
const emojiReactCount = reduceEmoji(
(status.pleroma.get('emoji_reactions') || ImmutableList()) as ImmutableList<any>,
fromJS(status.pleroma?.emoji_reactions ?? []) as ImmutableList<any>,
favouriteCount,
status.favourited,
allowedEmoji,
@ -583,7 +583,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
reblogIcon = require('@tabler/icons/lock.svg');
}
if ((status.group as Group)?.membership_required && !groupRelationship?.member) {
if (status.group?.membership_required && !groupRelationship?.member) {
replyDisabled = true;
replyTitle = intl.formatMessage(messages.replies_disabled_group);
}

Wyświetl plik

@ -12,7 +12,7 @@ import Markup from './markup';
import Poll from './polls/poll';
import type { Sizes } from 'soapbox/components/ui/text/text';
import type { Status, Mention } from 'soapbox/types/entities';
import type { Status, Mention } from 'soapbox/schemas';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
const BIG_EMOJI_LIMIT = 10;
@ -130,7 +130,7 @@ const StatusContent: React.FC<IStatusContent> = ({
});
const parsedHtml = useMemo((): string => {
return translatable && status.translation ? status.translation.get('content')! : status.contentHtml;
return translatable && status.translation ? status.translation.content : status.contentHtml;
}, [status.contentHtml, status.translation]);
if (status.content.length === 0) {
@ -168,12 +168,11 @@ const StatusContent: React.FC<IStatusContent> = ({
output.push(<ReadMoreButton onClick={onClick} key='read-more' />);
}
const hasPoll = status.poll && typeof status.poll === 'string';
if (hasPoll) {
output.push(<Poll id={status.poll} key='poll' status={status.url} />);
if (status.poll) {
output.push(<Poll id={status.poll.id} key='poll' status={status.url} />);
}
return <div className={clsx({ 'bg-gray-100 dark:bg-primary-800 rounded-md p-4': hasPoll })}>{output}</div>;
return <div className={clsx({ 'bg-gray-100 dark:bg-primary-800 rounded-md p-4': status.poll })}>{output}</div>;
} else {
const output = [
<Markup

Wyświetl plik

@ -10,13 +10,12 @@ import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-compo
import { useAppDispatch, useSettings } from 'soapbox/hooks';
import { addAutoPlay } from 'soapbox/utils/media';
import type { List as ImmutableList } from 'immutable';
import type VideoType from 'soapbox/features/video';
import type { Status, Attachment } from 'soapbox/types/entities';
import type { Status, Attachment } from 'soapbox/schemas';
interface IStatusMedia {
/** Status entity to render media for. */
status: Status
status: Pick<Status, 'media_attachments' | 'card' | 'sensitive' | 'account' | 'spoiler_text' | 'quote' | 'expectsCard'>
/** Whether to display compact media. */
muted?: boolean
/** Callback when compact media is clicked. */
@ -41,8 +40,8 @@ const StatusMedia: React.FC<IStatusMedia> = ({
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
const size = status.media_attachments.size;
const firstAttachment = status.media_attachments.first();
const size = status.media_attachments.length;
const [firstAttachment] = status.media_attachments;
let media: JSX.Element | null = null;
@ -64,7 +63,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
return <div className='media-spoiler-audio' style={{ height: '285px' }} />;
};
const openMedia = (media: ImmutableList<Attachment>, index: number) => {
const openMedia = (media: Attachment[], index: number) => {
dispatch(openModal('MEDIA', { media, status, index }));
};
@ -82,8 +81,8 @@ const StatusMedia: React.FC<IStatusMedia> = ({
if (video.external_video_id && status.card) {
const getHeight = (): number => {
const width = Number(video.meta.getIn(['original', 'width']));
const height = Number(video.meta.getIn(['original', 'height']));
const width = Number(video.meta.original?.width);
const height = Number(video.meta.original?.height);
return Number(mediaWrapperWidth) / (width / height);
};
@ -110,7 +109,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
blurhash={video.blurhash}
src={video.url}
alt={video.description}
aspectRatio={Number(video.meta.getIn(['original', 'aspect']))}
aspectRatio={video.meta.original?.aspect}
height={285}
visible={showMedia}
inline
@ -128,11 +127,11 @@ const StatusMedia: React.FC<IStatusMedia> = ({
<Component
src={attachment.url}
alt={attachment.description}
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.meta.getIn(['colors', 'background'])}
foregroundColor={attachment.meta.getIn(['colors', 'foreground'])}
accentColor={attachment.meta.getIn(['colors', 'accent'])}
duration={attachment.meta.getIn(['original', 'duration'], 0)}
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}
height={263}
/>
)}

Wyświetl plik

@ -8,21 +8,20 @@ import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper';
import { useAppDispatch } from 'soapbox/hooks';
import { isPubkey } from 'soapbox/utils/nostr';
import type { Account, Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/schemas';
interface IStatusReplyMentions {
status: Status
status: Pick<Status, 'id' | 'account' | 'mentions' | 'in_reply_to_id'>
hoverable?: boolean
}
const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable = true }) => {
const dispatch = useAppDispatch();
const { account, mentions } = status;
const handleOpenMentionsModal: React.MouseEventHandler<HTMLSpanElement> = (e) => {
e.stopPropagation();
const account = status.account as Account;
dispatch(openModal('MENTIONS', {
username: account.acct,
statusId: status.id,
@ -33,11 +32,9 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
return null;
}
const to = status.mentions;
// The post is a reply, but it has no mentions.
// Rare, but it can happen.
if (to.size === 0) {
if (mentions.length === 0) {
return (
<div className='reply-mentions'>
<FormattedMessage
@ -49,15 +46,15 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
}
// The typical case with a reply-to and a list of mentions.
const accounts = to.slice(0, 2).map(account => {
const accounts = mentions.slice(0, 2).map((mention) => {
const link = (
<Link
key={account.id}
to={`/@${account.acct}`}
key={mention.id}
to={`/@${mention.acct}`}
className='reply-mentions__account max-w-[200px] truncate align-bottom'
onClick={(e) => e.stopPropagation()}
>
@{isPubkey(account.username) ? account.username.slice(0, 8) : account.username}
@{isPubkey(mention.username) ? mention.username.slice(0, 8) : mention.username}
</Link>
);
@ -70,12 +67,12 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable
} else {
return link;
}
}).toArray();
});
if (to.size > 2) {
if (mentions.length > 2) {
accounts.push(
<span key='more' className='cursor-pointer hover:underline' role='button' onClick={handleOpenMentionsModal} tabIndex={0}>
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: to.size - 2 }} />
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: mentions.length - 2 }} />
</span>,
);
}

Wyświetl plik

@ -26,8 +26,9 @@ import { Card, Icon, Stack, Text } from './ui';
import type {
Account as AccountEntity,
Event,
Status as StatusEntity,
} from 'soapbox/types/entities';
} from 'soapbox/schemas';
// Defined in components/scrollable-list
export type ScrollPosition = { height: number, top: number };
@ -92,7 +93,7 @@ const Status: React.FC<IStatus> = (props) => {
const statusUrl = `/@${actualStatus.account.acct}/posts/${actualStatus.id}`;
const group = actualStatus.group;
const filtered = (status.filtered.size || actualStatus.filtered.size) > 0;
const filtered = (status.filtered.length || actualStatus.filtered.length) > 0;
// Track height changes we know about to compensate scrolling.
useEffect(() => {
@ -134,7 +135,7 @@ const Status: React.FC<IStatus> = (props) => {
const handleHotkeyOpenMedia = (e?: KeyboardEvent): void => {
const status = actualStatus;
const firstAttachment = status.media_attachments.first();
const [firstAttachment] = status.media_attachments;
e?.preventDefault();
@ -203,7 +204,7 @@ const Status: React.FC<IStatus> = (props) => {
_expandEmojiSelector();
};
const handleUnfilter = () => dispatch(unfilterStatus(status.filtered.size ? status.id : actualStatus.id));
const handleUnfilter = () => dispatch(unfilterStatus(status.filtered.length ? status.id : actualStatus.id));
const _expandEmojiSelector = (): void => {
const firstEmoji: HTMLDivElement | null | undefined = node.current?.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
@ -360,14 +361,14 @@ const Status: React.FC<IStatus> = (props) => {
let quote;
if (actualStatus.quote) {
if (actualStatus.pleroma.get('quote_visible', true) === false) {
if ((actualStatus.pleroma?.quote_visible ?? true) === false) {
quote = (
<div className='quoted-status-tombstone'>
<p><FormattedMessage id='statuses.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
</div>
);
} else {
quote = <QuotedStatus statusId={actualStatus.quote as string} />;
quote = <QuotedStatus statusId={actualStatus.quote.id} />;
}
}
@ -453,7 +454,9 @@ const Status: React.FC<IStatus> = (props) => {
/>
)}
{actualStatus.event ? <EventPreview className='shadow-xl' status={actualStatus} /> : (
{actualStatus.event ? (
<EventPreview className='shadow-xl' status={actualStatus as StatusEntity & { event: Event }} />
) : (
<Stack space={4}>
<StatusContent
status={actualStatus}
@ -464,7 +467,7 @@ const Status: React.FC<IStatus> = (props) => {
<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}

Wyświetl plik

@ -10,7 +10,7 @@ import { defaultMediaVisibility } from 'soapbox/utils/status';
import DropdownMenu from '../dropdown-menu';
import { Button, HStack, Text } from '../ui';
import type { Status as StatusEntity } from 'soapbox/types/entities';
import type { Status as StatusEntity } from 'soapbox/schemas';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
@ -27,7 +27,7 @@ const messages = defineMessages({
});
interface ISensitiveContentOverlay {
status: StatusEntity
status: Pick<StatusEntity, 'id' | 'account' | 'reblog' | 'visibility' | 'sensitive' | 'spoiler_text' | 'spoilerHtml'>
onToggleVisibility?(): void
visible?: boolean
}
@ -42,7 +42,7 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
const { links } = useSoapboxConfig();
const isUnderReview = status.visibility === 'self';
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
const isOwnStatus = status.account.id === account?.id;
const displayMedia = settings.get('displayMedia') as string;
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));

Wyświetl plik

@ -8,10 +8,10 @@ import { isLocal } from 'soapbox/utils/accounts';
import { Stack, Button, Text } from './ui';
import type { Account, Status } from 'soapbox/types/entities';
import type { Status } from 'soapbox/schemas';
interface ITranslateButton {
status: Status
status: Pick<Status, 'id' | 'account' | 'language' | 'translation' | 'visibility' | 'contentHtml'>
}
const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
@ -28,7 +28,7 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
const sourceLanguages = instance.pleroma.getIn(['metadata', 'translation', 'source_languages']) as ImmutableList<string>;
const targetLanguages = instance.pleroma.getIn(['metadata', 'translation', 'target_languages']) as ImmutableList<string>;
const renderTranslate = (me || allowUnauthenticated) && (allowRemote || isLocal(status.account as Account)) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language;
const renderTranslate = (me || allowUnauthenticated) && (allowRemote || isLocal(status.account)) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language;
const supportsLanguages = (!sourceLanguages || sourceLanguages.includes(status.language!)) && (!targetLanguages || targetLanguages.includes(intl.locale));
@ -47,7 +47,7 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
if (status.translation) {
const languageNames = new Intl.DisplayNames([intl.locale], { type: 'language' });
const languageName = languageNames.of(status.language!);
const provider = status.translation.get('provider');
const provider = status.translation.provider;
return (
<Stack space={3} alignItems='start'>

Wyświetl plik

@ -1,18 +1,11 @@
import React, { useCallback } from 'react';
import React from 'react';
import { useStatus } from 'soapbox/api/hooks';
import Status, { IStatus } from 'soapbox/components/status';
import { useAppSelector } from 'soapbox/hooks';
import { makeGetStatus } from 'soapbox/selectors';
interface IStatusContainer extends Omit<IStatus, 'status'> {
id: string
contextType?: string
/** @deprecated Unused. */
otherAccounts?: any
/** @deprecated Unused. */
getScrollPosition?: any
/** @deprecated Unused. */
updateScrollBottom?: any
}
/**
@ -20,10 +13,8 @@ interface IStatusContainer extends Omit<IStatus, 'status'> {
* @deprecated Use the Status component directly.
*/
const StatusContainer: React.FC<IStatusContainer> = (props) => {
const { id, contextType, ...rest } = props;
const getStatus = useCallback(makeGetStatus(), []);
const status = useAppSelector(state => getStatus(state, { id, contextType }));
const { id, ...rest } = props;
const { status } = useStatus(id);
if (status) {
return <Status status={status} {...rest} />;

Wyświetl plik

@ -7,7 +7,7 @@ import { Button } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import type { ButtonThemes } from 'soapbox/components/ui/button/useButtonStyles';
import type { Status as StatusEntity } from 'soapbox/types/entities';
import type { Status } from 'soapbox/schemas';
const messages = defineMessages({
leaveConfirm: { id: 'confirmations.leave_event.confirm', defaultMessage: 'Leave event' },
@ -15,7 +15,7 @@ const messages = defineMessages({
});
interface IEventAction {
status: StatusEntity
status: Status
theme?: ButtonThemes
}

Wyświetl plik

@ -4,15 +4,13 @@ import { FormattedDate } from 'react-intl';
import Icon from 'soapbox/components/icon';
import { HStack } from 'soapbox/components/ui';
import type { Status as StatusEntity } from 'soapbox/types/entities';
import type { Event } from 'soapbox/schemas';
interface IEventDate {
status: StatusEntity
event: Event
}
const EventDate: React.FC<IEventDate> = ({ status }) => {
const event = status.event!;
const EventDate: React.FC<IEventDate> = ({ event }) => {
if (!event.start_time) return null;
const startDate = new Date(event.start_time);

Wyświetl plik

@ -1,14 +1,12 @@
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import React, { useState, useEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
import { HStack, Stack, Text } from 'soapbox/components/ui';
import { normalizeAttachment } from 'soapbox/normalizers';
import { addAutoPlay } from 'soapbox/utils/media';
import type { Card as CardEntity, Attachment } from 'soapbox/types/entities';
import type { Card as CardEntity, Attachment } from 'soapbox/schemas';
const trim = (text: string, len: number): string => {
const cut = text.indexOf(' ', len);
@ -24,7 +22,7 @@ interface ICard {
card: CardEntity
maxTitle?: number
maxDescription?: number
onOpenMedia: (attachments: ImmutableList<Attachment>, index: number) => void
onOpenMedia: (attachments: Attachment[], index: number) => void
compact?: boolean
defaultWidth?: number
cacheWidth?: (width: number) => void
@ -52,19 +50,24 @@ const Card: React.FC<ICard> = ({
const trimmedDescription = trim(card.description, maxDescription);
const handlePhotoClick = () => {
const attachment = normalizeAttachment({
const attachment: Attachment = {
id: '',
type: 'image',
url: card.embed_url,
preview_url: card.embed_url,
remote_url: null,
description: trimmedTitle,
blurhash: null,
meta: {
original: {
width: card.width,
height: card.height,
aspect: card.width / card.height,
},
},
});
};
onOpenMedia(ImmutableList([attachment]), 0);
onOpenMedia([attachment], 0);
};
const handleEmbedClick: React.MouseEventHandler = (e) => {

Wyświetl plik

@ -110,7 +110,7 @@ interface IVideo {
inline?: boolean
cacheWidth?: (width: number) => void
visible?: boolean
blurhash?: string
blurhash?: string | null
link?: React.ReactNode
aspectRatio?: number
displayMedia?: string

Wyświetl plik

@ -61,7 +61,6 @@ import sidebar from './sidebar';
import soapbox from './soapbox';
import status_hover_card from './status-hover-card';
import status_lists from './status-lists';
import statuses from './statuses';
import suggestions from './suggestions';
import tags from './tags';
import timelines from './timelines';
@ -71,11 +70,13 @@ import user_lists from './user-lists';
import verification from './verification';
import type { AnyAction, Reducer } from 'redux';
import type { EntityStore } from 'soapbox/entity-store/types';
import type { Account } from 'soapbox/schemas';
import type { Entity, EntityStore } from 'soapbox/entity-store/types';
import type { Account, Status } from 'soapbox/schemas';
type LegacyReducer<T extends Entity> = EntityStore<T> & LegacyStore<T>
const reducers = {
accounts: ((state: any = {}) => state) as (state: any) => EntityStore<Account> & LegacyStore<Account>,
accounts: ((state: any = {}) => state) as (state: any) => LegacyReducer<Account>,
account_notes,
accounts_meta,
admin,
@ -130,7 +131,7 @@ const reducers = {
soapbox,
status_hover_card,
status_lists,
statuses,
statuses: ((state: any = {}) => state) as (state: any) => LegacyReducer<Status>,
suggestions,
tags,
timelines,
@ -182,12 +183,19 @@ const accountsSelector = createSelector(
(accounts) => immutableizeStore<Account, EntityStore<Account>>(accounts),
);
const statusesSelector = createSelector(
(state: InferState<typeof appReducer>) => state.entities[Entities.STATUSES]?.store as EntityStore<Status> || {},
(statuses) => immutableizeStore<Status, EntityStore<Status>>(statuses),
);
const extendedRootReducer = (
state: InferState<typeof appReducer>,
action: AnyAction,
): ReturnType<typeof rootReducer> => {
const extendedState = rootReducer(state, action);
return extendedState.set('accounts', accountsSelector(extendedState));
return extendedState
.set('accounts', accountsSelector(extendedState))
.set('statuses', statusesSelector(extendedState));
};
export default extendedRootReducer as Reducer<ReturnType<typeof extendedRootReducer>>;

Wyświetl plik

@ -4,6 +4,7 @@ export { cardSchema, type Card } from './card';
export { chatMessageSchema, type ChatMessage } from './chat-message';
export { customEmojiSchema, type CustomEmoji } from './custom-emoji';
export { emojiReactionSchema, type EmojiReaction } from './emoji-reaction';
export { eventSchema, type Event } from './event';
export { groupSchema, type Group } from './group';
export { groupMemberSchema, type GroupMember } from './group-member';
export { groupRelationshipSchema, type GroupRelationship } from './group-relationship';

Wyświetl plik

@ -18,7 +18,6 @@ import {
MentionRecord,
NotificationRecord,
StatusEditRecord,
StatusRecord,
TagRecord,
} from 'soapbox/normalizers';
import { LogEntryRecord } from 'soapbox/reducers/admin-log';
@ -51,12 +50,6 @@ type Tag = ReturnType<typeof TagRecord>;
type Account = SchemaAccount & LegacyMap;
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>>;
@ -82,7 +75,6 @@ export {
Location,
Mention,
Notification,
Status,
StatusEdit,
Tag,
@ -100,4 +92,5 @@ export type {
Poll,
PollOption,
Relationship,
Status,
} from 'soapbox/schemas';