kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Start replacing legacy statuses code
rodzic
4c99889812
commit
a3c1404eef
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 };
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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>,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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} />;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
Ładowanie…
Reference in New Issue