Move status info to above Account
This commit is contained in:
parent
1412385382
commit
024b161504
4 changed files with 132 additions and 87 deletions
|
@ -2,7 +2,7 @@ import classNames from 'clsx';
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
import { NavLink, useHistory } from 'react-router-dom';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { mentionCompose, replyCompose } from 'soapbox/actions/compose';
|
||||
import { toggleFavourite, toggleReblog } from 'soapbox/actions/interactions';
|
||||
|
@ -16,12 +16,14 @@ import { useAppDispatch, useSettings } from 'soapbox/hooks';
|
|||
import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status';
|
||||
|
||||
import EventPreview from './event-preview';
|
||||
import RelativeTimestamp from './relative-timestamp';
|
||||
import StatusActionBar from './status-action-bar';
|
||||
import StatusContent from './status-content';
|
||||
import StatusMedia from './status-media';
|
||||
import StatusReplyMentions from './status-reply-mentions';
|
||||
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
||||
import { Card, HStack, Stack, Text } from './ui';
|
||||
import StatusInfo from './statuses/status-info';
|
||||
import { Card, Stack, Text } from './ui';
|
||||
|
||||
import type {
|
||||
Account as AccountEntity,
|
||||
|
@ -37,6 +39,7 @@ const messages = defineMessages({
|
|||
|
||||
export interface IStatus {
|
||||
id?: string,
|
||||
avatarSize?: number,
|
||||
status: StatusEntity,
|
||||
onClick?: () => void,
|
||||
muted?: boolean,
|
||||
|
@ -56,6 +59,7 @@ export interface IStatus {
|
|||
const Status: React.FC<IStatus> = (props) => {
|
||||
const {
|
||||
status,
|
||||
avatarSize = 42,
|
||||
focusable = true,
|
||||
hoverable = true,
|
||||
onClick,
|
||||
|
@ -84,7 +88,7 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
const [minHeight, setMinHeight] = useState(208);
|
||||
|
||||
const actualStatus = getActualStatus(status);
|
||||
|
||||
const isReblog = status.reblog && typeof status.reblog === 'object';
|
||||
const statusUrl = `/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`;
|
||||
|
||||
// Track height changes we know about to compensate scrolling.
|
||||
|
@ -201,8 +205,49 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
firstEmoji?.focus();
|
||||
};
|
||||
|
||||
const renderStatusInfo = () => {
|
||||
if (isReblog) {
|
||||
return (
|
||||
<StatusInfo
|
||||
avatarSize={avatarSize}
|
||||
to={`/@${status.getIn(['account', 'acct'])}`}
|
||||
icon={<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id='status.reblogged_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<bdi className='truncate pr-1 rtl:pl-1'>
|
||||
<strong
|
||||
className='text-gray-800 dark:text-gray-200'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: String(status.getIn(['account', 'display_name_html'])),
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else if (featured) {
|
||||
return (
|
||||
<StatusInfo
|
||||
avatarSize={avatarSize}
|
||||
icon={<Icon src={require('@tabler/icons/pinned.svg')} className='text-gray-600 dark:text-gray-400' />}
|
||||
text={
|
||||
<Text size='xs' theme='muted' weight='medium'>
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (!status) return null;
|
||||
let rebloggedByText, reblogElement, reblogElementMobile;
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
|
@ -228,55 +273,8 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
);
|
||||
}
|
||||
|
||||
let rebloggedByText;
|
||||
if (status.reblog && typeof status.reblog === 'object') {
|
||||
const displayNameHtml = { __html: String(status.getIn(['account', 'display_name_html'])) };
|
||||
|
||||
reblogElement = (
|
||||
<NavLink
|
||||
to={`/@${status.getIn(['account', 'acct'])}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className='hidden sm:flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 rtl:space-x-reverse hover:underline'
|
||||
>
|
||||
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />
|
||||
|
||||
<HStack alignItems='center'>
|
||||
<FormattedMessage
|
||||
id='status.reblogged_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: <bdi className='max-w-[100px] truncate pr-1 rtl:px-1'>
|
||||
<strong className='text-gray-800 dark:text-gray-200' dangerouslySetInnerHTML={displayNameHtml} />
|
||||
</bdi>,
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
reblogElementMobile = (
|
||||
<div className='pb-5 -mt-2 sm:hidden truncate'>
|
||||
<NavLink
|
||||
to={`/@${status.getIn(['account', 'acct'])}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
|
||||
>
|
||||
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />
|
||||
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='status.reblogged_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: <bdi>
|
||||
<strong className='text-gray-800 dark:text-gray-200' dangerouslySetInnerHTML={displayNameHtml} />
|
||||
</bdi>,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
rebloggedByText = intl.formatMessage(
|
||||
messages.reblogged_by,
|
||||
{ name: String(status.getIn(['account', 'acct'])) },
|
||||
|
@ -312,7 +310,13 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
react: handleHotkeyReact,
|
||||
};
|
||||
|
||||
const accountAction = props.accountAction || reblogElement;
|
||||
const timestampEl = (
|
||||
<Link to={statusUrl} className='hover:underline' onClick={(event) => event.stopPropagation()}>
|
||||
<RelativeTimestamp timestamp={actualStatus.created_at} theme='muted' size='sm' className='whitespace-nowrap' />
|
||||
</Link>
|
||||
);
|
||||
|
||||
const accountAction = props.accountAction || timestampEl;
|
||||
|
||||
const isUnderReview = actualStatus.visibility === 'self';
|
||||
const isSensitive = actualStatus.hidden;
|
||||
|
@ -328,21 +332,9 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
onClick={handleClick}
|
||||
role='link'
|
||||
>
|
||||
{featured && (
|
||||
<div className='pt-4 px-4'>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Icon src={require('@tabler/icons/pinned.svg')} className='text-gray-600 dark:text-gray-400' />
|
||||
|
||||
<Text size='sm' theme='muted' weight='medium'>
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card
|
||||
variant={variant}
|
||||
className={classNames('status__wrapper', `status-${actualStatus.visibility}`, {
|
||||
className={classNames('status__wrapper space-y-4', `status-${actualStatus.visibility}`, {
|
||||
'py-6 sm:p-5': variant === 'rounded',
|
||||
'status-reply': !!status.in_reply_to_id,
|
||||
muted,
|
||||
|
@ -350,21 +342,18 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
})}
|
||||
data-id={status.id}
|
||||
>
|
||||
{reblogElementMobile}
|
||||
{renderStatusInfo()}
|
||||
|
||||
<div className='mb-4'>
|
||||
<AccountContainer
|
||||
key={String(actualStatus.getIn(['account', 'id']))}
|
||||
id={String(actualStatus.getIn(['account', 'id']))}
|
||||
timestamp={actualStatus.created_at}
|
||||
timestampUrl={statusUrl}
|
||||
action={accountAction}
|
||||
hideActions={!accountAction}
|
||||
showEdit={!!actualStatus.edited_at}
|
||||
showProfileHoverCard={hoverable}
|
||||
withLinkToProfile={hoverable}
|
||||
/>
|
||||
</div>
|
||||
<AccountContainer
|
||||
key={String(actualStatus.getIn(['account', 'id']))}
|
||||
id={String(actualStatus.getIn(['account', 'id']))}
|
||||
action={accountAction}
|
||||
hideActions={!accountAction}
|
||||
showEdit={!!actualStatus.edited_at}
|
||||
showProfileHoverCard={hoverable}
|
||||
withLinkToProfile={hoverable}
|
||||
avatarSize={avatarSize}
|
||||
/>
|
||||
|
||||
<div className='status__content-wrapper'>
|
||||
<StatusReplyMentions status={actualStatus} hoverable={hoverable} />
|
||||
|
|
38
app/soapbox/components/statuses/status-info.tsx
Normal file
38
app/soapbox/components/statuses/status-info.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface IStatusInfo {
|
||||
avatarSize: number
|
||||
to?: string
|
||||
icon: React.ReactNode
|
||||
text: React.ReactNode
|
||||
}
|
||||
|
||||
const StatusInfo = (props: IStatusInfo) => {
|
||||
const { avatarSize, to, icon, text } = props;
|
||||
|
||||
const onClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const Container = to ? Link : 'div';
|
||||
const containerProps: any = to ? { onClick, to } : {};
|
||||
|
||||
return (
|
||||
<Container
|
||||
{...containerProps}
|
||||
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-3 rtl:space-x-reverse hover:underline'
|
||||
>
|
||||
<div
|
||||
className='flex justify-end'
|
||||
style={{ width: avatarSize }}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
{text}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusInfo;
|
|
@ -151,6 +151,8 @@ const buildMessage = (
|
|||
});
|
||||
};
|
||||
|
||||
const avatarSize = 48;
|
||||
|
||||
interface INotificaton {
|
||||
hidden?: boolean,
|
||||
notification: NotificationEntity,
|
||||
|
@ -290,7 +292,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
<AccountContainer
|
||||
id={account.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
avatarSize={avatarSize}
|
||||
/>
|
||||
) : null;
|
||||
case 'follow_request':
|
||||
|
@ -298,7 +300,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
<AccountContainer
|
||||
id={account.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
avatarSize={avatarSize}
|
||||
actionType='follow_request'
|
||||
/>
|
||||
) : null;
|
||||
|
@ -307,7 +309,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
<AccountContainer
|
||||
id={notification.target.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
avatarSize={avatarSize}
|
||||
/>
|
||||
) : null;
|
||||
case 'favourite':
|
||||
|
@ -327,6 +329,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
hidden={hidden}
|
||||
onMoveDown={handleMoveDown}
|
||||
onMoveUp={handleMoveUp}
|
||||
avatarSize={avatarSize}
|
||||
/>
|
||||
) : null;
|
||||
default:
|
||||
|
@ -358,13 +361,18 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
>
|
||||
<div className='p-4 focusable'>
|
||||
<div className='mb-2'>
|
||||
<HStack alignItems='center' space={1.5}>
|
||||
{renderIcon()}
|
||||
<HStack alignItems='center' space={3}>
|
||||
<div
|
||||
className='flex justify-end'
|
||||
style={{ flexBasis: avatarSize }}
|
||||
>
|
||||
{renderIcon()}
|
||||
</div>
|
||||
|
||||
<div className='truncate'>
|
||||
<Text
|
||||
theme='muted'
|
||||
size='sm'
|
||||
size='xs'
|
||||
truncate
|
||||
data-testid='message'
|
||||
>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { FormattedDate, FormattedMessage, useIntl } from 'react-intl';
|
|||
|
||||
import Account from 'soapbox/components/account';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import RelativeTimestamp from 'soapbox/components/relative-timestamp';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import StatusMedia from 'soapbox/components/status-media';
|
||||
import StatusReplyMentions from 'soapbox/components/status-reply-mentions';
|
||||
|
@ -58,6 +59,15 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
const isUnderReview = actualStatus.visibility === 'self';
|
||||
const isSensitive = actualStatus.hidden;
|
||||
|
||||
const timestampEl = (
|
||||
<RelativeTimestamp
|
||||
timestamp={actualStatus.created_at}
|
||||
theme='muted'
|
||||
size='sm'
|
||||
className='whitespace-nowrap'
|
||||
/>
|
||||
);
|
||||
|
||||
let statusTypeIcon = null;
|
||||
|
||||
let quote;
|
||||
|
@ -87,7 +97,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
<Account
|
||||
key={account.id}
|
||||
account={account}
|
||||
timestamp={actualStatus.created_at}
|
||||
action={timestampEl}
|
||||
avatarSize={42}
|
||||
hideActions
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue