@@ -575,28 +587,7 @@ class Status extends ImmutablePureComponent {
>
{(connectReply || connectUp || connectToRoot) &&
}
-
-
-
-
{status.get('edited_at') &&
*}
-
-
-
-
- {statusAvatar}
-
-
-
- {isQuotedPost && !!this.props.onQuoteCancel && (
-
- )}
-
+ {header}
{matchedFilters &&
}
diff --git a/app/javascript/mastodon/components/status/header.tsx b/app/javascript/mastodon/components/status/header.tsx
new file mode 100644
index 00000000000..3416c39dd29
--- /dev/null
+++ b/app/javascript/mastodon/components/status/header.tsx
@@ -0,0 +1,128 @@
+import type { FC, HTMLAttributes, MouseEventHandler, ReactNode } from 'react';
+
+import { defineMessage, useIntl } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import { isStatusVisibility } from '@/mastodon/api_types/statuses';
+import type { Account } from '@/mastodon/models/account';
+import type { Status } from '@/mastodon/models/status';
+
+import { Avatar } from '../avatar';
+import { AvatarOverlay } from '../avatar_overlay';
+import type { DisplayNameProps } from '../display_name';
+import { LinkedDisplayName } from '../display_name';
+import { RelativeTimestamp } from '../relative_timestamp';
+import { VisibilityIcon } from '../visibility_icon';
+
+export interface StatusHeaderProps {
+ status: Status;
+ account?: Account;
+ avatarSize?: number;
+ children?: ReactNode;
+ wrapperProps?: HTMLAttributes
;
+ displayNameProps?: DisplayNameProps;
+ onHeaderClick?: MouseEventHandler;
+}
+
+export type StatusHeaderRenderFn = (args: StatusHeaderProps) => ReactNode;
+
+export const StatusHeader: FC = ({
+ status,
+ account,
+ children,
+ avatarSize = 48,
+ wrapperProps,
+ onHeaderClick,
+}) => {
+ const statusAccount = status.get('account') as Account | undefined;
+ const editedAt = status.get('edited_at') as string;
+
+ return (
+ /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
+
+
+
+
+ {editedAt && }
+
+
+
+
+ {children}
+
+ );
+};
+
+export const StatusVisibility: FC<{ visibility: unknown }> = ({
+ visibility,
+}) => {
+ if (typeof visibility !== 'string' || !isStatusVisibility(visibility)) {
+ return null;
+ }
+ return (
+
+
+
+ );
+};
+
+const editMessage = defineMessage({
+ id: 'status.edited',
+ defaultMessage: 'Edited {date}',
+});
+
+export const StatusEditedAt: FC<{ editedAt: string }> = ({ editedAt }) => {
+ const intl = useIntl();
+ return (
+
+ {' '}
+ *
+
+ );
+};
+
+export const StatusDisplayName: FC<{
+ statusAccount?: Account;
+ friendAccount?: Account;
+ avatarSize: number;
+}> = ({ statusAccount, friendAccount, avatarSize }) => {
+ const AccountComponent = friendAccount ? AvatarOverlay : Avatar;
+ return (
+
+
+
+ );
+};
diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx
index 33e791a548b..8effec874f2 100644
--- a/app/javascript/mastodon/components/status_quoted.tsx
+++ b/app/javascript/mastodon/components/status_quoted.tsx
@@ -1,9 +1,10 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { FormattedMessage } from 'react-intl';
+import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
import type { Map as ImmutableMap } from 'immutable';
+import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import { fetchRelationships } from 'mastodon/actions/accounts';
import { revealAccount } from 'mastodon/actions/accounts_typed';
import { fetchStatus } from 'mastodon/actions/statuses';
@@ -18,6 +19,9 @@ import type { RootState } from 'mastodon/store';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { Button } from './button';
+import { IconButton } from './icon_button';
+import type { StatusHeaderRenderFn } from './status/header';
+import { StatusHeader } from './status/header';
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
@@ -147,6 +151,11 @@ interface QuotedStatusProps {
onQuoteCancel?: () => void; // Used for composer.
}
+const quoteCancelMessage = defineMessage({
+ id: 'status.quote.cancel',
+ defaultMessage: 'Cancel quote',
+});
+
export const QuotedStatus: React.FC = ({
quote,
contextType,
@@ -213,6 +222,22 @@ export const QuotedStatus: React.FC = ({
if (accountId && hiddenAccount) dispatch(fetchRelationships([accountId]));
}, [accountId, hiddenAccount, dispatch]);
+ const intl = useIntl();
+ const headerRenderFn: StatusHeaderRenderFn = useCallback(
+ (props) => (
+
+
+
+ ),
+ [intl, onQuoteCancel],
+ );
+
const isFilteredAndHidden = loadingState === 'filtered';
let quoteError: React.ReactNode = null;
@@ -314,7 +339,7 @@ export const QuotedStatus: React.FC = ({
id={quotedStatusId}
contextType={contextType}
avatarSize={32}
- onQuoteCancel={onQuoteCancel}
+ headerRenderFn={headerRenderFn}
>
{canRenderChildQuote && (