Merge branch 'tombstone' into 'develop'

Support soft-deleted statuses via tombstones

See merge request soapbox-pub/soapbox!2509
This commit is contained in:
Chewbacca 2023-05-09 19:48:04 +00:00
commit 2d087be65b
8 changed files with 56 additions and 18 deletions

View file

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

View file

@ -19,10 +19,17 @@ const Tombstone: React.FC<ITombstone> = ({ id, onMoveUp, onMoveDown }) => {
return ( return (
<HotKeys handlers={handlers}> <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}> <div className='h-16'>
<Text> <div
<FormattedMessage id='statuses.tombstone' defaultMessage='One or more posts are unavailable.' /> className='focusable flex h-[42px] items-center justify-center rounded-lg border-2 border-gray-200 text-center'
</Text> >
<Text theme='muted'>
<FormattedMessage
id='statuses.tombstone'
defaultMessage='One or more posts are unavailable.'
/>
</Text>
</div>
</div> </div>
</HotKeys> </HotKeys>
); );

View file

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

View file

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

View file

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

View file

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

View file

@ -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 };

View file

@ -12,14 +12,4 @@
.status__content-wrapper { .status__content-wrapper {
@apply pl-[calc(42px+12px)] rtl:pl-0 rtl:pr-[calc(42px+12px)]; @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);
}
}
} }