[Glitch] Refactors header from Status component
Port 7f53a77fa3585a36a054f058105e0fe170d2bc0b to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
072c30681e
commit
84cc0dcac4
@ -7,7 +7,6 @@ import classNames from 'classnames';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
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 { Hotkeys } from 'flavours/glitch/components/hotkeys';
|
||||||
import { ContentWarning } from 'flavours/glitch/components/content_warning';
|
import { ContentWarning } from 'flavours/glitch/components/content_warning';
|
||||||
import { PictureInPicturePlaceholder } from 'flavours/glitch/components/picture_in_picture_placeholder';
|
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 { displayMedia } from '../initial_state';
|
||||||
|
|
||||||
import AttachmentList from './attachment_list';
|
import AttachmentList from './attachment_list';
|
||||||
import { Avatar } from './avatar';
|
import { StatusHeader } from './status/header'
|
||||||
import { AvatarOverlay } from './avatar_overlay';
|
|
||||||
import { LinkedDisplayName } from './display_name';
|
|
||||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||||
import { MentionsPlaceholder } from './mentions_placeholder';
|
import { MentionsPlaceholder } from './mentions_placeholder';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusIcons from './status_icons';
|
import StatusIcons from './status_icons';
|
||||||
import StatusPrepend from './status_prepend';
|
import StatusPrepend from './status_prepend';
|
||||||
import { IconButton } from './icon_button';
|
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
@ -111,7 +107,6 @@ class Status extends ImmutablePureComponent {
|
|||||||
onToggleCollapsed: PropTypes.func,
|
onToggleCollapsed: PropTypes.func,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
onQuoteCancel: PropTypes.func,
|
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
unread: PropTypes.bool,
|
unread: PropTypes.bool,
|
||||||
@ -130,6 +125,8 @@ class Status extends ImmutablePureComponent {
|
|||||||
skipPrepend: PropTypes.bool,
|
skipPrepend: PropTypes.bool,
|
||||||
avatarSize: PropTypes.number,
|
avatarSize: PropTypes.number,
|
||||||
deployPictureInPicture: PropTypes.func,
|
deployPictureInPicture: PropTypes.func,
|
||||||
|
unfocusable: PropTypes.bool,
|
||||||
|
headerRenderFn: PropTypes.func,
|
||||||
settings: ImmutablePropTypes.map.isRequired,
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
pictureInPicture: ImmutablePropTypes.contains({
|
pictureInPicture: ImmutablePropTypes.contains({
|
||||||
inUse: PropTypes.bool,
|
inUse: PropTypes.bool,
|
||||||
@ -160,7 +157,6 @@ class Status extends ImmutablePureComponent {
|
|||||||
'expanded',
|
'expanded',
|
||||||
'unread',
|
'unread',
|
||||||
'pictureInPicture',
|
'pictureInPicture',
|
||||||
'onQuoteCancel',
|
|
||||||
'previousId',
|
'previousId',
|
||||||
'nextInReplyToId',
|
'nextInReplyToId',
|
||||||
'rootId',
|
'rootId',
|
||||||
@ -342,10 +338,6 @@ class Status extends ImmutablePureComponent {
|
|||||||
deployPictureInPicture(status, type, mediaProps);
|
deployPictureInPicture(status, type, mediaProps);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleQuoteCancel = () => {
|
|
||||||
this.props.onQuoteCancel?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleHotkeyReply = e => {
|
handleHotkeyReply = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onReply(this.props.status);
|
this.props.onReply(this.props.status);
|
||||||
@ -455,27 +447,39 @@ class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
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 {
|
const {
|
||||||
status,
|
status,
|
||||||
account,
|
account,
|
||||||
settings,
|
settings,
|
||||||
muted,
|
muted,
|
||||||
intersectionObserverWrapper,
|
|
||||||
onOpenVideo,
|
onOpenVideo,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
notification,
|
notification,
|
||||||
history,
|
history,
|
||||||
showActions = true,
|
|
||||||
isQuotedPost = false,
|
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let attachments = null;
|
let attachments = null;
|
||||||
|
|
||||||
let media = [];
|
let media = [];
|
||||||
let mediaIcons = [];
|
let mediaIcons = [];
|
||||||
let statusAvatar;
|
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return 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') });
|
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 {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 (
|
return (
|
||||||
<Hotkeys handlers={handlers} focusable={!unfocusable}>
|
<Hotkeys handlers={handlers} focusable={!unfocusable}>
|
||||||
<div
|
<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 })} />}
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
|
|
||||||
{(!muted) && (
|
{(!muted) && header}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ContentWarning status={status} expanded={expanded} onClick={this.handleExpandedToggle} icons={mediaIcons} />
|
<ContentWarning status={status} expanded={expanded} onClick={this.handleExpandedToggle} icons={mediaIcons} />
|
||||||
|
|
||||||
|
|||||||
115
app/javascript/flavours/glitch/components/status/header.tsx
Normal file
115
app/javascript/flavours/glitch/components/status/header.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
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 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 { LearnMoreLink } from 'flavours/glitch/components/learn_more_link';
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||||
import { domain } from 'flavours/glitch/initial_state';
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
@ -19,6 +20,9 @@ import { makeGetStatusWithExtraInfo } from '../selectors';
|
|||||||
import { getAccountHidden } from '../selectors/accounts';
|
import { getAccountHidden } from '../selectors/accounts';
|
||||||
|
|
||||||
import { Button } from './button';
|
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;
|
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
|
||||||
|
|
||||||
@ -148,6 +152,11 @@ interface QuotedStatusProps {
|
|||||||
onQuoteCancel?: () => void; // Used for composer.
|
onQuoteCancel?: () => void; // Used for composer.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const quoteCancelMessage = defineMessage({
|
||||||
|
id: 'status.quote.cancel',
|
||||||
|
defaultMessage: 'Cancel quote',
|
||||||
|
});
|
||||||
|
|
||||||
export const QuotedStatus: React.FC<QuotedStatusProps> = ({
|
export const QuotedStatus: React.FC<QuotedStatusProps> = ({
|
||||||
quote,
|
quote,
|
||||||
contextType,
|
contextType,
|
||||||
@ -214,6 +223,22 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
|
|||||||
if (accountId && hiddenAccount) dispatch(fetchRelationships([accountId]));
|
if (accountId && hiddenAccount) dispatch(fetchRelationships([accountId]));
|
||||||
}, [accountId, hiddenAccount, dispatch]);
|
}, [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';
|
const isFilteredAndHidden = loadingState === 'filtered';
|
||||||
|
|
||||||
let quoteError: React.ReactNode = null;
|
let quoteError: React.ReactNode = null;
|
||||||
@ -315,7 +340,7 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
|
|||||||
id={quotedStatusId}
|
id={quotedStatusId}
|
||||||
contextType={contextType}
|
contextType={contextType}
|
||||||
avatarSize={32}
|
avatarSize={32}
|
||||||
onQuoteCancel={onQuoteCancel}
|
headerRenderFn={headerRenderFn}
|
||||||
>
|
>
|
||||||
{canRenderChildQuote && (
|
{canRenderChildQuote && (
|
||||||
<QuotedStatus
|
<QuotedStatus
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user