[Glitch] Refactors header from Status component

Port 7f53a77fa3 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2026-02-04 14:12:21 +01:00
committed by Claire
parent 072c30681e
commit 84cc0dcac4
3 changed files with 180 additions and 49 deletions

View File

@@ -7,7 +7,6 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import { Hotkeys } from 'flavours/glitch/components/hotkeys';
import { ContentWarning } from 'flavours/glitch/components/content_warning';
import { PictureInPicturePlaceholder } from 'flavours/glitch/components/picture_in_picture_placeholder';
@@ -23,16 +22,13 @@ import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_conte
import { displayMedia } from '../initial_state';
import AttachmentList from './attachment_list';
import { Avatar } from './avatar';
import { AvatarOverlay } from './avatar_overlay';
import { LinkedDisplayName } from './display_name';
import { StatusHeader } from './status/header'
import { getHashtagBarForStatus } from './hashtag_bar';
import { MentionsPlaceholder } from './mentions_placeholder';
import StatusActionBar from './status_action_bar';
import StatusContent from './status_content';
import StatusIcons from './status_icons';
import StatusPrepend from './status_prepend';
import { IconButton } from './icon_button';
const domParser = new DOMParser();
@@ -111,7 +107,6 @@ class Status extends ImmutablePureComponent {
onToggleCollapsed: PropTypes.func,
onTranslate: PropTypes.func,
onInteractionModal: PropTypes.func,
onQuoteCancel: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
@@ -130,6 +125,8 @@ class Status extends ImmutablePureComponent {
skipPrepend: PropTypes.bool,
avatarSize: PropTypes.number,
deployPictureInPicture: PropTypes.func,
unfocusable: PropTypes.bool,
headerRenderFn: PropTypes.func,
settings: ImmutablePropTypes.map.isRequired,
pictureInPicture: ImmutablePropTypes.contains({
inUse: PropTypes.bool,
@@ -160,7 +157,6 @@ class Status extends ImmutablePureComponent {
'expanded',
'unread',
'pictureInPicture',
'onQuoteCancel',
'previousId',
'nextInReplyToId',
'rootId',
@@ -342,10 +338,6 @@ class Status extends ImmutablePureComponent {
deployPictureInPicture(status, type, mediaProps);
};
handleQuoteCancel = () => {
this.props.onQuoteCancel?.();
}
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this.props.status);
@@ -455,27 +447,39 @@ class Status extends ImmutablePureComponent {
}
render () {
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props;
const {
intl,
hidden,
featured,
unfocusable,
unread,
showActions = true,
isQuotedPost = false,
pictureInPicture,
previousId,
nextInReplyToId,
rootId,
skipPrepend,
avatarSize = 46,
children,
} = this.props;
// glitch-soc-specific
const {
status,
account,
settings,
muted,
intersectionObserverWrapper,
onOpenVideo,
onOpenMedia,
notification,
history,
showActions = true,
isQuotedPost = false,
...other
} = this.props;
let attachments = null;
let media = [];
let mediaIcons = [];
let statusAvatar;
if (status === null) {
return null;
@@ -678,14 +682,25 @@ class Status extends ImmutablePureComponent {
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
}
if (account === undefined || account === null) {
statusAvatar = <Avatar account={status.get('account')} size={avatarSize} />;
} else {
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
}
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const header = this.props.headerRenderFn
? this.props.headerRenderFn({ status, account, avatarSize, messages, onHeaderClick: this.handleHeaderClick })
: (
<StatusHeader
status={status}
account={account}
avatarSize={avatarSize}
onHeaderClick={this.handleHeaderClick}
>
<StatusIcons
status={status}
mediaIcons={mediaIcons}
settings={settings.get('status_icons')}
/>
</StatusHeader>
);
return (
<Hotkeys handlers={handlers} focusable={!unfocusable}>
<div
@@ -716,31 +731,7 @@ class Status extends ImmutablePureComponent {
>
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
{(!muted) && (
<header onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
<LinkedDisplayName displayProps={{account: status.get('account')}} className='status__display-name'>
<div className='status__avatar'>
{statusAvatar}
</div>
</LinkedDisplayName>
{isQuotedPost && !!this.props.onQuoteCancel ? (
<IconButton
onClick={this.handleQuoteCancel}
className='status__quote-cancel'
title={intl.formatMessage(messages.quote_cancel)}
icon="cancel-fill"
iconComponent={CancelFillIcon}
/>
) : (
<StatusIcons
status={status}
mediaIcons={mediaIcons}
settings={settings.get('status_icons')}
/>
)}
</header>
)}
{(!muted) && header}
<ContentWarning status={status} expanded={expanded} onClick={this.handleExpandedToggle} icons={mediaIcons} />

View File

@@ -0,0 +1,115 @@
import type { FC, HTMLAttributes, MouseEventHandler, ReactNode } from 'react';
import { defineMessage, useIntl } from 'react-intl';
import { isStatusVisibility } from '@/flavours/glitch/api_types/statuses';
import type { Account } from '@/flavours/glitch/models/account';
import type { Status } from '@/flavours/glitch/models/status';
import { Avatar } from '../avatar';
import { AvatarOverlay } from '../avatar_overlay';
import type { DisplayNameProps } from '../display_name';
import { LinkedDisplayName } from '../display_name';
import { VisibilityIcon } from '../visibility_icon';
export interface StatusHeaderProps {
status: Status;
account?: Account;
avatarSize?: number;
children?: ReactNode;
wrapperProps?: HTMLAttributes<HTMLDivElement>;
displayNameProps?: DisplayNameProps;
onHeaderClick?: MouseEventHandler<HTMLDivElement>;
}
export type StatusHeaderRenderFn = (args: StatusHeaderProps) => ReactNode;
export const StatusHeader: FC<StatusHeaderProps> = ({
status,
account,
children,
avatarSize = 48,
wrapperProps,
onHeaderClick,
}) => {
const statusAccount = status.get('account') as Account | undefined;
return (
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
<header
onClick={onHeaderClick}
onAuxClick={onHeaderClick}
{...wrapperProps}
className='status__info'
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
>
<StatusDisplayName
statusAccount={statusAccount}
friendAccount={account}
avatarSize={avatarSize}
/>
{children}
</header>
);
};
export const StatusVisibility: FC<{ visibility: unknown }> = ({
visibility,
}) => {
if (typeof visibility !== 'string' || !isStatusVisibility(visibility)) {
return null;
}
return (
<span className='status__visibility-icon'>
<VisibilityIcon visibility={visibility} />
</span>
);
};
const editMessage = defineMessage({
id: 'status.edited',
defaultMessage: 'Edited {date}',
});
export const StatusEditedAt: FC<{ editedAt: string }> = ({ editedAt }) => {
const intl = useIntl();
return (
<abbr
title={intl.formatMessage(editMessage, {
date: intl.formatDate(editedAt, {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
}),
})}
>
{' '}
*
</abbr>
);
};
export const StatusDisplayName: FC<{
statusAccount?: Account;
friendAccount?: Account;
avatarSize: number;
}> = ({ statusAccount, friendAccount, avatarSize }) => {
const AccountComponent = friendAccount ? AvatarOverlay : Avatar;
return (
<LinkedDisplayName
displayProps={{ account: statusAccount }}
className='status__display-name'
>
<div className='status__avatar'>
<AccountComponent
account={statusAccount}
friend={friendAccount}
size={avatarSize}
/>
</div>
</LinkedDisplayName>
);
};

View File

@@ -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 { LearnMoreLink } from 'flavours/glitch/components/learn_more_link';
import StatusContainer from 'flavours/glitch/containers/status_container';
import { domain } from 'flavours/glitch/initial_state';
@@ -19,6 +20,9 @@ import { makeGetStatusWithExtraInfo } from '../selectors';
import { getAccountHidden } from '../selectors/accounts';
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;
@@ -148,6 +152,11 @@ interface QuotedStatusProps {
onQuoteCancel?: () => void; // Used for composer.
}
const quoteCancelMessage = defineMessage({
id: 'status.quote.cancel',
defaultMessage: 'Cancel quote',
});
export const QuotedStatus: React.FC<QuotedStatusProps> = ({
quote,
contextType,
@@ -214,6 +223,22 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
if (accountId && hiddenAccount) dispatch(fetchRelationships([accountId]));
}, [accountId, hiddenAccount, dispatch]);
const intl = useIntl();
const headerRenderFn: StatusHeaderRenderFn = useCallback(
(props) => (
<StatusHeader {...props}>
<IconButton
onClick={onQuoteCancel}
className='status__quote-cancel'
title={intl.formatMessage(quoteCancelMessage)}
icon='cancel-fill'
iconComponent={CancelFillIcon}
/>
</StatusHeader>
),
[intl, onQuoteCancel],
);
const isFilteredAndHidden = loadingState === 'filtered';
let quoteError: React.ReactNode = null;
@@ -315,7 +340,7 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
id={quotedStatusId}
contextType={contextType}
avatarSize={32}
onQuoteCancel={onQuoteCancel}
headerRenderFn={headerRenderFn}
>
{canRenderChildQuote && (
<QuotedStatus