Support soft-deleted statuses via tombstones

environments/review-tombstone-tyju5y/deployments/3356
Chewbacca 2023-05-09 15:16:13 -04:00
rodzic e9fee8aad3
commit 4a2b7faa59
8 zmienionych plików z 56 dodań i 18 usunięć

Wyświetl plik

@ -21,6 +21,7 @@ import StatusMedia from './status-media';
import StatusReplyMentions from './status-reply-mentions';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
import StatusInfo from './statuses/status-info';
import Tombstone from './tombstone';
import { Card, Icon, Stack, Text } from './ui';
import type {
@ -388,6 +389,17 @@ const Status: React.FC<IStatus> = (props) => {
const isUnderReview = actualStatus.visibility === 'self';
const isSensitive = actualStatus.hidden;
const isSoftDeleted = status.tombstone?.reason === 'deleted';
if (isSoftDeleted) {
return (
<Tombstone
id={status.id}
onMoveUp={(id) => onMoveUp ? onMoveUp(id) : null}
onMoveDown={(id) => onMoveDown ? onMoveDown(id) : null}
/>
);
}
return (
<HotKeys handlers={handlers} data-testid='status'>

Wyświetl plik

@ -19,10 +19,17 @@ const Tombstone: React.FC<ITombstone> = ({ id, onMoveUp, onMoveDown }) => {
return (
<HotKeys handlers={handlers}>
<div className='focusable flex items-center justify-center border border-solid border-gray-200 bg-gray-100 p-9 dark:border-gray-800 dark:bg-gray-900 sm:rounded-xl' tabIndex={0}>
<Text>
<FormattedMessage id='statuses.tombstone' defaultMessage='One or more posts are unavailable.' />
</Text>
<div className='h-16'>
<div
className='focusable flex h-[42px] items-center justify-center rounded-lg border-2 border-gray-200 text-center'
>
<Text theme='muted'>
<FormattedMessage
id='statuses.tombstone'
defaultMessage='One or more posts are unavailable.'
/>
</Text>
</div>
</div>
</HotKeys>
);

Wyświetl plik

@ -31,9 +31,8 @@ const ThreadStatus: React.FC<IThreadStatus> = (props): JSX.Element => {
return (
<div
className={clsx('thread__connector', {
'thread__connector--top': isConnectedTop,
'thread__connector--bottom': isConnectedBottom,
className={clsx('absolute left-5 z-[1] hidden w-0.5 bg-gray-200 rtl:left-auto rtl:right-5 dark:bg-primary-800', {
'!block top-[calc(12px+42px)] h-[calc(100%-42px-8px-1rem)]': isConnectedBottom,
})}
/>
);

Wyświetl plik

@ -13,7 +13,7 @@ import {
import { normalizeAttachment } from 'soapbox/normalizers/attachment';
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { normalizeMention } from 'soapbox/normalizers/mention';
import { cardSchema, pollSchema } from 'soapbox/schemas';
import { cardSchema, pollSchema, tombstoneSchema } from 'soapbox/schemas';
import type { ReducerAccount } from 'soapbox/reducers/accounts';
import type { Account, Attachment, Card, Emoji, Group, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities';
@ -36,6 +36,10 @@ export const EventRecord = ImmutableRecord({
links: ImmutableList<Attachment>(),
});
interface Tombstone {
reason: 'deleted'
}
// https://docs.joinmastodon.org/entities/status/
export const StatusRecord = ImmutableRecord({
account: null as EmbeddedEntity<Account | ReducerAccount>,
@ -72,6 +76,7 @@ export const StatusRecord = ImmutableRecord({
sensitive: false,
spoiler_text: '',
tags: ImmutableList<ImmutableMap<string, any>>(),
tombstone: null as Tombstone | null,
uri: '',
url: '',
visibility: 'public' as StatusVisibility,
@ -116,6 +121,15 @@ const normalizeStatusPoll = (status: ImmutableMap<string, any>) => {
}
};
const normalizeTombstone = (status: ImmutableMap<string, any>) => {
try {
const tombstone = tombstoneSchema.parse(status.get('tombstone').toJS());
return status.set('tombstone', tombstone);
} catch (_e) {
return status.set('tombstone', null);
}
};
// Normalize card
const normalizeStatusCard = (status: ImmutableMap<string, any>) => {
try {
@ -246,6 +260,7 @@ export const normalizeStatus = (status: Record<string, any>) => {
fixContent(status);
normalizeFilterResults(status);
normalizeDislikes(status);
normalizeTombstone(status);
}),
);
};

Wyświetl plik

@ -14,6 +14,7 @@ export { pollSchema, type Poll, type PollOption } from './poll';
export { relationshipSchema, type Relationship } from './relationship';
export { statusSchema, type Status } from './status';
export { tagSchema, type Tag } from './tag';
export { tombstoneSchema, type Tombstone } from './tombstone';
// Soapbox
export { adSchema, type Ad } from './soapbox/ad';

Wyświetl plik

@ -10,6 +10,10 @@ import { pollSchema } from './poll';
import { tagSchema } from './tag';
import { contentSchema, dateSchema, filteredArray } from './utils';
const tombstoneSchema = z.object({
reason: z.enum(['deleted']),
});
const baseStatusSchema = z.object({
account: accountSchema,
application: z.object({
@ -46,6 +50,7 @@ const baseStatusSchema = z.object({
sensitive: z.coerce.boolean(),
spoiler_text: contentSchema,
tags: filteredArray(tagSchema),
tombstone: tombstoneSchema.nullable().optional(),
uri: z.string().url().catch(''),
url: z.string().url().catch(''),
visibility: z.string().catch('public'),

Wyświetl plik

@ -0,0 +1,9 @@
import { z } from 'zod';
const tombstoneSchema = z.object({
reason: z.enum(['deleted']),
});
type Tombstone = z.infer<typeof tombstoneSchema>;
export { tombstoneSchema, type Tombstone };

Wyświetl plik

@ -12,14 +12,4 @@
.status__content-wrapper {
@apply pl-[calc(42px+12px)] rtl:pl-0 rtl:pr-[calc(42px+12px)];
}
&__connector {
@apply bg-gray-200 dark:bg-primary-800 absolute w-0.5 left-5 hidden z-[1] rtl:right-5 rtl:left-auto;
&--bottom {
@apply block;
height: calc(100% - 42px - 8px - 1rem);
top: calc(12px + 42px);
}
}
}