From 84cc0dcac4cbc05b279b4083b285dc8e9786b239 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 4 Feb 2026 14:12:21 +0100 Subject: [PATCH] [Glitch] Refactors header from Status component Port 7f53a77fa3585a36a054f058105e0fe170d2bc0b to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/status.jsx | 85 ++++++------- .../glitch/components/status/header.tsx | 115 ++++++++++++++++++ .../glitch/components/status_quoted.tsx | 29 ++++- 3 files changed, 180 insertions(+), 49 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/status/header.tsx diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index b9728b23df..b369579219 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -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 = ; - } else { - statusAvatar = ; - } - const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); + const header = this.props.headerRenderFn + ? this.props.headerRenderFn({ status, account, avatarSize, messages, onHeaderClick: this.handleHeaderClick }) + : ( + + + + ); + return (
{(connectReply || connectUp || connectToRoot) &&
} - {(!muted) && ( -
- -
- {statusAvatar} -
-
- - {isQuotedPost && !!this.props.onQuoteCancel ? ( - - ) : ( - - )} -
- )} + {(!muted) && header} diff --git a/app/javascript/flavours/glitch/components/status/header.tsx b/app/javascript/flavours/glitch/components/status/header.tsx new file mode 100644 index 0000000000..ecb2414e70 --- /dev/null +++ b/app/javascript/flavours/glitch/components/status/header.tsx @@ -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; + 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; + + return ( + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ +
+ + + {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/flavours/glitch/components/status_quoted.tsx b/app/javascript/flavours/glitch/components/status_quoted.tsx index c3e07ecef5..858096f964 100644 --- a/app/javascript/flavours/glitch/components/status_quoted.tsx +++ b/app/javascript/flavours/glitch/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 { 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 = ({ quote, contextType, @@ -214,6 +223,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; @@ -315,7 +340,7 @@ export const QuotedStatus: React.FC = ({ id={quotedStatusId} contextType={contextType} avatarSize={32} - onQuoteCancel={onQuoteCancel} + headerRenderFn={headerRenderFn} > {canRenderChildQuote && (